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.pswp.cn/news/906015.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/906015.shtml
英文地址,請注明出處:http://en.pswp.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;這個數字會慢慢變小。 當然…