WPF Datagrid 数据加载和性能

        这篇文章并非讨论 WPF Datagrid 的性能数据,而只是简单介绍一下为了使其性能良好,你需要注意哪些方面。我不太想使用性能分析器来展示实际数据,而是尽可能地使用了 Stopwatch 类。这篇文章不会深入探讨处理海量数据的技术,例如分页或如何实现分页,而是专注于如何让 Datagrid 处理大数据。

这是生成我想要加载到 Datagrid 中的数据的 C# 类。

public class DataItem
    {
        public long Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public long Age { get; set; }
        public string City { get; set; }
        public string Designation { get; set; }
        public string Department { get; set; }
    }

    public static class DataGenerator
    {
        private static int _next = 1;
        public static IEnumerable GetData(int count)
        {
            for (var i = 0; i < count; i++)
            {
                string nextRandomString = NextRandomString(30);
                yield return new DataItem
                                 {
                                     Age = rand.Next(100),
                                     City = nextRandomString,
                                     Department = nextRandomString,
                                     Designation = nextRandomString,
                                     FirstName = nextRandomString,
                                     LastName = nextRandomString,
                                     Id = _next++
                                 };
            }
        }

        private static readonly Random rand = new Random();

        private static string NextRandomString(int size)
        {
            var bytes = new byte[size];
            rand.NextBytes(bytes);
            return Encoding.UTF8.GetString(bytes);
        }
    }

ViewModel 定义如下所示:

 public class MainWindowViewModel : INotifyPropertyChanged
    {
        private void Notify(string propName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
        public event PropertyChangedEventHandler PropertyChanged;

        private Dispatcher _current;
        public MainWindowViewModel()
        {
            _current = Dispatcher.CurrentDispatcher;
            DataSize = 50;
            EnableGrid = true;
            _data = new ObservableCollection();
        }

        private int _dataSize;
        public int DataSize
        {
            get { return _dataSize; }
            set
            {
                LoadData(value - _dataSize);
                _dataSize = value;
                Notify("DataSize");
            }
        }

        private ObservableCollection _data;
        public ObservableCollection Data
        {
            get { return _data; }
            set
            {
                _data = value;
                Notify("Data");
            }
        }

        private bool _enableGrid;
        public bool EnableGrid
        {
            get { return _enableGrid; }
            set { _enableGrid = value; Notify("EnableGrid"); }
        }

        private void LoadData(int more)
        {
            Action act = () =>
                             {
                                 EnableGrid = false;
                                 if (more > 0)
                                 {
                                     foreach (var item in DataGenerator.GetData(more))
                                         _data.Add(item);
                                 }
                                 else
                                 {
                                     int itemsToRemove = -1 * more;
                                     for (var i = 0; i < itemsToRemove; i++)
                                         _data.RemoveAt(_data.Count - i - 1);
                                 }
                                 EnableGrid = true;
                             };
            //act.BeginInvoke(null, null);
            _current.BeginInvoke(act, DispatcherPriority.ApplicationIdle);
        }
    }

如您所见,随着 DataSize 的改变,数据也会被加载。目前我使用滑块来调整加载大小。这一切都非常简单,而且有趣的事情从 XAML 开始。

为了将此“数据”应用到我的WPF数据网格,我将这个ViewModel实例应用到我的类的DataContext中。请参阅下面的窗口代码:

public partial class MainWindow : Window
    {
        private MainWindowViewModel vm;

        public MainWindow()
        {
            InitializeComponent();
            vm = new MainWindowViewModel();
            this.Loaded += (s, e) => DataContext = vm;
        }
    }
    
让我们从以下 XAML 开始:

<stackpanel>
    <slider maximum="100" minimum="50" value="{Binding DataSize}" />
        <label grid.row="1" content="{Binding DataSize}">
        <datagrid grid.row="2" isenabled="{Binding EnableGrid}" itemssource="{Binding Data}">
    </datagrid>
</stackpanel>

现在构建应用程序并运行。结果如下所示:

        如上所示,我加载了 100 个项目,却看不到滚动条。让我们将滑块的 Maximum 属性从 100 改为 1000,然后重新运行应用程序。一次性将滑块拖到 1000。所以,即使加载了 1000 个项目,网格的响应也不太好。

让我们看一下内存使用情况:

        对于一个只加载了 1000 条数据的应用程序来说,这已经相当繁重了。那么,究竟是什么占用了这么多内存呢?你可以连接内存分析器或使用 Windbg 查看内存内容,但由于我已经知道导致这个问题的原因,所以就不赘述了。

        这个问题是由于 DataGrid 被放置在 StackPanel 中。垂直堆叠时,StackPanel 基本上会为其子项分配所需的所有空间。这使得 DataGrid 创建 1000 行(每行每列所需的所有 UI 元素!)并进行渲染。DataGrid 的虚拟化功能在这里没有发挥作用。

        让我们做一个简单的修改,将 DataGrid 放入网格中。其 XAML 代码如下所示:

<Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Slider Value="{Binding DataSize}" Minimum="50" Maximum="1000"/>
        <Label Content="{Binding DataSize}" Grid.Row="1"/>
        <DataGrid ItemsSource="{Binding Data}" Grid.Row="2" IsEnabled="{Binding EnableGrid}">           
        </DataGrid>
    </Grid> 

当我运行应用程序时,你会注意到,当我加载 1000 个项目时,同一个应用程序的性能(除了我刚才提到的 XAML 代码之外,没有任何代码更改)比以前好了很多。而且我还看到了漂亮的滚动条。 

让我们看一下内存使用情况:

哇!差别简直是十倍!可以参考WPF虚拟化的文章:https://blog.csdn.net/hefeng_aspnet/article/details/147305605

那么我在这里还要谈论什么呢?

    如果你注意到 ViewModel 的代码,你应该会看到我在加载数据时禁用了网格,并在完成后重新启用它。我还没有真正测试过这项技术是否有用,但我在 HTML 页面中使用过这项技术,当时列表框中的大量项目都需要被选中,这项技术非常有用。

    在我展示的所有截图中,网格都是排序的。因此,当数据发生变化时,网格必须继续对数据进行排序,并根据您选择的排序方式进行显示。我认为这会造成很大的开销。如果可行的话,在更改数据之前,请考虑移除数据网格的排序功能,并且这样做不会对最终用户造成影响。我还没有测试过这一点,但分组功能应该也应该如此(大多数情况下,分组功能无法简单地移除)。

    只需将 DataGrid 加载到任何其他面板(例如 Grid)而不是 StackPanel 中,您就能看到很大的区别。只要您将网格的可视区域保持在较小的范围内,WPF DataGrid 的性能就很好。


    下面显示的是我的网格,加载了近一百万个数据项。与加载的数据量相比,占用空间相当小。这意味着,要么是WPF控件占用大量内存,要么是WPF UI虚拟化带来了好处。

排序对 DataGrid 的影响

    由于没有对数据网格进行排序,将 100 万个项目加载到我的集合中花了将近 20 秒。

    启用排序后,加载一半的数据项本身就花了 2 分钟多,加载全部数据项则花了 5 分钟多,我甚至因为太麻烦而关掉了应用程序。这很重要,因为应用程序会一直忙于处理数据变化时必须进行的排序,从而占用大量 CPU 资源。因此,由于我直接将其放入可观察集合中,因此每次添加数据项都可能触发排序。

    相反,考虑在后端进行排序而不是使用数据网格。

如果虚拟化得到正确利用,尽管网格绑定到 100 万个项目,我仍然可以滚动应用程序。

在数据网格上使用 BeginInit() 和 EndInit()。

        修改了 ViewModel 的 LoadData() 方法,使其在开始加载数据时调用 BeginInit(),并在加载完成后调用 EndInit()。这确实很有帮助。加载 100 万个项目(网格上未进行任何排序)仅花费了大约 8 秒(之前需要 18 秒)。可惜的是,没有花足够的时间使用分析器来显示实际数据。

窗口更改后的后台代码如下所示:

public partial class MainWindow : Window
    {
        private MainWindowViewModel vm;

        public MainWindow()
        {
            InitializeComponent();
            vm = new MainWindowViewModel();
            this.Loaded += (s, e) => DataContext = vm;
            vm.DataChangeStarted += () => dg.BeginInit();
            vm.DataChangeCompleted += () => dg.EndInit();
        }
    }
    
我还必须将 DataChangeStarted 和 DataChangeCompleted 操作添加到 ViewModel 类中。ViewModel 类的更改部分如下所示:

public event Action DataChangeStarted ;
        public event Action DataChangeCompleted;

        private void LoadData(int more)
        {
            Action act = () =>
                             {
                 //Before the data starts change, call the method.
                                 if (DataChangeStarted != null) DataChangeStarted();
                                 var sw = Stopwatch.StartNew();
                                 EnableGrid = false;
                                 if (more > 0)
                                 {
                                     foreach (var item in DataGenerator.GetData(more))
                                         _data.Add(item);
                                 }
                                 else
                                 {
                                     int itemsToRemove = -1 * more;
                                     for (var i = 0; i < itemsToRemove; i++)
                                         _data.RemoveAt(_data.Count - i - 1);
                                 }
                                 EnableGrid = true;
                                 sw.Stop();
                                 Debug.WriteLine(sw.ElapsedMilliseconds);
                                 if (DataChangeCompleted != null) DataChangeCompleted();
                             };
            //act.BeginInvoke(null, null);
            _current.BeginInvoke(act, DispatcherPriority.ApplicationIdle);
        }

您可以尝试一下并亲自观察性能差异。

        如果在数据网格上进行排序,即使使用了上述技巧,性能仍然会受到影响。排序的开销抵消了调用 BeginInit 和 EndInit 所获得的性能提升。拥有 100 万条记录可能不太现实。

如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/906015.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

matlab求矩阵的逆、行列式、秩、转置

inv - 计算矩阵的逆 用途&#xff1a;计算一个可逆矩阵的逆矩阵。 D [1, 2; 3, 4]; % 定义一个2x2矩阵 D_inv inv(D); % 计算矩阵D的逆 disp(D_inv);det - 计算矩阵的行列式 用途&#xff1a;计算方阵的行列式。 E [1, 2; 3, 4]; determinant det(E); % 计算行列式 disp…

ridecore流水线解读

文章目录 流水线stage分属前后端PCpipelineIFIDDPDP 与 SW 中间没有latchSWCOM 源码地址 流水线stage分属前后端 IF -> ID -> DP -> SW -> EX -> COM分类阶段说明前端IF指令获取阶段。PC 使用分支预测器&#xff0c;访问指令存储器。典型前端操作。前端ID解码并…

【SpringBoot】关于MP使用中配置了数据库表前缀的问题

problem 使用MP时&#xff0c;在application.yml配置文件中配置了MP匹配数据库表中的表名时的前缀作了规定&#xff0c;如下&#xff1a; 那么当我运行时报错了错误&#xff0c;报错信息如下&#xff1a; 因为我数据库表的书类表名是book&#xff0c;MP在匹配时使用了表名前…

印度Rummy游戏支付通道申请策略:技巧类游戏的合规与创新

本文为印度支付申请科普文&#xff0c;自去年开始&#xff0c;印度Rummy类游戏申请印度支付都需要拥有AIGF的会员及产品证书。 如需要rummy可以通过AIGF审核的源。码&#xff0c;或咨询AIGF的相关内容&#xff0c;可以联。系老妙。 印度作为全球棋牌类游戏增长最快的市场之一&…

日志与策略模式

什么是设计模式 IT⾏业 ,为了让 菜鸡们不太拖⼤佬的后腿, 于是⼤佬们针对⼀些经典的常⻅的场景, 给定了⼀些对应的解决⽅案, 这个就是 设计模式 日志认识 计算机中的⽇志是记录系统和软件运⾏中发⽣事件的⽂件&#xff0c;主要作⽤是监控运⾏状态、记录异常信 息&#xff…

解锁Ubuntu高效部署!自动安装配置文件YAML全解析

我们之前介绍了两种Ubuntu系统的安装方式&#xff0c;分别对应桌面版&#xff08;准备搞OpenStack了&#xff0c;先装一台最新的Ubuntu 23.10&#xff09;和服务器版&#xff08;Ubuntu 22.04 LTS服务器版本安装演示&#xff09;。但对于有些用户&#xff0c;因为技术问题&…

关系代数和关系数据库语言(SQL)

阅读提示&#xff1a;本篇文章较长&#xff0c;建议从目录上选取想看的内容。代码上的话&#xff0c;我习惯用小写&#xff0c;如果看不习惯建议跳过。有问题欢迎讨论&#xff01;&#xff01;&#xff01; 一、基础概念 1.1数据库的概念 数据库(Database)是按照数据结构来组…

EXO 可以将 Mac M4 和 Mac Air 连接起来,并通过 Ollama 运行 DeepSeek 模型

EXO 可以将 Mac M4 和 Mac Air 连接起来&#xff0c;并通过 Ollama 运行 DeepSeek 模型。以下是具体实现方法&#xff1a; 1. EXO 的分布式计算能力 EXO 是一个支持 分布式 AI 计算 的开源框架&#xff0c;能够将多台 Mac 设备&#xff08;如 M4 和 Mac Air&#xff09;组合成…

区块链基本理解

文章目录 前言一、什么是分布式账本(DLT)二、什么是P2P网络?二、共识算法三、密码算法前言 区块链是由一个一个数据块组成的链条,按照时间顺序将数据块逐一链接,通过哈希指针链接,所有的数据块共同维护一份分布式账本(DLT),每个节点(可以理解为一个玩家,一台计算机)都拥…

Node.js中的洋葱模型

文章目录 前言 前言 Node.js中的洋葱模型是一种中间件执行机制&#xff0c;主要用于处理HTTP请求和响应的流程控制。该模型通过层层包裹的中间件结构&#xff0c;实现请求从外到内穿透、响应从内向外返回的顺序执行。以下从核心概念、实现原理、框架差异及实际应用等方面解析&…

UI-TARS Desktop:用自然语言操控电脑,AI 重新定义人机交互

在人工智能技术飞速发展的今天,从文本生成到图像识别,AI 的能力边界不断被打破。而字节跳动近期开源的 UI-TARS Desktop,则将这一技术推向了更复杂的交互场景——通过自然语言直接控制计算机界面,实现了图形用户界面(GUI)的智能化自动化。这款工具不仅降低了操作门槛,更…

一个可拖拉实现列表排序的WPF开源控件

从零学习构建一个完整的系统 推荐一个可通过拖拉&#xff0c;来实现列表元素的排序的WPF控件。 项目简介 gong-wpf-dragdrop是一个开源的.NET项目&#xff0c;用于在WPF应用程序中实现拖放功能&#xff0c;可以让开发人员快速、简单的实现拖放的操作功能。 可以在同一控件内…

C语言中字符串函数的详细讲解

C语言提供了丰富的字符串处理函数&#xff0c;这些函数在<string.h>头文件中声明。以下是一些常用字符串函数的详细讲解&#xff1a; 字符串拷贝函数 strcpy 功能&#xff1a;将源字符串&#xff08;包括结尾的\0&#xff09;复制到目标字符串。原型&#xff1a;char *s…

可视化数据图表怎么做?如何实现三维数据可视化?

目录 一、三维数据可视化的要点 1. 明确数据可视化的目标 2. 筛选与整理数据 3. 选择合适的图表类型 4. 运用专业工具制作 5. 优化图表的展示效果 二、数据可视化图表怎么做&#xff1f; 1. 理解三维数据的特性 2. 数据处理与三维建模 3. 设置光照与材质效果 4. 添加…

在Linux服务器上部署Jupyter Notebook并实现ssh无密码远程访问

Jupyter notebook版本7.4.2&#xff08;这个版本AI提示我Jupyter7&#xff08;底层是 jupyter_server 2.x&#xff09; 服务器开启服务 安装Jupyter notebook 7.4.2成功后&#xff0c;终端输入 jupyter notebook --generate-config 这将在 ~/.jupyter/ 目录下生成 jupyter_…

走出 Demo,走向现实:DeepSeek-VL 的多模态工程路线图

目录 一、引言&#xff1a;多模态模型的关键转折点 &#xff08;一&#xff09;当前 LMM 的三个关键挑战 1. 数据的真实性不足 2. 模型设计缺乏场景感知 3. 语言能力与视觉能力难以兼顾 &#xff08;二&#xff09;DeepSeek-VL 的根本出发点&#xff1a;以真实任务为锚点…

数据库原理及其应用 第六次作业

题目 参考答案 题目1. 教材P148第1题 问题&#xff1a;什么是数据库的安全性&#xff1f; 答案&#xff1a;数据库的安全性是指保护数据库以防止不合法的使用所造成的数据泄露、更改或破坏 。它通过用户身份鉴别、存取控制&#xff08;包括自主存取控制和强制存取控制&#x…

2025系统架构师---选择题知识点(押题)

1.《计算机信息系统安全保护等级划分准则》(GB 17859-1999)由低到高定义了五个不同级别的计算机系统安全保护能力。 第一级:用户自主保护级---通过隔离用户与数据实现访问控制,保护用户信息安全; 第二级:系统审计保护级---实施更细粒度的访问控制,通过审计和隔离资源确…

Qt操作SQLite数据库教程

Qt 中操作 SQLite 数据库的步骤如下&#xff1a; 1. 添加 SQLite 驱动并打开数据库 #include <QSqlDatabase> #include <QSqlError> #include <QSqlQuery>// 创建数据库连接 QSqlDatabase db QSqlDatabase::addDatabase("QSQLITE"); db.setData…

从紫光集团看基本财务分析

PE 46PE 代表投资人对他的期望是它的业绩至少要增长50%才算及格。 但实际业绩 一年不如一年. 所以&#xff0c;这个PE 应该是 业绩倒退了&#xff0c;但是市值还没有掉下去&#xff0c;导致运算的结果处在高PE阶段。 那么随着股价的下跌&#xff0c;这个数字会慢慢变小。 当然…