WPF依赖属性

理解依赖项属性

依赖项属性是专门为WPF创建的,在WPF的核心特征中使用。

创建依赖项属性

public class DP: DependencyObject
{
	//声明依赖项属性  
	public static readonly DependencyProperty MydpProperty;
    
     static DP()
	{
         //指示依赖属性使用什么服务(如数据绑定、动画以及日志)
		FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata(default(double),
			FrameworkPropertyMetadataOptions.AffectsMeasure);
		
         //注册依赖属性
		MydpProperty = DependencyProperty.Register("Mydp", typeof(double), typeof(DP), metadata,
			new ValidateValueCallback(ShirtValidateCallback));
         //以上Register方法参数
         //1、属性名 2、属性数据类型 3、拥有该属性的类型 4、附加属性设置的FrameworkPropertyMetadata对象(可选) 5、验证属性的回调函数(可选)
	}
    
    //添加属性包装器 DependencyObject.SetValue() or DependencyObject.GetValue()   
    //DependencyObject.Clear(DP.MydpProperty) 删除本地值设置
	public double Mydp
	{
		set { SetValue(MydpProperty, value); }
		get { return (double)GetValue(MydpProperty); }
	}
    
    
	private static bool ShirtValidateCallback(object value)
	{
		return true;
	}
}

使用依赖项属性

依赖项属性的两个关键行为——更改通知和动态值识别。Visual Studio快捷键 (propdp+Tab)

  • 更改通知:当属性值发生改变,依赖项属性不会自动引发事件,以通知一个属性值发生变化。而是触发一个受保护的OnPropertyChangedCallBack()方法,该方法通过两个WPF服务(数据绑定和触发器)传递信息,并调用PropertyChangedCallback回调函数。

  • 动态值识别:按照一定优先级来检索基本值,检索改变属性值的提供者。

    优先级由小到大让如下:

    1、默认值(FrameworkPropertyMetadata对象设置的值)。2、继承而来的值。3、主题样式值。 4、项目样式值。5、本地值(元素对象直接设置的值)

    1、基本值(如上)。2、表达式值(数据绑定和资源)3、动画的目标,应用该动画。4、运行CoerceValueback回调函数修的正属性值。

共享的依赖项属性

DependencyProperty.AddOwner(Typeof(DependencyObject));
//注册在TextElement静态构造函数中,在TextBlock静态构造函数中只是简单的重用
TextBlock.FontFamilyProperty = TextElement.FontFamilyProperty.AddOwner(typeof(TextBlock));

附加属性

注册附加属性使用RegisterAttached()方法,与注册依赖属性相同,附加属性不设置属性包装器,通过调用两个静态方法来设置和获取属性值。Visual Studio快捷键

(propa+Tab)

//注册附加属性  
public static readonly DependencyProperty PasswordProperty =
	DependencyProperty.RegisterAttached("Password", typeof(string), typeof(ADP), new PropertyMetadata(string.Empty));

public static string GetPassword(DependencyObject obj)
{
	return (string)obj.GetValue(PasswordProperty);
}
public static void SetPassword(DependencyObject obj, string value)
{
	obj.SetValue(PasswordProperty, value);
}

//这段代码不会抛异常,这是因为Button不会去找他不知道的属性值
Button btn = sender as Button;
btn.SetValue(PasswordBox.PasswordCharProperty, '*');

//PasswordCharProperty是属于PasswordBox的属性值,所以密码图形会发生改变。
if (this.pwd is PasswordBox passwordbox)
{
	passwordbox.SetValue(PasswordBox.PasswordCharProperty, '*');
}

示例,以PasswordBox添加附加属性来对密码进行binding

public class PasswordHelper
{
	/// <summary>
	/// 回调函数中避免无意义的赋值操作
	/// </summary>
	static bool _isUpdate = false;
		
	/// <summary>
	/// PasswordProperty  附加属性的功能就像是一个桥梁,通过回调函数和密码框事件来实现数据通知。
	/// </summary>
	public static readonly DependencyProperty PasswordProperty =
		DependencyProperty.RegisterAttached("Password", typeof(string), typeof(PasswordHelper),
			new PropertyMetadata(string.Empty, new PropertyChangedCallback(PasswordChangedCallBack)));

	public static string GetPassword(DependencyObject obj)
	{
		return (string)obj.GetValue(PasswordProperty);
	}

	public static void SetPassword(DependencyObject obj, string value)
	{
		obj.SetValue(PasswordProperty, value);
	}
	private static void PasswordChangedCallBack(DependencyObject o, DependencyPropertyChangedEventArgs e)
	{
		//当附加属性PasswordHelper.PasswordProperty发生改变时,触发此回调方法
		PasswordBox passwordbox = o as PasswordBox;
		passwordbox.PasswordChanged -= Passwordbox_PasswordChanged;
		if (!_isUpdate)
		{
			//PasswordProperty绑定属性发生变化时,将PasswordProperty值赋给界面PasswordBox的Password属性
			passwordbox.Password = e.NewValue?.ToString();
		}
		passwordbox.PasswordChanged += Passwordbox_PasswordChanged;
	}

	private static void Passwordbox_PasswordChanged(object sender, RoutedEventArgs e)
	{
		//当界面PasswordBox的Password发生变化时触发此事件处理器。
		PasswordBox passwordBox = sender as PasswordBox;
		_isUpdate = true;
		//当密码值发生变化时,将密码赋值给附加属性PasswordProperty
		SetPassword(passwordBox, passwordBox.Password);
		_isUpdate = false;
	}

	public static bool GetAttach(DependencyObject obj)
	{
		return (bool)obj.GetValue(AttachProperty);
	}

	public static void SetAttach(DependencyObject obj, bool value)
	{
		obj.SetValue(AttachProperty, value);
	}

	//初始化时为PasswordBox的PasswordChanged事件添加事件处理器
	public static readonly DependencyProperty AttachProperty =
		DependencyProperty.RegisterAttached("Attach", typeof(bool), typeof(PasswordHelper),
			new PropertyMetadata(false, new PropertyChangedCallback(AttachChangedCallBack)));
	private static void AttachChangedCallBack(DependencyObject o, DependencyPropertyChangedEventArgs e)
	{
		PasswordBox passwordBox = o as PasswordBox;
		if (passwordBox != null)
			return;
		//当旧值是true时,清理事件处理器
		if ((bool)e.OldValue)
		{
			passwordBox.PasswordChanged -= Passwordbox_PasswordChanged;
		}
		//当此附加属性设置为True时添加事件处理启
		if ((bool)e.NewValue)
		{
			passwordBox.PasswordChanged += Passwordbox_PasswordChanged;
		}
	}
}
  <PasswordBox Width="200" Height="20" Margin="0,10" local:PasswordHelper.Attach="True"
                local:PasswordHelper.Password="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

属性验证

WPF提供两种方法来阻止非法值:

  • ValidateValueCallback:该回调函数可接受或拒绝新值,通常用与捕获违反属性约束的明显错误。作为DependencyProperty.Register()的一个参数提供该回调函数。
  • CoerceValueCallback:该回调函数能将新值修改为更能接受的值。作为FrameworkPropertyMetadata对象的构造函数的一个参数提供该回调函数。

进行属性验证的过程:

1)首先,CoerceValueCallback方法有机会修改提供的值,或者返回DependencyProperty.UnsetValue,这会完全拒绝修改。

2)接下来激活ValiadataValueCallback方法,返回true接受一个合法值,返回false拒绝值。ValiadataValueCallback方法不能访问设置属性的对象,不能检查其他属性值。

3)最后,如果前两个阶段都成功,就会触发PropertyChangedCallback方法。

public class MyBtn : Button
{
	public double Max
	{
		get { return (double)GetValue(MaxProperty); }
		set { SetValue(MaxProperty, value); }
	}

	// Using a DependencyProperty as the backing store for Max.  This enables animation, styling, binding, etc...
	public static readonly DependencyProperty MaxProperty =
		DependencyProperty.Register("Max", typeof(double), typeof(MyBtn),
			new PropertyMetadata(default(double), new PropertyChangedCallback(MaxPropertyChangedCallback),
				new CoerceValueCallback(MaxCoerceValueCallback)), new ValidateValueCallback(MaxValidateValueCallback));

    //当此回调返回DependencyProperty.UnsetValue时,不会进入PropertyChangedCallback回调函数,回调修改后返回的值类型要与DependncyProperty类型一致,否则会在INorifyPropertyChanged接口的PropertyChanged事件的调用的地方抛出异常。
    public static object MaxCoerceValueCallback(DependencyObject sender, object obj)
    {
		if (double.Parse(obj.ToString()) > 50)
		{
			return (object)50.0;
		}
		return obj;

#if test
        return DependencyProperty.UnsetValue;
#endif
    }

    public static void MaxPropertyChangedCallback(DependencyObject o, DependencyPropertyChangedEventArgs e)
	{
        MyBtn button = (MyBtn)o;
        button.Height = (double)e.NewValue;
    }
    
    //当此回调函数返回false时,不会再进入CoerceValueCallback和PropertyChangedCallback回调函数,会在INorifyPropertyChanged接口的PropertyChanged事件的调用的地方抛出异常。
    public static bool MaxValidateValueCallback(object value)
    {
        if (double.TryParse(value.ToString(), out double i))
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}
posted @ 2022-04-23 21:20  蓝白永恒  阅读(518)  评论(0编辑  收藏  举报