C#中的multithreading启animation面?
我想要一个启animation面显示,而应用程序正在加载。 我有一个与系统托盘控制绑定的表单。 我希望在加载这个表单时显示启animation面,这需要一些时间,因为它正在访问一个Web服务API来填充一些下拉菜单。 我也想在加载前做一些基本的依赖性testing(也就是说,Web服务可用,configuration文件是可读的)。 随着启动的每个阶段,我想要更新启animation面的进展。
我一直在阅读很多关于线程的知识,但是我正在迷失于从main()方法来控制的地方?)。 我也缺less如何Application.Run()
作品,这是应该从哪个线程创build? 现在,如果系统托盘控制的表单是“活”的forms,那么应该从哪里来? 无论如何,它不会加载,直到表单完成?
我不是在寻找代码讲义,更多的是algorithm/方法,所以我可以一劳永逸的了解这一点:)
那么,对于我过去部署的ClickOnce应用程序,我们使用Microsoft.VisualBasic命名空间来处理启animation面线程。 你可以在.NET 2.0中引用和使用C#中的Microsoft.VisualBasic程序集,它提供了很多很好的服务。
- 具有从Microsoft.VisualBasic.WindowsFormsApplicationBaseinheritance的主窗体
-
重写“OnCreateSplashScreen”方法,如下所示:
protected override void OnCreateSplashScreen() { this.SplashScreen = new SplashForm(); this.SplashScreen.TopMost = true; }
非常简单,它表明你的SplashForm(你需要创build),而加载正在进行,然后closures它自动主表单完成加载。
这确实使事情变得简单,而VisualBasic.WindowsFormsApplicationBase当然也经过了Microsoft的很好的testing,并且具有许多function,可以使Winforms中的生活变得更加简单,甚至在100%C#的应用程序中也是如此。
在一天结束的时候,无论如何都是IL和字节码,为什么不使用它呢?
诀窍是创build独立的线程负责启动屏幕显示。
当你运行你的应用程序.net创build主线程并加载指定的(主)forms。 隐藏艰苦的工作,你可以隐藏主窗体,直到加载完成。
假设Form1 – 是你的主要forms和SplashForm是顶级的,边界不错的飞溅forms:
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(); }
看完了谷歌和所有的解决scheme,这是我的最爱: 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
解决scheme的问题 – 在XP上find工作,但在Windows 2003terminal服务器上,主应用程序窗体将在后台显示(闪屏之后),任务栏将闪烁。 把代码中的前景/焦点放在窗口上是Google / SO可以使用的另一种蠕虫。
我build议调用Activate();
直接在最后的Show();
在aku提供的答案。
引用MSDN:
如果这是活动的应用程序,则激活表单会将其显示在前面;如果这不是活动的应用程序,则会激活窗口标题。 该表单必须可见才能使此方法有任何效果。
如果你没有激活你的主窗体,它可能会显示在任何其他打开的窗口后面 ,使它看起来有点傻。
这是一个古老的问题,但我试图find一个WPF的线程启animation面解决scheme,可能包括animation,我不断遇到它。
这是我最终拼凑在一起的:
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(); }
我认为使用像aku或Guy这样的方法是可行的 ,但是要从具体的例子中去除一些东西:
-
基本的前提是将尽快显示在单独的线程上。 这就是我会倾斜的方式,类似于aku的说明,因为这是我最熟悉的方式。 我没有意识到任何人提到的VB函数。 而且,即使认为这是一个VB库,他是对的 – 最终都是IL。 所以,即使感觉脏 ,也不是那么糟糕! :)我想你会想要确保VB提供一个单独的线程,在那个覆盖或你自己创build一个 – 绝对研究。
-
假设你创build另一个线程来显示这个飞溅,你会想要小心跨线程的UI更新。 我提起这个是因为你提到更新进度。 基本上,为了安全起见,您需要使用委托在启动窗体上调用更新函数(您创build的)。 您将该委托传递给启animation面的窗体对象上的调用function。 事实上,如果您直接调用splashforms来更新其上的进度/ UI元素,只要您在.Net 2.0 CLR上运行,就会得到一个exception。 作为一个经验法则,表单上的任何UI元素必须由创build它的线程更新 – 这就是Form.Invoke所保证的。
最后,我可能会select在代码的主要方法中创buildsplash(如果不使用VB重载)。 对我来说,这比主表单执行对象的创build并且紧紧地绑定它要好。 如果你采取这种方法,我会build议创build一个简单的界面,启动屏幕实现 – 像IStartupProgressListener – 通过成员函数接收启动进度更新。 这将允许您根据需要轻松地调入/调出两个类,并很好地分离代码。 如果在启动完成时通知飞溅窗体,也可以知道何时closures自身。
一个简单的方法是使用像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启动您的应用程序时,它将创build一个“主”线程并开始在该线程上执行main()。 Application.Run(myMainForm)最后做了两件事:
- 使用执行main()的线程作为GUI线程启动Windows'消息泵'。
- 指定您的“主窗体”作为应用程序的“closures窗体”。 如果用户closures了这个表单,那么Application.Run()就会终止并返回到你的main(),在那里你可以做任何你想要的closures。
没有必要产生一个线程来照顾飞溅窗口,事实上这是一个坏主意,因为那么你将不得不使用线程安全技术来更新从main()的飞溅内容。
如果您需要其他线程在您的应用程序中执行后台操作,则可以从main()中产生它们。 只要记住将Thread.IsBackground设置为True,以便在主/ GUI线程终止时它们将死亡。 否则,你将不得不安排你自己终止所有其他线程,否则当主线程终止时,它们将保持你的应用程序活着(但没有GUI)。
我在代码项目的应用程序上发布了启animation面合并的文章。 它是multithreading的,可能是你的兴趣所在
另一个在C#中的初始屏幕
private void MainForm_Load(object sender, EventArgs e) { FormSplash splash = new FormSplash(); splash.Show(); splash.Update(); System.Threading.Thread.Sleep(3000); splash.Hide(); }
我从互联网上得到这个,但似乎无法再find它。 简单但有效。
我非常喜欢Aku的回答,但是代码是用于C#3.0以上的,因为它使用了lambda函数。 对于需要在C#2.0中使用代码的人来说,下面是使用匿名代理而不是lambda函数的代码。 你需要一个FormBorderStyle = None
称为formSplash
的最顶层的winform。 该窗体的TopMost = True
参数非常重要,因为初始屏幕可能看起来像是出现在屏幕上,如果它不是最顶层,它会很快消失。 我也selectStartPosition=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
的其他答案。 根据我的经验,这可能会减慢你的应用程序。 更准确地说,当它与启animation面并行运行你的表单的构造函数时,它将推迟你的表单的显示事件。
考虑一个应用程序(没有飞溅屏幕)与一个构造函数,需要1秒和事件处理程序显示需要2秒。 这个应用程序在3秒后可用。
但是,假设您使用WindowsFormsApplicationBase
安装启animation面。 你可能会认为3秒的MinimumSplashScreenDisplayTime
是明智的,不会减慢你的应用程序。 但是,尝试一下,你的应用程序现在需要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(); }
结论:如果您的应用程序在Slown事件上有处理程序,请勿使用WindowsFormsApplicationBase
。 您可以编写更好的代码,并行执行构造函数和Shown事件。
实际上这里multithreading是没有必要的。
让您的业务逻辑生成一个事件,每当你想更新启animation面。
然后让你的表单在挂接到eventhandler的方法中相应地更新启animation面。
要区分更新,您可以触发不同的事件,也可以在从EventArgsinheritance的类中提供数据。
这样你就可以很好的改变启animation面,而不需要multithreading头痛。
实际上,你甚至可以支持,例如,飞溅forms的GIF图像。 为了使其工作,请在您的处理程序中调用Application.DoEvents():
private void SomethingChanged(object sender, MyEventArgs e) { formSplash.Update(e); Application.DoEvents(); //this will update any animation }