2008-09-08 2 views
58

Я хочу, чтобы во время загрузки приложения отображался экран всплеска. У меня есть форма с привязкой к системному лотку. Я хочу, чтобы экран заставки отображался при загрузке этой формы, что занимает немного времени, так как он обращается к API веб-сервисов для заполнения некоторых раскрывающихся списков. Я также хочу выполнить базовое тестирование зависимостей перед загрузкой (т. Е. Доступен веб-сервис, файл конфигурации доступен для чтения). Поскольку каждый этап запуска идет, я хочу обновить заставку с прогрессом.Многопоточный заставки в C#?

Я много читал в потоковом режиме, но я теряюсь там, где это должно контролироваться (метод main()?). Мне также не хватает того, как работает Application.Run(), это где потоки для этого должны быть созданы? Теперь, если форма с управлением в системном лотке - это «живая» форма, следует ли всплеск оттуда? Разве это не будет загружаться, пока форма не будет завершена?

Я не ищу для кода раздаточный, скорее алгоритма/подход, так что я могу понять это раз и навсегда :)

ответ

47

Ну, для приложения ClickOnce, что я развернутый в прошлом, мы использовали пространство имен Microsoft.VisualBasic для обработки заставки заставки. Вы можете ссылаться и использовать сборку Microsoft.VisualBasic из C# в .NET 2.0 и предоставляет множество приятных сервисов.

  1. Имейте основную форму, наследуемую от Microsoft.VisualBasic.WindowsFormsApplicationBase
  2. переопределять значение «OnCreateSplashScreen» метод как так:

    protected override void OnCreateSplashScreen() 
    { 
        this.SplashScreen = new SplashForm(); 
        this.SplashScreen.TopMost = true; 
    } 
    

Очень просто, это показывает ваш SplashForm (что вам нужно создать) при загрузке происходит, а затем закрывает его автоматически, как только основная форма завершила загрузку.

Это действительно упрощает работу, и VisualBasic.WindowsFormsApplicationBase, конечно, хорошо протестирован Microsoft и имеет множество функций, которые могут сделать вашу жизнь намного проще в Winforms, даже в приложении, которое является 100% C#.

В конце концов, это все ИЛ и байт-код в любом случае, так почему бы не использовать его?

6

Один простой способ заключается в использовании что-то подобное, как основной():

<STAThread()> Public Shared Sub Main() 

    splash = New frmSplash 
    splash.Show() 

    ' Your startup code goes here... 

    UpdateSplashAndLogMessage("Startup part 1 done...") 

    ' ... and more as needed... 

    splash.Hide() 
    Application.Run(myMainForm) 
End Sub 

Когда .NET CLR запускает приложение, он создает «главный» поток и начинает выполнять ваш главный() на эту тему. Application.Run (myMainForm) в конце делает две вещи:

  1. Запускает «сообщение насос» в Windows, используя нить, которая была выполняющийся основной() в качестве GUI потока.
  2. Обозначает вашу «основную форму» как «форму выключения» для приложения. Если пользователь закрывает эту форму, то Application.Run() завершает работу и управление возвращается к вашему main(), где вы можете сделать любое выключение.

Нет необходимости создавать нить, чтобы заботиться о всплывающем окне, и на самом деле это плохая идея, потому что тогда вам придется использовать поточно-безопасные методы для обновления содержимого всплеска из основного ().

Если вам нужны другие темы для фоновых операций в вашем приложении, вы можете создать их из main(). Не забудьте установить Thread.IsBackground в значение True, чтобы они погибли, когда поток main/GUI завершился. В противном случае вам придется договориться о завершении всех ваших других потоков самостоятельно, или они будут поддерживать ваше приложение в живых (но без GUI), когда основной поток завершается.

47

Хитрость заключается в том, чтобы создать отдельную тему, отвечающую за отображение заставки.
При запуске приложения .net создает основной поток и загружает указанную (основную) форму. Чтобы скрыть тяжелую работу, вы можете скрыть основную форму до завершения загрузки.

Предполагая, что Form1 - это ваша основная форма и SplashForm является верхним уровнем, borderles хорошего вида выплеска:

private void Form1_Load(object sender, EventArgs e) 
{ 
    Hide(); 
    bool done = false; 
    ThreadPool.QueueUserWorkItem((x) => 
    { 
     using (var splashForm = new SplashForm()) 
     { 
      splashForm.Show(); 
      while (!done) 
       Application.DoEvents(); 
      splashForm.Close(); 
     } 
    }); 

    Thread.Sleep(3000); // Emulate hardwork 
    done = true; 
    Show(); 
} 
+1

Мне нравится этот метод, спасибо, бутон, я использую это часто сейчас. – Echostorm 2008-12-24 15:22:39

+0

Это, наверное, лучший метод, который я видел для создания заставки. Работает на компактном компакт-диске .NET с моим мобильным приложением для ПК. – Damien 2009-07-09 09:53:39

+5

@aku: Это хорошая идея использовать Application.DoEvents() в приложении? Я много читал о том, чтобы не использовать эту функцию в Интернете, например http://blogs.msdn.com/jfoscoding/archive/2005/08/06/448560.aspx – 2010-01-22 13:19:07

7

Я думаю, что с помощью какого-то метода, как aku's или Guy's это путь, но пара вещей, чтобы забрать из конкретных примеров:

  1. Основная предпосылка будет показать свой всплеск на отдельную нить как можно скорее. Это то, как я наклоняюсь, похоже на то, что иллюстрирует аку, так как это то, с чем я больше всего знаком. Я не знал о функции VB, о которой говорил Гай. И даже подумал, что это библиотека VB, он прав - это все ИЛ в конце. Итак, даже если он чувствует себя грязный это еще не все так плохо! :) Я думаю, вам нужно быть уверенным, что либо VB предоставляет отдельный поток для этого переопределения, либо тот, который вы сами создаете, - определенно исследуйте это.

  2. Предполагая, что вы создаете еще один поток для отображения этого всплеска, вы захотите быть осторожным с обновлением пользовательских интерфейсов поперечной нити. Я рассказываю об этом, потому что вы упомянули о ходе обновления. В принципе, чтобы быть в безопасности, вам нужно вызвать функцию обновления (которую вы создаете) в форме всплеска с помощью делегата. Вы передаете этот делегат функции Invoke на объект формы вашего заставки. Фактически, если вы вызываете брызговую форму непосредственно для обновления элементов прогресса/пользовательского интерфейса на ней, вы получите исключение, если вы работаете в среде .NET Net CLR. Как правило, любой элемент пользовательского интерфейса в форме должен быть обновлен потоком, который его создал, - это то, что обеспечивает Form.Invoke.

Наконец, я бы предпочел создать всплеск (если не использовать перегрузку VB) в основном методе вашего кода. Для меня это лучше, чем если бы основная форма выполняла создание объекта и была настолько тесно связана с ним. Если вы примете такой подход, я бы предложил создать простой интерфейс, который реализует экран заставки - что-то вроде IStartupProgressListener - который получает обновления прогона обновления через функцию-член. Это позволит вам легко переключаться в любом классе по мере необходимости и красиво отделять код. Форма всплеска также может знать, когда закрыть, если вы сообщите, когда пуск завершен.

9

Я рекомендую позвонить Activate(); непосредственно после последнего Show(); в ответе, предоставленном aku.

Цитирование MSDN:

Активация формы приводит его к фронт, если это активный приложение, или он мигает окно подпись, если это не является активным приложений. Форма должна быть видна , чтобы этот метод имел какой-либо эффект.

Если вы не активируете свою основную форму, он может отображаться за любые другие открытые окна, что делает его выглядеть немного глупо.

5

Я разместил статью о включении заставки в приложении в кодепроекте. Это многопоточное и может быть интерес

Yet Another Splash Screen in C#

9

Это старый вопрос, но я постоянно сталкивался с ним, пытаясь найти резьбовое решение заставки для WPF, который может включать в себя анимацию.

Вот что я в конечном счете кусочкам:

app.xaml:

<Application Startup="ApplicationStart" … 

App.xaml.cs:

void ApplicationStart(object sender, StartupEventArgs e) 
{ 
     var thread = new Thread(() => 
     { 
      Dispatcher.CurrentDispatcher.BeginInvoke ((Action)(() => new MySplashForm().Show())); 
      Dispatcher.Run(); 
     }); 
     thread.SetApartmentState(ApartmentState.STA); 
     thread.IsBackground = true; 
     thread.Start(); 

     // call synchronous configuration process 
     // and declare/get reference to "main form" 

     thread.Abort(); 

     mainForm.Show(); 
     mainForm.Activate(); 
    } 
14

После просмотра всей Google и SO решений, это мой любимый: http://bytes.com/topic/c-sharp/answers/277446-winform-startup-splash-screen

FormSplash.cs:

public partial class FormSplash : Form 
{ 
    private static Thread _splashThread; 
    private static FormSplash _splashForm; 

    public FormSplash() { 
     InitializeComponent(); 
    } 

    /// <summary> 
    /// Show the Splash Screen (Loading...) 
    /// </summary> 
    public static void ShowSplash() 
    { 
     if (_splashThread == null) 
     { 
      // show the form in a new thread 
      _splashThread = new Thread(new ThreadStart(DoShowSplash)); 
      _splashThread.IsBackground = true; 
      _splashThread.Start(); 
     } 
    } 

    // called by the thread 
    private static void DoShowSplash() 
    { 
     if (_splashForm == null) 
      _splashForm = new FormSplash(); 

     // create a new message pump on this thread (started from ShowSplash) 
     Application.Run(_splashForm); 
    } 

    /// <summary> 
    /// Close the splash (Loading...) screen 
    /// </summary> 
    public static void CloseSplash() 
    { 
     // need to call on the thread that launched this splash 
     if (_splashForm.InvokeRequired) 
      _splashForm.Invoke(new MethodInvoker(CloseSplash)); 

     else 
      Application.ExitThread(); 
    } 
} 

Program.cs:

static class Program 
{ 
    /// <summary> 
    /// The main entry point for the application. 
    /// </summary> 
    [STAThread] 
    static void Main(string[] args) 
    { 
     // splash screen, which is terminated in FormMain 
     FormSplash.ShowSplash(); 

     Application.EnableVisualStyles(); 
     Application.SetCompatibleTextRenderingDefault(false); 
     // this is probably where your heavy lifting is: 
     Application.Run(new FormMain()); 
    } 
} 

FormMain.cs

... 

    public FormMain() 
    { 
     InitializeComponent();    

     // bunch of database access, form loading, etc 
     // this is where you could do the heavy lifting of "loading" the app 
     PullDataFromDatabase(); 
     DoLoadingWork();    

     // ready to go, now close the splash 
     FormSplash.CloseSplash(); 
    } 

Я имел проблемы с Microsoft.VisualBasic решения - Работала найти на XP, но на сервере терминалов Windows 2003 основная форма заявки (после заставки) в фоновом режиме, и панель задач начнет мигать. И приведение окна на передний план/фокус в коде - это целая другая червь червей, для которой вы можете использовать Google/SO.

5
private void MainForm_Load(object sender, EventArgs e) 
{ 
    FormSplash splash = new FormSplash(); 
    splash.Show(); 
    splash.Update(); 
    System.Threading.Thread.Sleep(3000); 
    splash.Hide(); 
} 

Я получил это из Интернета где-то, но не могу найти его снова. Простой, но эффективный.

4

Мне очень нравится ответ Аку, но код для C# 3.0 и выше, поскольку он использует функцию лямбда. Для людей, нуждающихся в использовании кода в C# 2.0, вот код, использующий анонимный делегат вместо функции лямбда. Вам нужна самая верхняя winform под названием formSplash с FormBorderStyle = None. Параметр формы TopMost = True важен потому, что экран заставки может выглядеть так, как будто он появляется, а затем быстро исчезает, если он не является самым верхним. Я также выбираю StartPosition=CenterScreen, поэтому похоже, что профессиональное приложение будет делать с заставкой. Если вам нужен еще более холодный эффект, вы можете использовать свойство TrasparencyKey, чтобы создать заставку неправильной формы.

private void formMain_Load(object sender, EventArgs e) 
    { 

    Hide(); 
    bool done = false; 
    ThreadPool.QueueUserWorkItem(delegate 
    { 
     using (formSplash splashForm = new formSplash()) 
     { 
      splashForm.Show(); 
      while (!done) 
       Application.DoEvents(); 
      splashForm.Close(); 
     } 
    }, null); 

    Thread.Sleep(2000); 
    done = true; 
    Show(); 
    } 
3

Я не согласен с другими ответами, рекомендую WindowsFormsApplicationBase. По моему опыту, это может замедлить ваше приложение. Если быть точным, в то время как он запускает конструктор формы параллельно с заставкой, он откладывает событие Shown в форме.

Рассмотрите приложение (без экрана всплесков) с конструктором, который занимает 1 секунду, и обработчик события в Показе, который занимает 2 секунды. Это приложение можно использовать через 3 секунды.

Но предположим, что вы установили заставку с помощью WindowsFormsApplicationBase. Вы можете подумать, что MinimumSplashScreenDisplayTime из 3 секунд разумно и не замедлит ваше приложение. Но попробуйте, ваше приложение теперь займет 5 секунд для загрузки.


class App : WindowsFormsApplicationBase 
{ 
    protected override void OnCreateSplashScreen() 
    { 
     this.MinimumSplashScreenDisplayTime = 3000; // milliseconds 
     this.SplashScreen = new Splash(); 
    } 

    protected override void OnCreateMainForm() 
    { 
     this.MainForm = new Form1(); 
    } 
} 

и

public Form1() 
{ 
    InitializeComponent(); 
    Shown += Form1_Shown; 
    Thread.Sleep(TimeSpan.FromSeconds(1)); 
} 

void Form1_Shown(object sender, EventArgs e) 
{ 
    Thread.Sleep(TimeSpan.FromSeconds(2)); 
    Program.watch.Stop(); 
    this.textBox1.Text = Program.watch.ElapsedMilliseconds.ToString(); 
} 

Вывод: не используйте WindowsFormsApplicationBase, если ваше приложение имеет обработчик на событие Slown. Вы можете написать лучший код, который запускает всплеск параллельно как с конструктором, так и с событием Shown.

0

На самом деле mutlithreading здесь не требуется.

Позвольте вашей бизнес-логике генерировать событие всякий раз, когда вы хотите обновить заставку.

Затем пусть ваша форма обновит заставку соответствующим образом в методе, подключенном к обработчику событий.

Чтобы различать обновления, вы можете запускать различные события или предоставлять данные в классе, унаследованном от EventArgs.

Таким образом, у вас может быть приятный сменный всплеск без какой-либо многопоточной головной боли.

Фактически с этим вы можете даже поддерживать, например, изображение gif на форме всплеска. Чтобы он работал, позвоните Application.DoEvents() в обработчик:

private void SomethingChanged(object sender, MyEventArgs e) 
{ 
    formSplash.Update(e); 
    Application.DoEvents(); //this will update any animation 
} 
Смежные вопросы