用WPF实现“等待”小转盘
我们在开发过程中常常会遇到一些比较耗时的操作,比如一次较慢的网络访问,或者复制一个较大的文件等,如果不使用多线程,而是直接用UI线程去做这些动作,那么就会让用户界面失去响应,带来很不好的用户体验。较好的做法是在界面上呈现出一个小动画,用户看到有东西在动,就不认为程序已经死掉,例如这个:
当然了,更好的做法是实现一个进度条,但很多时候我们根本不能获取到进度,所以只能显示这么一个小转盘,小转盘效果的比较简单的实现方法是显示一个小小的gif,但WPF默认的Image控件并不直接支持gif动画效果,所幸的是我们可以通过一个叫“WpfAnimatedGif”第三方的库来很方便地把这个动画效果显示出来,这个库可以通过NuGet获取到,我提供的完整代码下载里也有。这是我的Demo的效果图:
小转盘出现的同时,我们希望能够暂时阻挡用户的操作,我最早想到的办法是把窗口Disable掉,也就是把它的IsEnabled属性设为False,但这样做可能达不到我们想要的效果,因为这样只能把窗口的客户区Disabled掉,而非客户区却仍然可以操作,比如标题栏上的最小化,最大化和关闭按钮,所以比较好的做法是用一个模态对话框。这是我设计的模态对话框:
<Window x:Class="WaitingDemo.WaitingDlg" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:gif="http://wpfanimatedgif.codeplex.com" Title="WaitingDlg" Height="100" Width="400" WindowStyle="None" Background="Transparent" AllowsTransparency="True" WindowStartupLocation="CenterOwner" Closing="Window_Closing" Loaded="Window_Loaded" TextOptions.TextFormattingMode="Display"> <Grid> <Border CornerRadius="5" Height="40" BorderBrush="Black" BorderThickness="1" Background="White" Width="350"> <Border.Effect> <DropShadowEffect Color="Black"></DropShadowEffect> </Border.Effect> <Grid VerticalAlignment="Center"> <Image gif:ImageBehavior.AnimatedSource="loading.gif" Width="28" Height="28" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="5,0,0,0"/> <TextBlock Name="tbPrompt" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.ColumnSpan="2" Margin="0,7">任务执行中...</TextBlock> </Grid> </Border> </Grid> </Window>
这是几个要注意的属性设置:
- WindowStyle - 设置为None来使得这个模态对话框没有标题栏,当然也就没有了最小化、最大化和关闭按钮
- Background - 设置为Transparent来让这个模态对话框背景透明
- AllowsTransparency - 如果不把这个属性设置为True,那模态对话框的依然是不透明的,你将看到一个黑框
- WindowStartupLocation - 设置为CenterOwner让模态对话框默认居中显示
细心的你也许还发觉了:只是不显示关闭按钮还是不够的,用户还是可以通过<Alt>+<F4>来关闭这个对话框,所以需要处理Closing事件,判断这个关闭动作是否我们的程序提出来的。
对于处理任务,我创建了一个接口:
public interface ILongTimeTask { void Start(WaitingDlg dlg); }
将WaitingDlg传入的原因是想让工作线程结束的时候关闭掉这个模态对话框:
public void TaskEnd(Object result) { m_taskResult = result; //用于返回执行的结果(也可以为null) m_bCloseByMe = true; //这个标志表示“关闭”动作由我们的程序提出 Dispatcher.BeginInvoke(new CloseMethod(Close)); //工作线程对界面元素的操作必须用这种调用方式 }
完整代码:下载