Многопоточный экран-заставка на C#?
-
09-06-2019 - |
Вопрос
Я хочу, чтобы во время загрузки приложения отображалась заставка.У меня есть форма с привязанным к ней элементом управления на панели задач.Я хочу, чтобы экран-заставка отображался во время загрузки этой формы, что занимает некоторое время, поскольку он обращается к API веб-службы для заполнения некоторых раскрывающихся списков.Я также хочу провести базовое тестирование зависимостей перед загрузкой (то есть веб-сервис доступен, файл конфигурации доступен для чтения).По ходу каждого этапа запуска я хочу обновлять заставку с прогрессом.
Я много читал о многопоточности, но теряюсь в том, откуда этим следует управлять ( main()
метод?).мне тоже не хватает как Application.Run()
работает, отсюда следует создавать темы для этого?Вот если форма с элементом управления в системном трее является "живой" формой, то заставка должна идти оттуда?Разве он все равно не загрузится, пока форма не будет заполнена?
Мне не нужен раздаточный материал по коду, а скорее алгоритм/подход, чтобы я мог разобраться в этом раз и навсегда :)
Решение
Что ж, для приложения ClickOnce, которое я развернул ранее, мы использовали метод Microsoft.VisualBasic
пространство имен для обработки потоков заставки.Вы можете ссылаться и использовать Microsoft.VisualBasic
сборку из C# в .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% состоит из C#.
В конце концов, это все ИЛ и bytecode
в любом случае, так почему бы не использовать его?
Другие советы
Хитрость заключается в том, чтобы создать отдельный поток, отвечающий за показ заставки.
Когда вы запускаете приложение .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();
}
После поиска решений в Google и SO это мое любимое:http://bytes.com/topic/c-sharp/answers/277446-winform-startup-splash-screen
ФормаСплэш.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();
}
}
Программа.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.
Это старый вопрос, но я постоянно сталкивался с ним, пытаясь найти решение для многопоточного экрана-заставки для WPF, которое могло бы включать анимацию.
Вот что я в итоге собрал воедино:
Приложение.XAML:
<Application Startup="ApplicationStart" …
Приложение.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();
}
Я рекомендую позвонить Activate();
сразу после последнего Show();
в ответе, предоставленном aku.
Цитирую MSDN:
Активация формы приводит ее к фронту, если это активное приложение, или она мигает заголовок окна, если это не активное приложение.Форма должна быть видна для этого метода, чтобы иметь какой -либо эффект.
Если вы не активируете свою основную форму, она может отображаться позади любые другие открытые окна, из-за чего это выглядит немного глупо.
Я думаю, используя какой-то метод, например аку или Ребята это правильный путь, но из конкретных примеров следует вынести несколько вещей:
Основной предпосылкой было бы как можно скорее показать вашу заставку в отдельной теме.Я бы наклонился именно так, как показано на рисунке Аку, поскольку именно этот способ мне наиболее знаком.Я не знал о функции VB, упомянутой Гаем.И даже подумал, что это ВБ библиотека, он прав — в конце концов, это все ИЛ.Итак, даже если это кажется грязный не все так плохо!:) Я думаю, вам нужно быть уверенным, что либо VB предоставляет отдельный поток для этого переопределения, либо вы создаете его самостоятельно - обязательно изучите это.
Предполагая, что вы создаете еще один поток для отображения этой заставки, вам следует быть осторожным с обновлениями пользовательского интерфейса между потоками.Я поднял этот вопрос, потому что вы упомянули прогресс в обновлении.По сути, на всякий случай вам нужно вызвать функцию обновления (которую вы создаете) в форме-заставке с помощью делегата.Вы передаете этот делегат в Вызов функция на объекте формы вашего экрана-заставки.Фактически, если вы вызываете форму-заставку напрямую, чтобы обновить в ней элементы прогресса/UI, вы получите исключение, если вы работаете в среде .Net 2.0 CLR.Как правило, любой элемент пользовательского интерфейса в форме должен обновляться потоком, который его создал — именно это и гарантирует Form.Invoke.
Наконец, я бы, скорее всего, решил создать заставку (если не использовать перегрузку VB) в основном методе вашего кода.На мой взгляд, это лучше, чем когда основная форма выполняет создание объекта и так тесно с ним связана.Если вы воспользуетесь этим подходом, я бы предложил создать простой интерфейс, который реализует экран-заставка — что-то вроде IStartupProgressListener — который получает обновления о ходе запуска через функцию-член.Это позволит вам легко менять местами любой класс по мере необходимости и прекрасно отделяет код.Заставка также может знать, когда следует закрыться, если вы уведомите ее о завершении запуска.
Один простой способ — использовать что-то вроде этого в качестве 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(), в качестве потока графического интерфейса.
- Назначает вашу «основную форму» как «форму завершения работы» приложения.Если пользователь закрывает эту форму, то Application.Run() завершается, и управление возвращается в ваш main(), где вы можете выполнить любое завершение работы по вашему желанию.
Нет необходимости создавать поток для обслуживания окна-заставки, и на самом деле это плохая идея, потому что тогда вам придется использовать потокобезопасные методы для обновления содержимого заставки из main().
Если вам нужны другие потоки для выполнения фоновых операций в вашем приложении, вы можете создать их из main().Просто не забудьте установить для Thread.IsBackground значение True, чтобы они умирали при завершении основного потока/потока графического интерфейса.В противном случае вам придется самостоятельно завершить все остальные потоки, иначе они сохранят работоспособность вашего приложения (но без графического интерфейса), когда основной поток завершится.
Я опубликовал статью о включении заставки в приложение на сайте codeproject.Он многопоточный и может вас заинтересовать.
private void MainForm_Load(object sender, EventArgs e)
{
FormSplash splash = new FormSplash();
splash.Show();
splash.Update();
System.Threading.Thread.Sleep(3000);
splash.Hide();
}
Я взял это где-то в Интернете, но больше не могу найти.Простой, но эффективный.
Мне очень нравится ответ Аку, но код предназначен для 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();
}
Я не согласен с другими ответами, рекомендующими WindowsFormsApplicationBase
.По моему опыту, это может замедлить работу вашего приложения.Точнее, хотя он запускает конструктор вашей формы параллельно с заставкой, он откладывает событие Shown вашей формы.
Рассмотрим приложение (без экрана-заставки) с конструктором, который занимает 1 секунду, и обработчиком событий Shown, который занимает 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.
На самом деле многопоточность здесь не нужна.
Пусть ваша бизнес-логика генерирует событие всякий раз, когда вы хотите обновить заставку.
Затем позвольте вашей форме соответствующим образом обновить заставку в методе, подключенном к обработчику событий.
Чтобы различать обновления, вы можете либо запускать разные события, либо предоставлять данные в классе, унаследованном от EventArgs.
Таким образом, вы можете иметь красивый изменяющийся экран-заставку без головной боли при многопоточности.
На самом деле с помощью этого вы даже можете поддерживать, например, gif-изображение на заставке.Чтобы это работало, вызовите Application.DoEvents() в своем обработчике:
private void SomethingChanged(object sender, MyEventArgs e)
{
formSplash.Update(e);
Application.DoEvents(); //this will update any animation
}