Многопоточный заставки в С#?
Я хочу, чтобы во время загрузки приложения отображался заставочный экран. У меня есть форма с привязкой к панели управления системой. Я хочу, чтобы заставка отображалась во время загрузки этой формы, что занимает некоторое время с момента доступа к API веб-службы для заполнения некоторых раскрывающихся списков. Я также хочу провести некоторое базовое тестирование зависимостей перед загрузкой (то есть веб-сервис доступен, файл конфигурации доступен для чтения). Поскольку каждая фаза запуска идет, я хочу обновить заставку с прогрессом.
Я много читал о потоках, но заблудился из-за того, откуда это нужно контролировать (метод main()
?). Мне также не хватает как работает Application.Run()
, это где потоки для этого должны быть созданы? Теперь, если форма с управлением в системном трее является "живой" формой, должен ли всплеск исходить оттуда? Разве он не загрузится, пока форма не будет заполнена в любом случае?
Я не ищу раздаточный материал, скорее алгоритм/подход, чтобы я мог понять это раз и навсегда :)
Ответы
Ответ 1
Ну, для приложения ClickOnce, которое я развернул в прошлом, мы использовали пространство имен Microsoft.VisualBasic
для обработки потоков на заставке. Вы можете ссылаться и использовать сборку Microsoft.VisualBasic
из С# в .NET 2.0, и она предоставляет множество полезных сервисов.
- Основная форма наследуется от
Microsoft.VisualBasic.WindowsFormsApplicationBase
-
Переопределите метод "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
}