我想在应用程序加载时显示启动画面.我有一个带有系统托盘控件的表单.我希望在加载此表单时显示启动画面,这需要一些时间,因为它正在访问Web服务API以填充一些下拉菜单.我还想在加载之前对依赖项进行一些基本测试(也就是说,Web服务可用,配置文件是可读的).随着启动的每个阶段的进行,我想要通过进度更新启动屏幕.
我一直在阅读很多关于线程的内容,但是我对它应该控制的地方感到迷茫(main()方法?).我也想知道它是如何main()
工作的,这是应该创建的线程吗?现在,如果带有系统托盘控件的表单是"生命"表单,那么它应该来自那里吗?在形式完成之前不会加载吗?
我不是在寻找代码讲义,更多的是算法/方法,所以我可以一劳永逸地解决这个问题:)
诀窍是创建单独的线程负责启动屏幕显示.
运行时,app .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(); }
好吧,对于我过去部署的ClickOnce应用程序,我们使用Microsoft.VisualBasic命名空间来处理启动画面线程.您可以在.NET 2.0中引用和使用C#中的Microsoft.VisualBasic程序集,它提供了许多不错的服务.
主表单是否继承自Microsoft.VisualBasic.WindowsFormsApplicationBase
像这样覆盖"OnCreateSplashScreen"方法:
protected override void OnCreateSplashScreen() { this.SplashScreen = new SplashForm(); this.SplashScreen.TopMost = true; }
非常简单,它会在加载过程中显示您的SplashForm(您需要创建),然后在主窗体完成加载后自动关闭它.
这真的让事情变得简单,VisualBasic.WindowsFormsApplicationBase当然经过了微软的测试,并且有很多功能可以让你在Winforms中的生活变得更轻松,即使在100%C#的应用程序中也是如此.
在一天结束时,无论如何都是IL和字节码,为什么不使用呢?
在查看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(); } ////// Show the Splash Screen (Loading...) /// 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); } ////// Close the splash (Loading...) screen /// 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 { ////// The main entry point for the application. /// [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的蠕虫.
这是一个老问题,但在尝试为WPF找到可能包含动画的线程闪屏解决方案时,我一直遇到它.
这是我最终拼凑在一起的内容:
App.xaml中:
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> DelftRed..:我建议在aku提供的答案中
Activate();
的最后一个之后直接打电话Show();
.引用MSDN:
如果这是活动应用程序,则激活表单会将其显示在前面,如果这不是活动应用程序,则会闪烁窗口标题.该表单必须可见才能使此方法产生任何效果.
如果您没有激活主表单,它可能会显示在任何其他打开的窗口后面,使它看起来有点傻.
6> Peter Meyer..:我认为使用像aku或Guy这样的方法是要走的路,但是从具体的例子中可以看出几件事:
基本前提是尽快在单独的线程上显示您的启动.这就是我倾向的方式,类似于aku的说明,因为这是我最熟悉的方式.我不知道Guy提到的VB函数.而且,即使认为它是一个VB库,他也是对的 - 最终它都是IL.所以,即使它感觉很脏,也不是那么糟糕!:)我认为你要确保VB提供一个单独的线程,或者你自己创建一个 - 绝对是研究.
假设您创建另一个线程来显示此启动,您将需要小心跨线程UI更新.我提出这个问题是因为你提到了更新进度.基本上,为了安全起见,您需要使用委托在splash表单上调用更新函数(您创建的).您将该委托传递到初始屏幕的表单对象上的Invoke函数.实际上,如果您直接调用splash表单来更新其上的progress/UI元素,只要您在.Net 2.0 CLR上运行,就会遇到异常.根据经验,表单上的任何UI元素都必须由创建它的线程更新 - 这就是Form.Invoke所保证的.
最后,我可能会选择在代码的main方法中创建启动(如果不使用VB重载).对我来说,这比主表单执行对象的创建并且与它紧密绑定更好.如果采用这种方法,我建议创建一个简单的界面,即启动画面实现 - 类似于IStartupProgressListener - 它通过成员函数接收启动进度更新.这将允许您根据需要轻松地交换/输出任一类,并很好地解耦代码.如果在启动完成时通知,则启动表单还可以知道何时关闭自身.
7> McKenzieG1..:一种简单的方法是使用像main()这样的东西:
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)最后做了两件事:
使用已执行main()作为GUI线程的线程启动Windows'消息泵'.
将"主表单"指定为应用程序的"关闭表单".如果用户关闭该表单,则Application.Run()终止并控制返回到main(),您可以在其中执行任何所需的关闭.
没有必要产生一个线程来处理启动窗口,事实上这是一个坏主意,因为那时你必须使用线程安全技术来更新main()中的启动内容.
如果您需要其他线程在您的应用程序中执行后台操作,您可以从main()生成它们.只需记住将Thread.IsBackground设置为True,这样它们就会在主/ GUI线程终止时死掉.否则,您将不得不安排自己终止所有其他线程,或者当主线程终止时,它们将使您的应用程序保持活动状态(但没有GUI).