zero0r1

导航

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:

posted on 2017-09-20 00:03  zero0r1  阅读(164)  评论(0编辑  收藏  举报