C#/WPF Main Window 在将数据加载到数据网格时冻结
C#/WPF Main Window freezes when loading data into datagrid
伙计们,我目前正在使用 Prism build 和 .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>
视图模型的一部分
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());
});
}
}
Class 合集
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();
}
}
您可以尝试如下所示的操作。它在后台线程中加载 Product 集合,并且仅将它们添加到 UI 线程中的 ObservableCollection。
public async Task LoadProducts()
{
var products = await Task.Run(() => FileImport.ImportProducts());
ProductCollection.AddRange(products);
}
伙计们,我目前正在使用 Prism build 和 .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>
视图模型的一部分
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());
});
}
}
Class 合集
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();
}
}
您可以尝试如下所示的操作。它在后台线程中加载 Product 集合,并且仅将它们添加到 UI 线程中的 ObservableCollection。
public async Task LoadProducts()
{
var products = await Task.Run(() => FileImport.ImportProducts());
ProductCollection.AddRange(products);
}