.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 方法进行操作。	
posted @ 2024-08-31 10:52  zh89233  阅读(15)  评论(0编辑  收藏  举报