抖音粉丝群1
『7x24小时有问必答』

前言

从传统的 WinForm DataGridView 转向 WPF 的 DataGrid,满怀期待地加载了 10 万条数据,结果界面直接卡死 30 秒,用户体验瞬间崩塌。
事实上,在 WinForm 下 10 万条数据也最好使用虚拟数据加载或分页处理。本文将带你彻底解决 WPF DataGrid 的大数据分页与筛选难题,让你的应用从"卡顿王"变成"性能王"。

正文

在现代桌面应用开发中,高效展示大量数据是常见需求。WPF 的 DataGrid 控件虽然功能强大,但在处理大数据量时,若不加以优化,极易造成界面卡顿、内存暴涨。本文将从问题分析、解决方案、代码实战到进阶优化,全面解析如何打造高性能的 WPF DataGrid。

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

数据绑定的性能陷阱

WPF 的数据绑定机制虽然强大,但也带来了性能挑战:
1、UI 线程阻塞
大量数据绑定时,UI 线程被长时间占用,导致界面无响应。  
2、内存消耗激增
每个数据项都会创建对应的 UI 元素,10 万条数据意味着大量内存占用。
  3、虚拟化失效
不正确的绑定方式或设置会导致虚拟化机制失效,所有项都被渲染。

WinForm 与 WPF 的核心差异

// WinForm 传统做法(性能较好)

dataGridView1.DataSource = dataTable;  // 直接绑定,这块 WinForm 还是可以的

// WPF 错误做法(性能灾难)

dataGrid.ItemsSource = database.GetAllRecords();  // 一次性加载所有数据

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

核心思路

1、服务端分页:只加载当前页数据,减少网络和内存压力。
2、虚拟化优化:启用行虚拟化和列虚拟化,只渲染可见区域。
3、异步加载:使用异步操作避免阻塞 UI 线程。
4、智能缓存:缓存常用页面数据,提升重复访问速度。

代码实战

数据模型设计

public  classEmployee  :  INotifyPropertyChanged

{

       privateint  _id;

       privatestring  _name;

       privatestring  _department;

       publicint  Id

      {

             get  => _id;

             set

            {

                  _id =  value;

                  OnPropertyChanged();

            }

      }

       publicstring  Name

      {

             get  => _name;

             set

            {

                  _name =  value;

                  OnPropertyChanged();

            }

      }

       publicstring  Department

      {

             get  => _department;

             set

            {

                  _department =  value;

                  OnPropertyChanged();

            }

      }

       publicevent  PropertyChangedEventHandler PropertyChanged;

       protected  virtual  void  OnPropertyChanged([CallerMemberName]  string  propertyName =  null)

      {

            PropertyChanged?.Invoke(this,  new  PropertyChangedEventArgs(propertyName));

      }

}

数据服务层

public  classEmployeeService

{

       privatereadonly  Dictionary<string, PageResult<employee>> _cache =  new();

       publicasync  Task<pageresult<employee>> GetPagedEmployeesAsync(PageRequest request)

      {

             var  cacheKey =  $"{request.PageIndex}_{request.PageSize}_{request.SearchText}";

             if  (_cache.TryGetValue(cacheKey,  outvar  cachedResult))

            {

                   return  cachedResult;

            }

             // 模拟数据库查询

             await  Task.Delay(500);  // 模拟网络延迟

             var  totalItems =  100000;  // 假设有 10 万条数据

             var  items =  new  List<employee>();

             var  start = (request.PageIndex -  1) * request.PageSize;

             for  (int  i = start; i < Math.Min(start + request.PageSize, totalItems); i++)

            {

                  items.Add(new  Employee

                  {

                        Id = i +  1,

                        Name =  $"员工_{i +  1}",

                        Department =  $"部门_{(i %  20) +  1}"

                  });

            }

             var  result =  new  PageResult<employee>

            {

                  Items = items,

                  TotalCount = totalItems,

                  PageIndex = request.PageIndex,

                  PageSize = request.PageSize

            };

            _cache[cacheKey] = result;

             return  result;

      }

}

publicclassPageRequest

{

       publicint  PageIndex {  get;  set; } =  1;

       publicint  PageSize {  get;  set; } =  50;

       publicstring  SearchText {  get;  set; }

}

publicclassPageResult<T>

{

       public  List<t> Items {  get;  set; }

       publicint  TotalCount {  get;  set; }

       publicint  PageIndex {  get;  set; }

       publicint  PageSize {  get;  set; }

}

转换器

public  classBooleanToVisibilityConverter  :  IValueConverter

{

       public  object  Convert(object  value, Type targetType,  object  parameter, CultureInfo culture)

      {

             if  (valueisbool  boolValue)

            {

                   return  boolValue ? Visibility.Visible : Visibility.Collapsed;

            }

             return  Visibility.Collapsed;

      }

       public  object  ConvertBack(object  value, Type targetType,  object  parameter, CultureInfo culture)

      {

             if  (valueis  Visibility visibility)

            {

                   return  visibility == Visibility.Visible;

            }

             returnfalse;

      }

}

XAML 界面设计

<Window  x:Class="WpfDataGridPerformance.MainWindow"

             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

             xmlns:local="clr-namespace:WpfDataGridPerformance"

             Title="高性能 DataGrid 示例"  Height="600"  Width="800">

       <Window.Resources>

             <local:BooleanToVisibilityConverter  x:Key="BoolToVis"/>

       <!--Window.Resources-->

       <Grid>

             <Grid.RowDefinitions>

                   <RowDefinition  Height="Auto"/>

                   <RowDefinition  Height="*"/>

                   <RowDefinition  Height="Auto"/>

             <!--Grid.RowDefinitions-->

             <!-- 搜索和操作栏 -->

             <StackPanel  Orientation="Horizontal"  Margin="10">

                   <TextBox  x:Name="SearchBox"  Width="200"  Margin="0,0,10,0"  

                                Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"  

                                PlaceholderText="搜索..."  />

                   <Button  Content="搜索"  Command="{Binding SearchCommand}"  Margin="0,0,10,0"/>

                   <ProgressBar  x:Name="LoadingBar"  Height="20"  Width="100"  

                                      Visibility="{Binding IsLoading, Converter={StaticResource BoolToVis}}"  

                                      IsIndeterminate="True"/>

             <!--StackPanel-->

             <!-- DataGrid -->

             <DataGrid  Grid.Row="1"  Margin="10"  

                            ItemsSource="{Binding Employees}"  

                            AutoGenerateColumns="False"

                            VirtualizingPanel.IsVirtualizing="True"

                            VirtualizingPanel.VirtualizationMode="Recycling"

                            EnableRowVirtualization="True"

                            EnableColumnVirtualization="True"

                            CanUserAddRows="False">

                   <DataGrid.Columns>

                         <DataGridTextColumn  Header="ID"  Binding="{Binding Id}"  Width="80"/>

                         <DataGridTextColumn  Header="姓名"  Binding="{Binding Name}"  Width="*"/>

                         <DataGridTextColumn  Header="部门"  Binding="{Binding Department}"  Width="*"/>

                   <!--DataGrid.Columns-->

             <!--DataGrid-->

             <!-- 分页控件 -->

             <StackPanel  Grid.Row="2"  Orientation="Horizontal"  HorizontalAlignment="Center"  Margin="10">

                   <Button  Content="首页"  Command="{Binding FirstPageCommand}"  Margin="5"/>

                   <Button  Content="上一页"  Command="{Binding PreviousPageCommand}"  Margin="5"/>

                   <TextBlock  Text="{Binding CurrentPageText}"  VerticalAlignment="Center"  Margin="10,0"/>

                   <Button  Content="下一页"  Command="{Binding NextPageCommand}"  Margin="5"/>

                   <Button  Content="末页"  Command="{Binding LastPageCommand}"  Margin="5"/>

             <!--StackPanel-->

       <!--Grid-->

<!--Window-->

主窗口代码

public  partial  class  MainWindow  :  Window

{

       public  MainWindow()

      {

            InitializeComponent();

            DataContext =  new  EmployeeViewModel();

      }

}

1.png

性能技巧

虚拟化设置

<!-- 必须设置的虚拟化属性 -->

<datagrid virtualizingpanel.isvirtualizing=""True"" [="" span]

[="" p][="" span]="" virtualizingpanel.virtualizationmode=""Recycling"" enablerowvirtualization=""True"" enablecolumnvirtualization=""True"">

数据绑定优化

//  错误做法:频繁更新整个集合

Employees =  new  ObservableCollection<employee>(newData);

//  正确做法:清空后逐个添加

Employees.Clear();

foreach  (var  item  in  newData)

{

      Employees.Add(item);

}

异步加载最佳实践

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

            }

      });

}

进阶建议

缓存策略

// 实现页面缓存

privatereadonly  Dictionary<string, PageResult<employee>> _cache =  new();

publicasync  Task<pageresult<employee>> GetPagedDataAsync(PageRequest request)

{

       var  cacheKey =  $"{request.PageIndex}_{request.PageSize}_{request.SearchText}";

       if  (_cache.TryGetValue(cacheKey,  outvar  cachedResult))

      {

             return  cachedResult;

      }

       var  result =  await  LoadDataFromDatabaseAsync(request);

      _cache[cacheKey] = result;

       return  result;

}

预加载策略

// 预加载下一页数据

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

关键词

最后
如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。也可以加入微信公众号[DotNet技术匠]  社区,与其他热爱技术的同行一起交流心得,共同成长!
作者:技术老小子
出处:mp.weixin.qq.com/s/mirv-7456BHKB3oRZ1Q8vA
声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!

END

方便大家交流、资源共享和共同成长
纯技术交流群,需要加入的小伙伴请扫码,并备注加群

推荐阅读

觉得有收获?不妨分享让更多人受益
关注「DotNet技术匠」,共同提升技术实力

收藏
点赞
分享
在看
</pageresult<employee></employee></employee></datagrid></t></employee></employee></pageresult<employee></employee>

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

上一主题上一主题         下一主题下一主题
QQ手机版小黑屋粤ICP备17165530号

关于我们·投诉举报· 用户帮助· 联系我们 · 本站服务 · 版权声明· 隐私政策 · 投搞指南

法律保护:PLC技术网,plcjs.com,plcjs.net等字样
Copyright 2010-2030. All rights reserved. 


微信公众号二维码 抖音二维码 百家号二维码 今日头条二维码哔哩哔哩二维码