C# WPF 中的​属性示例详解

 更新时间:2026年02月05日 10:08:44   作者:工业程序猿老赵  
本文详细介绍了C# WPF中的属性,包括基础的CLR属性和WPF特有的依赖属性以及附加属性,通过实战和示例,解释了如何在XAML中使用属性进行数据绑定、样式设置和动画等操作,感兴趣的朋友跟随小编一起看看吧

想要深入浅出地理解 C# WPF 中的​属性​,核心要区分「C# 语言本身的属性」和「WPF 特有的依赖属性 / 附加属性」,还要掌握 WPF 中属性的核心用法(数据绑定、样式、动画等)—— 这也是 WPF 数据驱动 UI 的基础。我会从​基础到进阶​,先讲 C# 普通属性,再过渡到 WPF 的核心属性体系,结合示例和实际场景讲清楚设计思想和用法,保证易懂且贴近实战。

一、先铺垫:C# 普通属性(CLR 属性)—— WPF 属性的基础

WPF 的所有高级属性都是基于 C# 语言的CLR 属性(Common Language Runtime 属性) 实现的,先回顾这个基础,才能理解 WPF 对属性的扩展。

1. 什么是 CLR 属性

CLR 属性是​字段的封装​,通过get/set访问器控制字段的读写,核心作用是​封装数据、添加访问逻辑​,避免直接操作私有字段。

// 私有字段(实际存储数据)
private string _name = "默认名称";
// CLR属性(对外暴露的访问接口)
public string Name
{
    get => _name; // 读访问器:获取字段值
    set => _name = value; // 写访问器:设置字段值,value是关键字,表示传入的新值
}

2. 带逻辑的 CLR 属性(实战常用)

可以在get/set中添加校验、计算等逻辑,这是封装的核心价值:

private int _age;
public int Age
{
    get => _age;
    set
    {
        // 校验逻辑:年龄不能为负数
        if (value >= 0) 
            _age = value;
        else
            throw new ArgumentException("年龄不能为负数");
    }
}
// 计算属性:无对应的私有字段,值由其他属性计算而来
public string AgeDesc
{
    get => _age >= 18 ? "成年人" : "未成年人";
}

3. WPF 中 CLR 属性的局限

在之前的 MVVM 示例中,我们用 CLR 属性 +INotifyPropertyChanged实现了​数据变化通知 UI​,但这种方式有明显局限:

  • 仅能实现「单向通知」,无法让多个 UI 控件共享一个属性值且自动同步;
  • 不支持 WPF 的核心特性:​**数据绑定的高级特性(如验证、转换)、样式触发、动画、继承属性(如字体、颜色)**​;
  • 性能一般,频繁更新时通知机制的开销较高。

正是为了解决这些问题,WPF 设计了依赖属性(Dependency Property) —— 这是 WPF 属性体系的​核心​。

二、WPF 核心:依赖属性(Dependency Property)—— 专为 WPF 设计的属性

1. 什么是依赖属性

依赖属性是​WPF 自定义的属性系统​,它的核心特点是:​属性的值不是由自身字段存储,而是「依赖」于 WPF 的属性系统(DependencyObject)统一管理​。

简单理解:普通 CLR 属性是「自己存值自己用」,依赖属性是「把值交给 WPF 全局管理,谁需要谁去取」,这种设计让它天然支持 WPF 的所有高级特性。

2. 依赖属性的设计初衷(解决什么问题)

WPF 作为​声明式 UI 框架​,需要属性支持「动态变化、共享、扩展」,依赖属性完美解决:

  1. 属性值继承​:子控件自动继承父控件的属性(如 Window 设置 FontSize,内部所有 TextBlock 自动继承);
  2. 动态值解析​:属性值可来自样式、数据绑定、动画、模板等,WPF 会自动解析优先级;
  3. 轻量级存储​:大量控件的相同属性(如 Visibility)默认值一致,WPF 仅存储「修改过的属性值」,节省内存;
  4. 变更通知​:内置属性值变化通知,无需手动实现INotifyPropertyChanged
  5. 支持附加行为​:如数据验证、样式触发、动画绑定。

3. 依赖属性的核心规则(必记)

  • 必须定义在继承自 DependencyObject的类中(WPF 所有控件如 Button、TextBox、Window 都继承自 DependencyObject);
  • 必须是公共静态只读的字段,类型为DependencyProperty,命名规范:属性名 + Property(如TextProperty);
  • 必须通过​CLR 属性包装​,对外暴露常规的get/set,内部调用GetValue/SetValue(WPF 属性系统的核心方法);
  • 必须通过DependencyProperty.Register方法注册到 WPF 属性系统中。

4. 实战 1:自定义一个依赖属性(最基础示例)

我们以「自定义一个带MyText依赖属性的控件」为例,一步一步实现,理解核心写法:

using System.Windows;
using System.Windows.Controls;
// 自定义控件:继承自Control(间接继承DependencyObject)
public class MyCustomControl : Control
{
    // 1. 注册依赖属性:公共静态只读字段,命名规范【属性名+Property】
    public static readonly DependencyProperty MyTextProperty =
        DependencyProperty.Register(
            nameof(MyText), // 依赖属性对应的CLR属性名(必须一致)
            typeof(string), // 属性的类型
            typeof(MyCustomControl), // 所属的控件类型
            new PropertyMetadata( // 属性元数据:设置默认值、变化回调等
                "默认文本", // ① 属性默认值
                OnMyTextChanged // ② 属性值变化时的回调方法(可选)
            )
        );
    // 2. CLR属性包装:对外暴露常规访问方式,内部调用GetValue/SetValue
    public string MyText
    {
        get => (string)GetValue(MyTextProperty); // 从WPF属性系统取值
        set => SetValue(MyTextProperty, value); // 向WPF属性系统赋值
    }
    // 3. 可选:属性值变化的回调方法(处理逻辑,如通知UI、更新其他属性)
    private static void OnMyTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // d:当前控件实例;e:变化参数(OldValue旧值,NewValue新值)
        var control = (MyCustomControl)d;
        string oldText = (string)e.OldValue;
        string newText = (string)e.NewValue;
        // 这里可以添加属性变化后的逻辑,比如更新控件样式、触发事件等
        control.ToolTip = $"文本从「{oldText}」改为「{newText}」";
    }
}

5. 实战 2:在 XAML 中使用自定义依赖属性

定义好依赖属性后,就可以像使用 WPF 原生控件的属性(如 Button 的 Content)一样在 XAML 中使用,支持​数据绑定、样式、静态赋值​:

<!-- 引用自定义控件的命名空间(假设命名空间是WpfPropertyDemo) -->
<Window x:Class="WpfPropertyDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfPropertyDemo"
        Title="依赖属性示例" Height="200" Width="300">
    <StackPanel Spacing="10" HorizontalAlignment="Center" VerticalAlignment="Center">
        <!-- 1. 静态赋值:直接设置MyText -->
        <local:MyCustomControl MyText="我是自定义控件" Width="200" Height="50" Background="LightBlue"/>
        <!-- 2. 数据绑定:和ViewModel的属性关联(支持WPF所有绑定特性) -->
        <local:MyCustomControl MyText="{Binding ViewModelText}" Width="200" Height="50" Background="LightGreen"/>
    </StackPanel>
</Window>

6. 实战 3:使用 WPF 原生依赖属性(开发中最常用)

实际开发中,我们​很少自定义依赖属性​,更多是使用 WPF 原生控件的依赖属性(如 TextBox 的Text、Button 的Command、TextBlock 的Foreground),结合数据绑定实现核心功能 —— 这也是之前 MVVM 示例的基础:

<!-- 原生依赖属性的核心用法:数据绑定 -->
<TextBox Text="{Binding UserName, Mode=TwoWay}"/>
<Button Content="确认" Command="{Binding ConfirmCommand}"/>
<TextBlock Foreground="{Binding TipColor}" Text="{Binding TipMessage}"/>

这些Text/Content/Foreground都是 WPF 内置的依赖属性,天然支持数据绑定、样式、动画等特性。

三、WPF 进阶:附加属性(Attached Property)—— 「跨控件」的依赖属性

1. 什么是附加属性

附加属性是​依赖属性的特殊形式​,核心特点是:​属性定义在 A 类中,但可以被 B 类(继承自 DependencyObject)使用​,简单说就是「​借属性用​」。

设计初衷:解决控件之间的布局 / 关系属性问题 —— 比如布局控件(Grid、StackPanel)需要控制子控件的布局(如 Grid 的行 / 列),但子控件(Button、TextBox)本身不需要内置这些布局属性,此时就可以用附加属性。

2. 核心规则(与依赖属性的区别)

  • 同样继承自DependencyObject,注册方法不是Register,而是 **RegisterAttached**;
  • 没有常规的 CLR 属性包装,而是提供静态的GetXXX/SetXXX方法供外部调用;
  • 命名规范和依赖属性一致:属性名 + Property

3. 最经典的示例:Grid 的附加属性

WPF 中 Grid 的Grid.Row/Grid.Column是最常用的附加属性,定义在Grid类中,但可以被所有子控件使用:

<!-- Grid.Row/Grid.Column是Grid的附加属性,被Button/TextBox使用 -->
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <!-- Button使用Grid的Row附加属性,指定在第0行 -->
    <Button Grid.Row="0" Content="第0行按钮"/>
    <!-- TextBox使用Grid的Row附加属性,指定在第1行 -->
    <TextBox Grid.Row="1" PlaceholderText="第1行输入框"/>
</Grid>

底层原理:Grid 在布局时,会通过Grid.GetRow(控件)获取附加属性的值,然后根据值排列子控件。

4. 实战:自定义一个附加属性(理解底层)

我们实现一个「自定义附加属性MyAttachProperty」,让所有控件都能设置「是否显示边框」,一步一步来:

using System.Windows;
using System.Windows.Controls;
// 附加属性的定义类(可任意类,只要静态方法+注册Attached)
public static class MyAttachHelper
{
    // 1. 注册附加属性:使用RegisterAttached
    public static readonly DependencyProperty ShowBorderProperty =
        DependencyProperty.RegisterAttached(
            "ShowBorder", // 附加属性名
            typeof(bool), // 属性类型
            typeof(MyAttachHelper), // 所属类
            new PropertyMetadata(false, OnShowBorderChanged) // 默认值false,变化回调
        );
    // 2. 公共静态Get方法:供外部获取属性值(命名规范:Get+属性名)
    public static bool GetShowBorder(DependencyObject obj)
    {
        return (bool)obj.GetValue(ShowBorderProperty);
    }
    // 3. 公共静态Set方法:供外部设置属性值(命名规范:Set+属性名)
    public static void SetShowBorder(DependencyObject obj, bool value)
    {
        obj.SetValue(ShowBorderProperty, value);
    }
    // 4. 属性值变化的回调:处理逻辑(给控件加/去边框)
    private static void OnShowBorderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // d是使用该附加属性的控件实例,判断是否是FrameworkElement(所有可视化控件的基类)
        if (d is FrameworkElement element)
        {
            bool isShow = (bool)e.NewValue;
            // 根据值设置边框:如果是Button/TextBox,直接设置BorderBrush和BorderThickness
            if (element is Button button)
            {
                button.BorderBrush = isShow ? Brushes.Black : Brushes.Transparent;
                button.BorderThickness = isShow ? new Thickness(1) : new Thickness(0);
            }
            else if (element is TextBox textBox)
            {
                textBox.BorderBrush = isShow ? Brushes.Red : Brushes.Gray;
                textBox.BorderThickness = isShow ? new Thickness(2) : new Thickness(1);
            }
        }
    }
}

5. 在 XAML 中使用自定义附加属性

和使用原生附加属性一样,通过「​命名空间。类名。属性名​」的方式设置,所有 WPF 控件都能使用:

<Window x:Class="WpfPropertyDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfPropertyDemo"
        Title="附加属性示例" Height="200" Width="300">
    <StackPanel Spacing="10" HorizontalAlignment="Center" VerticalAlignment="Center">
        <!-- Button使用自定义附加属性:显示黑色边框 -->
        <Button local:MyAttachHelper.ShowBorder="True" Content="带边框按钮" Width="100"/>
        <!-- TextBox使用自定义附加属性:显示红色粗边框 -->
        <TextBox local:MyAttachHelper.ShowBorder="True" PlaceholderText="带边框输入框" Width="200"/>
        <!-- 普通TextBlock:不设置,默认无边框 -->
        <TextBlock Text="无边框文本" Width="200"/>
    </StackPanel>
</Window>

四、WPF 属性的核心知识点:值优先级(必懂)

WPF 的依赖属性 / 附加属性支持​多来源赋值​(如默认值、样式、数据绑定、动画、代码手动设置),当一个属性被多个来源赋值时,WPF 会按照固定的优先级解析最终值,优先级从依次为:

  1. 动画正在运行的值 → 2. 代码手动设置的本地值(如button.Content = "测试") → 3. 数据绑定的值 → 4. 样式触发器 → 5. 样式设置 → 6. 继承的值(如父控件的 FontSize) → 7. 属性的默认值(PropertyMetadata 中设置)。

示例​:如果给 Button 的Content同时设置「样式默认值 = 按钮」、「数据绑定 = ViewModel.Text」、「代码设置 = 测试」,最终 Button 显示​测试​(本地值优先级最高)。

这个规则能帮你解决开发中「属性值不生效」的问题 —— 比如动画运行时,数据绑定的属性值会被覆盖,动画结束后才会恢复。

五、CLR 属性 vs 依赖属性 vs 附加属性(核心对比)

为了让你快速区分和选择,用表格总结三者的核心区别、使用场景:

类型核心特点存储方式定义位置核心使用场景
CLR 属性C# 原生,get/set 封装字段私有字段自行存储任意类ViewModel 中的数据属性(需配合 INotifyPropertyChanged)
依赖属性WPF 自定义,支持高级特性WPF 属性系统统一管理继承 DependencyObject 的类WPF 控件的核心属性(如 Text、Content)、自定义控件的属性
附加属性依赖属性的特殊形式,跨控件使用WPF 属性系统统一管理任意类(静态方法)布局控制(Grid.Row)、跨控件的通用属性(如自定义附加行为)

核心选择原则​:

  1. ViewModel 层​:用​CLR 属性 + INotifyPropertyChanged​(ViewModel 不需要继承 DependencyObject,方便单元测试);
  2. View 层(控件 / 自定义控件)​:用​依赖属性​(支持数据绑定、样式等 WPF 特性);
  3. 跨控件的通用属性 / 布局​:用​附加属性​(如让所有控件都能设置某个通用特性)。

六、WPF 属性与数据驱动 UI 的关联(回归核心)

WPF 的数据驱动 UI核心是「​数据绑定 + 属性变化通知​」,而属性是这个核心的基础:

  1. ViewModel 的CLR 属性通过INotifyPropertyChanged实现「数据变化→通知 WPF」;
  2. View 层控件的依赖属性天然支持「数据绑定」,WPF 监听到 ViewModel 的通知后,自动更新依赖属性的值,进而刷新 UI;
  3. 依赖属性的内置变更通知让控件自身的属性变化时,也能触发 UI 刷新(如 Slider 的 Value 变化,触发 TextBlock 的 Text 更新)。

简单说:​没有 WPF 的属性体系,就没有数据驱动 UI 的实现​。

总结

  1. 基础​:CLR 属性是 C# 原生封装,ViewModel 层专用,需配合INotifyPropertyChanged实现数据通知;
  2. 核心​:依赖属性是 WPF 的灵魂,定义在继承DependencyObject的类中,支持数据绑定、样式、动画等所有高级特性,View 层控件的属性均为依赖属性;
  3. 扩展​:附加属性是跨控件的依赖属性,通过Get/Set静态方法使用,核心用于布局和跨控件的通用属性;
  4. 关键规则​:依赖属性 / 附加属性的值由 WPF 属性系统管理,遵循​值优先级​,解决多来源赋值的冲突;
  5. 核心关联​:WPF 的属性体系是数据驱动 UI 的基础,CLR 属性负责 ViewModel 的数封装,依赖属性负责 View 层的 UI 绑定和刷新。

到此这篇关于深入浅出地理解 C# WPF 中的​属性的文章就介绍到这了,更多相关C# WPF属性内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解WPF如何把checkbox改成开关样式

    详解WPF如何把checkbox改成开关样式

    这篇文章主要为大家详细介绍了WPF如何把checkbox改成开关样式,文中的示例代码讲解详细,具有一定的借鉴价值,有需要的小伙伴可以参考一下
    2024-11-11
  • C#结合Spire.Doc for .NET实现智能复制Word文档内容

    C#结合Spire.Doc for .NET实现智能复制Word文档内容

    在日常的开发工作或文档处理中,我们常常需要将一个Word文档中的内容复制到另一个文档,本文将为您揭示如何利用C#编程语言与强大的Spire.Doc for .NET库,实现Word文档内容的智能复制,感兴趣的小伙伴可以了解下
    2026-01-01
  • unity shader 较完整光照(含有多光源阴影)

    unity shader 较完整光照(含有多光源阴影)

    这篇文章主要介绍了unity shader 较完整光照(含有多光源阴影),本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-09-09
  • C#简单数字图像处理程序

    C#简单数字图像处理程序

    这篇文章主要为大家详细介绍了C#简单数字图像处理程序,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-09-09
  • C# 文件下载之断点续传实现代码

    C# 文件下载之断点续传实现代码

    本篇文章主要介绍了C# 文件下载之断点续传实现代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-01-01
  • C# XAML中x:Type的用法详解

    C# XAML中x:Type的用法详解

    x:Type是 XAML 中获取.NET 类型Type对象的核心工具,主要用于泛型类型声明、依赖属性的 Type 参数设置、反射场景的类型传递等,本文介绍C# XAML中x:Type的用法,感兴趣的朋友跟随小编一起看看吧
    2025-12-12
  • C#实现奇偶排序的示例代码

    C#实现奇偶排序的示例代码

    奇偶排序是一种简单的排序算法,该算法通过比较并交换相邻的元素来完成排序,本文主要介绍了C#实现奇偶排序的示例代码,具有一定的参考价值,感兴趣的可以了解一下
    2023-11-11
  • C#中读写INI配置文件的方法

    C#中读写INI配置文件的方法

    这篇文章主要介绍了C#中读写INI配置文件的方法,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-07-07
  • C#索引属性用法实例分析

    C#索引属性用法实例分析

    这篇文章主要介绍了C#索引属性用法,实例分析了C#声明索引属性的相关技巧,需要的朋友可以参考下
    2015-06-06
  • Unity中协程IEnumerator的使用方法介绍详解

    Unity中协程IEnumerator的使用方法介绍详解

    本文主要介绍了Unity中协程IEnumerator的使用方法介绍详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06

最新评论