wpf 解決DataGridTemplateColumn中width綁定失效問題

感謝@酪酪烤奶 提供的Solution

文章目錄

  • 感謝`@酪酪烤奶` 提供的`Solution`
      • 使用示例
      • 示例代碼分析
        • 各類交互流程
  • WPF DataGrid 列寬綁定機制分析
    • 整體架構
    • 數據流分析
      • 1. ViewModel到Slider的綁定
      • 2. ViewModel到DataGrid列的綁定
        • a. 綁定代理(BindingProxy)
        • b. 列寬綁定
        • c. 數據流
    • 關鍵機制詳解
      • 1. BindingProxy的作用
      • 2. DataGridHelper附加屬性
      • 3. 數據關聯路徑
        • 為什么這樣設計
      • 解決方案分析
      • 核心問題分析
      • 關鍵解決方案組件
        • 1. **BindingProxy類(Freezable輔助類)**
        • 2. **DoubleToDataGridLengthConverter轉換器**
        • 3. **DataGridHelper附加屬性**
        • 4. **XAML中的關鍵綁定修改**
      • 為什么這個方案有效

使用示例

<Window x:Class="WpfApp1.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="clr-namespace:WpfApp1"Title="DataGrid列寬綁定示例" Height="450" Width="800"><Window.Resources><!-- 創建綁定代理 --><local:BindingProxy x:Key="Proxy" Data="{Binding}"/><!-- 列寬轉換器 --><local:DoubleToDataGridLengthConverter x:Key="DoubleToDataGridLengthConverter"/></Window.Resources><Grid><Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="*"/></Grid.RowDefinitions><!-- 列寬調整滑塊 --><StackPanel Orientation="Horizontal" Margin="10"><TextBlock Text="姓名列寬度:" VerticalAlignment="Center" Margin="0,0,10,0"/><Slider Minimum="50" Maximum="300" Value="{Binding NameColumnWidth, Mode=TwoWay}" Width="200" Margin="0,10"/><TextBlock Text="{Binding NameColumnWidth, StringFormat={}{0}px}" VerticalAlignment="Center" Margin="10,0,0,0"/></StackPanel><!-- DataGrid控件 --><DataGrid ItemsSource="{Binding People}" AutoGenerateColumns="False" Grid.Row="1" Margin="10"><DataGrid.Columns><!-- 使用TemplateColumn并通過代理綁定Width屬性 --><DataGridTemplateColumn Header="姓名" local:DataGridHelper.BindableWidth="{Binding Data.NameColumnWidth, Source={StaticResource Proxy},Converter={StaticResource DoubleToDataGridLengthConverter}}"><DataGridTemplateColumn.CellTemplate><DataTemplate><TextBlock Text="{Binding Name}" Margin="5"/></DataTemplate></DataGridTemplateColumn.CellTemplate></DataGridTemplateColumn><DataGridTextColumn Header="年齡" Binding="{Binding Age}" Width="100"/><DataGridTextColumn Header="職業" Binding="{Binding Occupation}" Width="150"/></DataGrid.Columns></DataGrid></Grid>
</Window>    
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;namespace WpfApp1
{public partial class MainWindow : Window{public MainWindow(){InitializeComponent();DataContext = new MainViewModel();}}public class MainViewModel : INotifyPropertyChanged{private double _nameColumnWidth = 150;public double NameColumnWidth{get { return _nameColumnWidth; }set{if (_nameColumnWidth != value){_nameColumnWidth = value;OnPropertyChanged(nameof(NameColumnWidth));}}}public ObservableCollection<Person> People { get; set; }public MainViewModel(){People = new ObservableCollection<Person>{new Person { Name = "張三", Age = 25, Occupation = "工程師" },new Person { Name = "李四", Age = 30, Occupation = "設計師" },new Person { Name = "王五", Age = 28, Occupation = "產品經理" }};}public event PropertyChangedEventHandler? PropertyChanged;protected virtual void OnPropertyChanged(string propertyName){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}}public class Person{public string Name { get; set; }public int Age { get; set; }public string Occupation { get; set; }}// 列寬轉換器public class DoubleToDataGridLengthConverter : IValueConverter{public object Convert(object value, Type targetType, object parameter, CultureInfo culture){if (value is double doubleValue){return new DataGridLength(doubleValue);}return DependencyProperty.UnsetValue;}public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){if (value is DataGridLength dataGridLength){return dataGridLength.Value;}return DependencyProperty.UnsetValue;}}// 綁定代理類public class BindingProxy : Freezable{protected override Freezable CreateInstanceCore(){return new BindingProxy();}public object Data{get { return (object)GetValue(DataProperty); }set { SetValue(DataProperty, value); }}public static readonly DependencyProperty DataProperty =DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));}// 關鍵修改:添加附加屬性來處理列寬綁定public static class DataGridHelper{public static readonly DependencyProperty BindableWidthProperty =DependencyProperty.RegisterAttached("BindableWidth",typeof(DataGridLength),typeof(DataGridHelper),new PropertyMetadata(new DataGridLength(1, DataGridLengthUnitType.SizeToHeader), OnBindableWidthChanged));public static DataGridLength GetBindableWidth(DependencyObject obj){return (DataGridLength)obj.GetValue(BindableWidthProperty);}public static void SetBindableWidth(DependencyObject obj, DataGridLength value){obj.SetValue(BindableWidthProperty, value);}private static void OnBindableWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){if (d is DataGridColumn column){column.Width = (DataGridLength)e.NewValue;}}}
}    

示例代碼分析

各類交互流程

在這里插入圖片描述

WPF DataGrid 列寬綁定機制分析

這段代碼實現了通過ViewModel屬性動態控制DataGrid列寬的功能,下面我將詳細分析Width是如何被更新的,以及Data是如何關聯起來的。

整體架構

代碼主要包含以下幾個關鍵部分:

  1. MainWindow.xaml:定義UI結構和綁定
  2. MainViewModel:提供數據和NameColumnWidth屬性
  3. BindingProxy:解決DataContext綁定問題
  4. DataGridHelper:實現列寬綁定的附加屬性
  5. DoubleToDataGridLengthConverter:類型轉換器

數據流分析

1. ViewModel到Slider的綁定

<Slider Value="{Binding NameColumnWidth, Mode=TwoWay}" />
  • Slider的Value屬性雙向綁定到ViewModel的NameColumnWidth屬性
  • 當用戶拖動滑塊時,NameColumnWidth會被更新
  • 同時,TextBlock顯示當前寬度值也是綁定到同一屬性

2. ViewModel到DataGrid列的綁定

這是最復雜的部分,涉及多層綁定:

a. 綁定代理(BindingProxy)
<local:BindingProxy x:Key="Proxy" Data="{Binding}"/>
  • 創建了一個BindingProxy實例,其Data屬性綁定到當前DataContext
  • 這使得在DataGrid列定義中可以通過靜態資源訪問ViewModel
b. 列寬綁定
local:DataGridHelper.BindableWidth="{Binding Data.NameColumnWidth, Source={StaticResource Proxy}}"
  • 使用DataGridHelper.BindableWidth附加屬性
  • 綁定路徑為Data.NameColumnWidth,通過Proxy訪問
  • 這意味著實際上綁定到ViewModel的NameColumnWidth屬性
c. 數據流
  1. 用戶拖動Slider → NameColumnWidth更新
  2. 由于Proxy.Data綁定到整個DataContext,Proxy能感知到變化
  3. BindableWidth屬性通過Proxy獲取到新的NameColumnWidth值
  4. DataGridHelper的OnBindableWidthChanged回調被觸發
  5. 回調中將新的值賦給DataGridColumn.Width

關鍵機制詳解

1. BindingProxy的作用

BindingProxy解決了DataGrid列定義中無法直接訪問DataContext的問題:

  • DataGrid列不是可視化樹的一部分,沒有繼承DataContext
  • 通過創建Proxy作為靜態資源,綁定到當前DataContext
  • 然后在列綁定中通過Source={StaticResource Proxy}訪問

2. DataGridHelper附加屬性

這是實現列寬綁定的核心:

  1. 定義BindableWidth附加屬性
  2. 當屬性值變化時,OnBindableWidthChanged回調被觸發
  3. 回調中將新值賦給DataGridColumn的Width屬性
private static void OnBindableWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{if (d is DataGridColumn column){column.Width = (DataGridLength)e.NewValue;}
}

3. 數據關聯路徑

完整的綁定路徑是:
Slider.ValueViewModel.NameColumnWidthProxy.Data.NameColumnWidthDataGridHelper.BindableWidthDataGridColumn.Width

為什么這樣設計
  1. 解決DataContext問題:DataGrid列不在可視化樹中,無法直接綁定到ViewModel
  2. 類型兼容:DataGridColumn.Width是DataGridLength類型,而Slider操作的是double
  3. 重用性:通過附加屬性和代理,可以方便地在其他地方重用這種綁定方式

解決方案分析

問題涉及WPF中兩個復雜的技術點:DataGridTemplateColumn的特殊綁定行為和屬性變更通知機制。

核心問題分析

最初遇到的問題是由以下因素共同導致的:

  1. DataGridTemplateColumn不在可視化樹中
    這導致它無法通過RelativeSourceElementName綁定到窗口或DataGrid的DataContext。

  2. Width屬性類型不匹配
    DataGridColumn.Width屬性類型是DataGridLength,直接綁定了double類型,需要類型轉換。

  3. 列寬屬性變更通知缺失
    即使綁定成功,DataGridTemplateColumnWidth屬性默認不會自動響應綁定源的變化。

關鍵解決方案組件

1. BindingProxy類(Freezable輔助類)
public class BindingProxy : Freezable
{protected override Freezable CreateInstanceCore(){return new BindingProxy();}public object Data{get { return (object)GetValue(DataProperty); }set { SetValue(DataProperty, value); }}public static readonly DependencyProperty DataProperty =DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}

作用
通過繼承Freezable,這個類能夠存在于資源樹中(而非可視化樹),從而突破DataGridTemplateColumn的綁定限制。它捕獲窗口的DataContext并使其可被模板列訪問。

2. DoubleToDataGridLengthConverter轉換器
public class DoubleToDataGridLengthConverter : IValueConverter
{public object Convert(object value, Type targetType, object parameter, CultureInfo culture){if (value is double doubleValue){return new DataGridLength(doubleValue);}return DependencyProperty.UnsetValue;}public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){if (value is DataGridLength dataGridLength){return dataGridLength.Value;}return DependencyProperty.UnsetValue;}
}

作用
將ViewModel中的double類型屬性轉換為DataGridLength類型,解決類型不匹配問題。

3. DataGridHelper附加屬性
public static class DataGridHelper
{public static readonly DependencyProperty BindableWidthProperty =DependencyProperty.RegisterAttached("BindableWidth",typeof(DataGridLength),typeof(DataGridHelper),new PropertyMetadata(new DataGridLength(1, DataGridLengthUnitType.SizeToHeader), OnBindableWidthChanged));public static DataGridLength GetBindableWidth(DependencyObject obj){return (DataGridLength)obj.GetValue(BindableWidthProperty);}public static void SetBindableWidth(DependencyObject obj, DataGridLength value){obj.SetValue(BindableWidthProperty, value);}private static void OnBindableWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){if (d is DataGridColumn column){column.Width = (DataGridLength)e.NewValue;}}
}

作用
通過附加屬性機制,創建一個可綁定的BindableWidth屬性,并在屬性值變化時強制更新列寬。這解決了列寬不響應綁定變化的問題。

4. XAML中的關鍵綁定修改
<Window.Resources><local:DoubleToDataGridLengthConverter x:Key="DoubleToDataGridLengthConverter"/><local:BindingProxy x:Key="Proxy" Data="{Binding}"/>
</Window.Resources><DataGridTemplateColumn Header="姓名" local:DataGridHelper.BindableWidth="{Binding Data.NameColumnWidth, Source={StaticResource Proxy}, Converter={StaticResource DoubleToDataGridLengthConverter}}">

綁定路徑解析

  • Source={StaticResource Proxy}:從資源中獲取BindingProxy實例
  • Data.NameColumnWidth:通過Proxy的Data屬性訪問ViewModel的NameColumnWidth屬性
  • Converter:將double轉換為DataGridLength
  • local:DataGridHelper.BindableWidth:使用附加屬性而非直接設置Width

為什么這個方案有效

  1. 突破可視化樹限制
    通過BindingProxy,我們將DataContext從資源樹引入,避開了DataGridTemplateColumn不在可視化樹中的問題。

  2. 類型安全轉換
    轉換器確保了從doubleDataGridLength的正確類型轉換。

  3. 強制屬性更新
    附加屬性的PropertyChangedCallbackOnBindableWidthChanged)在值變化時主動更新列寬,解決了通知缺失問題。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/83843.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/83843.shtml
英文地址,請注明出處:http://en.pswp.cn/web/83843.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

語音轉文本ASR、文本轉語音TTS

ASR Automatic Speech Recognition&#xff0c;語音轉文本。 技術難點&#xff1a; 聲學多樣性 口音、方言、語速、背景噪聲會影響識別準確性&#xff1b;多人對話場景&#xff08;如會議錄音&#xff09;需要區分說話人并分離語音。 語言模型適配 專業術語或網絡新詞需要動…

通用embedding模型和通用reranker模型,觀測調研

調研Qwen3-Embedding和Qwen3-Reranker 現在有一個的問答庫&#xff0c;包括150個QA-pair&#xff0c;用10個query去同時檢索問答庫的300個questionanswer Embedding模型&#xff0c;query-question的匹配分數 普遍高于 query-answer的匹配分數。比如對于10個query&#xff0c…

基于YOLOv8+Deepface的人臉檢測與識別系統

摘要 人臉檢測與識別系統是一個集成了先進計算機視覺技術的應用&#xff0c;通過深度學習模型實現人臉檢測、識別和管理功能。系統采用雙模式架構&#xff1a; ??注冊模式??&#xff1a;檢測新人臉并添加到數據庫??刪除模式??&#xff1a;識別數據庫中的人臉并移除匹…

Grdle版本與Android Gradle Plugin版本, Android Studio對應關系

Grdle版本與Android Gradle Plugin版本&#xff0c; Android Studio對應關系 各個 Android Gradle 插件版本所需的 Gradle 版本&#xff1a; https://developer.android.com/build/releases/gradle-plugin?hlzh-cn Maven上發布的Android Gradle Plugin&#xff08;AGP&#x…

用c語言實現簡易c語言掃雷游戲

void test(void) {int input 0;do{menu();printf("請選擇&#xff1a; >");scanf("%d", &input);switch (input){menu();case 1:printf("掃雷\n");game();break;case 2:printf("退出游戲\n");break;default:printf("輸入…

系統辨識的研究生水平讀書報告期末作業參考

這是一份關于系統辨識的研究生水平讀書報告&#xff0c;內容系統完整、邏輯性強&#xff0c;并深入探討了理論、方法與實際應用。報告字數超過6000字 從理論到實踐&#xff1a;系統辨識的核心思想、方法論與前沿挑戰 摘要 系統辨識作為連接理論模型與客觀世界的橋梁&#xff…

開源、免費、美觀的 Vue 后臺管理系統模板

隨著前端技術的不斷發展&#xff0c;Vue.js 憑借其輕量、高效、易上手的特性&#xff0c;成為國內外開發者最喜愛的前端框架之一。在構建后臺管理系統時&#xff0c;Vue 提供了以下優勢&#xff1a; 響應式數據綁定&#xff1a;讓頁面和數據保持同步&#xff0c;開發效率高。 …

適合 Acrobat DC 文件類型解析

文件類型 (File Type)ProgID (Continuous)ProgID (Classic)主要用途.pdfAcroExch.Document.DCAcroExch.Document.20XX (版本特定)Adobe PDF文檔格式&#xff0c;用于存儲文檔內容和格式.pdfxmlAcroExch.pdfxmlAcroExch.pdfxmlPDF與XML結合的格式&#xff0c;可能用于結構化數據…

C/C++數據結構之漫談

概述 在當今的數字化時代&#xff0c;無論是刷短視頻、社交聊天&#xff0c;還是使用導航軟件、網絡購物&#xff0c;背后都離不開計算機技術的支持。但你是否想過&#xff1a;為什么同樣的功能&#xff0c;有的軟件運行得飛快&#xff0c;有的卻嚴重卡頓&#xff0c;半天沒有響…

4步使用 vue3 路由

路由的基本使用步驟分為以下4步 第一步&#xff1a;定義路由組件&#xff1a;略 第二步&#xff1a;定義路由鏈接和路由視圖&#xff1a; <template><div class"app-container"><h1>App根組件</h1><router-link to"/home">…

VScode使用npm啟動項目以及npm install ,npm start報錯問題處理

安裝啟動步驟 打開cmd 輸入指令 npm -v 查看npm是否安裝&#xff0c;需要先安裝node.js node.js安裝&#xff1a;node.js安裝 安裝包下載后&#xff0c;一直點擊next &#xff0c;安裝完成&#xff0c;打開cmd 輸入 node -v 查看安裝是否成功 使用VScode 打開項目&#xf…

《仿盒馬》app開發技術分享-- 回收金提現記錄查詢(端云一體)

開發準備 上一節我們實現了回收金提現的功能&#xff0c;并且成功展示了當前賬戶的支出列表&#xff0c;但是我們的提現相關的記錄并沒有很好的給用戶做出展示&#xff0c;用戶只知道當前賬戶提現扣款&#xff0c;并不知道回收金的去向&#xff0c;這一節我們就要實現回收金記…

芯片的起點——從硅到晶圓制造

第1篇&#xff1a;芯片的起點——從硅到晶圓制造 在討論汽車芯片如何“上車”之前&#xff0c;我們必須先回到源頭&#xff0c;從一顆芯片是如何從沙子一步步煉成講起。很多人知道芯片很復雜&#xff0c;卻未必清楚它的每一層結構、每一道工藝有何意義。本系列文章將從硅的提純…

vscode python debugger 如何調試老版本python

找到老版本資源&#xff1a; 找到老版本python debugger插件&#xff0c;現在官方github 都是24之后的release 了&#xff0c;調不了3.6 老項目 pdb&#xff1a; 太麻煩 debugpy vscode python debugger 的底層實現&#xff0c;我們可以指定老版本的debugger 來調試&#…

MVCC 怎么實現的

? 什么是 MVCC?它是怎么實現的?(適合基礎不牢固者) 一、MVCC 是什么? MVCC 全稱是:Multi-Version Concurrency Control,中文叫:多版本并發控制。 主要用于解決數據庫的讀寫并發沖突問題,它的作用是讓讀操作無需加鎖,也能讀到符合事務隔離要求的數據版本。 你可以…

深度解析企業風控API技術實踐:構建全方位企業風險畫像系統

引言 在當前的商業環境中&#xff0c;企業風險評估已成為各類商業決策的重要依據。本文將從技術實踐的角度&#xff0c;詳細介紹企業風控API的集成應用&#xff0c;重點關注API的調用方式、數據結構以及風險維度的劃分&#xff0c;幫助開發者快速構建企業風險畫像系統。 關鍵…

Mac 系統 Node.js 安裝與版本管理指南

Mac 系統 Node.js 安裝與版本管理指南 一、環境檢查 在終端執行以下命令驗證當前環境&#xff1a; node -v # 查看 Node.js 版本&#xff08;未安裝會提示命令不存在&#xff09; npm -v # 查看 npm 版本&#xff08;需 Node.js 安裝完成后生效&#xff09;二、安裝方法 …

設備健康管理系統搭建全技術解析:從架構設計到智能運維實踐

在工業 4.0 與智能制造深度融合的當下&#xff0c;設備健康管理系統已成為企業實現數字化轉型的核心基礎設施。據 Gartner 數據顯示&#xff0c;采用智能設備健康管理系統的企業&#xff0c;平均可降低 30% 的非計劃停機成本。如何基于現代技術棧構建一套高效、精準的設備健康管…

React-router 路由歷史的模式和原理

在現代Web開發中,React Router已成為管理React應用程序中路由的流行工具。它不僅簡化了在單頁應用程序(SPA)中導航的過程,還提供了多種路由歷史的模式來適應不同的開發需求和環境。了解這些模式及其背后的原理對于構建高效、可維護的Web應用程序至關重要。本文將深入探討Re…

C++題解(35) 2025年順德區中小學生程序設計展示活動(初中組C++) 換位(一)

題目描述 小明班上是n行m列的座位排列&#xff0c;座位按照行列順序編號&#xff0c;如6行7列&#xff0c;那么第1行第1列座位號為1號、第1行第7列為7號、第3行第4列為18號&#xff0c;如此遞推。 現在期中考剛結束要進行全班換座位。班主任剛剛公布了換位指令&#xff0c;指…