WPF依赖附加属性

发布时间 2023-11-22 10:43:57作者: ZHIZRL

依赖附加属性的定义

可使用代码片段-propa快速生成,输入propa后按两次Tab键

        public static int GetMyProperty(DependencyObject obj)
        {
            return (int)obj.GetValue(MyPropertyProperty);
        }

        public static void SetMyProperty(DependencyObject obj, int value)
        {
            obj.SetValue(MyPropertyProperty, value);
        }

        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MyPropertyProperty =
            DependencyProperty.RegisterAttached("MyProperty", typeof(int), typeof(ownerclass), new PropertyMetadata(0));

属性名称MyProperty,方法名默认为GetMyProperty和SetMyProperty不要随意更改。

DependencyProperty.RegisterAttached的参数与依赖属性一致。

依赖属性所在的类必须继承DependencyObject,而依赖附加属性所在的类不需要。

依赖属性必须在依赖对象,而依赖附加属性不一定,关注的是被附加的对象是否是依赖对象。

在普通类中定义依赖属性,GetValue与SetValue报错,因为这两个方法定义在DependencyObject中。

在普通类中可以定义依赖附加属性。

依赖附加属性的意义与作用

给其他对象提供依赖属性功能

    public class ZxPanel : Panel
    {
        // 依赖附加属性的方法封装
        public static int GetIndex(DependencyObject obj)
        {
            return (int)obj.GetValue(IndexProperty);
        }

        public static void SetIndex(DependencyObject obj, int value)
        {
            obj.SetValue(IndexProperty, value);
        }

        // 依赖附加属性的 声明与注册
        public static readonly DependencyProperty IndexProperty =
            DependencyProperty.RegisterAttached("Index", typeof(int), typeof(ZxPanel), new PropertyMetadata(-1));
    }

在ZxPanel类中定义一个依赖附加属性Index。

<Border Background="Orange" Height="50" local:ZxPanel.Index="3"/>

在Xaml中为Border添加依赖附加属性。

有些对象中不能做绑定的功能

PasswordBox控件的Password属性不具备绑定功能。

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

        public static void SetPassword(DependencyObject obj, string value)
        {
            obj.SetValue(PasswordProperty, value);
        }
        public static readonly DependencyProperty PasswordProperty =
            DependencyProperty.RegisterAttached("Password", typeof(string), typeof(PWHelper), new PropertyMetadata(""
                , new PropertyChangedCallback(OnPasswordChanged)));
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="dependencyObject">实附加的对象</param>
        /// <param name="e"></param>
        private static void OnPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //var control = (d as PasswordBox);
            //if (control == null) return;

            if (d is PasswordBox control)
            {
                control.Password = e.NewValue.ToString();
            }
        }
    }

在PWHelper类中定义一个依赖附加属性Password,并添加值变化回调,将新值赋值给PasswordBox控件的Password属性。

public static object GetAttached(DependencyObject obj)
        {
            return (object)obj.GetValue(AttachedProperty);
        }

        public static void SetAttached(DependencyObject obj, object value)
        {
            obj.SetValue(AttachedProperty, value);
        }
        public static readonly DependencyProperty AttachedProperty =
            DependencyProperty.RegisterAttached("Attached", typeof(object),
                typeof(PWHelper), new PropertyMetadata(null, new PropertyChangedCallback(OnAttachedChanged)));

        private static void OnAttachedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var control = (d as PasswordBox);
            if (control == null) return;

            // 
            control.PasswordChanged -= Control_PasswordChanged;
            control.PasswordChanged += Control_PasswordChanged;
        }
        
        
        private static void Control_PasswordChanged(object sender, RoutedEventArgs e)
        {
            SetPassword((DependencyObject)sender, (sender as PasswordBox).Password);
        }

再定义一个依赖附加属性Attached,并添加值回调,用来挂载事件。

依赖附加属性可以使用绑定。

布局控件做动态子项

    public class ZxPanel : Panel
    {
        // 依赖附加属性的方法封装
        public static int GetIndex(DependencyObject obj)
        {
            return (int)obj.GetValue(IndexProperty);
        }

        public static void SetIndex(DependencyObject obj, int value)
        {
            obj.SetValue(IndexProperty, value);
        }

        // 依赖附加属性的 声明与注册
        public static readonly DependencyProperty IndexProperty =
            DependencyProperty.RegisterAttached("Index", typeof(int), typeof(ZxPanel), new PropertyMetadata(-1));
    }

在继承Panel类的ZxPanel类中定义依赖附加属性Index。

        <local:ZxPanel ColumnSpace="10" RowSpace="10" Test="123">
            <Border Background="Orange" Height="50" local:ZxPanel.Index="3"/>
            <Border Background="Green" Height="50"/>
            <Border Background="Blue" Height="50"/>
            <Border Background="Red" Height="50"/>

            <local:DependencyPropertyStudy local:ZxPanel.Index="2" Height="50" Background="Gray"
                                           x:Name="dps"/>
        </local:ZxPanel>

在ZxPanel控件下的子项可以使用local:ZxPanel.Index="3"。

protected override Size MeasureOverride(Size availableSize)
        {
            double total_y = 0;

            double per_width = (availableSize.Width - ColumnSpace * 2) / 3;

            foreach (UIElement item in this.InternalChildren)
            {
                item.Measure(new Size(per_width, double.MaxValue));
                total_y += item.DesiredSize.Height + RowSpace;
            }
            return new Size(availableSize.Width, total_y);
        }

        // 排列
        protected override Size ArrangeOverride(Size finalSize)
        {
            double offset_y = 0, offset_x = 0;
            double per_width = (finalSize.Width - ColumnSpace * 2) / 3;
            double maxHeight = 0;

            List<FrameworkElement> elements = new List<FrameworkElement>();
            for (int i = 0; i < this.InternalChildren.Count; i++)
            {
                elements.Add((FrameworkElement)this.InternalChildren[i]);
            }

            for (int i = 1; i < this.InternalChildren.Count + 1; i++)
            {
                UIElement item = elements.FirstOrDefault(e => GetIndex(e) == i);
                if (item == null)
                {
                    // 这个是容器中的子控件(DependencyPropertyStudy)
                    item = this.InternalChildren[i - 1];
                }
                //var index = GetIndex(item);// 获取对应对象的Index附加属性值

                item.Arrange(new Rect(offset_x, offset_y, per_width, item.DesiredSize.Height));
                maxHeight = Math.Max(maxHeight, item.DesiredSize.Height);

                if (i % 3 == 0)
                {
                    offset_y += maxHeight + RowSpace;
                    offset_x = 0;
                    maxHeight = 0;
                }
                else
                    offset_x += per_width + ColumnSpace;


            }
            return base.ArrangeOverride(finalSize);
        }

在ZxPanel类中重写MeasureOverride与ArrangeOverride方法,重新定义控件的测量与排序方法。安装子控件的Index属性重新进行排序。

此用法与<Grid的属性<Grid.Column>和<Grid.Row>类似。

<Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <local:DependencyPropertyStudy Tag="123" Command="{Binding}" CP="{Binding}"
                                       DPS="{Binding Value}" Grid.Column="1"/>
    </Grid>

Grid.Column="1"用于辅助布局控件