2025-11-12
C#
00

目录

🔍 问题分析:为什么WPF DataGrid会卡死?
数据绑定的性能陷阱
WinForm与WPF的核心差异
💡 解决方案:分页+筛选的完美组合
🚀 核心思路
🔧 代码实战:打造高性能DataGrid
数据模型设计
数据服务层
转换器
ViewModel设计
XAML界面设计
主窗口代码
⚡ 性能优化技巧
虚拟化设置
数据绑定优化
异步加载最佳实践
💡 进阶优化建议
缓存策略
预加载策略
🎊 总结

你刚从传统的WinForm DataGridView转向WPF的DataGrid,满怀期待地加载了10万条数据,结果界面直接卡死30秒,用户体验瞬间崩塌,事实上在Winform下10万条其实也最好用虚数据加载或分页了。

本文将带你彻底解决WPF DataGrid的大数据分页与筛选难题,让你的应用从"卡顿王"变成"性能王"。

🔍 问题分析:为什么WPF DataGrid会卡死?

数据绑定的性能陷阱

WPF的数据绑定机制虽然强大,但也带来了性能挑战:

  1. UI线程阻塞:大量数据绑定时,UI线程被占用
  2. 内存消耗激增:每个数据项都会创建对应的UI元素
  3. 虚拟化失效:不正确的绑定方式会导致虚拟化机制失效

WinForm与WPF的核心差异

C#
// WinForm 传统做法(性能较好) dataGridView1.DataSource = dataTable; // 直接绑定,这块Winform还是可以的 // WPF 错误做法(性能灾难) dataGrid.ItemsSource = database.GetAllRecords(); // 一次性加载所有数据

💡 解决方案:分页+筛选的完美组合

🚀 核心思路

  1. 服务端分页:只加载当前页数据
  2. 虚拟化优化:启用行虚拟化和列虚拟化
  3. 异步加载:避免阻塞UI线程
  4. 智能缓存:缓存常用页面数据

🔧 代码实战:打造高性能DataGrid

数据模型设计

C#
using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; namespace AppDataGridPage { // 分页请求模型 public class PageRequest { public int PageIndex { get; set; } = 1; public int PageSize { get; set; } = 50; public string SearchText { get; set; } = ""; public string SortColumn { get; set; } = ""; public bool IsAscending { get; set; } = true; } // 分页结果模型 public class PageResult<T> { public List<T> Data { get; set; } public int TotalCount { get; set; } public int PageCount { get; set; } public int CurrentPage { get; set; } } // 业务数据模型 public class Employee : INotifyPropertyChanged { private string _name; private string _department; private decimal _salary; public int Id { get; set; } public string Name { get => _name; set { _name = value; OnPropertyChanged(); } } public string Department { get => _department; set { _department = value; OnPropertyChanged(); } } public decimal Salary { get => _salary; set { _salary = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }

数据服务层

C#
using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using System.Windows.Input; using System.Windows; namespace AppDataGridPage { public class EmployeeViewModel : INotifyPropertyChanged { private readonly EmployeeService _employeeService; private ObservableCollection<Employee> _employees; private int _currentPage = 1; private int _pageSize = 50; private int _totalCount; private int _pageCount; private string _searchText = ""; private bool _isLoading; public EmployeeViewModel() { _employeeService = new EmployeeService(); Employees = new ObservableCollection<Employee>(); // 🎯 命令初始化 LoadDataCommand = new RelayCommand(async () => await LoadDataAsync()); SearchCommand = new RelayCommand(async () => await SearchAsync()); FirstPageCommand = new RelayCommand(async () => await GoToPageAsync(1)); PreviousPageCommand = new RelayCommand(async () => await GoToPageAsync(CurrentPage - 1)); NextPageCommand = new RelayCommand(async () => await GoToPageAsync(CurrentPage + 1)); LastPageCommand = new RelayCommand(async () => await GoToPageAsync(PageCount)); // 初始加载 _ = LoadDataAsync(); } #region 属性 public ObservableCollection<Employee> Employees { get => _employees; set { _employees = value; OnPropertyChanged(); } } public int CurrentPage { get => _currentPage; set { _currentPage = value; OnPropertyChanged(); OnPropertyChanged(nameof(PageInfo)); } } public int PageSize { get => _pageSize; set { _pageSize = value; OnPropertyChanged(); } } public int TotalCount { get => _totalCount; set { _totalCount = value; OnPropertyChanged(); OnPropertyChanged(nameof(PageInfo)); } } public int PageCount { get => _pageCount; set { _pageCount = value; OnPropertyChanged(); OnPropertyChanged(nameof(PageInfo)); } } public string SearchText { get => _searchText; set { _searchText = value; OnPropertyChanged(); } } public bool IsLoading { get => _isLoading; set { _isLoading = value; OnPropertyChanged(); } } public string PageInfo => $"第 {CurrentPage} 页,共 {PageCount} 页,总计 {TotalCount} 条记录"; #endregion #region 命令 public ICommand LoadDataCommand { get; } public ICommand SearchCommand { get; } public ICommand FirstPageCommand { get; } public ICommand PreviousPageCommand { get; } public ICommand NextPageCommand { get; } public ICommand LastPageCommand { get; } #endregion #region 方法 /// <summary> /// 加载数据 - 核心方法 /// </summary> private async Task LoadDataAsync() { IsLoading = true; try { var request = new PageRequest { PageIndex = CurrentPage, PageSize = PageSize, SearchText = SearchText }; var result = await _employeeService.GetPagedEmployeesAsync(request); // 🔄 UI线程更新 Application.Current.Dispatcher.Invoke(() => { Employees.Clear(); foreach (var employee in result.Data) { Employees.Add(employee); } TotalCount = result.TotalCount; PageCount = result.PageCount; CurrentPage = result.CurrentPage; }); } catch (Exception ex) { MessageBox.Show($"加载数据失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } finally { IsLoading = false; } } /// <summary> /// 搜索功能 /// </summary> private async Task SearchAsync() { CurrentPage = 1; // 搜索时重置到第一页 await LoadDataAsync(); } /// <summary> /// 跳转到指定页 /// </summary> private async Task GoToPageAsync(int page) { if (page < 1 || page > PageCount) return; CurrentPage = page; await LoadDataAsync(); } #endregion public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } // 简单的Command实现 public class RelayCommand : ICommand { private readonly Func<Task> _execute; private readonly Func<bool> _canExecute; public RelayCommand(Func<Task> execute, Func<bool> canExecute = null) { _execute = execute ?? throw new ArgumentNullException(nameof(execute)); _canExecute = canExecute; } public bool CanExecute(object parameter) => _canExecute?.Invoke() ?? true; public async void Execute(object parameter) => await _execute(); public event EventHandler CanExecuteChanged { add => CommandManager.RequerySuggested += value; remove => CommandManager.RequerySuggested -= value; } } }

转换器

C#
using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Data; using System.Windows; namespace AppDataGridPage { /// <summary> /// 布尔值到可见性转换器 /// </summary> public class BooleanToVisibilityConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is bool boolValue) { // 支持反向转换:参数为"Inverse"时,true显示为Hidden/Collapsed bool inverse = parameter?.ToString() == "Inverse"; if (inverse) { return boolValue ? Visibility.Collapsed : Visibility.Visible; } else { return boolValue ? Visibility.Visible : Visibility.Collapsed; } } return Visibility.Collapsed; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { if (value is Visibility visibility) { bool inverse = parameter?.ToString() == "Inverse"; if (inverse) { return visibility != Visibility.Visible; } else { return visibility == Visibility.Visible; } } return false; } } /// <summary> /// 字符串空值到可见性转换器 /// </summary> public class StringNullOrEmptyToVisibilityConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { bool isEmpty = string.IsNullOrEmpty(value?.ToString()); bool inverse = parameter?.ToString() == "Inverse"; if (inverse) { return isEmpty ? Visibility.Visible : Visibility.Collapsed; } else { return isEmpty ? Visibility.Collapsed : Visibility.Visible; } } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } }

ViewModel设计

C#
public class EmployeeViewModel : INotifyPropertyChanged { private readonly EmployeeService _employeeService; private ObservableCollection<Employee> _employees; private int _currentPage = 1; private int _pageSize = 50; private int _totalCount; private int _pageCount; private string _searchText = ""; private bool _isLoading; public EmployeeViewModel() { _employeeService = new EmployeeService(); Employees = new ObservableCollection<Employee>(); // 🎯 命令初始化 LoadDataCommand = new RelayCommand(async () => await LoadDataAsync()); SearchCommand = new RelayCommand(async () => await SearchAsync()); FirstPageCommand = new RelayCommand(async () => await GoToPageAsync(1)); PreviousPageCommand = new RelayCommand(async () => await GoToPageAsync(CurrentPage - 1)); NextPageCommand = new RelayCommand(async () => await GoToPageAsync(CurrentPage + 1)); LastPageCommand = new RelayCommand(async () => await GoToPageAsync(PageCount)); // 初始加载 _ = LoadDataAsync(); } #region 属性 public ObservableCollection<Employee> Employees { get => _employees; set { _employees = value; OnPropertyChanged(); } } public int CurrentPage { get => _currentPage; set { _currentPage = value; OnPropertyChanged(); OnPropertyChanged(nameof(PageInfo)); } } public int PageSize { get => _pageSize; set { _pageSize = value; OnPropertyChanged(); } } public int TotalCount { get => _totalCount; set { _totalCount = value; OnPropertyChanged(); OnPropertyChanged(nameof(PageInfo)); } } public int PageCount { get => _pageCount; set { _pageCount = value; OnPropertyChanged(); OnPropertyChanged(nameof(PageInfo)); } } public string SearchText { get => _searchText; set { _searchText = value; OnPropertyChanged(); } } public bool IsLoading { get => _isLoading; set { _isLoading = value; OnPropertyChanged(); } } public string PageInfo => $"第 {CurrentPage} 页,共 {PageCount} 页,总计 {TotalCount} 条记录"; #endregion #region 命令 public ICommand LoadDataCommand { get; } public ICommand SearchCommand { get; } public ICommand FirstPageCommand { get; } public ICommand PreviousPageCommand { get; } public ICommand NextPageCommand { get; } public ICommand LastPageCommand { get; } #endregion #region 方法 /// <summary> /// 加载数据 - 核心方法 /// </summary> private async Task LoadDataAsync() { IsLoading = true; try { var request = new PageRequest { PageIndex = CurrentPage, PageSize = PageSize, SearchText = SearchText }; var result = await _employeeService.GetPagedEmployeesAsync(request); // 🔄 UI线程更新 Application.Current.Dispatcher.Invoke(() => { Employees.Clear(); foreach (var employee in result.Data) { Employees.Add(employee); } TotalCount = result.TotalCount; PageCount = result.PageCount; CurrentPage = result.CurrentPage; }); } catch (Exception ex) { MessageBox.Show($"加载数据失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } finally { IsLoading = false; } } /// <summary> /// 搜索功能 /// </summary> private async Task SearchAsync() { CurrentPage = 1; // 搜索时重置到第一页 await LoadDataAsync(); } /// <summary> /// 跳转到指定页 /// </summary> private async Task GoToPageAsync(int page) { if (page < 1 || page > PageCount) return; CurrentPage = page; await LoadDataAsync(); } #endregion public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } // 简单的Command实现 public class RelayCommand : ICommand { private readonly Func<Task> _execute; private readonly Func<bool> _canExecute; public RelayCommand(Func<Task> execute, Func<bool> canExecute = null) { _execute = execute ?? throw new ArgumentNullException(nameof(execute)); _canExecute = canExecute; } public bool CanExecute(object parameter) => _canExecute?.Invoke() ?? true; public async void Execute(object parameter) => await _execute(); public event EventHandler CanExecuteChanged { add => CommandManager.RequerySuggested += value; remove => CommandManager.RequerySuggested -= value; } }

XAML界面设计

XML
<Window x:Class="AppDataGridPage.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:AppDataGridPage" mc:Ignorable="d" Title="员工管理系统" Height="550" Width="900" WindowStartupLocation="CenterScreen" ResizeMode="CanResize" Background="#F5F5F5"> <Window.Resources> <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/> <!-- 🎨 现代化按钮样式 --> <Style x:Key="ModernButton" TargetType="Button"> <Setter Property="Background" Value="#2196F3"/> <Setter Property="Foreground" Value="White"/> <Setter Property="BorderThickness" Value="0"/> <Setter Property="FontSize" Value="12"/> <Setter Property="FontWeight" Value="Medium"/> <Setter Property="Cursor" Value="Hand"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Border Background="{TemplateBinding Background}" CornerRadius="4" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="#1976D2"/> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter Property="Background" Value="#0D47A1"/> </Trigger> <Trigger Property="IsEnabled" Value="False"> <Setter Property="Background" Value="#BDBDBD"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <!-- 🔍 搜索按钮样式 --> <Style x:Key="SearchButton" TargetType="Button" BasedOn="{StaticResource ModernButton}"> <Setter Property="Background" Value="#4CAF50"/> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="#388E3C"/> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter Property="Background" Value="#2E7D32"/> </Trigger> </Style.Triggers> </Style> <!-- 🔄 重置按钮样式 --> <Style x:Key="ResetButton" TargetType="Button" BasedOn="{StaticResource ModernButton}"> <Setter Property="Background" Value="#FF9800"/> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="#F57C00"/> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter Property="Background" Value="#E65100"/> </Trigger> </Style.Triggers> </Style> <!-- 📄 分页按钮样式 --> <Style x:Key="PaginationButton" TargetType="Button" BasedOn="{StaticResource ModernButton}"> <Setter Property="Background" Value="#607D8B"/> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="#455A64"/> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter Property="Background" Value="#263238"/> </Trigger> </Style.Triggers> </Style> <!-- 🎯 现代化文本框样式 --> <Style x:Key="ModernTextBox" TargetType="TextBox"> <Setter Property="Background" Value="White"/> <Setter Property="BorderBrush" Value="#E0E0E0"/> <Setter Property="BorderThickness" Value="1"/> <Setter Property="Padding" Value="10,5"/> <Setter Property="FontSize" Value="12"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="TextBox"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="4"> <ScrollViewer x:Name="PART_ContentHost" Padding="{TemplateBinding Padding}"/> </Border> <ControlTemplate.Triggers> <Trigger Property="IsFocused" Value="True"> <Setter Property="BorderBrush" Value="#2196F3"/> <Setter Property="BorderThickness" Value="2"/> </Trigger> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="BorderBrush" Value="#BDBDBD"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <!-- 📊 现代化DataGrid样式 --> <Style x:Key="ModernDataGrid" TargetType="DataGrid"> <Setter Property="Background" Value="White"/> <Setter Property="BorderBrush" Value="#E0E0E0"/> <Setter Property="BorderThickness" Value="1"/> <Setter Property="GridLinesVisibility" Value="Horizontal"/> <Setter Property="HorizontalGridLinesBrush" Value="#F0F0F0"/> <Setter Property="RowHeaderWidth" Value="0"/> <Setter Property="FontSize" Value="12"/> <!-- 🎯 重点:优化选中单元格样式 --> <Setter Property="CellStyle"> <Setter.Value> <Style TargetType="DataGridCell"> <Setter Property="BorderThickness" Value="0"/> <Setter Property="Padding" Value="10,8"/> <Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="DataGridCell"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}"> <ContentPresenter VerticalAlignment="{TemplateBinding VerticalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/> </Border> </ControlTemplate> </Setter.Value> </Setter> <Style.Triggers> <!-- 移除默认的选中单元格样式 --> <Trigger Property="IsSelected" Value="True"> <Setter Property="Background" Value="Transparent"/> <Setter Property="Foreground" Value="White"/> <Setter Property="BorderBrush" Value="Transparent"/> </Trigger> <!-- 移除焦点样式 --> <Trigger Property="IsFocused" Value="True"> <Setter Property="Background" Value="Transparent"/> <Setter Property="BorderBrush" Value="Transparent"/> </Trigger> </Style.Triggers> </Style> </Setter.Value> </Setter> </Style> <!-- 📋 优化后的DataGrid行样式 --> <Style x:Key="ModernDataGridRow" TargetType="DataGridRow"> <Setter Property="Height" Value="45"/> <Setter Property="Background" Value="White"/> <Setter Property="BorderThickness" Value="0"/> <Setter Property="Margin" Value="0,1"/> <Style.Triggers> <!-- 🎯 悬停效果 --> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background"> <Setter.Value> <LinearGradientBrush StartPoint="0,0" EndPoint="1,0"> <GradientStop Color="#F0F8FF" Offset="0"/> <GradientStop Color="#E6F3FF" Offset="1"/> </LinearGradientBrush> </Setter.Value> </Setter> <Setter Property="Effect"> <Setter.Value> <DropShadowEffect Color="#2196F3" Opacity="0.3" BlurRadius="5" ShadowDepth="0"/> </Setter.Value> </Setter> </Trigger> <!-- 🌟 重点:优化选中行样式 --> <Trigger Property="IsSelected" Value="True"> <Setter Property="Background"> <Setter.Value> <LinearGradientBrush StartPoint="0,0" EndPoint="1,0"> <GradientStop Color="#2196F3" Offset="0"/> <GradientStop Color="#1976D2" Offset="1"/> </LinearGradientBrush> </Setter.Value> </Setter> <Setter Property="Foreground" Value="White"/> <Setter Property="FontWeight" Value="Medium"/> <Setter Property="Effect"> <Setter.Value> <DropShadowEffect Color="#1976D2" Opacity="0.4" BlurRadius="8" ShadowDepth="2"/> </Setter.Value> </Setter> </Trigger> <!-- 🎨 交替行背景 --> <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=ItemsControl.AlternationIndex}" Value="1"> <Setter Property="Background" Value="#FAFAFA"/> </DataTrigger> <!-- 🎯 选中且悬停的组合效果 --> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="IsSelected" Value="True"/> <Condition Property="IsMouseOver" Value="True"/> </MultiTrigger.Conditions> <Setter Property="Background"> <Setter.Value> <LinearGradientBrush StartPoint="0,0" EndPoint="1,0"> <GradientStop Color="#1976D2" Offset="0"/> <GradientStop Color="#0D47A1" Offset="1"/> </LinearGradientBrush> </Setter.Value> </Setter> <Setter Property="Effect"> <Setter.Value> <DropShadowEffect Color="#0D47A1" Opacity="0.5" BlurRadius="10" ShadowDepth="3"/> </Setter.Value> </Setter> </MultiTrigger> </Style.Triggers> </Style> <!-- 🎯 现代化DataGrid列头样式 --> <Style x:Key="ModernDataGridColumnHeader" TargetType="DataGridColumnHeader"> <Setter Property="Background" Value="#F8F9FA"/> <Setter Property="Foreground" Value="#495057"/> <Setter Property="FontWeight" Value="SemiBold"/> <Setter Property="FontSize" Value="12"/> <Setter Property="Height" Value="40"/> <Setter Property="BorderBrush" Value="#DEE2E6"/> <Setter Property="BorderThickness" Value="0,0,1,1"/> <Setter Property="Padding" Value="12,0"/> <Setter Property="HorizontalContentAlignment" Value="Left"/> <Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="DataGridColumnHeader"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}"> <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="#E9ECEF"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <!-- 💫 加载动画样式 --> <Style x:Key="LoadingText" TargetType="TextBlock"> <Setter Property="FontSize" Value="12"/> <Setter Property="FontWeight" Value="Medium"/> <Setter Property="Foreground" Value="#2196F3"/> <Style.Triggers> <Trigger Property="Visibility" Value="Visible"> <Trigger.EnterActions> <BeginStoryboard> <Storyboard RepeatBehavior="Forever"> <DoubleAnimation Storyboard.TargetProperty="Opacity" From="0.5" To="1" Duration="0:0:0.8" AutoReverse="True"/> </Storyboard> </BeginStoryboard> </Trigger.EnterActions> </Trigger> </Style.Triggers> </Style> <!-- 📊 页面信息样式 --> <Style x:Key="PageInfoText" TargetType="TextBlock"> <Setter Property="FontSize" Value="12"/> <Setter Property="FontWeight" Value="Medium"/> <Setter Property="Foreground" Value="#666666"/> <Setter Property="Background" Value="White"/> <Setter Property="Padding" Value="15,8"/> <Setter Property="Effect"> <Setter.Value> <DropShadowEffect Color="Black" Opacity="0.1" BlurRadius="3" ShadowDepth="1"/> </Setter.Value> </Setter> </Style> </Window.Resources> <Grid> <!-- 🌟 主容器 --> <Border Background="White" CornerRadius="8" Margin="15" > <Border.Effect> <DropShadowEffect Color="Black" Opacity="0.1" BlurRadius="10" ShadowDepth="2"/> </Border.Effect> <Grid Margin="20"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <!-- 🏷️ 标题区域 --> <TextBlock Grid.Row="0" Text="员工信息管理" FontSize="20" FontWeight="Bold" Foreground="#2196F3" Margin="0,0,0,20" HorizontalAlignment="Left"/> <!-- 🔍 搜索区域 --> <Border Grid.Row="1" Background="#F9F9F9" CornerRadius="6" Padding="15" Margin="0,0,0,15"> <StackPanel Orientation="Horizontal"> <TextBlock Text="🔍" FontSize="16" VerticalAlignment="Center" Margin="0,0,8,0"/> <TextBox x:Name="SearchTextBox" Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ModernTextBox}" Width="250" Height="35" VerticalContentAlignment="Center" Margin="0,0,15,0"/> <Button Content="搜索" Command="{Binding SearchCommand}" Style="{StaticResource SearchButton}" Width="80" Height="35" Margin="0,0,10,0"/> <Button Content="重置" Command="{Binding LoadDataCommand}" Style="{StaticResource ResetButton}" Width="80" Height="35" Margin="0,0,20,0"/> <!-- 💫 加载状态 --> <StackPanel Orientation="Horizontal" Visibility="{Binding IsLoading, Converter={StaticResource BooleanToVisibilityConverter}}"> <TextBlock Text="⏳" FontSize="14" VerticalAlignment="Center" Margin="0,0,5,0"/> <TextBlock Text="加载中..." Style="{StaticResource LoadingText}" VerticalAlignment="Center"/> </StackPanel> </StackPanel> </Border> <!-- 📊 数据表格 --> <DataGrid Grid.Row="2" ItemsSource="{Binding Employees}" Style="{StaticResource ModernDataGrid}" RowStyle="{StaticResource ModernDataGridRow}" ColumnHeaderStyle="{StaticResource ModernDataGridColumnHeader}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False" SelectionMode="Single" SelectionUnit="FullRow" VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling" EnableRowVirtualization="True" EnableColumnVirtualization="True" AlternationCount="2"> <DataGrid.Columns> <DataGridTextColumn Header="🆔 ID" Binding="{Binding Id}" Width="100" IsReadOnly="True"/> <DataGridTextColumn Header="👤 姓名" Binding="{Binding Name}" Width="*" MinWidth="150"/> <DataGridTextColumn Header="🏢 部门" Binding="{Binding Department}" Width="*" MinWidth="150"/> <DataGridTextColumn Header="💰 薪资" Binding="{Binding Salary, StringFormat='{}{0:C}'}" Width="130"/> </DataGrid.Columns> </DataGrid> <!-- 📄 分页控件 --> <Border Grid.Row="3" Background="#F9F9F9" CornerRadius="6" Padding="15" Margin="0,15,0,0"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center"> <Button Content="⏮️ 首页" Command="{Binding FirstPageCommand}" Style="{StaticResource PaginationButton}" Width="80" Height="35" Margin="5,0"/> <Button Content="⏪ 上一页" Command="{Binding PreviousPageCommand}" Style="{StaticResource PaginationButton}" Width="80" Height="35" Margin="5,0"/> <Border Style="{x:Null}" Background="White" CornerRadius="4" BorderBrush="#E0E0E0" BorderThickness="1" Margin="20,0"> <TextBlock Text="{Binding PageInfo}" Style="{StaticResource PageInfoText}" VerticalAlignment="Center" HorizontalAlignment="Center"/> </Border> <Button Content="下一页 ⏩" Command="{Binding NextPageCommand}" Style="{StaticResource PaginationButton}" Width="80" Height="35" Margin="5,0"/> <Button Content="尾页 ⏭️" Command="{Binding LastPageCommand}" Style="{StaticResource PaginationButton}" Width="80" Height="35" Margin="5,0"/> </StackPanel> </Border> </Grid> </Border> </Grid> </Window>

主窗口代码

C#
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); DataContext = new EmployeeViewModel(); } }

image.png

⚡ 性能优化技巧

虚拟化设置

XML
<!-- 必须设置的虚拟化属性 --> <DataGrid VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling" EnableRowVirtualization="True" EnableColumnVirtualization="True">

数据绑定优化

C#
// ❌ 错误做法:频繁更新整个集合 Employees = new ObservableCollection<Employee>(newData); // ✅ 正确做法:清空后逐个添加 Employees.Clear(); foreach (var item in newData) { Employees.Add(item); }

异步加载最佳实践

C#
private async Task LoadDataWithProgressAsync() { var progress = new Progress<int>(value => { // 更新进度条 LoadingProgress = value; }); await Task.Run(() => { // 数据加载逻辑 for (int i = 0; i < totalItems; i++) { // 处理数据 ((IProgress<int>)progress).Report((i * 100) / totalItems); } }); }

💡 进阶优化建议

缓存策略

C#
// 实现页面缓存 private readonly Dictionary<string, PageResult<Employee>> _cache = new(); public async Task<PageResult<Employee>> GetPagedDataAsync(PageRequest request) { var cacheKey = $"{request.PageIndex}_{request.PageSize}_{request.SearchText}"; if (_cache.TryGetValue(cacheKey, out var cachedResult)) { return cachedResult; } var result = await LoadDataFromDatabaseAsync(request); _cache[cacheKey] = result; return result; }

预加载策略

C#
// 预加载下一页数据 private async Task PreloadNextPageAsync() { var nextPageRequest = new PageRequest { PageIndex = CurrentPage + 1, PageSize = PageSize, SearchText = SearchText }; // 后台预加载 _ = Task.Run(async () => await _service.GetPagedEmployeesAsync(nextPageRequest)); }

🎊 总结

通过本文的完整实战,我们成功解决了WPF DataGrid大数据展示的三个核心问题:

  1. 性能问题:通过分页加载和虚拟化,从卡顿30秒优化到毫秒级响应
  2. 内存问题:从一次性加载10万条数据到按需加载50条,内存使用量降低99%
  3. 用户体验:添加搜索、排序、分页功能,让用户操作更加流畅

三个"收藏级"关键点:

  • 🔥 分页是王道:服务端分页 + 虚拟化 = 性能飞跃
  • 异步是关键:UI线程 + 后台数据处理 = 流畅体验
  • 🎯 缓存是利器:智能缓存 + 预加载 = 极致性能

从WinForm到WPF的转型之路并不平坦,但掌握了这些核心技术,你就能轻松应对各种大数据场景。记住:好的用户体验,从优雅的数据展示开始!


💬 互动时间:

你在WPF DataGrid开发中遇到过哪些性能问题?欢迎在评论区分享你的经验,或者说说你希望了解的其他WPF进阶技巧!

觉得这篇文章对你有帮助吗?请转发给更多需要的同行! 🚀

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!