UI进程的WPF加载动画
本文关键字:加载 动画 WPF 进程 UI | 更新日期: 2023-09-27 18:03:20
在我的WPF应用程序中,我有一个关于框与应用程序的信息和版本。当这个窗口被加载时,它需要一点时间,特别是当它第一次被打开时。我试图实现加载动画,而窗口正在打开,使应用程序继续看起来响应。
我已经尝试使用c# BackgroundWorker来实现这一点,但它不会工作,因为我试图添加加载动画(关于框打开)的过程是一个只能在UI线程上运行。我试着创建一个新的线程,并把它放在一个STA公寓,但它没有工作。
这是我启动about框并控制加载动画的开始/停止的方法:
private void AboutMenuItem_OnClick(object sender, RoutedEventArgs e)
{
LoadingCircle.Start();
LoadingCircle.Visibility = Visibility.Visible;
var aboutBox = new AboutBox { Owner = this };
aboutBox.Show();
LoadingCircle.Stop();
LoadingCircle.Visibility = Visibility.Hidden;
}
加载圈不出现并开始移动,直到aboutBox.Show()
被调用,我不明白为什么会这样。如果我用上面的代码运行我的应用程序,加载圆将在窗口加载之前短暂出现,但它不旋转。
似乎创建短延迟的只是创建窗口,创建AboutBox的代码很简单:
public partial class AboutBox : Window
{
public AboutBox()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Close();
}
}
public class Version
{
public string UiVersion { get; set; }
public string ServiceVersion { get; set; }
public static Version GetVersion()
{
var ver = new Version();
Assembly assembly = Assembly.GetExecutingAssembly();
FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
ver.UiVersion = fvi.FileVersion;
ver.ServiceVersion = "<Service Version>";
return ver;
}
}
这里是XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
x:Class="NDO.PC.DataViewer.AboutBox"
mc:Ignorable="d"
Height="384" Width="600" Title="About NanoDrop One" WindowStartupLocation="CenterOwner" AllowsTransparency="true" WindowStyle="None" Background="White">
<Window.Resources>
<Style x:Key="ButtonFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle Margin="2" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<LinearGradientBrush x:Key="ButtonNormalBackground" EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#F3F3F3" Offset="0"/>
<GradientStop Color="#EBEBEB" Offset="0.5"/>
<GradientStop Color="#DDDDDD" Offset="0.5"/>
<GradientStop Color="#CDCDCD" Offset="1"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ButtonNormalBorder" Color="#FF707070"/>
<Style x:Key="OKButton" TargetType="{x:Type Button}">
<Setter Property="FocusVisualStyle" Value="{StaticResource ButtonFocusVisual}"/>
<Setter Property="Background" Value="{StaticResource ButtonNormalBackground}"/>
<Setter Property="BorderBrush" Value="{StaticResource ButtonNormalBorder}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate x:Name="OKButton" TargetType="{x:Type Button}">
<Border x:Name="Chrome" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true" CornerRadius="5" BorderThickness="1">
<ContentPresenter Name="TextName" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsKeyboardFocused" Value="true">
<Setter Property="BorderBrush" TargetName="Chrome" Value="White"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="#ADADAD"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" Value="White" TargetName="Chrome"/>
<Setter Property="TextBlock.Foreground" Value="#FF0086FF" TargetName="TextName"/>
<Setter Property="BorderBrush" Value="#FF0086FF" TargetName="Chrome"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid x:Uid="ImageGrid" x:Name="ImageGrid" Grid.Row="0" VerticalAlignment="Top" HorizontalAlignment="Left">
<Grid.RowDefinitions>
<RowDefinition Height="79"/>
<RowDefinition Height="13"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Image x:Uid="AboutImage" x:Name="AboutImage" Source="Resources/AboutPageImage.jpg" Width="600" Height="79" Stretch="UniformToFill" />
<Border Grid.Row="1" x:Uid="Border_1" Margin="0,0,0,0" Height="13" MinWidth="600" VerticalAlignment="Bottom">
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<LinearGradientBrush.RelativeTransform>
<TransformGroup>
<ScaleTransform CenterY="0.5" CenterX="0.5"/>
<SkewTransform CenterY="0.5" CenterX="0.5"/>
<RotateTransform Angle="90" CenterY="0.5" CenterX="0.5"/>
<TranslateTransform/>
</TransformGroup>
</LinearGradientBrush.RelativeTransform>
<GradientStop Color="#FFE5EAEE" Offset="1"/>
<GradientStop Color="#FF0086FF" Offset="0.36"/>
</LinearGradientBrush>
</Border.Background>
</Border>
<Grid Margin="36,18,36,36" Grid.Row="2" Height="238">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="128px"/>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock FontFamily="Segoe UI Semibold" FontSize="20" FontWeight="Bold" Foreground="#FF0086FF" Margin="0,0,0,12" VerticalAlignment="Top" HorizontalAlignment="Left"><Run Text="About Application"/></TextBlock>
<StackPanel Orientation="Horizontal">
<TextBlock Foreground="#FF0086FF"
Text="Software UI version: " />
<TextBlock Foreground="#FF0086FF"
Margin="5,0,0,0"
Text="{Binding UiVersion}" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Foreground="#FF0086FF"
Text="Software Service version: " />
<TextBlock Foreground="#FF0086FF"
Margin="5,0,0,0"
Text="{Binding ServiceVersion}" />
</StackPanel>
</StackPanel>
<Image Grid.Column="1" Source="Resources/TS_logo_rgb 200x61with spacing.png" Width="128" VerticalAlignment="Bottom" Margin="0,0,-38,-18"/>
<Button Height="25" Width="75" Background="#FF0086FF" BorderBrush="{x:Null}" Foreground="White" VerticalAlignment="Bottom" HorizontalAlignment="Left" Click="Button_Click" Style="{DynamicResource OKButton}" Content="OK"/>
</Grid>
</Grid>
您试图在UI线程上做所有事情的问题。您需要在后台运行,否则不会阻塞长时间运行的逻辑。
建议使用await/async
。这样就可以了:
LoadingCircle.Start();
LoadingCircle.Visibility = Visibility.Visible;
var aboutBox = new AboutBox { Owner = this };
await Task.Run(() =>
{
aboutBox.Init();
});
aboutBox.Show();
LoadingCircle.Stop();
LoadingCircle.Visibility = Visibility.Hidden;
将长逻辑放入异步Task
中,并停止当前方法的执行,直到它返回。它不会阻塞调用线程。注意,您需要为此将事件处理程序标记为async
。
我在About框上创建了一个假的"Init"方法,这样你的构造函数就可以有效地什么都不做(但是在UI线程上做)。
此外,当使用MVVM以"正确"的方式使用WPF时,这些问题就会消失,所以可以考虑在将来使用这种模式。它迫使您将视图和业务逻辑分开,因此处理后者变得微不足道。
正如前面提到的,您应该分析为什么加载窗口需要这么长时间。你还可以做一些其他的选择:
-
移动代码,使窗口快速显示,然后其他代码受到影响。在实际窗口上使用忙碌指示器来标记它正在加载
-
不要在点击的时候加载about窗口,提前加载,比如在启动的时候,然后在点击按钮的时候显示出来。
-
使用关于窗口的
Window_Loaded
事件来完成工作,将该工作放在其他线程中。不要使用窗口的构造函数来做任何事情。
繁忙指示灯
您应该签出WPF工具包(也可以通过nuget获得),它有一个名为繁忙指示器的控件。
忙碌指示器是一个控件,它只显示进度条或其他内容,并使背景变暗。您只需将属性IsBusy设置为true,它就会打开。如果about窗口做了很多工作,你可以在about窗口上显示这个,直到工作完成。