这是自己平时根据自己需要写的一些小代码,未必对大家有用。另外,这是根据个人想法而写,未必严谨和符合设计原则,若有任何不妥之处,还请不吝赐教。
提示窗体在所有软件中都是必不可少的。其特征在于按需显示一个对话框,原操作界面被禁用,程序继续运行;工作完成后,再将对话框关闭。
看似很简单的一个功能,但由于涉及到了线程、窗体之间的微妙关系,其实现要比想象中的要复杂得多。
最简单的办法是,直接在工作中调用Form.Show显示提示窗体,然后继续工作,完成后调用Form.Close关闭提示窗体。使用这个方法的程序看起来能够正常工作,不过,由于非模态窗口与创建窗口共用窗口线程,因此,实际上,调用Show后,虽然提示窗体能够显示,程序也没有被阻止,但由于窗口线程仍在执行工作,因此提示窗口将无法响应,其界面也将无法刷新。
既然不能在同一个线程中,那自然就新建个线程来显示了。
FrmFlexLabelTest dialog = new FrmFlexLabelTest(); Thread th = new Thread(new ThreadStart(dialog.Show)); th.Start(); Thread.Sleep(5000); dialog.Invoke(new ThreadStart(dialog.Close));
看起来挺好一段代码,不过执行后就会发现问题所在。程序执行后,弹出窗体一闪即逝,主窗体等待5秒后恢复正常。仔细一想,就不难发现问题所在:由于Show是非阻止函数,线程th在执行完dialog.Show使窗体显示出来之后就立即退出了,而由于dialog在线程th上显示,th的终止也导致了dialog的关闭。
然后,这里有一点小小的疑问。调用了dialog.Show的线程为什么没有成为窗体线程呢?哈,突然想到了Program.cs的里那句Application.Run,找出来看了一下提示:在当前线程上创建一个标准消息循环…。果不其然,看来问题在这个,那就改在th上调用Application.Run好了:
private void button4_Click(object sender, EventArgs e) { FrmFlexLabelTest dialog = new FrmFlexLabelTest(); Thread th = new Thread(new ParameterizedThreadStart(this.Run)); th.Start(dialog); Thread.Sleep(5000); dialog.Invoke(new ThreadStart(dialog.Close)); } public void Run(object obj) { Application.Run(obj as Form); }
再执行一遍,这次终于正常了。弹出窗体能够响应和被关闭,原线程也未阻塞。基本上,用上面的方式就能实现一个用于显示提示窗体的功能接口了。当然,由于ShowDialog是阻塞函数,将Application.Run换成ShowDialog也可达到同样的效果。
不过,这样的一个实现虽然可用,但其效率却让人难以恭维。使用Show打开的窗体关闭后,窗体即被销毁,同时消息循环的终止也导致窗体线程的终止。也就是说,每显示一次提示窗体的代价是创建一个窗体、销毁一个窗体、创建一个线程并为其创建消息队列。显然,这样的代价很难让人接受。那么,又应当如何提高改进呢?同样显然的是,在一次程序运行期间,提示窗体总是要显示很多次。因此,若能重用窗体对象与线程,则可以均分创建、销毁它们的代价。要达到重用的目的,上述代码需要做两个改变:
1. 使用ShowDialog显示提示窗体,使窗体对象可重复显示。
2. 让线程在循环中调用ShowDialog显示窗体,窗体关闭后线程即转入挂起或休眠,直到需要再次显示窗体时被唤醒。
注(疑问):其实ShowDialog也应该是创建一个新线程来作为窗体线程的,这么说来的,创建线程和消息队列的代价似乎不可避免。