C# 中的多线程启动屏幕?
我希望在加载应用程序时显示启动屏幕。 我有一个带有系统托盘控件的表单。 我希望在加载此表单时显示初始屏幕,这需要一些时间,因为它正在访问 Web 服务 API 来填充一些下拉列表。 我还想在加载之前对依赖项进行一些基本测试(即Web服务可用,配置文件可读)。 随着启动的每个阶段的进行,我想根据进度更新启动屏幕。
我已经阅读了很多有关线程的内容,但是我不知道应该从哪里控制它(main()
方法?)。 我还缺少 Application.Run()
的工作原理,这是应该从中创建线程的地方吗? 现在,如果带有系统托盘控件的窗体是“活动”窗体,那么启动画面应该来自那里吗? 无论如何,在表单完成之前它不会加载吗?
我不是在寻找代码讲义,更多的是算法/方法,这样我就可以一劳永逸地解决这个问题:)
I want a splash screen to show while the application is loading. I have a form with a system tray control tied to it. I want the splash screen to display while this form loads, which takes a bit of time since it's accessing a web service API to populate some drop-downs. I also want to do some basic testing for dependencies before loading (that is, the web service is available, the configuration file is readable). As each phase of the startup goes, I want to update the splash screen with progress.
I have been reading a lot on threading, but I am getting lost on where this should be controlled from (the main()
method?). I am also missing how Application.Run()
works, is this where the threads for this should be created from? Now, if the form with the system tray control is the "living" form, should the splash come from there? Wouldn't it not load until the form is completed anyway?
I'm not looking for a code handout, more of an algorithm/approach so I can figure this out once and for all :)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(12)
实际上这里的多线程是没有必要的。
每当您想要更新启动屏幕时,让您的业务逻辑生成一个事件。
然后让您的表单在挂接到事件处理程序的方法中相应地更新启动屏幕。
要区分更新,您可以触发不同的事件或在从 EventArgs 继承的类中提供数据。
这样,您就可以拥有漂亮的变化启动屏幕,而无需担心多线程问题。
实际上,您甚至可以在启动画面上支持 gif 图像等。 为了使其正常工作,请在处理程序中调用 Application.DoEvents() :
Actually mutlithreading here is not necessary.
Let your business logic generate an event whenever you want to update splash screen.
Then let your form update the splash screen accordingly in the method hooked to eventhandler.
To differentiate updates you can either fire different events or provide data in a class inherited from EventArgs.
This way you can have nice changing splash screen without any multithreading headache.
Actually with this you can even support, for example, gif image on a splash form. In order for it to work, call Application.DoEvents() in your handler:
我不同意推荐
WindowsFormsApplicationBase
的其他答案。 根据我的经验,它会减慢您的应用程序的速度。 准确地说,当它与启动屏幕并行运行表单的构造函数时,它会推迟表单的 Shown 事件。考虑一个应用程序(没有闪屏),其构造函数需要 1 秒,而 Shown 上的事件处理程序需要 2 秒。 该应用程序在 3 秒后即可使用。
但假设您使用 WindowsFormsApplicationBase 安装启动屏幕。 您可能认为 3 秒的
MinimumSplashScreenDisplayTime
比较合理,不会减慢您的应用程序的速度。 但是,尝试一下,您的应用程序现在需要 5 秒才能加载。结论
:如果您的应用有 Slown 事件处理程序,请不要使用
WindowsFormsApplicationBase
。 您可以编写更好的代码,与构造函数和 Shown 事件并行运行启动。I disagree with the other answers recommending
WindowsFormsApplicationBase
. In my experience, it can slow your app. To be precise, while it runs your form's constructor in parallel with the splash screen, it postpone your form's Shown event.Consider an app (without splashs screen) with a constructor that takes 1 second and a event handler on Shown that takes 2 seconds. This app is usable after 3 seconds.
But suppose you install a splash screen using
WindowsFormsApplicationBase
. You might thinkMinimumSplashScreenDisplayTime
of 3 seconds is sensible and won't slow your app. But, try it, your app will now take 5 seconds to load.and
Conclusion: don't use
WindowsFormsApplicationBase
if your app has a handler on the Slown event. You can write better code that runs the splash in parallel to both the constructor and the Shown event.我非常喜欢 Aku 的答案,但该代码适用于 C# 3.0 及更高版本,因为它使用 lambda 函数。 对于需要使用 C# 2.0 中的代码的人,这里是使用匿名委托而不是 lambda 函数的代码。 您需要一个名为
formSplash
且具有FormBorderStyle = None
的最顶层 winform。 表单的TopMost = True
参数很重要,因为如果不是最顶层,启动屏幕可能看起来像是出现然后很快消失。 我还选择了StartPosition=CenterScreen
,因此它看起来就像专业应用程序对启动屏幕所做的那样。 如果您想要更酷的效果,可以使用TrasparencyKey
属性制作不规则形状的启动屏幕。I like Aku's answer a lot, but the code is for C# 3.0 and up since it uses a lambda function. For people needing to use the code in C# 2.0, here's the code using anonymous delegate instead of the lambda function. You need a topmost winform called
formSplash
withFormBorderStyle = None
. TheTopMost = True
parameter of the form is important because the splash screen might look like it appears then disappears quickly if it's not topmost. I also chooseStartPosition=CenterScreen
so it looks like what a professional app would do with a splash screen. If you want an even cooler effect, you can use theTrasparencyKey
property to make an irregular shaped splash screen.我从互联网上的某个地方得到了这个,但似乎无法再找到它。 简单但有效。
I got this from the Internet somewhere but cannot seem to find it again. Simple but yet effective.
我在 codeproject 上发布了一篇关于将启动屏幕合并到应用程序中的文章。 它是多线程的,您可能会感兴趣
C# 中的另一个启动屏幕
I posted an article on splash screen incorporation in the application at codeproject. It is multithreaded and might be of your interest
Yet Another Splash Screen in C#
一种简单的方法是使用类似 main() 的方法:
当 .NET CLR 启动应用程序时,它会创建一个“主”线程并开始在该线程上执行 main()。 最后的 Application.Run(myMainForm) 做了两件事:
不需要生成一个线程来处理启动窗口,事实上这是一个坏主意,因为这样您就必须使用线程安全技术来更新 main() 中的启动内容。
如果您需要其他线程在应用程序中执行后台操作,您可以从 main() 生成它们。 只需记住将 Thread.IsBackground 设置为 True,这样它们就会在主/GUI 线程终止时死亡。 否则,您将必须自己安排终止所有其他线程,否则当主线程终止时,它们将使您的应用程序保持活动状态(但没有 GUI)。
One simple way is the use something like this as main():
When the .NET CLR starts your application, it creates a 'main' thread and starts executing your main() on that thread. The Application.Run(myMainForm) at the end does two things:
There is no need to spawn a thread to take care of the splash window, and in fact this is a bad idea, because then you would have to use thread-safe techniques to update the splash contents from main().
If you need other threads to do background operations in your application, you can spawn them from main(). Just remember to set Thread.IsBackground to True, so that they will die when the main / GUI thread terminates. Otherwise you will have to arrange to terminate all your other threads yourself, or they will keep your application alive (but with no GUI) when the main thread terminates.
我认为使用诸如 aku's 或 Guy 的 是可行的方法,但从具体示例中可以得出一些结论:
基本前提是尽快在单独的线程上显示您的启动画面。 这就是我倾向于的方式,类似于 aku 所说明的方式,因为这是我最熟悉的方式。 我不知道Guy提到的VB函数。 而且,即使认为它是一个 VB 库,他是对的——最终都是 IL。 所以,即使感觉肮脏,也并没有那么糟糕! :) 我认为您需要确保 VB 在该覆盖中提供一个单独的线程,或者您自己创建一个线程 - 一定要研究这一点。
假设您创建另一个线程来显示此启动画面,您将需要小心跨线程 UI 更新。 我提出这个问题是因为你提到了更新进度。 基本上,为了安全起见,您需要使用委托在启动表单上调用更新函数(您创建的)。 您将该委托传递给初始屏幕表单对象上的 Invoke 函数。 事实上,如果您直接调用启动窗体来更新其上的进度/UI 元素,只要您在 .Net 2.0 CLR 上运行,您就会收到异常。 根据经验,表单上的任何 UI 元素都必须由创建它的线程进行更新——这就是 Form.Invoke 所保证的。
最后,我可能会选择在代码的 main 方法中创建启动画面(如果不使用 VB 重载)。 对我来说,这比让主窗体执行对象的创建并与其紧密绑定要好。 如果您采用这种方法,我建议创建一个闪屏实现的简单界面(例如 IStartupProgressListener),它通过成员函数接收启动进度更新。 这将允许您根据需要轻松地换入/换出任一类,并很好地解耦代码。 如果您在启动完成时发出通知,启动窗体还可以知道何时自行关闭。
I think using some method like aku's or Guy's is the way to go, but a couple of things to take away from the specific examples:
The basic premise would be to show your splash on a separate thread as soon as possible. That's the way I would lean, similar to what aku's illustrated, since it's the way I'm most familiar with. I was not aware of the VB function Guy mentioned. And, even thought it's a VB library, he is right -- it's all IL in the end. So, even if it feels dirty it's not all that bad! :) I think you'll want to be sure that either VB provides a separate thread for in that override or that you create one yourself -- definitely research that.
Assuming you create another thread to display this splash, you will want to be careful of cross thread UI updates. I bring this up because you mentioned updating progress. Basically, to be safe, you need to call an update function (that you create) on the splash form using a delegate. You pass that delegate to the Invoke function on your splash screen's form object. In fact if you call the splash form directly to update progress/UI elements on it, you'll get an exception provided you are running on the .Net 2.0 CLR. As a rule of thumb, any UI element on a form must be updated by the thread that created it -- that's what Form.Invoke insures.
Finally, I would likely opt to create the splash (if not using the VB overload) in the main method of your code. To me this is better than having the main form perform creation of the object and to be so tightly bound to it. If you take that approach, I'd suggest creating a simple interface that the splash screen implements -- something like IStartupProgressListener -- which receives start-up progress updates via a member function. This will allow you to easily swap in/out either class as needed, and nicely decouples the code. The splash form can also know when to close itself if you notify when start-up is complete.
这是一个老问题,但当我尝试为 WPF 寻找可以包含动画的线程启动屏幕解决方案时,我不断遇到它。
这是我最终拼凑而成的:
App.XAML:
App.XAML.cs:
This is an old question, but I kept coming across it when trying to find a threaded splash screen solution for WPF that could include animation.
Here is what I ultimately pieced together:
App.XAML:
App.XAML.cs:
我建议在 aku 提供的答案中的最后一个
Show();
之后直接调用Activate();
。引用MSDN:
如果您不激活主窗体,它可能会显示在任何其他打开的窗口后面,使其看起来有点愚蠢。
I recommend calling
Activate();
directly after the lastShow();
in the answer provided by aku.Quoting MSDN:
If you don't activate your main form, it may be displayed behind any other open windows, making it look a bit silly.
在搜索了 Google 和 SO 的解决方案后,这是我最喜欢的:
http://bytes.com/topic/c-sharp/answers/277446- winform-startup-splash-screen
FormSplash.cs:
Program.cs:
FormMain.cs
我遇到了问题
Microsoft.VisualBasic
解决方案 - 在 XP 上可以找到,但在 Windows 2003 终端服务器上,主应用程序窗体将在后台显示(在启动屏幕之后),并且任务栏将闪烁。 在代码中将窗口置于前台/焦点是另一堆蠕虫,您可以通过 Google/SO 找到它。After looking all over Google and SO for solutions, this is my favorite:
http://bytes.com/topic/c-sharp/answers/277446-winform-startup-splash-screen
FormSplash.cs:
Program.cs:
FormMain.cs
I had issues with the
Microsoft.VisualBasic
solution -- Worked find on XP, but on Windows 2003 Terminal Server, the main application form would show up (after the splash screen) in the background, and the taskbar would blink. And bringing a window to foreground/focus in code is a whole other can of worms you can Google/SO for.嗯,对于我过去部署的 ClickOnce 应用程序,我们使用 Microsoft.VisualBasic 命名空间来处理启动屏幕线程。 您可以在 .NET 2.0 中引用和使用 C# 中的
Microsoft.VisualBasic
程序集,它提供了许多很好的服务。Microsoft.VisualBasic.WindowsFormsApplicationBase
重写“OnCreateSplashScreen”方法,如下所示:
非常简单,它在加载过程中显示您的 SplashForm(您需要创建),然后在主窗体加载完成后自动关闭它。
这确实使事情变得简单,并且
VisualBasic.WindowsFormsApplicationBase
当然经过了 Microsoft 的充分测试,并且具有许多功能,可以使您在 Winforms 中的生活变得更加轻松,即使在 100% 的应用程序中也是如此。 C#。归根结底,这都是 IL 和字节码,所以为什么不使用它呢?
Well, for a ClickOnce app that I deployed in the past, we used the
Microsoft.VisualBasic
namespace to handle the splash screen threading. You can reference and use theMicrosoft.VisualBasic
assembly from C# in .NET 2.0 and it provides a lot of nice services.Microsoft.VisualBasic.WindowsFormsApplicationBase
Override the "OnCreateSplashScreen" method like so:
Very straightforward, it shows your SplashForm (which you need to create) while loading is going on, then closes it automatically once the main form has completed loading.
This really makes things simple, and the
VisualBasic.WindowsFormsApplicationBase
is of course well tested by Microsoft and has a lot of functionality that can make your life a lot easier in Winforms, even in an application that is 100% C#.At the end of the day, it's all IL and
bytecode
anyway, so why not use it?诀窍是创建负责启动屏幕显示的单独线程。
当您运行应用程序时,.net 创建主线程并加载指定的(主)表单。 要隐藏辛苦的工作,您可以隐藏主窗体,直到加载完成。
假设 Form1 - 是您的主窗体,SplashForm 是顶级窗体,边框漂亮的启动窗体:
The trick is to to create separate thread responsible for splash screen showing.
When you run you app .net creates main thread and loads specified (main) form. To conceal hard work you can hide main form until loading is done.
Assuming that Form1 - is your main form and SplashForm is top level, borderles nice splash form: