WPF 依赖属性

控件中绝大多数都是依赖属性。

依赖属性只有继承了DependencyProperty的控件类才能注册,依赖属性与普通属性相比可以绑定并通知界面。此外依赖属性注册时还有三个回调函数,分别为。

给控件添加依赖属性可以在控件模板里绑定其他控件的属性如:继承button添加CornerRadius依赖属性在控件模板里绑定到Border的CornerRadius属性中,实现给button添加圆角效果。

一、继承Button增加圆角依赖属性

使用Code Snippet , 在派生里写propdp双击Tab自动写入依赖属性模板:

csharp 复制代码
    public int MyProperty
    {
        get { return (int)GetValue(MyPropertyProperty); }
        set { SetValue(MyPropertyProperty, value); }
    }

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

其中,将int改为自己需要的类型(这里改为CornerRadius);

MyProperty属性名字改为通俗易懂的名字(这里改为ButtonCornerRadius);

ownerclass 改为依赖属性所在类的类名(这里为CornerButton);

其中 PropertyMetadata 为元数据,实例化时可以给出初始值,如模板中的0就是元数据的初始值。

此外还有三个回调,分别为:

1、PropertyMetadata 构造函数的参数propertyChangedCallback(值回调),coerceValueCallback(强制回调)

2、DependencyProperty Register函数参数validateValueCallback(验证回调)

写完后依赖属性注册如下:

csharp 复制代码
 public class CornerButton : Button
 {
     public CornerRadius ButtonCornerRadius
     {
         get { return (CornerRadius)GetValue(ButtonCornerRadiusProperty); }
         set { SetValue(ButtonCornerRadiusProperty, value); }
     }

     // Using a DependencyProperty as the backing store for ButtonCornerRadius.  This enables animation, styling, binding, etc...
     public static readonly DependencyProperty ButtonCornerRadiusProperty =
         DependencyProperty.Register(
             "ButtonCornerRadius",
             typeof(CornerRadius),
             typeof(CornerButton),
             new PropertyMetadata(
                 new CornerRadius(1),
                 propertyChangedCallback: ButtonRaidusChangedCallback,
                 coerceValueCallback: ButtonRaidusCoerceValueCallback
             ),
             validateValueCallback: ButtonRaidusValiationCallback
         );
 
   /*
   回调函数具体实现,如下面的 二
   ...
   */
     static CornerButton()
     {
         DefaultStyleKeyProperty.OverrideMetadata(
             typeof(CornerButton),
             new FrameworkPropertyMetadata(typeof(CornerButton))
         );
     }
 }

其中,静态构造函数中的代码用于 "指定控件的默认样式键" ,即此控件样式会去寻找TargetType 为此控件类型的Style,不受基础控件样式的影响(但目前就这个例子来说,写不写看不出区别来)。

二、 依赖属性里的回调函数:

1、propertyChangedCallback(值回调):当值发生改变时,可以对自定义控件进行一些变化。其中参数d为属性发生变化所在对象实例,e为属性更改的数据(如新值,旧值)。

ini 复制代码
 private static void ButtonRaidusChangedCallback(
     DependencyObject d,
     DependencyPropertyChangedEventArgs e
 )
 {
     var cornerButton = (CornerButton)d;
     var cornerRadius = (CornerRadius)e.NewValue;
     var triggervalue = 10;
     if (
         cornerRadius.BottomLeft < triggervalue
         && cornerRadius.BottomRight < triggervalue
         && cornerRadius.TopRight < triggervalue
         && cornerRadius.TopLeft < triggervalue
     )
     {
         cornerButton.Background = new SolidColorBrush(Colors.Red);
     }
     else
     {
         cornerButton.Background = new SolidColorBrush(Colors.Purple);
     }
 }

这里当属性值在一定范围时改变控件的背景颜色。

2、coerceValueCallback(强制回调):当值发生改变时,如果不是自己希望的值可以强制返回一个希望值。

csharp 复制代码
        private static object ButtonRaidusCoerceValueCallback(DependencyObject d, object baseValue)
        {
            var cornerRadius = (CornerRadius)baseValue;
            var triggervalue = 5;
            if (
                cornerRadius.BottomLeft < triggervalue
                && cornerRadius.BottomRight < triggervalue
                && cornerRadius.TopRight < triggervalue
                && cornerRadius.TopLeft < triggervalue
            )
            {
                return new CornerRadius(0);
            }
            return baseValue;
        }

这里当低于某个值时,强行返回一个值。

3、validateValueCallback(验证回调):在给属性赋值时,先验证是否符合自己希望的范围或值。要是不是自己希望的值可以返回false,

此时要是在运行时会抛出ArgumentException异常。要是在设计时,会报错无法通过编译。

csharp 复制代码
        private static bool ButtonRaidusValiationCallback(object value)
        {
            var cornerRadius = (CornerRadius)value;
            var maxValue = 15;
            if (
                cornerRadius.BottomLeft < maxValue
                && cornerRadius.BottomRight < maxValue
                && cornerRadius.TopRight < maxValue
                && cornerRadius.TopLeft < maxValue
            )
                return true;
            else
                return false;
        }

这里当属性值大于某个值时,返回false。

回调触发顺序: 当属性被赋值时:验证回调->强制回调->验证回调->值回调

三、样式编写及外部调用:

1、样式:

ini 复制代码
  <Style TargetType="{x:Type c:CornerButton}">
      <Style.Setters>
          <Setter Property="Template">
              <Setter.Value>
                  <ControlTemplate TargetType="{x:Type c:CornerButton}">
                      <Border
                          Background="{TemplateBinding Background}"
                          BorderBrush="{TemplateBinding BorderBrush}"
                          BorderThickness="{TemplateBinding BorderThickness}"
                          CornerRadius="{TemplateBinding ButtonCornerRadius}">
                          <ContentPresenter />
                      </Border>
                  </ControlTemplate>
              </Setter.Value>
          </Setter>
      </Style.Setters>
      <Style.Triggers>
          <Trigger Property="ButtonCornerRadius" Value="8">
              <Setter Property="BorderThickness" Value="8" />
              <Setter Property="BorderBrush" Value="Blue" />
          </Trigger>
          <Trigger Property="ButtonCornerRadius" Value="10">
              <Setter Property="BorderThickness" Value="10" />
              <Setter Property="BorderBrush" Value="Yellow" />
          </Trigger>
      </Style.Triggers>
  </Style>

第一行中的c为命名空间。

可以看到依赖属性也可以使用Triggers来使样式进行变化。

2、调用:

ini 复制代码
 <Grid>
     <StackPanel>
         <c:CornerButton
             x:Name="cornerbtn"
             Width="120"
             Height="60"
             Background="Purple"
             ButtonCornerRadius="6" />

         <StackPanel
             Margin="0,10"
             HorizontalAlignment="Center"
             Orientation="Horizontal">
             <Button
                 Width="60"
                 Height="30"
                 Click="Button_Click_3"
                 Content="3" />
             <Button
                 Width="60"
                 Height="30"
                 Click="Button_Click_8"
                 Content="8" />
             <Button
                 Width="60"
                 Height="30"
                 Click="Button_Click_10"
                 Content="10" />

             <Button
                 Width="60"
                 Height="30"
                 Click="Button_Click_15"
                 Content="15" />
         </StackPanel>
     </StackPanel>
 </Grid>
csharp 复制代码
  private void Button_Click_3(object sender, RoutedEventArgs e)
  {
      cornerbtn.ButtonCornerRadius = new CornerRadius(3);
  }

  private void Button_Click_8(object sender, RoutedEventArgs e)
  {
      cornerbtn.ButtonCornerRadius = new CornerRadius(8);

  }

  private void Button_Click_10(object sender, RoutedEventArgs e)
  {
      cornerbtn.ButtonCornerRadius = new CornerRadius(10);

  }

  private void Button_Click_15(object sender, RoutedEventArgs e)
  {
      try
      {
          cornerbtn.ButtonCornerRadius = new CornerRadius(15);
      }
      catch (ArgumentException ex)
      {

          MessageBox.Show($"{ex.Message}");
      }
  }

3、效果展示:

默认值为6,触发值回调,背景颜色从默认设置的紫色改为红色。

点击按钮3后,属性值被设置为3,由于小于强制回调里的条件,被强制设置成0.

点击8按钮,样式里的Trigger将边框设置成8且边框颜色为蓝色。

同理点击10按钮,样式里的Trigger将边框设置成10且边框颜色为黄色,且触发值回调将背景设置成紫色。

点击15按钮,值超出了验证回调里的条件,抛出异常。

参考链接:

bash 复制代码
https://www.cnblogs.com/douzi2/p/17002595.html
vbnet 复制代码
https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/properties/dependency-property-callbacks-and-validation
arduino 复制代码
【WPF 中依赖属性及附加属性的概念及用法】 https://www.bilibili.com/video/BV1oV4y1F7TU
相关推荐
yuren_xia2 小时前
Spring Boot中保存前端上传的图片
前端·spring boot·后端
JohnYan5 小时前
Bun技术评估 - 04 HTTP Client
javascript·后端·bun
shangjg35 小时前
Kafka 的 ISR 机制深度解析:保障数据可靠性的核心防线
java·后端·kafka
青莳吖6 小时前
使用 SseEmitter 实现 Spring Boot 后端的流式传输和前端的数据接收
前端·spring boot·后端
我的golang之路果然有问题6 小时前
ElasticSearch+Gin+Gorm简单示例
大数据·开发语言·后端·elasticsearch·搜索引擎·golang·gin
mldong8 小时前
我的全栈工程师之路:全栈学习路线分享
前端·后端
噼里啪啦啦.9 小时前
SpringBoot统一功能处理
java·spring boot·后端
考虑考虑9 小时前
JPA自定义sql参数为空和postgresql遇到问题
spring boot·后端·spring
橘子青衫10 小时前
Java多线程编程:深入探索线程同步与互斥的实战策略
java·后端·性能优化
shengjk110 小时前
一文搞懂 python __init__.py 文件
后端