【问题标题】:C#/WPF Main Window freezes when loading data into datagrid将数据加载到数据网格时 C#/WPF 主窗口冻结
【发布时间】:2021-11-15 21:38:18
【问题描述】:

伙计们,我目前正在使用 Prism 构建和 .NET 4.6 构建现有的 WPF 应用程序。我添加了一个包含一些用户控件的新页面。在其中一个用户控件中,我需要将包含几百行(始终低于千行)的 csv 加载到数据网格中。

每次我将数据加载到数据网格中时,整个应用程序都会冻结几秒钟。我试图与调度程序进行异步加载,但应用程序在加载时冻结。我也试过用一个任务来做,但我总是遇到异常(“只允许 UI 线程更改可观察集合”)

在我当前不起作用的异步实现下面,非常感谢任何帮助或想法。

谢谢

产品 XAML

<UserControl x:Class="Module.UserControls.Products"
             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:local="clr-namespace:Module.UserControls"
             xmlns:mvvm="http://prismlibrary.com/"
             mvvm:ViewModelLocator.AutoWireViewModel="true"
             mc:Ignorable="d" 
             d:DesignHeight="650" d:DesignWidth="800" Background="{DynamicResource Module.Background}" MaxHeight="1500">
    <UserControl.Resources>
        <Style TargetType="DataGridCell">
            <Setter Property="VerticalAlignment" Value="Center" />
        </Style>
        <Style TargetType="TextBox">
            <Setter Property="VerticalContentAlignment" Value="Center" />
            <Setter Property="VerticalAlignment" Value="Center" />
            <Setter Property="FontSize" Value="16" />
            <Setter Property="Foreground" Value="{DynamicResource Module.Textbox.Foreground}" />
            <Setter Property="Margin" Value="5,5,5,5" />
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="true">
                    <Setter Property="ToolTip"
                            Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}" />
                </Trigger>
            </Style.Triggers>
        </Style>
        <Style TargetType="CheckBox">
            <Setter Property="Margin" Value="5,5,5,5" />
            <Setter Property="HorizontalAlignment" Value="Left" />
            <Setter Property="VerticalAlignment" Value="Center" />
        </Style>
        <Style TargetType="Button">
            <Setter Property="FontSize" Value="16" />
            <Setter Property="FontWeight" Value="Normal" />
            <Setter Property="Background" Value="{DynamicResource Module.Button.Background}" />
            <Setter Property="Foreground" Value="White" />
        </Style>
        <Style TargetType="ComboBox">
            <Setter Property="Margin" Value="5,5,5,5" />
            <Setter Property="VerticalAlignment" Value="Center" />
            <Setter Property="IsSynchronizedWithCurrentItem" Value="True" />
        </Style>
    </UserControl.Resources>
    <Grid>
        <Grid Margin="0,20,0,0" Background="{DynamicResource Module.Block.Background}">
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="2*"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Label Grid.Column="0" Grid.Row="0">
                <TextBlock Text="Products" Style="{DynamicResource Module.H2}" />
            </Label>
            <ScrollViewer Grid.Column="0" Grid.Row="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Margin="0,10,0,10" OverridesDefaultStyle="True" >
                <ScrollViewer.Resources>
                    <Style TargetType="{x:Type ScrollBar}">
                        <Setter Property="Background" Value="LightGray"/>
                    </Style>
                </ScrollViewer.Resources>
                <DockPanel  HorizontalAlignment="Stretch">
                    <DataGrid  AutoGenerateColumns = "False" IsReadOnly="False" ItemsSource="{Binding ProductCollection, UpdateSourceTrigger=PropertyChanged, IsAsync=True, Mode=TwoWay}" HorizontalAlignment="Center"  
                          Width="Auto" HorizontalContentAlignment="Center">
                        <DataGrid.Resources>
                            <Style  TargetType="DataGridCell">
                                <Setter Property="Foreground" Value="Black" />
                                <Setter Property="FontSize" Value="16" />
                                <Setter Property="BorderBrush" Value="White" />
                                <Style.Triggers>
                                    <Trigger Property="IsSelected" Value="True">
                                        <Setter Property="Background" Value="{x:Null}" />
                                        <Setter Property="BorderBrush" Value="{x:Null}" />
                                    </Trigger>
                                </Style.Triggers>
                            </Style>
                        </DataGrid.Resources>
                        <DataGrid.Columns>
                            <DataGridTextColumn Header = "Position" Binding="{Binding Path=InProductPos, UpdateSourceTrigger=PropertyChanged}" Width="Auto" MinWidth="120" />
                            <DataGridTextColumn Header = "Layout Position" Binding="{Binding Path = InProductLayout, UpdateSourceTrigger=PropertyChanged}" Width="Auto" MinWidth="120"/>
                            <DataGridTextColumn Header = "Name" Binding="{Binding Path=InProductDescription, UpdateSourceTrigger=PropertyChanged}" Width="Auto" MinWidth="300"/>
                            <DataGridTextColumn Header = "Product Quantity" Binding="{Binding Path=InProductPieces, UpdateSourceTrigger=PropertyChanged}" Width="Auto" MinWidth="120"/>
                            <DataGridTextColumn Header = "Class" Binding="{Binding Path=InProductClass, UpdateSourceTrigger=PropertyChanged}" Width="Auto" MinWidth="120"/>
                            <DataGridTextColumn Header = "Product Part ID" Binding="{Binding Path=InProductPartID, UpdateSourceTrigger=PropertyChanged}" Width="Auto" MinWidth="120"/>
                            <DataGridTextColumn Header = "Number" Binding="{Binding Path=InProductNumber, UpdateSourceTrigger=PropertyChanged}" Width="Auto" MinWidth="120"/>
                            <DataGridTextColumn Header = "Part list Name" Binding="{Binding Path=InProductPartlistName, UpdateSourceTrigger=PropertyChanged}" Width="Auto" MinWidth="300"/>
                        </DataGrid.Columns>
                    </DataGrid>
                </DockPanel>
            </ScrollViewer>
        </Grid>
    </Grid>
</UserControl>

按钮 XAML

<UserControl x:Class="Module.UserControls.ButtonRow"
             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:resx="clr-namespace:Module.Properties"
             xmlns:local="clr-namespace:Module.UserControls"
             xmlns:mvvm="http://prismlibrary.com/"
             mvvm:ViewModelLocator.AutoWireViewModel="true"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <BooleanToVisibilityConverter x:Key="BoolToVis" />
        <Style TargetType="DataGridCell">
            <Setter Property="VerticalAlignment" Value="Center" />
        </Style>
        <Style TargetType="TextBox">
            <Setter Property="VerticalContentAlignment" Value="Center" />
            <Setter Property="FontSize" Value="16" />
            <Setter Property="Foreground" Value="{DynamicResource Module.Textbox.Foreground}" />
            <Setter Property="Margin" Value="5,5,5,5" />
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="true">
                    <Setter Property="ToolTip"
                            Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}" />
                </Trigger>
            </Style.Triggers>
        </Style>
        <Style x:Key="StyleButtonWhite" TargetType="Button">
            <Setter Property="Foreground" Value="White" />
        </Style>
        <Style x:Key="StyleButtonWhiteWhite" TargetType="Button">
            <Setter Property="Foreground" Value="White" />
            <Setter Property="Background" Value="{x:Null}" />
        </Style>
        <Style TargetType="Button">
            <Setter Property="FontSize" Value="16" />
            <Setter Property="FontWeight" Value="Normal" />
            <Setter Property="Background" Value="{DynamicResource Module.Button.Background}" />
            <Setter Property="Foreground" Value="White" />
        </Style>
        <Style TargetType="ComboBox">
            <Setter Property="Margin" Value="5,5,5,5" />
            <Setter Property="VerticalAlignment" Value="Center" />
        </Style>
    </UserControl.Resources>
    <Grid>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,10">
            <Button
                Margin="12,0,12,0"
                Command="{Binding LoadProductsCommand}"
                Width="101" Height="40"
                Style="{StaticResource StyleButtonWhite}"
                Background="{DynamicResource Module.Button.Background}">
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Import Products" Foreground="White" />
                </StackPanel>
            </Button>
        </StackPanel>
    </Grid>
</UserControl>

ViewModel 的一部分

public DelegateCommand LoadProductsCommand => new DelegateCommand(LoadProducts, () => true);

private ObservableCollection<Product> _productCollection;

        public ObservableCollection<Products> ProductCollection
        {
            get => _productCollection;
            set
            {
                _productCollection = value;
                OnPropertyChanged();
            }
        }

        public async void LoadProducts() 
        {
            if (Dispatcher.CurrentDispatcher.CheckAccess())
            {
               await Dispatcher.CurrentDispatcher.InvokeAsync(() =>
               {
                   ProductCollection.AddRange(FileImport.ImportProducts());
               });
            }
        }

集合的类

        public string InProductPos
        {
            get => _inProductPos;
            set
            {
                _inProductPos = value;
                OnPropertyChanged();
            }
        }

        public string InProductLayout
        {
            get => _inProductLayout;
            set
            {
                _inProductLayout = value;
                OnPropertyChanged();
            }
        }

        public string InProductDescription
        {
            get => _inProductDescription;
            set
            {
                _inProductDescription = value;
                OnPropertyChanged();
            }
        }

        public int InProductPieces
        {
            get => _inProductPieces;
            set
            {
                _inProductPieces = value;
                OnPropertyChanged();
            }
        }

        public int InProductClass
        {
            get => _inProductClass;
            set
            {
                _inProductClass = value;
                OnPropertyChanged();
            }
        }

        public int InProductPartID
        {
            get => _inProductPartID;
            set
            {
                _inProductPartID = value;
                OnPropertyChanged();
            }
        }

        public string InProductNumber
        {
            get => _inProductNumber;
            set
            {
                _inProductNumber = value;
                OnPropertyChanged();
            }
        }

        public string InProductPartlistName
        {
            get => _inProductPartlistName;
            set
            {
                _inProductPartlistName = value;
                OnPropertyChanged();
            }
        }

【问题讨论】:

  • Dispatcher.InvokeAsync 在这里没有帮助,因为ProductCollection.AddRange 仍然在 UI 线程中运行。你应该使用这个:stackoverflow.com/a/14602121/1136211, stackoverflow.com/a/39977500/1136211
  • 或者你可以在工作线程中加载项目,存储它们,然后只在ui线程中添加它们。或者您可以异步加载它们并在 ui-thread 中一一添加。
  • 我已经尝试了解决方案,但是当我尝试将项目加载到集合中时应用程序冻结
  • 删除 UpdateSourceTrigger=PropertyChanged 并且在您了解它的作用之前不要再次使用它。
  • 我会在不同的线程上获取数据。将其作为行视图模型列表返回。等待并返回 ui 线程,新建一个 observablecollection,传递 ctor 中的列表。然后将 productcollection 设置为那个。

标签: c# wpf xaml prism


【解决方案1】:

您可以尝试如下所示的方法。它在后台线程中加载 Product 集合,并且只将它们添加到 UI 线程中的 ObservableCollection 中。

public async Task LoadProducts() 
{
    var products = await Task.Run(() => FileImport.ImportProducts());
    
    ProductCollection.AddRange(products);
}

【讨论】:

  • ImportsProducts 方法加载 .csv 文件。 csv 的导入速度足够快,不会冻结应用程序。但正如您提到的,加载到可观察集合中似乎会冻结应用程序。
  • 那么你应该使用 BindingOperations.EnableCollectionSynchronization。
  • 或者将产品添加到多个合理大小的块中,并在其间添加一个短 await Task.Delay(...) 以让 UI 跟上。
  • 一个短暂的任务延迟解决了 UI 冻结,同时在 Observable Collection 中添加另一个线程的项目。非常感谢!
猜你喜欢
  • 2019-02-02
  • 2012-10-08
  • 2011-11-03
  • 2010-12-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多