WPF load data asynchronouly and batch by batch, virtualization, haraware accleration which will enhance performance and resolve UI block issues.
Install-Package CommunityToolkit.mvvm;
1.Virtualization
//Virtualization <DataGrid ItemsSource="{Binding BooksCollection,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" VirtualizingPanel.IsVirtualizing="True" EnableColumnVirtualization="True" VirtualizingPanel.IsContainerVirtualizable="True" VirtualizingPanel.VirtualizationMode="Recycling" AutoGenerateColumns="False" CanUserAddRows="False"> </DataGrid>
2. The Two Ways to Render: Hardware vs. Software
-
Hardware Rendering (Using the GPU):
-
What it is: WPF uses your computer's graphics card to draw the visuals. Graphics cards are built specifically for this job and are extremely fast at it.
-
Analogy: It's like hiring a team of professional painters (the GPU) with specialized spray guns. They are very fast and efficient, especially for complex scenes.
-
Benefit: Much better performance. Animations are smoother, scrolling is faster, and the main CPU is free to handle your application's logic.
-
-
Software Rendering (Using the CPU):
-
What it is: WPF uses the computer's main processor to calculate and draw every single pixel.
-
Analogy: It's like asking the architect (the CPU) to paint the entire building by hand with a tiny brush. It's possible, but it's slow and uses a lot of their energy.
-
Drawback: Much worse performance. It can make your UI feel sluggish, especially if it's complex.
-
//Hardware acceleration public MainWindow() { InitializeComponent(); //Render by gpu RenderOptions.ProcessRenderMode = RenderMode.Default; var vm = new MainVM(this); this.DataContext = vm; }
RenderMode.Default;
Use your automatic detection system. If my user's graphics card is good and compatible, use it for a fast, smooth experience. But if their graphics card or drivers are problematic, switch to the slower but safer software mode to prevent errors.
3.Load asynchronously small batch by batch instead of one huge block at once
Dispatcher.Invoke
is synchronous. Every batch blocks the background thread until the UI finishes rendering those 500 rows. That kills parallelism and can still cause UI stutter with 10,000,000
items.
Dispatcher.BeginInvoke
(asynchronous) instead of Invoke
so the background thread can keep producing while the UI thread consumes at its own pace.
private async Task LoadDataAsync() { string dir = @"../../../Images"; if (!Directory.Exists(dir)) { return; } BooksCollection=new ObservableCollection<Book>(); await Task.Run(() => { var imgs = Directory.GetFiles(dir); int imgsCount = imgs.Count(); var list = new List<Book>(); for (int i = 1; i<=10000000; i++) { list.Add(new Book() { Id=i, Name=$"Name_{i}", Author=$"Author_{i}", Chapter=$"Chapter_{i}", Summary=$"Summary_{i}", Title=$"Title_{i}", Topic=$"Topic_{i}", Press=$"Press_{i}", ISBN=$"ISBN_{i}", ImgSource=GetImgSourceFromImgUrl(imgs[i%imgsCount]) }); if (i%500==0) { var chunks = list.ToList(); list.Clear(); Application.Current?.Dispatcher.Invoke(() => { foreach (var bk in chunks) { BooksCollection.Add(bk); } }); } } }); }
//
//xaml <Window x:Class="WpfApp70.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp70" WindowState="Maximized" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <DataGrid ItemsSource="{Binding BooksCollection,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" VirtualizingPanel.IsVirtualizing="True" EnableColumnVirtualization="True" VirtualizingPanel.IsContainerVirtualizable="True" VirtualizingPanel.VirtualizationMode="Recycling" AutoGenerateColumns="False" CanUserAddRows="False"> <DataGrid.ItemContainerStyle> <Style TargetType="{x:Type DataGridRow}"> <Setter Property="Height" Value="{Binding DataContext.RowHeight, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}"/> <Setter Property="Width" Value="{Binding DataContext.RowWidth, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}"/> <Setter Property="FontSize" Value="30"/> <Setter Property="VerticalAlignment" Value="Center"/> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="FontSize" Value="50"/> <Setter Property="Foreground" Value="Red"/> </Trigger> </Style.Triggers> </Style> </DataGrid.ItemContainerStyle> <DataGrid.Columns> <DataGridTemplateColumn> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <Grid Width="{Binding DataContext.RowWidth, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}"> <Grid.Background> <ImageBrush ImageSource="{Binding ImgSource}"/> </Grid.Background> <Grid.Resources> <Style TargetType="{x:Type TextBlock}"> <Setter Property="VerticalAlignment" Value="Center"/> </Style> </Grid.Resources> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <TextBlock Text="{Binding Id}" Grid.Column="0" VerticalAlignment="Center" /> <TextBlock Text="{Binding Author}" Grid.Column="1"/> <TextBlock Text="{Binding Name}" Grid.Column="2" /> <TextBlock Text="{Binding Chapter}" Grid.Column="3" /> <TextBlock Text="{Binding Summary}" Grid.Column="4" /> <TextBlock Text="{Binding Title}" Grid.Column="5" /> <TextBlock Text="{Binding Topic}" Grid.Column="6"/> <TextBlock Text="{Binding Press}" Grid.Column="7" /> <TextBlock Text="{Binding ISBN}" Grid.Column="8" /> </Grid> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> </DataGrid> </Grid> </Window> //cs using CommunityToolkit.Mvvm.ComponentModel; using System.Collections.ObjectModel; using System.IO; using System.Windows; using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Imaging; namespace WpfApp70 { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); RenderOptions.ProcessRenderMode = RenderMode.Default; var vm = new MainVM(this); this.DataContext = vm; } } public partial class MainVM : ObservableObject { private Window win; public MainVM(Window winValue) { win=winValue; if (win!=null) { win.SizeChanged+=Win_SizeChanged; } LoadDataAsync(); } private async Task LoadDataAsync() { string dir = @"../../../Images"; if (!Directory.Exists(dir)) { return; } BooksCollection=new ObservableCollection<Book>(); await Task.Run(() => { var imgs = Directory.GetFiles(dir); int imgsCount = imgs.Count(); var list = new List<Book>(); for (int i = 1; i<=10000000; i++) { list.Add(new Book() { Id=i, Name=$"Name_{i}", Author=$"Author_{i}", Chapter=$"Chapter_{i}", Summary=$"Summary_{i}", Title=$"Title_{i}", Topic=$"Topic_{i}", Press=$"Press_{i}", ISBN=$"ISBN_{i}", ImgSource=GetImgSourceFromImgUrl(imgs[i%imgsCount]) }); if (i%500==0) { var chunks = list.ToList(); list.Clear(); Application.Current?.Dispatcher.Invoke(() => { foreach (var bk in chunks) { BooksCollection.Add(bk); } }); } } }); } private void Win_SizeChanged(object sender, SizeChangedEventArgs e) { var fe = win.Content as FrameworkElement; if (fe!=null) { RowWidth = fe.ActualWidth; RowHeight = fe.ActualHeight/2; } } private ImageSource GetImgSourceFromImgUrl(string imgUrl) { if (!File.Exists(imgUrl)) { return null; } BitmapImage bmi = new BitmapImage(); bmi.BeginInit(); bmi.UriSource=new Uri(imgUrl, UriKind.RelativeOrAbsolute); bmi.EndInit(); if (bmi.CanFreeze) { bmi.Freeze(); } return bmi; } [ObservableProperty] private ObservableCollection<Book> booksCollection; [ObservableProperty] private double rowHeight; [ObservableProperty] private double rowWidth; } public class Book { public int Id { get; set; } public string Author { get; set; } public string Name { get; set; } public string Chapter { get; set; } public string Summary { get; set; } public string Title { get; set; } public string Topic { get; set; } public string Press { get; set; } public string ISBN { get; set; } public ImageSource ImgSource { get; set; } } }