Многопоточный заставки в С#?

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

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

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

Ответ 1

Ну, для приложения ClickOnce, которое я развернул в прошлом, мы использовали пространство имен Microsoft.VisualBasic для обработки потоков на заставке. Вы можете ссылаться и использовать сборку Microsoft.VisualBasic из С# в .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% является С#.

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

Ответ 2

Хитрость заключается в том, чтобы создать отдельный поток, отвечающий за отображение заставки.
При запуске приложения .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();
}

Ответ 3

Просматривая все решения 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.

Ответ 4

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

Ответ 5

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

Цитата MSDN:

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

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

Ответ 6

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

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

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

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

Ответ 7

Один простой способ - использовать что-то вроде 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) в конце делает две вещи:

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

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

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

Ответ 8

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

Еще один экран заставки на С#

Ответ 9

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

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

Ответ 10

Мне нравится Aku много, но код для С# 3.0 и выше, поскольку он использует лямбда-функцию. Для людей, которым необходимо использовать код в С# 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();
  }

Ответ 11

Я не согласен с другими ответами, рекомендующими 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.

Ответ 12

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

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

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

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

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

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

private void SomethingChanged(object sender, MyEventArgs e)
{
    formSplash.Update(e);
    Application.DoEvents(); //this will update any animation
}