首页 > 解决方案 > 将数据加载到数据网格时 C#/WPF 主窗口冻结

问题描述

伙计们,我目前正在使用 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>

视图模型的一部分

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();
            }
        }

标签: c#wpfxamlprism

解决方案


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

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

推荐阅读