Многопоточный экран-заставка в C #? - PullRequest
60 голосов
/ 08 сентября 2008

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

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

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

Ответы [ 12 ]

46 голосов
/ 08 сентября 2008

Что ж, для приложения 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 #. *

В конце концов, это все IL и bytecode в любом случае, так почему бы не использовать его?

46 голосов
/ 08 сентября 2008

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

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

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();
}
13 голосов
/ 14 февраля 2011

После поиска во всем 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 Terminal Server форма основного приложения отображалась (после заставки) в фоновом режиме, и панель задач мигала. А вывод окна на передний план / фокус в коде - это еще одна банка червей, для которой вы можете использовать Google / SO.

9 голосов
/ 21 июля 2010

Это старый вопрос, но я постоянно сталкивался с ним, когда пытался найти многопоточное решение для экрана-заставки для 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();
  }
8 голосов
/ 22 января 2010

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

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

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

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

6 голосов
/ 08 сентября 2008

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

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

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

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

5 голосов
/ 08 сентября 2008

Один простой способ - использовать что-то вроде этого в качестве main ():

<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 запускает ваше приложение, оно создает «основной» поток и начинает выполнять ваш main () в этом потоке. Application.Run (myMainForm) в конце делает две вещи:

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

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

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

4 голосов
/ 19 июля 2011
private void MainForm_Load(object sender, EventArgs e)
{
     FormSplash splash = new FormSplash();
     splash.Show();
     splash.Update();
     System.Threading.Thread.Sleep(3000);
     splash.Hide();
}

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

4 голосов
/ 22 января 2010

Я разместил статью о включении заставки в приложение на codeproject. Он многопоточный и может вас заинтересовать

Еще одна заставка в C #

3 голосов
/ 23 ноября 2011

Мне очень нравится ответ Аку, но код для 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();
  }
...