将数据加载到数据网格时 C#/WPF 主窗口冻结
Posted
技术标签:
【中文标题】将数据加载到数据网格时 C#/WPF 主窗口冻结【英文标题】:C#/WPF Main Window freezes when loading data into datagrid 【发布时间】: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 线程中运行。你应该使用这个:***.com/a/14602121/1136211, ***.com/a/39977500/1136211
或者你可以在工作线程中加载项目,存储它们,然后只在ui线程中添加它们。或者您可以异步加载它们并在 ui-thread 中一一添加。
我已经尝试了解决方案,但是当我尝试将项目加载到集合中时应用程序冻结
删除 UpdateSourceTrigger=PropertyChanged 并且在您了解它的作用之前不要再次使用它。
我会在不同的线程上获取数据。将其作为行视图模型列表返回。等待并返回 ui 线程,新建一个 observablecollection,传递 ctor 中的列表。然后将 productcollection 设置为那个。
【参考方案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 中添加另一个线程的项目。非常感谢!以上是关于将数据加载到数据网格时 C#/WPF 主窗口冻结的主要内容,如果未能解决你的问题,请参考以下文章