Home > Back-end >  C#/WPF Main Window freezes when loading data into datagrid
C#/WPF Main Window freezes when loading data into datagrid

Time:09-23

guys I am currently working on a existing WPF Application build with Prism build and .NET 4.6. I have added a new Page which contains a few User Controls. In one of the User Controls I need to load a csv with a few hundred rows(always under thousand) into a datagrid.

Every time I load the data into the datagrid the whole Application freezes for a few seconds. I have tried to do the loading async with the dispatcher but the application freezes while loading. I have also tried to do it with a task but then I always got an exception("only UI Thread is allowed to change the observable collection")

Below my current async implementation which did not work, any help or idea is highly appreciated.

Thank you

Product 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>

Button 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>

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

Class of the collection

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

CodePudding user response:

You may try something like shown below. It loads the Product collection in a background thread, and only adds them to the ObservableCollection in the UI thread.

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