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