设计启动屏幕
*********************************************************
版权声明:
此文章是本人正在撰写之.NET 4.0著作中的一部分,出于技术共享与交流目的而发布,作者金旭亮拥有全部版权。任何人及机构不得将其用于商业用途,如收费培训及出版同类技术书籍,有这方面需求的个人及培训机构请与本人直接联系。
本人联系方式:
金旭亮
2009.8.26
*************************************************************************
许多大家熟知的应用程序都有启动屏幕(另一常见称呼为“启动窗体”),比如Word在启动时就会先呈现一张图片,然后再显示主窗体。
另外一些应用程序具有更复杂的功能的启动窗体,比如著名的PhotoShop软件在启动屏幕上会动态扫描并装载用户安装的各种插件,并在启动窗体上显示出相关信息。
在这一小节中,我们将介绍两种类型的启动屏幕:一种似于Word,仅快速显示一张图片以通知用户“程序已经运行,请稍候……”,另一种则类似于PhotoShop,在显示启动屏幕的同时,程序会在后台进行系统初始化。
第一种方式的启动屏幕非常易于实现,甚至简单到不用写一行代码。
第二种方式则比较复杂一些,详细分析一下:
这种类型的应用程序在程序初起时都有两个线程,一个是主线程,通常负责在完成所有的系统初始化工作后显示主窗体,而启动屏幕则由另一线程负责创建并显示,通常要求将主线程所执行系统初始化的信息显示在启动屏幕上,很明显,这是一个两个线程同步及跨线程访问可视化控件的问题。如果读者阅读了本书前面的章节,则在这儿解决这个问题已没有任何难度,但仍然需要有一些技巧。
1在程序启动时显示一张图片
这是最容易实现的一种启动屏幕。只要安装了.NET Framework 3.5 SP1以上的版本,可以不需要写一句代码。其步骤如下:
1 在Visual Studio 2008 SP1或Visual Studio 2010中将启动屏幕图片加入到项目中。
2 设定此图片的“Build Action”属性为“SplashScreen”(图 1):
图1 为启动屏幕图片文件设置“Build Action”
编译并运行程序,可以看到你所选择的图片在屏幕上显示约0.5秒后自动消失,程序主窗体出现。
2 多功能程序启动屏幕的实现
请看示例程序SplashScreenForWPF,程序运行时如图2所示:
图2 启动屏幕
此示例程序在运行时启动了两个线程:主线程和UI线程,这两个线程所完成的工作如图3所示:
图3 两个线程的任务流程图
图3中,双向箭头表示这两个线程的这两个处理工作之间存在着线程同步关系。下面简要叙述一下示例程序中的技术关键点。
使用Visual studio创建WPF应用程序时,默认情况下会生成一个App.xaml和App.xaml.cs作为程序的入口点,但如果要显示启动屏幕,就不能使用它来启动程序了。
首先删除这两个文件,然后,向项目中添加一个Program.cs类文件,此文件与Visual studio为控制台应用程序生成的结构一样,也是在Program类中放置一个Main()函数作为程序入口点。
双击“解决方案资源管理器”中项目节点下的“Properties”节点,在打开的项目属性卡片中设置程序的启动对象为Program类,这将确保程序从Main()函数开始执行。
下面向项目中添加一个将作为启动屏幕的窗体,可以根据你的美术天份自行设计,在本示例中,我就放了一个背景图片,一个TextBlock用于显示文字信息,一个ProgressBar用于显示工作进度。
很重要的,由于启动屏幕的窗体需要被主线程访问,因此,需要给其添加一个公有的方法用于显示外部传过来的信息
public void ShowProgress(int Value)
{
pgbProcess.Value = Value;
tbInfo.Text ="已完成"+ Value.ToString() + "%";
}
所有的关键工作由运行于主线程中的Main()函数完成。
[STAThread]
static void Main()
{
//在一个独立UI线程中显示启动屏幕
Thread th = new Thread(ShowSplashScreenThenMainWindow);
th.SetApartmentState(ApartmentState.STA);
th.Start();
//启动初始化过程
SystemInit();
}
注意需要设置线程模式为STA,这是为了与Windows Form相兼容而一直沿用下来的。
上述代码中的线程函数ShowSplashScreenThenMainWindow()负责创建启动屏幕和显示主窗体:
static winSplash win = null; //引用启动屏幕对象
static void ShowSplashScreenThenMainWindow()
{
win = new winSplash();
win.ShowDialog();
//显示主窗体
Application myApp = new Application();
myApp.Run(new winMain());
}
注意上述代码中通过调用ShowDialog()方法显示启动屏幕并阻塞当前UI线程的进一步执行。
主线程在启动UI线程之后,调用SystemInit()方法开始系统初始化过程,在此可以放置各种初始化代码,然后使用win.Dispatcher.Invoke和BeginInvoke方法在启动屏幕上显示信息。
这里面的关键之处在于,SystemInit()方法在执行时必须确保启动屏幕窗体已经显示并可以接收外界传入的信息,为此,示例程序设置了一个ManualResetEvent对象作为线程同步对象,并将其初始化为non-singaled状态:
public static ManualResetEvent mre = new ManualResetEvent(false);
在SystemInit()方法的开头,调用
mre.WaitOne();
阻塞等待通知。
而在启动屏幕的Loaded事件(它表示窗体已初始化完成)中触发ManualResetEvent对象的状态转换:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
//通知主线程自己已经启动完毕
Program.mre.Set();
}
当系统初始化完成,SystemInit()方法再通过win.Dispatcher.BeginInvoke方法向UI线程提交一个“关闭启动屏幕”的工作项请求,从而导致UI线程关闭启动屏幕,进而创建应用程序主窗体并显示,应用程序就可以被用户所访问了。
上面就是本示例的所有技术关键点,其余的技术细节请自行阅读源码。
这个示例采用WPF开发,但其中所介绍的技巧完全可用于Windows Form。笔者开发了对应的Windows Form,其示例项目名为SplashScreenForWinForm,供读者参考。
===============================
(注:给的示例项目源码为VS2010格式,使用VS2008的读者请新建一个空白的Windows Form或WPF项目,然后手工向其中追加文件)。