在实际项目中,有些后台操作耗时较久,这时前台最好有一个动画加载框给用户提示。
该加载框的动画显示和后台操作需要同时进行,因此需要采用多线程的方式进行实现。
UI界面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| <Window x:Class="TestWPF.LoadingBar" 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:local="clr-namespace:TestWPF" mc:Ignorable="d" ResizeMode="NoResize" WindowStyle="None" WindowStartupLocation="CenterScreen" Background="#E0E0E0" SizeToContent="WidthAndHeight" MaxWidth="200">
<StackPanel Orientation="Vertical"> <StackPanel.Resources> <Style x:Key="rec" TargetType="Rectangle"> <Setter Property="Width" Value="13"/> <Setter Property="Height" Value="30"/> <Setter Property="Margin" Value="4,0"/> <Setter Property="Fill" Value="#f1404b"/> </Style> <PowerEase x:Key="powerEase" Power="3" EasingMode="EaseInOut"/> </StackPanel.Resources>
<StackPanel.Triggers> <EventTrigger RoutedEvent="Loaded"> <BeginStoryboard> <Storyboard RepeatBehavior="Forever" Storyboard.TargetProperty="Height"> <DoubleAnimation Storyboard.TargetName="rec1" To="55" BeginTime="0:0:0.0" Duration="0:0:0.2" EasingFunction="{StaticResource powerEase}" AutoReverse="True"/> <DoubleAnimation Storyboard.TargetName="rec2" To="55" BeginTime="0:0:0.1" Duration="0:0:0.2" EasingFunction="{StaticResource powerEase}" AutoReverse="True"/> <DoubleAnimation Storyboard.TargetName="rec3" To="55" BeginTime="0:0:0.2" Duration="0:0:0.2" EasingFunction="{StaticResource powerEase}" AutoReverse="True"/> <DoubleAnimation Storyboard.TargetName="rec4" To="55" BeginTime="0:0:0.3" Duration="0:0:0.2" EasingFunction="{StaticResource powerEase}" AutoReverse="True"/> <DoubleAnimation Storyboard.TargetName="rec5" To="55" BeginTime="0:0:0.4" Duration="0:0:0.2" EasingFunction="{StaticResource powerEase}" AutoReverse="True"/> <DoubleAnimation Storyboard.TargetName="rec6" To="55" BeginTime="0:0:0.5" Duration="0:0:0.2" EasingFunction="{StaticResource powerEase}" AutoReverse="True"/> </Storyboard> </BeginStoryboard> </EventTrigger> </StackPanel.Triggers>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Height="60"> <Rectangle x:Name="rec1" Style="{StaticResource rec}"/> <Rectangle x:Name="rec2" Style="{StaticResource rec}"/> <Rectangle x:Name="rec3" Style="{StaticResource rec}"/> <Rectangle x:Name="rec4" Style="{StaticResource rec}"/> <Rectangle x:Name="rec5" Style="{StaticResource rec}"/> <Rectangle x:Name="rec6" Style="{StaticResource rec}"/> </StackPanel>
<TextBlock Text="{Binding Prompt}" FontWeight="Bold" Margin="0,2" TextWrapping="Wrap" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.ColumnSpan="3" Grid.Row="1"/> </StackPanel> </Window>
|
后台代码
后台代码是本窗体的重点,涉及到很多编程技巧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
| using System; using System.ComponentModel; using System.Runtime.CompilerServices; using System.Threading; using System.Windows;
namespace TestWPF { public partial class LoadingBar : Window, INotifyPropertyChanged { private LoadingBar() { InitializeComponent();
ShowInTaskbar = false; DataContext = this; Topmost = true; }
private static LoadingBar instance = null;
private static object loadingBarLock = new object();
private static bool isClosed = true;
#region 静态方法 public static void ShowLoadingbar(string prompt = "处理中...") { isClosed = false; Thread thread = new Thread(() => { bool lockTaken = false; Monitor.TryEnter(loadingBarLock, 1000, ref lockTaken);
if (lockTaken) { if (instance == null) instance = new LoadingBar(); instance.Prompt = prompt; instance.ShowDialog(); Monitor.Exit(loadingBarLock); } }); thread.SetApartmentState(ApartmentState.STA); thread.Start(); }
public static void SetPrompt(string prompt = "处理中...") { if (instance != null) instance.Prompt = prompt; }
public static void CloseLoadingBar() { if (isClosed) return;
while (true) { if (instance != null && instance.IsVisible) { instance.Dispatcher.Invoke(() => { instance.Close(); instance = null; isClosed = true; }); return; } } } #endregion
#region 定义可通知UI的属性 private String _prompt; public string Prompt { get { return _prompt; } set { if (_prompt != value) { _prompt = value; OnPropertyChanged(); } } }
public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } #endregion } }
|
效果
LoadingBar.ShowLoadingbar()
可以显示加载框,且可以传入参数,用来设置加载框的提示语。
LoadingBar.SetPrompt()
可以设置提示语。
LoadingBar.CloseLoadingBar()
关闭加载框。
特点
- 加锁控制,防止多个线程同时显示进度框。
- 可设置提示语并中途修改。
- 稳定性较好,对于以下非常规调用依然有效。
1 2 3 4 5 6 7 8 9 10
| private void btn01_Click(object sender, RoutedEventArgs e) { LoadingBar.CloseLoadingBar(); LoadingBar.ShowLoadingbar(); LoadingBar.ShowLoadingbar(); LoadingBar.ShowLoadingbar(); Thread.Sleep(2000); LoadingBar.CloseLoadingBar(); LoadingBar.CloseLoadingBar(); LoadingBar.CloseLoadingBar(); }
|