WPF 依赖属性
定义依赖属性
对于 WPF 程序员来说, 理解依赖属性的定义对于理解依赖属性的内部细节是非常有帮助的. 在 WPF 中创建依赖属性与创建普通的属性有较大的区别.
EXAMPLE:
namespace WPFlayout.ContentControl.DemoDependencyProperty
{
public class MyBook : DependencyObject
{
//定义一个依赖属性
//依赖属性有可能在几个对象之间传递, 因此需要定义为静态属性.
//在 WPF 中通常以 Property 结尾的属性名称即为依赖属性.
public static readonly DependencyProperty BookNameProperty;
//为依赖属性定义标准的 .NET 属性的包装
public string BookName
{
get { return (string)GetValue(BookNameProperty); }
set { SetValue(BookNameProperty, value); }
}
static MyBook()
{
/*
* Register方法的参数如下:
* 属性名称: BookName
* 属性的类型: String
* 具有依赖属性的类型: Mybook
* 可选的属性元数据: 用于为属性添加额外的功能
* 可选的依赖属性的回调函数: 用于实现属性的验证
**/
//由于 DependencyObject 类没有 Public 级别的构造函数, 目的是确保 DependencyProperty 不会被直接实例化,
//所以需要使用 DependencyProperty.Register 方法
//WPF 也确保 DependencyProperty 在创建后不会修改, 因此所有的 DependencyProperty 为只读的.
BookNameProperty = DependencyProperty.Register("BookName", typeof(string), typeof(MyBook),
new PropertyMetadata("No Name", //PropertyMetadata 的第一个参数作为默认值.
new PropertyChangedCallback(BookNameChangedCallback), //属性变更回调
new CoerceValueCallback(BookNameCoerceCallback)), //强制值回调
new ValidateValueCallback(BookNameValidateCallback)); //属性验证回调
}
//最后一步, 如果 优先 BookNameCoerceCallback(CoerceValueCallback), 再次 BookNameValidateCallback(ValidateValueCallback)
//都回调成功, 触发自身 BookNameChangedCallback(PropertyChangedCallback). 如果想为其他的类提供更改通知,
//可在此回调中触发事件.
static void BookNameChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine(e.OldValue + " " + e.NewValue);
}
//可修改提供的值, 或者返回一个 DependencyProperty.UnsetValue, 表示属性系统没有明确赋值, 而拒绝当前的值
static object BookNameCoerceCallback(DependencyObject obj, object o)
{
string s = o as string;
if (s.Length > 8)
s = s.Substring(0, 8);
return s;
}
//在 BookNameCoerceCallback(CoerceValueCallback) 之后触发, 如果验证成功, 返回 TRUE
static bool BookNameValidateCallback(object value)
{
return value != null;
}
}
}
依赖属性实例
将演示一个具有经纬度的输入框的用户控件, 并且创建一个依赖属性 Value. 这是一个类似 Point 结构的属性, 用于获取和设置经纬度值.
EXAMPLE:
SETP 1:
Create Coordinate Class
namespace WPFlayout.ContentControl.DemoDependencyProperty { /// <summary> /// 定义依赖属性的类型,为了支持绑定更新功能,该类实现了INotifyPropertyChanged接口 /// </summary> public class Coordinate : INotifyPropertyChanged { //定义事件, 供调用方订阅次事件 public event PropertyChangedEventHandler PropertyChanged; //定义纬度 double lat; public double Lat { get => lat; set { lat = value; FirePropertyChanged("Latitude"); } } //定义经度 double lon; public double Lon { get => lon; set { lon = value; FirePropertyChanged("Longitude"); } } //构造函数 public Coordinate(double lat, double lon) { this.Lat = lat; this.Lon = lon; } //用于触发事件的辅助方法 internal void FirePropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } } }
SETP 2:
Create CoordinateBox.xaml UserControl
XAML CODE
<UserControl x:Class="WPFlayout.ContentControl.DemoDependencyProperty.CoordinateBox" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="{Binding ElementName=lat,Path=Height}"/> <RowDefinition Height="{Binding ElementName=lon,Path=Height}"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="{Binding ElementName=tbLon,Path=Width}"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions > <TextBlock>请输入维度值</TextBlock> <TextBox Name="Lat" Grid.Column="1" TextChanged="Lat_TextChanged"/> <TextBlock Grid.Row="1" Name="tbLon">请输入经度值</TextBlock> <TextBox Name="Lon" Grid.Column="1" Grid.Row="1" TextChanged="Lon_TextChanged"/> </Grid> </UserControl>
BACKGROUND CODE
namespace WPFlayout.ContentControl.DemoDependencyProperty { /// <summary> /// CoordinateBox.xaml 的交互逻辑 /// </summary> public partial class CoordinateBox : UserControl { public CoordinateBox() { InitializeComponent(); } private void Lat_TextChanged(object sender, TextChangedEventArgs e) { OnTextChanged(); } private void Lon_TextChanged(object sender, TextChangedEventArgs e) { OnTextChanged(); } //当文本框内容改变时,为用户控件的Value属性赋值 void OnTextChanged() { double lat = 0; double lon = 0; if (double.TryParse(Lat.Text == String.Empty ? "0" : Lat.Text, out lat) & double.TryParse(Lon.Text == String.Empty ? "0" : Lon.Text, out lon)) { Value = new Coordinate(lat, lon); } else { UpdateValue(); } } public static DependencyProperty ValueProperty = DependencyProperty.Register("Value" , typeof(Coordinate) , typeof(CoordinateBox) , new FrameworkPropertyMetadata(null , FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Inherits , new PropertyChangedCallback(OnValueChanged) , new CoerceValueCallback(CoerceValue))); //使用标准.NET属性包装依赖属性 public Coordinate Value { get { return (Coordinate)GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } } //定义强制值回调,限制输入的数据不可以小于0,大于90,小于-180,大于180. static object CoerceValue(DependencyObject sender, object value) { Coordinate val = value as Coordinate; //如果值不为空,判断经纬度的大小,调整用户的输入,使其为特定值 if (val != null) { if (val.Lat < 0) val.Lat = 0; else if (val.Lat > 90) val.Lat = 90; if (val.Lon < -180) val.Lon = -180; else if (val.Lon > 180) val.Lon = 180; } return val; } //属性值变更时,触发用户界面的更新,并更新用户界面 static void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) { CoordinateBox box = sender as CoordinateBox; //更新文本框的值 box.UpdateValue(); //定义路由事件参数 RoutedPropertyChangedEventArgs<Coordinate> e = new RoutedPropertyChangedEventArgs<Coordinate>((Coordinate)args.OldValue , (Coordinate)args.NewValue , ValueChangedEvent); //触发路由事件 box.OnValueChanged(e); } //定义并注册路由事件 public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent("ValueChanged" , RoutingStrategy.Bubble , typeof(RoutedPropertyChangedEventHandler<Coordinate>) , typeof(CoordinateBox)); //定义标准的事件属性 public event RoutedPropertyChangedEventHandler<Coordinate> ValueChanged { add { AddHandler(ValueChangedEvent, value); } remove { RemoveHandler(ValueChangedEvent, value); } } //更新文本框 void UpdateValue() { this.Lat.Text = Value.Lat.ToString(); this.Lon.Text = Value.Lon.ToString(); } //触发事件 void OnValueChanged(RoutedPropertyChangedEventArgs<Coordinate> e) { RaiseEvent(e); } } }
STEP 3:
EXAMPLE:
Create WPF LatBox.xaml
XAML
<Window x:Class="WPFlayout.ContentControl.DemoDependencyProperty.LatBox" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WPFlayout.ContentControl.DemoDependencyProperty" mc:Ignorable="d" Title="LatBox" Height="300" Width="300"> <Grid> <StackPanel> <local:CoordinateBox x:Name="cb1"/> <local:CoordinateBox x:Name="cb2" Value="{Binding ElementName=cb1,Path=Value}" /> </StackPanel> </Grid> </Window>
BACKGROUND CODE:
NOPE......
illustrate: