依赖属性详解,持续更新

个人理解,依赖属性,就是可以绑定东西的属性,支持样式、动画。本身WPF控件就有很多依赖属性,比如TextBox 控件的Text就是依赖属性。

1.简单例子,为按钮类增加一个依赖属性

using System.Windows;
using System.Windows.Controls;
namespace WpfApp1
{
    public class CustomButton : Button
    {
        //CLR“包装器”
        public bool IsRed
        {
            get { return (bool)GetValue(IsRedProperty); }
            set { SetValue(IsRedProperty, value); }
        }
        //注册依赖属性
        public static readonly DependencyProperty IsRedProperty =
            DependencyProperty.Register(
                "IsRed",
                typeof(bool),
                typeof(CustomButton),
                new PropertyMetadata(false));

    }
}

 

在XML中使用

<Window x:Class="WpfApp1.MainWindow"
        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:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <!--样式:本地CustomButton类-->
        <Style TargetType="local:CustomButton">
            <!--属性触发器-->
            <Style.Triggers>
                <!--当IsRed==True时触发背景属性为Red-->
                <Trigger Property="IsRed" Value="True">
                    <Setter Property="Background" Value="Red" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <Grid>
        <GroupBox>
            <DockPanel>
                <!--设置自定义按钮类的依赖实行IsRed="True",那么关联的样式背景色就会显示-->
                <local:CustomButton IsRed="True" Content="依赖属性" Width="100" Height="20"/>
            </DockPanel>
        </GroupBox>
        
    </Grid>
</Window>

 2.附加属性

2-1自带的附加属性

<!--Grid.Row="1, DockPanel.Dock="Top"都是WPF自带的附加属性-->
<DockPanel Grid.Row="1">
    <TextBox DockPanel.Dock="Top" >Enter text</TextBox>
</DockPanel>

2-2自定义附加属性,我们定义一个附加属性代表所有控件都可以使用它。

下面的应用场景是鼠标悬停在控件上会有提示类似ToolTip,这么做的原因是并非所有的控件都有ToolTip这个属性,所以要定义一个。

// 附加属性通常放在静态类中
public static class TooltipService
{
    // 1. 声明并注册附加属性
    public static readonly DependencyProperty HelpTextProperty =
        DependencyProperty.RegisterAttached(
            "HelpText",                // 属性名称
            typeof(string),            // 属性类型
            typeof(TooltipService),    // 拥有者类型
            new PropertyMetadata(      // 元数据
                null,                   // 默认值
                OnHelpTextChanged       // 属性值变化时的回调函数
            )
        );

    // 2. 定义 Get 访问器 (必须是静态方法,命名规范: Get+属性名)
    public static string GetHelpText(DependencyObject obj)
    {
        return (string)obj.GetValue(HelpTextProperty);
    }

    // 3. 定义 Set 访问器 (必须是静态方法,命名规范: Set+属性名)
    public static void SetHelpText(DependencyObject obj, string value)
    {
        obj.SetValue(HelpTextProperty, value);
    }

    // 4. 属性值变化时的处理方法
    private static void OnHelpTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // 可以在这里添加属性值变化时的逻辑
        // 例如:自动设置控件的 ToolTip
        if (d is FrameworkElement element)
        {
            element.ToolTip = e.NewValue;
        }
    }
}
<!--以下时自定义附加属性 帮助提示-->
<StackPanel Margin="20" Grid.Row="2">
    <!-- 使用我们定义的附加属性,这里代替ToolTip属性-->
    <TextBox local:TooltipService.HelpText="请输入用户名" 
         Width="200" Height="30" Margin="5"/>

    <Button local:TooltipService.HelpText="点击提交表单" 
        Content="提交" 
        Width="100" Height="30" Margin="5"/>

    <CheckBox local:TooltipService.HelpText="勾选表示同意条款" 
          Content="同意条款" 
          Margin="5"/>
</StackPanel>
View Code

 3.集合类型依赖属性

WPF自带的比如 DataGrid.ItemsSource,Items的有ListBoxComboBoxListViewMenuTreeViewTabControl

那么自定义一个集合型依赖属性,下面的示例定义一个依赖属性集合,绑定在ListBox的ItemsSource上,然后按钮进行添加或删除的例子

namespace 集合依赖属性
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
           // DataContext = this;

            // 初始化集合
            Tasks = new ObservableCollection<TaskItem>
            {
                new TaskItem { Name = "学习WPF", IsCompleted = true },
                new TaskItem { Name = "理解依赖属性", IsCompleted = false },
                new TaskItem { Name = "实现集合类型依赖属性", IsCompleted = false }
            };

        }
        // 定义集合类型的依赖属性
        public static readonly DependencyProperty TasksProperty =
            DependencyProperty.Register(
                "Tasks",
                typeof(ObservableCollection<TaskItem>),
                typeof(MainWindow),
                new PropertyMetadata(null));
        public ObservableCollection<TaskItem> Tasks
        {
            get { return (ObservableCollection<TaskItem>)GetValue(TasksProperty); }
            set { SetValue(TasksProperty, value); }
        }

        private void AddTask_Click(object sender, RoutedEventArgs e)
        {
            Tasks.Add(new TaskItem
            {
                Name = $"新任务 {Tasks.Count + 1}",
                IsCompleted = false
            });
        }

        private void RemoveTask_Click(object sender, RoutedEventArgs e)
        {
            if (Tasks.Count > 0)
                Tasks.RemoveAt(Tasks.Count - 1);
        }
    }
    // 简单的数据模型
    public class TaskItem
    {
        public string Name { get; set; }
        public bool IsCompleted { get; set; }
    }
}
<Window x:Class="集合依赖属性.MainWindow"
        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:集合依赖属性"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:MainWindow/>
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <TextBlock Text="任务列表" FontSize="16" FontWeight="Bold" Margin="10"/>

        <ListBox Grid.Row="1" ItemsSource="{Binding Tasks}" Margin="10">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <CheckBox IsChecked="{Binding IsCompleted}" Margin="0,0,10,0"/>
                        <TextBlock Text="{Binding Name}" VerticalAlignment="Center"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

        <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center" Margin="10">
            <Button Content="添加任务" Click="AddTask_Click" Margin="5" Padding="10,5"/>
            <Button Content="移除任务" Click="RemoveTask_Click" Margin="5" Padding="10,5"/>
        </StackPanel>
    </Grid>
</Window>

 4.依赖属性的回调

注册依赖属性的时候添加的回调方法,属性更改时先执行验证回调,在执行强制回调,最后执行值改变回调

 public class CustomControl : Control
 {
     // 注册依赖属性
     public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
         "Value",                          // 属性名
         typeof(double),                   // 属性类型
         typeof(CustomControl),             // 拥有者类型
         new PropertyMetadata(              // 元数据
             0.0,                           // 默认值
             OnValueChanged,                // 值变更回调(最后触发)
             CoerceValue                    // 强制回调(第2触发触发)
         ),ValidateValue // 验证回调(最先触发)
     );

     // CLR属性包装器
     public double Value
     {
         get { return (double)GetValue(ValueProperty); }
         set { SetValue(ValueProperty, value); }
     }

     // 值变更回调
     private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
     {
         var control = d as CustomControl;
         double oldValue = (double)e.OldValue;
         double newValue = (double)e.NewValue;
         control.OnValueChanged(oldValue, newValue);
     }

     protected virtual void OnValueChanged(double oldValue, double newValue)
     {
         // 触发事件或更新UI(如显示值变更日志)
         Console.WriteLine($"Value changed from {oldValue} to {newValue}");
     }

     // 强制回调:确保值在0-100之间
     private static object CoerceValue(DependencyObject d, object baseValue)
     {
         double value = (double)baseValue;
         if (value < 0) return 0.0;
         if (value > 100) return 100.0;
         return value;
     }
     // 验证回调
     private static bool ValidateValue(object value)
     {
         double val = (double)value;
         return !double.IsNaN(val);  // 不允许NaN值
     }

 }
View Code
<StackPanel Margin="10">
    <!-- 显示依赖属性值:绑定值 值的来自自定义控件 -->
    <TextBlock Text="{Binding Value, ElementName=customControl, StringFormat=Value: {0:F2}}"/>

    <!-- 自定义控件 -->
    <local:CustomControl x:Name="customControl" Value="50" Margin="0,10,0,10" Height="39"/>

    <!-- 修改依赖属性值(触发回调)Slider可以修改值,因为绑定时双向的 -->
    <Slider Minimum="0" Maximum="100" Value="{Binding Value, ElementName=customControl, Mode=TwoWay}"/>
</StackPanel>
View Code

 

posted @ 2025-09-03 14:41  灰色淡季  阅读(20)  评论(0)    收藏  举报