c# - UI 进程的 WPF 加载动画

标签 c# wpf multithreading animation wpf-animation

在我的 WPF 应用程序中,我有一个包含应用程序信息和版本的关于框。加载此窗口需要一点时间,尤其是第一次打开时。我正在尝试在窗口打开时实现加载动画,以便应用程序继续响应。

我试过使用 C# BackgroundWorker实现这个,但它不会工作,因为我试图为(关于框打开)添加加载动画的过程是 can only be run on the UI thread .我试过创建一个新线程并将其放置在 STA 公寓中,但没有成功。

这是我启动关于框并控制加载动画开始/停止的方法:

        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

我在“关于”框中创建了一个伪造的“Init”方法,这样您的构造函数实际上什么都不做(但在 UI 线程上执行)。

此外,当使用 MVVM 以“正确”方式执行 WPF 时,这些问题就会消失,因此请考虑在将来使用该模式。它迫使您将 View 和业务逻辑分开,以便对后者进行线程处理变得微不足道。

关于c# - UI 进程的 WPF 加载动画,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31480944/

相关文章:

c# - 我应该绑定(bind)到 ICollectionView 还是 ObservableCollection

python - 每次迭代在多个 CPU 上训练不同的 scikit-learn 分类器

c# - 如何在 C# 中将一个 int 转换为两个字节?

c# - WPF - 我可以使用常量作为资源键名称吗?

c# - TaskContinuationOptions.OnlyOnFaulted 不考虑使用异常的流量控制?

wpf - DataContext 用于在 DataTemplate 中将 UserControl 与 ViewModel 绑定(bind)

python - pyqt中标签随时间变化

java - 在同步方法内等待信号量

c# - 使用 OWIN Identity v2 Claims 跟踪 WebAPI2 应用程序中的自定义属性

c# - 创建我自己的异常 c#