零、版本
日期 | 备注 |
---|---|
2020.05.01 | 初稿 |
2020.05.02 | 调整章节 |
2020.05.03 | 增加尾声 |
一、什么是启动屏幕
常见的大型软件,如Word、Excel、Photoshop、Visual Studio等等,由于启动时要加载很多模块,为了提高界面响应,都会在启动时会先打开一个小窗口,等到主界面初始化完成之后再关闭。
就像下面这个样子:
通常我们把这个显示启动进度的小界面称为启动屏幕
或者闪屏
,英文大概就叫Splash
。
二、使用启动屏幕的好处
在软件中集成启动屏幕好处有二:
其一:通过适当的反馈可以提升用户体验。
用户双击某个程序后,通过启动屏幕可以知道,程序打开了,正在启动中,需要耐心等待一会儿。
试想,如果用户双击了一个文件,等待5秒钟后还没打开,他的第一反应会不会以为刚刚没点到,再去打开一遍呢?其实进程已经启动了,只是界面还没加载出来,这样的体验是很糟糕的。
其二:彰显品牌概念。
在程序启动过程中显示Logo及程序名称,可以彰显软件品牌以及定位。
启动屏幕上的内容不是随意设置的,一般都会突出显示软件的名称和加载进度。
三、定义SplashWindow
由于工作原因,我经手的项目中也会碰到这种场景。程序启动时需要读取数据库,加载配置,还可能需要联网校验授权码,这些操作都是相当耗时的。
下面以WPF项目为例,说明如何制作一个自定义的闪屏界面。
首先新建一个Window
,这里命名为SplashWindow
,由于项目中用到了一个老外的开源控件包Mahapps.Metro
,这里就毫不吝啬地用了它的ProgressBar,经过一番排版之后,一个像样的启动界面就好了。
这个布局是不是似曾相识?没错,界面布局就是借鉴了微软的Word2016。
什么?你说这是抄袭?读书人怎么能用“抄袭”二字呢?这叫借鉴,借鉴。。。
XAML布局代码如下。
<Window x:Class="SetDataCo.Views.SplashWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mahapps="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:local="clr-namespace:SetDataCo.Views"
mc:Ignorable="d"
Title="{Binding AppName, Mode=OneWay, Source={StaticResource appConst}}"
Icon="{StaticResource appIcon}"
Height="300" Width="550" WindowStartupLocation="CenterScreen"
ResizeMode="NoResize"
Background="{StaticResource AccentBaseColorBrush}"
WindowStyle="None">
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<StackPanel Grid.RowSpan="2">
<TextBlock Text="Data Cooperating System"
FontStyle="Italic"
FontWeight="Bold"
Foreground="White" />
<Image Source="/SetDataCo;component/Resources/Images/AppLogo.png"
Height="64"
Margin="0,40,0,0" />
<TextBlock HorizontalAlignment="Center"
Text="{Binding AppName, Source={StaticResource appConst}}"
Foreground="White"
Margin="0,20,0,0"
FontSize="30">
</TextBlock>
<mahapps:MetroProgressBar IsIndeterminate="True"
Foreground="White"
Margin="0,30,0,0"/>
</StackPanel>
<TextBlock x:Name="txtMessage"
Text="正在启动..."
Foreground="White"
Grid.Row="1"/>
</Grid>
</Window>
给这个SplashWindow定义一个实时显示进度的方法,其实就是更新界面上的文本。
public void SetMessage(string message)
{
this.txtMessage.Text = message;
}
四、调用
接下来就该让这个界面动起来了。在App类中重写OnStartup
方法,新开一个线程,用来打开这个SplashWIndow。
SplashWindow splashWindow = null;
Thread t = new Thread(() =>
{
splashWindow = new SplashWindow();
splashWindow.ShowDialog();
});
t.SetApartmentState(ApartmentState.STA);// 设置单线程
t.Start();
为了保证主线程往下执行时SplashWindow已经初始化完毕,需要等待一下。
do
{
Thread.Sleep(50);
} while (splashWindow == null);
只要调用SetMessage
方法就可以实时更新进度。
splashWindow.Dispatcher.Invoke((Action)(() => splashWindow.SetMessage("初始化主程序...")));
等后台以及主窗口都加载完成再关闭这个SplashWindow。
splashWindow.Dispatcher.Invoke((Action)(() => splashWindow.Close()));
至此,我们便实现了一个自定义的启动屏幕。
五、完整的启动代码
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
SplashWindow splash = null;
Thread t = new Thread(() =>
{
splash = new SplashWindow();
splash.ShowDialog();
});
t.SetApartmentState(ApartmentState.STA);// 设置单线程
t.Start();
do
{
Thread.Sleep(50);
} while (splash == null);
splash.Dispatcher.Invoke((Action)(() => splash.SetMessage("检测运行环境...")));
// 这边可以执行一些校验以及初始化操作
// 加载A模块
InitModuleA();
splash.Dispatcher.Invoke((Action)(() => splash.SetMessage("初始化A模块...")));
// 加载B模块
InitModuleB();
splash.Dispatcher.Invoke((Action)(() => splash.SetMessage("初始化B模块...")));
MainWindow mainWindow = new MainWindow();
Current.MainWindow = mainWindow;
mainWindow.Show();
// 在sw的线程上关闭SplashWindow
if (plash != null)
{
splash.Dispatcher.Invoke((Action)(() => splash.Close()));
}
}
六、尾声
到这里,本文其实就可以结束了。坐在电脑前看这篇文章的你,也许会想,这跟网上看到的有什么区别呢?
其实不然。
按照上面的代码启动后,大部分情况下主窗口都不会再最前方显示,即不是活动窗口,除非设置TopMost
为True
。一旦这么做了,窗口将永远置顶,对用户体验是相当不好的。
可能你又想说,等主窗口加载完了再把TopMost改
为False
不就行了。
我也这么尝试过,经过多次试验,还是有一定概率不会是活动窗口。
其实只需要在启动完成后增加下面这行代码,就能解决烦恼。
mainWindow.Activate();
2020年5月1日星期五