为什么我的WPF应用程序加载动画钢冻结?

时间:2020-06-07 21:52:50

标签: c# wpf modern-ui

我正在开发用于管理森林目录的桌面应用程序。启动应用程序时,必须从MySql数据库中显示某些数据,这会减慢应用程序的启动速度,因此我想显示一个对话框,其中显示动画gif,直到数据加载完成。 问题是应用程序冻结,并且直到数据加载完成才显示对话框。我搜索了其他帖子,但找不到解决方案。感谢您的帮助。

这是我的MainWindow XAML:

<mui:ModernWindow x:Class="ModernUINavigationApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mui="http://firstfloorsoftware.com/ModernUI"
Title="Media Copy Manager" IsTitleVisible="True"
WindowStartupLocation="CenterScreen"


ContentSource="/gui/Pages/PHome.xaml" WindowState="Maximized">

<mui:ModernWindow.MenuLinkGroups>
    ( ...)
</mui:ModernWindow.MenuLinkGroups>

这是我的PHome.xaml页面:

<UserControl x:Class="MCP.gui.Pages.PHome"
         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"
         xmlns:mui="http://firstfloorsoftware.com/ModernUI"
         mc:Ignorable="d">

<Grid Style="{StaticResource ContentRoot}" Margin="0 0 0 0" Name="_contentRoot">
    <Grid.RowDefinitions>
        <RowDefinition></RowDefinition>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition></ColumnDefinition>
     </Grid.ColumnDefinitions>

    <TabControl Grid.Row="0" Grid.Column="0" Name="_TAB" TabStripPlacement="Left"/>
</Grid>

这是我的Home.cs代码:

{
    public PHome()
    {
        InitializeComponent();
        this.Loaded += ContentLoaded;
    }
    private void ContentLoaded(object sender, RoutedEventArgs e)
    {
         Thread t = new Thread(() =>
         {
             Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
               (Action)(() =>
               {
                   new LoadingDialog().Show();
               }));
         });
         t.SetApartmentState(ApartmentState.STA);
         t.IsBackground = true;
         t.Start();
        Task.Run(() =>
        {
            _TAB.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
                (Action)(() =>
                {
                    Populate_Tab(_TAB);
                }));
        });
    }
    private async void Populate_Tab(TabControl tabControl)
    {
        tabControl.Items.Clear();
        tabControl.ClipToBounds = true;

        List<categoria> ListaCategorias = await DBManager.CategoriasRepo.ListAsync;
        foreach (categoria categ in ListaCategorias)
        {
            TabItem tabitem = new TabItem();
            tabitem.Header = categ.categoria1;
            Thread.Sleep(1000);   //Do some long execution process
            tabControl.Items.Add(tabitem);
        }
    }
}

这是我的LoadingDialog XAML:

                  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" 
                  xmlns:mui="http://firstfloorsoftware.com/ModernUI"
                  mc:Ignorable="d" d:DesignWidth="300"
                  Title="ModernDialog" Background="{x:Null}" Height="197.368">
    <StackPanel>
        <TextBlock>LOADING</TextBlock>
        <mui:ModernProgressRing IsActive="True" Name="_LoaderGif" Width="100" Height="100" Style="{StaticResource ThreeBounceProgressRingStyle}" Margin="47,43,68,65" />
    </StackPanel>
</mui:ModernDialog>

1 个答案:

答案 0 :(得分:0)

您需要更好地了解所使用的方法,尤其是用来尝试将其移动到其他线程上的方法。在我解释之前,请记住以下几点:

  • WPF应用程序中的默认主主线程可以称为UI线程。在该线程上处理用于应用程序渲染和布局的所有代码。除非另有说明,否则您编写的所有代码也会在此线程上处理。
  • 线程一次运行一个方法,因此UI线程上任何长时间运行的方法都将阻止其运行布局和呈现应用程序所需的方法(即它将“冻结”)。
  • 对UI元素(应用程序的外观)的更改必须通过UI线程完成。

您似乎不了解的是Dispatcher.Invoke

就像Task.Run将代码执行移至新线程一样,Dispatcher.Invoke将代码执行移至返回UI主题

让我们采用您的方法ContentLoaded。在您的代码中,您尝试同时使用ThreadTask类将某些代码移到单独的线程上。但是,您从这些新线程中所做的全部工作就是立即调用Dispatcher.Invoke,它会在UI线程上执行代码。从理论上讲,这实际上会使您的应用程序变慢,因为它浪费时间创建新线程并无缘无故地在它们之间跳转。

您需要做的是Populate_Tab,并将其分成两种(或多种)不同的方法:一种会影响UI,而另一种不会。

tabControl.Items.Cleartabitem.Header = categ.categoria1这样的调用必须在UI线程上进行,因为它们直接引用UI元素,但是您长期运行的操作(例如访问数据库)可以并且应该移至另一个线程。

顺序应为:

  • 显示加载图标。
  • 在另一个线程上开始长时间运行的操作。我建议将Taskasync一起使用await,因为这是当前的最佳做法。
  • 获取所需的所有数据,并在另一个线程上进行任何计算。
  • 使用await将所需数据返回到UI线程,并根据需要更新UI。
  • 删除加载图标。