.NET|--WPF|--笔记合集|--依赖项属性|--3.属性包装器

前言

属性包装器的主要作用是将依赖属性的访问方式转换为标准的 CLR 属性访问方式, 从而使代码更加简洁、直观,并提供一致性和更好的开发体验。 通过属性包装器,开发者可以利用依赖属性的高级功能,同时保持代码的可读性和易用性。

"属性包装器"在TextBlock源码中使用

public class TextBlock : FrameworkElement // 其它继承的基类省略. { // 第1步:定义依赖项属性 public static readonly DependencyProperty TextWrappingProperty; // 第2步:注册依赖项属性( 本篇笔记要讲的 ) static TextBlock() { TextWrappingProperty = DependencyProperty.Register ( "TextWrapping", typeof(TextWrapping), //一个枚举类"System.Windows.TextWrapping" typeof(TextBlock), new FrameworkPropertyMetadata(TextWrapping.NoWrap, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender), IsValidTextWrap ); } // 注册依赖项属性的参数"ValidateValueCallback",用于验证属性值的回调函数. private static bool IsValidTextWrap(object o) { TextWrapping textWrapping = (TextWrapping)o; if (textWrapping != TextWrapping.Wrap && textWrapping != TextWrapping.NoWrap) { return textWrapping == TextWrapping.WrapWithOverflow; } return true; } // 第3步:属性包装器( 本笔记重点 ) public TextWrapping TextWrapping { get { return (TextWrapping)GetValue(TextWrappingProperty); } set { SetValue(TextWrappingProperty, value); } } }

GetValue 和 SetValue

属性包装器, 就几行代码, 看着很简单, 重点还是在2个方法上:GetValue和SetValue. GetValue 和 SetValue 方法是 DependencyObject 类的方法,用于获取和设置依赖属性的值。 为什么"TextBlock"中可以直接使用"DependencyObject"的方法, 就可以看下"TextBlock"的继承关系了,如图所示.

3f2c89f41031df2cb7a53bf087e51e2d.png

// DependencyObject 的GetValue和SetValue的源码 public class DependencyObject : DispatcherObject { public void SetValue(DependencyProperty dp, object value) { VerifyAccess(); PropertyMetadata metadata = SetupPropertyChange(dp); SetValueCommon(dp, value, metadata, coerceWithDeferredReference: false, coerceWithCurrentValue: false, OperationType.Unknown, isInternal: false); } public object GetValue(DependencyProperty dp) { VerifyAccess(); if (dp == null) { throw new ArgumentNullException("dp"); } return GetValueEntry(LookupEntry(dp.GlobalIndex), dp, null, RequestFlags.FullyResolved).Value; } }

给依赖项赋值( 使用LINQPad运行下面的代码)

/* 注意事项 : 不能直接启动(F5), 多次直接启动(F5)会报错 : "不能在同一 AppDomain 中创建多个 System.Windows.Application 实例。" 当报错的时候,有2种解决方案 : 1.使用"菜单栏--Query--Kill Process and Execute" 2.更改设置 : Edit -- Preference -- Advanced -- Execution Always use Fresh Process per Execution 这2种方法, "kill Process and Execute"和"Always use Fresh Process per Execution", 其实意思是一样~ */ /* */ #region 测试方法 internal void Test() { //System.Windows.Controls.Image image = new System.Windows.Controls.Image(); //System.Windows.DependencyObject dependencyObject; //System.Windows.DependencyProperty dependencyProperty; //System.Windows.DependencyProperty dp = new System.Windows.DependencyProperty(); } #endregion // 程序入口 void Main() { System.Windows.Application app = new System.Windows.Application(); var mainWindow = new MainWindows { Title = "Main Window", Width = 400, Height = 250, WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen }; app.Run(mainWindow); } // 主窗口 public class MainWindows : System.Windows.Window { public MainWindows() { Init(); } public void Init() { // 创建一个Grid System.Windows.Controls.Grid grid = new System.Windows.Controls.Grid(); // 创建一个TextBlock System.Windows.Controls.TextBlock textBlock = new System.Windows.Controls.TextBlock(); // *****赋值********************************************************************************** textBlock.Text = "使用属性包装器,为依赖项属性Text赋值.\r\n --zh89233"; // 将TextBlock添加到Grid grid.Children.Add(textBlock); // 将Grid设置为Window的内容 this.Content = grid; } }

5b9f6e24b9cf6519da5c972551a1153d.png

其实绕了一圈, 依赖项属性都写了三四篇笔记了, 最后只是给属性赋值操作, 感觉是不是有点虎头蛇尾, 哈哈... 其实重点都在注册那里, 所以最后用起来就感觉很方便了. 这种设计使得开发者在享受强大功能的同时,不需要被复杂的实现细节所困扰。 这也是为什么你会觉得使用依赖属性很简单的原因。 它体现了良好的设计理念,即在提供强大功能的同时,尽量简化开发者的使用体验。 并且现在只是使用, 依赖项属性, 主要还是在以后数据绑定, 动画等其他功能上体现出来强大, 那时候可以再深入研究. 下面一个例子演示下依赖项属性在数据绑定上的简单应用↓

依赖项属性在数据绑定上的简单应用(该代码在LINQPad上运行)

// 程序入口 void Main() { System.Windows.Application app = new System.Windows.Application(); var mainWindow = new MainWindows { Title = "Main Window", Width = 400, Height = 250, WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen }; app.Run(mainWindow); } // 主窗口 public class MainWindows : System.Windows.Window { public MainWindows() { Init(); } public void Init() { // 创建一个Grid System.Windows.Controls.Grid grid = new System.Windows.Controls.Grid(); // 创建一个TextBlock System.Windows.Controls.TextBlock textBlock = new System.Windows.Controls.TextBlock(); // 创建一个TextBox System.Windows.Controls.TextBox textBox = new System.Windows.Controls.TextBox(); // 创建一个Button( 此按钮只是为了演示, 更改了TextBlock的Text值, TextBox的Text值也会修改 ) System.Windows.Controls.Button button = new System.Windows.Controls.Button { Content = "更改TextBlock.Text值" }; // 设置TextBox的初始文本 textBox.Text = "使用属性包装器,为依赖项属性Text赋值.\r\n --zh89233"; #region *****************核心代码, 需要注意***************** // 创建Binding对象并绑定TextBlock的Text属性到TextBox的Text属性 System.Windows.Data.Binding binding = new System.Windows.Data.Binding("Text") { Source = textBox, Mode = System.Windows.Data.BindingMode.TwoWay }; textBlock.SetBinding(System.Windows.Controls.TextBlock.TextProperty, binding); // 为Button的Click事件添加处理程序 button.Click += (sender, e) => { // 更新TextBlock的Text属性 textBlock.Text = "点击,更改textBlock.Text值为:" + DateTime.Now.ToString(); }; #endregion // 设置Grid行定义 grid.RowDefinitions.Add(new System.Windows.Controls.RowDefinition()); grid.RowDefinitions.Add(new System.Windows.Controls.RowDefinition()); grid.RowDefinitions.Add(new System.Windows.Controls.RowDefinition()); // 将TextBlock、TextBox和Button添加到Grid grid.Children.Add(textBlock); System.Windows.Controls.Grid.SetRow(textBlock, 0); grid.Children.Add(textBox); System.Windows.Controls.Grid.SetRow(textBox, 1); grid.Children.Add(button); System.Windows.Controls.Grid.SetRow(button, 2); // 将Grid设置为Window的内容 this.Content = grid; } }

5a6679b6b7807a116294bea7cce4fc36.gif

DependencyObject.ClearValue()

属性包装器中, 看到了DependencyObject的GetValue()和SetValue()方法, 其实还有一个比较常用的方法::ClearValue()

插曲--Visual Studio查看定义是经过简化的,以"TextBlock"的"TextWrapping"为例

在Visual Studio中查看定义, 会发现, 只有属性包装器, 就可能会误认为和CLR属性是一样的, 其实并不是, 这是Visual Studio中简化的...
// Visual Studio中查看定义(快捷键F12) public class TextBlock : FrameworkElement, IServiceProvider, IContentHost, IAddChild, IAddChildInternal { public TextWrapping TextWrapping { get; set; } }

但是反编译的话,就可以看到完整的代码

// ILSpy中反编译出来的代码↓ public class TextBlock : FrameworkElement, IServiceProvider, IContentHost, IAddChild, IAddChildInternal { public TextWrapping TextWrapping { get { return (TextWrapping)GetValue(TextWrappingProperty); } set { SetValue(TextWrappingProperty, value); } } }
简化的表示只是告诉你这个属性存在,但它实际上是通过依赖属性机制实现的。 Visual Studio 显示简化的属性定义只是为了让代码更易读。 实际的实现仍然是依赖属性,利用 GetValue 和 SetValue 方法进行操作。

__EOF__

本文作者国产凌凌漆
本文链接https://www.cnblogs.com/love-zf/p/18389972.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zh89233  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示