[WPF] 万字拆解依赖属性与附加属性

@[TOC]依赖属性DP & 附加属性AP

属性Property

程序本质就是 数据+算法封装在类里面变量叫 字段表示 类或者实例的状态;封装在类里面的函数称为方法,表示类或实例的功能。

GET/SET 这对方法合并成属性

get/set是方法

属性

刚着手开始学习C#的时候,不明白为什么会有属性这个东西,不是已经有了字段了吗,你说属性里面有get和set方法对数据进行了封装,可以通过对方法的访问限定来控制该属性是否可以被赋值,但是不也有readonly这个关键字可以用来修饰字段吗,你又说可以通过在get或set方法里面对数据进行一系列的操作来对数据进行限制,可以将数据的获取和赋值分开进行不同的处理,好吧这个我是服气的。

复制代码
public int Age { get; set; }  //则就是个很普通的属性了
public int Age { get; }       //可以不写set方法,等同于通过访问限定符private来对set方法进行修饰

属性的定义

属性(Property) 是类(class)、结构(structure)和接口(interface)的命名(named)成员。类或结构中的成员变量或方法称为 域(Field) 。属性(Property)是域(Field)的扩展,且可使用相同的语法来访问。它们使用 访问器(accessors) 让私有域的值可被读写或操作。

**属性(Property)**不会确定存储位置。相反,它们具有可读写或计算它们值的 访问器(accessors)。例如,有一个名为 Student 的类,带有 age、name 和 code 的私有域。我们不能在类的范围以外直接访问这些域,但是我们可以拥有访问这些私有域的属性。

快捷键"propf"

用set和get方法来访问私有成员变量,它们相当于外界访问对象的一个通道,一个"接口"。set像门卫大叔

csharp 复制代码
public <返回类型(要与被访问变量的类型相同)> <属性名(不能与被访问变量同名)>
{
        get{ return <被访问变量>;}    //只读
        set{ <被访问变量> = value;}   //只写
}

**但是!**属性是没有办法发出通知的,比如我我把值改为了3,它是不会去通知别人说:"看!我变成小3了!!"。它要是会通知的话,那真是"见鬼了!"。

这里我们就不得提到INotifyPropertyChanged 接口

INotifyPropertyChanged

译为"属性变化时通告"

我们创建一个Student类实现这个接口。

发现它实现了一个事件"PropertyChanged"。

"说话的能力"PropertyChanged

接口默认实现了一个事件,

这个事件怎么用呢?在哪里执行呢?

  • 我们知道是当"属性的值发生改变时去通知"。
  • 在属性里赋值为set方法 ,所以我们的事件在set方法体里实行。

"不为空时就执行事件(?.Invoke())"

关于?.invoke作用

Invoke()的作用是:在应用程序的主线程 上执行指定的委托。一般应用:在辅助线程中修改UI线程( 主线程 )中对象的属性时,调用this.Invoke();

this.Invoke就是跨线程访问ui的方法

如果你的后台线程在更新一个UI控件的状态后不需要等待,而是要继续往下处理,那么你就应该使用BeginInvoke来进行异步处理。

如果你的后台线程需要操作UI控件 ,并且需要等 到该操作执行完毕才能继续执行,那么你就应该使用Invoke

第一个参数 也就是触发这个事件的对象(this)第二个参数 实际上告诉是哪个属性发生了变化(new PropertyChangedEventArgs(propName)) ,所以是个字符串,表示当前属性的名称。

依赖属性 Dependency Property

特性

依赖属性是 WPF 属性系统提供的一种扩展属性机制。与普通的 CLR 属性(例如 C# 中的 public 属性)不同,依赖属性具有以下关键特性:

属性系统支持

它们由 WPF 属性系统统一管理,并参与到 WPF 的各种特性中,如样式(Styles)、模板(Templates)、数据绑定(Data Binding)、动画(Animations)、继承(Property Value Inheritance)、以及属性值更改通知等。

存储与计算分离

依赖属性的实际值不一定直接存储在对象字段中。WPF 属性系统会维护属性的值,并且可以根据优先级规则(例如,本地值、样式、模板、继承、默认值等)动态计算属性的有效值。

元数据支持

在注册依赖属性时,可以关联元数据(PropertyMetadata),用于定义属性的默认值、继承行为、值验证回调、属性更改回调等。

更改通知

当依赖属性的值发生变化时,WPF 属性系统会自动通知所有注册了该属性更改回调的处理程序。

如何定义依赖属性

定义一个依赖属性通常需要以下几个步骤:

1.声明DependencyProperty 字段

声明一个 public static readonly 静态只读的 DependencyProperty 类型的字段。 这个字段是依赖属性的标识符,用于在属性系统中唯一标识该属性

字段的命名约定是**[PropertyName]Property**。

2.Register()注册依赖属性

在类的静态构造函数中,使用 DependencyProperty.Register() 方法注册依赖属性。Register() 方法需要提供

属性的名称(字符串)

属性的类型(Type)

拥有者的类型(Type),即声明该依赖属性的类

属性的元数据,可选的属性元数据 (PropertyMetadata),用于指定默认值、属性更改回调、强制值回调等可选的属性元数据 (PropertyMetadata),用于指定默认值、属性更改回调、强制值回调等。

3.包装器设置依赖属性值

为该依赖属性声明一个公共的 CLR 属性包装器(property wrapper)。 这个包装器包含 get 和 set 访问器,它们分别使用 GetValue() 和 SetValue() 方法来访问和设置依赖属性的值。

例子:自定义 ImageButton 中的 NormalSource 属性

在你的 ImageButton 代码中,NormalSource 就是一个依赖属性:

csharp 复制代码
/// <summary>
/// 正常状态图像
/// </summary>
public ImageSource NormalSource
{
    get => GetValue(NormalSourceProperty) as ImageSource;
    set => SetValue(NormalSourceProperty, value);
}

public static readonly DependencyProperty NormalSourceProperty =
    DependencyProperty.Register(
        "NormalSource", // 属性名称
        typeof(ImageSource), // 属性类型
        typeof(ImageButton), // 拥有者类型
        new PropertyMetadata(null, new PropertyChangedCallback(OnNormalSourceChanged))); // 属性元数据

解释:

public static readonly DependencyProperty NormalSourceProperty = ...;:声明了一个名为 NormalSourceProperty 的静态只读字段,它是 DependencyProperty 类型的。

DependencyProperty.Register(...):注册了名为 "NormalSource " 的依赖属性 ,它的类型是 ImageSource属于 ImageButton 类 。new PropertyMetadata(...) 创建了属性的元数据 ,这里指定了默认值为 null,并且当属性值改变时,会调用 OnNormalSourceChanged 回调函数

csharp 复制代码
public ImageSource NormalSource
{
  get => GetValue(NormalSourceProperty) as ImageSource;
  set => SetValue(NormalSourceProperty, value);
}:

这是一个 CLR 属性包装器。当你在 XAML 或代码中访问 ImageButton 实例的 NormalSource 属性时

实际上会调用 GetValue(NormalSourceProperty) 来获取依赖属性的值,或者调用 SetValue(NormalSourceProperty, value) 来设置依赖属性的值

csharp 复制代码
// 假设我们要在一个名为 MyControl 的自定义控件中定义一个名为 Text 的依赖属性
// 类型为 string,默认值为 "Hello"。
using System.Windows;
using System.Windows.Controls;

public class MyControl : Control
{
    // 1. 声明依赖属性标识符
    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register(
            "Text",             // 属性名称
            typeof(string),     // 属性类型
            typeof(MyControl),  // 属性所有者类型
            new FrameworkPropertyMetadata("Hello", // 默认值
                FrameworkPropertyMetadataOptions.AffectsRender, // 指示此属性的更改会影响呈现
                new PropertyChangedCallback(OnTextChanged)) // 属性更改回调
        );

    // 2. 定义 CLR 包装器属性
    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }

    // 3. 属性更改回调方法
    private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        MyControl control = (MyControl)d;
        // 在这里处理属性值更改的逻辑,例如更新 UI
        System.Diagnostics.Debug.WriteLine($"Text property changed from '{e.OldValue}' to '{e.NewValue}' in {control}.");
    }

    // (可选) 强制值回调
    private static object CoerceTextValue(DependencyObject d, object baseValue)
    {
        string value = baseValue as string;
        if (string.IsNullOrEmpty(value))
        {
            return "Default Value (Coerced)";
        }
        return value;
    }
}

在 XAML 中使用:

xml 复制代码
<local:MyControl Text="World" />

依赖属性的优势在这个例子中体现如下:

  • 样式和模板: 你可以在 ImageButton 的样式或模板中设置 NormalSource 的值。
  • 数据绑定: 你可以将 NormalSource 绑定到一个数据源的属性。
  • 属性更改通知: 当 NormalSource 的值改变时,OnNormalSourceChanged 回调函数会被自动调用,你的代码可以在这个回调中执行相应的逻辑(例如,更新显示的图片)。

依赖属性的定义

WPF相关,一般情况下只是使用,但如果自定义WPF控件或者给现有的控件增加新功能时侯就需要自定义。依赖属性是WPF中特有的,大部分只是支持UI更新。依赖属性是wpf提供的一种为丰富控件属性,使依赖属性可以依赖于控件绑定的值,并可以对这些值进行一些逻辑处理的东西。

依赖属性 (Dependency Properties) 是定义在类自身上的属性。它们是 WPF 属性系统的核心,提供了高级特性(如数据绑定、样式、动画、属性继承、默认值、验证和属性更改通知)。自定义控件通常会使用依赖属性来暴露其可配置的行为和外观。

这里说明一下WPF设计理念,数据驱动,UI与逻辑松耦合。

依赖属性 就是一种可以自己没有值并能 通过使用Binding从数据源获得值的属性 。拥有依赖属性的对象称为依赖对象

csharp 复制代码
private void Button_Click()
{
    Student stu = new Student(); //创建实例并引用
    stu.SetValue(Student.NameProperty,this.textBox1.Text);
    //调用setvalue方法,把textBox1.Text属性的值存进依赖属性
    textBox2.Text = (String)stu.GetValue(Student.NameProperty);
    //使用GetValue方法把值读取出来
}

依赖对象 :其实在NET开发中,一个对象所占用内存空间在调用new操作符实例化时就决定了,而且WPF允许对象在被创建时并不用包含内存数据,只需要保留在用到数据时能够获得默认值,借用其他对象数据或实时分配空间的能力

必须依赖对象作为依赖属性的宿主,二者结合起来。

什么是依赖对象/依赖属性

  • 什么是依赖对象DependencyObject 派生自DependencyObject 的类型就是依赖对象,而WPF中所有的控件都是 派生自DependencyObject
  • 什么是依赖属性DependencyProperty

依赖属性就是一种自己可以没有值,但可以通过绑定从其他数据源获取值,这种依赖在其它对象上的属性,就是依赖属性。

  • 依赖对象和依赖属性

只有 DependencyObject 类型可以定义依赖属性WPF的控件都是依赖对象,并且大多数的属性都是依赖属性

通知属性INotifyPropertyChanged与依赖属性DependencyProperty

DependencyObject 类表示参与依赖属性系统的对象。属性系统的主要功能是计算属性的值,并提供有关已更改的值的系统通知。 参与属性系统的另一个类DependencyPropertyDependencyProperty 允许将依赖属性注册到属性系统,并提供有关每个依赖属性的标识和信息,而DependencyObject 为基类,使对象能够使用此依赖属性。

依赖属性。 通过调用 Register 方法,并将该方法的返回值存储为类中的公共静态字段,来注册依赖属性。

附加属性。 通过调用 RegisterAttached 方法,并将该方法的返回值存储为类中的公共静态只读字段,来注册附加属性。

获取、设置和清除 DependencyObject上存在的任何依赖项属性的值的实用工具方法。

元数据、强制值支持、属性更改通知和替代依赖属性或附加属性的回调。 此外,DependencyObject 类简化了依赖属性的每个所有者的属性元数据。

是 ContentElement、Freezable或 Visual派生类的基类。

csharp 复制代码
public class Student :  DependencyObject
{
     public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Student));
}

INotifyPropertyChanged 类用于通知UI刷新,注重的仅仅是数据更新后的通知。DependencyObject 类用于给UI添加依赖和附加属性,注重数据与UI的关联。如果简单的数据通知,两者都可以实现的。

在WPF中的资源引用,数据绑定,样式模板的引用,动画设置,元数据重写,属性值继承等等功能的实现都需要依赖属性的支持。可以说不可缺少

资源,依赖属性值可以通过**引用资源(DynamicResource 或StaticResource)**来设置。

示例代码如下:

xml 复制代码
<Window.Resources>
    <SolidColorBrush x:Key="myBtnColor" Color="Red"/>
</Window.Resources>
<Grid Background="{DynamicResource myBtnColor}"></Grid>

注意:若要是使用动态资源引用,必须设置为依赖属性

数据绑定 ,依赖属性可以通过数据绑定来引用值 。使用数据绑定,最终属性值的确定将延迟到运行时,在运行时将从数据源获取属性值也只有依赖属性才可以进行数据绑定

示例代码如下:

xml 复制代码
<Grid>
   <TextBox x:Name="tb1" Width="200" Height="40" VerticalContentAlignment="Center"/>
   <Button Content="{Binding ElementName=tb1,Path=Text}" Width="200" Height="40"/>
</Grid>
  • 样式 ,样式和模板是使用依赖属性的两个主要场景,主要是通过StyleTemplate 两个依赖属性来设置一个控件的外观
  • 动画,可以对依赖属性进行动画处理,

示例代码如下:

xml 复制代码
<Button>I am animated
  <Button.Background>
    <SolidColorBrush x:Name="AnimBrush"/>
  </Button.Background>
  <Button.Triggers>
    <EventTrigger RoutedEvent="Button.Loaded">
      <BeginStoryboard>
        <Storyboard>
          <ColorAnimation
            Storyboard.TargetName="AnimBrush"
            Storyboard.TargetProperty="(SolidColorBrush.Color)"
            From="Red" To="Green" Duration="0:0:5"
            AutoReverse="True" RepeatBehavior="Forever" />
        </Storyboard>
      </BeginStoryboard>
    </EventTrigger>
  </Button.Triggers>
</Button>

元数据重写,通过依赖属性的OverrideMetadata来重写父类的属性默认行为

属性值继承,元素可以从其在对象树中的父级继承依赖属性的值。

示例代码如下:

csharp 复制代码
public class UserInfo
{
   public string Id { get; set; }
   public string Name { get; set; }
}

xml 复制代码
<Window.Resources>
    <local:UserInfo x:Key="info" Id="1" Name="测试"/>
</Window.Resources>
<StackPanel>
    <StackPanel  DataContext="{Binding Source={StaticResource info}}">
         <!--由于父类控件,指定了数据源,因此子类会继承DataContext属性,无需再次指定-->
         <TextBlock Text="{Binding Path=Id}"></TextBlock>
         <!--当父类已经指定数据源,并且Binding中只有一个Path的时候,可以省略-->
         <TextBlock Text="{Binding Name}"/>
     </StackPanel>
     <!--无父类指定数据源,则需自己指定,与前一种对比-->
         <TextBlock Text="{Binding Source={StaticResource info}, Path=Name}"/>
</StackPanel>

为什么要有依赖属性

依赖属性是一种可以自已没有值,但是可以通过Binding方式,从数据源(依赖别人的数据)获得值的属性

S传统的 CLR 属性虽然简单,但在构建复杂的 UI 框架时会遇到一些局限性。依赖属性的引入解决了这些问题,使得属性系统更加灵活、强大且可扩展。例如,实现属性更改通知和数据绑定等功能,如果完全依赖 CLR 属性的 set 访问器,将会非常繁琐且难以维护。

传统的CLR属性

csharp 复制代码
public class Person
{
    private string _Name;
    public string Name
    {
        get
        {
             return _Name;
        }
        set
        {
            _Name = value;
        }
    }
}

CLR属性存在的问题:

在多级继承的情况下,每次继承,父类的字段都被继承,孙孙辈对象占用内存空间不可避免的膨胀。

CLR属性包装器

csharp 复制代码
//CLR属性包装器  相当于为依赖属性准备了用于暴露数据的Binding Path,有了数据源和数据目标双重身份,依赖属性默认带有接到通知的功能,所以不用INotifyPropertyChanged接口
public string Name
{
      get { return (string)GetValue(NameProperty); }
      set { SetValue(NameProperty, value); }
}

如何添加依赖属性

在多级继承,大多数字段并没有被修改的情况下,如何少对象的体积。数据驱动指导思想下,数据如何保存简单一致,同步

csharp 复制代码
//使类型继承DependencyObject类
public class Person : DependencyObject
{
    // 1. 声明一个静态只读的DependencyProperty字段
    //使用DependencyProperty类定义一个对象,将这个对象作为name属性的依赖
     public static readonly DependencyProperty nameProperty;
     static Person()
     {
         // 2. 注册定义的依赖属性
         // 再将这个依赖对象注册到属性name所在的类上
         // typeof(你的属性所在类的名称)
         nameProperty = DependencyProperty.Register(
         "Name",
         typeof(string),
         typeof(Person),
         new PropertyMetadata("Learning Hard",OnValueChanged));
         }

        // 3. 属性包装器,通过它来读取和设置我们刚才注册的依赖属性
        // 如何应用绑定呢?将我们属性里定义的替换为刚刚定义的依赖即可
        // 直接使用GetValue和SetValue来赋值和获取依赖对象的值
        public string Name
        {
            get { return (string)GetValue(nameProperty); }
            set { SetValue(nameProperty, value); }
        }
        private static void OnValueChanged(DependencyObject dpobj, DependencyPropertyChangedEventArgs e)
        {
            // 当只发生改变时回调的方法
        }
    }

从注册代码中,我们可以看到,他注册了三个信息:

1,当前DependencyProperty类定义的对象nameProperty,依赖于属性Name。

2,对象nameProperty的依赖类型与属性Name的类型一样都是string。

3,对象nameProperty注册的类是声明属性Name的类,即,在其他类里,将看不到该依赖对象。

依赖属性使用大致分为两个步骤

普通写法(propdp)自动生成

第一步

在DependencyObject派生类中声明public static 修饰的DependencyObject成员变量,并使用DependencyProperty.Register方法 获得DependencyProperty实例

第二步

使用DependencyObject SetValue和GetValue方法,借助DependencyProperty实例存取值

下面的注释是自动生成的,可以看到这个对象就是用于MyProperty的封装数据,它可以作用于动画,样式,绑定等等...
Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...

csharp 复制代码
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register(
"MyProperty",
typeof(int),
typeof(ownerclass), new PropertyMetadata(0));

依赖属性的命名约定为以Property作为后缀,该对象使用了DependencyProperty类里面的Register静态方法的返回值

可以看到Register方法有三个重载,前三个参数

  • String:表示所要对其进行封装的属性的名称;
  • 第一个Type:表示该属性所属类型;
  • 第二个Type:表示该属性所有者的类型,也就是在哪个类里面定义的这个依赖属性;

依赖属性的实现也很简单,按以下步骤做就可以了:

第一步: 让自己的类继承自 DependencyObject基类。在WPF中,几乎所有的UI元素都继承自DependencyObject,这个类封装了对依赖属性的存储及 访问等操作,使用静态类型与依赖属性的内部存储机制相关。WPF处理依赖属性不再像普通.NET属性那样将属性值存储到一个私有变量中,而是使用一个字典 型的变量来存放用户显示设置的值。

第二步:依赖属性的定义必须使用 public static 声明一个 DependencyProperty的变量,并且有一个Property作为后缀,该变量才是真正的依赖属性 。

依赖属性的优势

1、解决多级继承,且大多数字段值不改变的情况下,减少内存占比将一个DependencyProperty对象存储在一个全局的Hashtable中;通过依赖对象(DependencyObject)的GetValue和SetValue存取数据;

2、以数据为中心,当数据源改变时,所以关联的UI数据改变;

依赖属性值可以通过Binding依赖于其它对象上,这就使得数据源一变动;依赖于此数据源的依赖属性全部进行更新


哈希表(HashTable)又叫做散列表,是根据关键码值(即键值对)而直接访问的数据结构。也就是说,它通过把关键码映射到表中一个位置来访问记录,以加快查找速度。这个映射函数就叫做散列(哈希)函数,存放记录的数组叫做散列表。
在数据结构中,我们对两种数据结构应该会非常熟悉:数组与链表。数组的特点就是查找容易,插入删除困难;而链表的特点就是查找困难,但是插入删除容易。既然两者各有优缺点,那么我们就将两者的有点结合起来,让它查找容易,插入删除也会快起来。哈希表就是讲两者结合起来的产物。


依赖属性定义时需要用到特殊的语法:

  • 静态的
  • 使用readonly只能在构造时初始化
  • 必须是DependencyProperty类实例
  • 属性名称后面要带上Property

定义一个Margin依赖项属性的例子

csharp 复制代码
public static readonly DependencyProperty MarginProperty;
  • 依赖属性加入了属性变化通知、限制、验证等功能。这样可以使我们更方便地实现应用,同时大大减少了代码量。许多之前需要写很多代码才能实现的功能,在WPF中可以轻松实现。
  • 节约内存 :在WinForm中,每个UI控件的属性都赋予了初始值,这样每个相同的控件在内存中都会保存一份初始值。而WPF依赖属性很好地解决了这个问题,它内部实现使用哈希表存储机制,对多个相同控件的相同属性的值都只保存一份。

使用WPF的时候,总会有一个疑问,依赖属性跟普通的类属性有什么区别?大概是因为在WinForm的时代,控件类(比如TextBox)都会包含许多属性,但是真正用到的少之又少(比如text),其他属性就会白白耗费内存资源。如果只生成一个控件对象,"无用"的属性对性能影响不大,当你在一个窗体实例化几 十个控件对象的时候,内存消耗就很可观了。

  • 支持多种提供对象 :可以通过多种方式来设置依赖属性的值。可以配合表达式、样式和绑定来对依赖属性设置值

下面通过把C#属性 进行改写成依赖属性的方式来介绍依赖属性的定义。下面是一个属性的定义:

csharp 复制代码
public class Person
{
    public string Name { get; set; }
}

在把上面属性改写为依赖属性之前,下面总结下定义依赖属性的步骤:

  1. 让依赖属性的所在类型继承自DependencyObject类。
  2. 使用public static 声明一个DependencyProperty的变量,该变量就是真正的依赖属性。
  3. 在类型的静态构造函数中通过Register方法完成依赖属性的元数据注册。
  4. 提供一个依赖属性的包装属性,通过这个属性来完成对依赖属性的读写操作。
csharp 复制代码
using System.Windows;

public class Person : DependencyObject
{
    // 声明 Name 依赖属性的静态只读字段
    public static readonly DependencyProperty NameProperty =
        DependencyProperty.Register(
            "Name", // 依赖属性的名称 (在 XAML 中使用)
            typeof(string), // 依赖属性的类型
            typeof(Person), // 拥有者类型 (当前类 Person)
            new PropertyMetadata(string.Empty)); // 属性元数据 (设置默认值为空字符串)

    // 为 Name 依赖属性提供 CLR 属性包装器
    public string Name
    {
        get { return (string)GetValue(NameProperty); }
        set { SetValue(NameProperty, value); }
    }
}

依赖属性是通过调用DependencyObject的GetValue和SetValue来对依赖属性进行读写的 。它使用哈希表来进行存储的,对应的Key就是属性的HashCode值,而值(Value)则是注册的DependencyPropery;而C#中的属性是类私有字段的封装,可以通过对该字段进行操作来对属性进行读写。总结为:属性是字段的包装,WPF中使用属性对依赖属性进行包装。

依赖属性的优先级

执行顺序

依赖属性的继承

xml 复制代码
<Window x:Class="Custom_DPInherited.DPInherited"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      mc:Ignorable="d"
      d:DesignHeight="300" d:DesignWidth="300"
      FontSize="18"
      Title="依赖属性的继承">
    <StackPanel >
        <Label Content="继承自Window的FontSize" />
        <Label Content="显式设置FontSize" TextElement.FontSize="36"/>
        <StatusBar>Statusbar没有继承自Window的FontSize</StatusBar>
    </StackPanel>
</Window>

在上面XAML代码中。Window.FontSize设置会影响所有内部子元素字体大小 ,这就是依赖属性的继承。如第一个Label没有定义FontSize,所以它继承了Window.FontSize值。但一旦子元素提供了显式设置,这种继承就会被打断,所以Window.FontSize值对于第二个Label不再起作用。

这时,你可能已经发现了问题:StatusBar没有显式设置FontSize值,但它的字体大小没有继承Window.FontSize的值,而是保持了系统的默认值 。那这是什么原因呢?其实导致这样的问题:并不是所有元素都支持属性值继承的,如StatusBar、Tooptip和Menu控件。另外,StatusBar等控件截获了从父元素继承来的属性,并且该属性也不会影响StatusBar控件的子元素。例如,如果我们在StatusBar中添加一个Button。那么这个Button的FontSize属性也不会发生改变,其值为默认值。

Button里的Content就是依赖属性,该字段的类型DependencyProperty

但是比如PassWordBox控件中,PassWord属性不支持绑定,password就是一个普通属性

属性和依赖属性的区别

现在, 我们来解决另外一个概念问题, 可能看到上面, 你还是不太清楚属性和依赖属性它们的区别在哪里?

属性

很常见, 在C#中的标准属性,通常会由一个非静态类型的私有字段支持, 假设当前有一个对象, 它拥有100个标准属性,

并且背后都定义了一个4字节的字段, 如果我们初始化10000个这样的对象, 那么这些字段将占用100×4×10000= 3.81M 内存。

但是实际上, 我们并非使用到所有的属性, 这就意味着大多数内存会被浪费!

依赖属性

如何解决属性带来的问题? 我们回到现实生活当中想象一种场景, 假设老王和你的女朋友去旅游, 他们准备东西的时候大都是把必要的带上, 而不是说女朋友想喝水,要不然在行李箱里面放一箱水? 那么是不是意味着上厕所把纸带上? 洗发水? 沐浴露? 天呐, 这真是一场糟糕的旅行。

我们都知道, 水、厕所纸、洗发水、沐浴露这些酒店里面都有阿, 为什么要我们自己带? 所以我们懂了, 这些不必要带的东西我们可以依赖外部提供给我们。是的, 我们把这种思想带到编程当中。

所以, 这就是WPF当中的依赖属性的理念, 也许你在其它的地方都听过别人讲解过依赖属性, 并且他们都告诉你依赖属性本身没有值, 可以依赖绑定来源获得值。

csharp 复制代码
//依赖属性
public static readonly DependencyProperty MyPropertyProperty =
     DependencyProperty.Register("MyProperty", typeof(int), typeof(Test), new PropertyMetadata(0));
public int MyProperty
{
      get { return (int)GetValue(MyPropertyProperty); }
      set { SetValue(MyPropertyProperty, value); }
}
//属性
private int name;
public int Name
{
     get { return name; }
     set { name = value; }
}

定义依赖属性, 它通过DependencyProperty类的静态Register方法注册, 如下所示:

csharp 复制代码
public class Test : DependencyObject
{
    public string Message
    {
        get { return (string)GetValue(MyPropertyProperty); }
        set { SetValue(MyPropertyProperty, value); }
    }

public static readonly DependencyProperty MyPropertyProperty =
     DependencyProperty.Register("Message", typeof(string), typeof(Test), new PropertyMetadata(null));
}

上面的代码当中, 我们定义了一个string类型的依赖属性Message, 如果你们有看过WPF的源代码, 你可能会了解, 在其背后, 生成了一个key/value存储在Hashtable里面。

  • 生成key的代码片段
  • 添加到Hashtable中

因为存在这个全局Hashtable的存在,意味着,他就是用来存储依赖属性实例的地方

通过对象中提供的GetValue/SetValue方法,可以获取/设置依赖属性的值

WPF中的两种属性

普通属性 是指一个类中的成员变量,其值只能通过该类的实例进行设置和访问普通属性不能被其他类使用,因此其范围比较局限

依赖属性 是一种特殊类型的属性,它可以在不同的对象之间进行共享和重用。依赖属性可以用于不同的对象类型,并且可以通过绑定和样式等方式对其进行控制和修改。

依赖属性主要特点是它可以被多个对象所共享 ,而不需要为每个对象创建一个新的属性实例。这些属性可以在 WPF 中进行数据绑定 ,使得在不同的对象之间进行数据传递变得更加简单。同时,依赖属性还可以通过属性系统提供的值转换器进行类型转换,以及支持动态值计算和动画效果。

普通属性适用于一些比较简单的场景,而依赖属性则更加适用于复杂的 WPF 应用程序中,可以帮助实现更加灵活和高效的数据绑定和样式控制。在使用 WPF 开发应用程序时,建议合理使用依赖属性,以便更好地发挥 WPF 技术的优势。

附加属性 Attached Property

是一种特殊的依赖属性
用于非定义该属性的类,例如Grid面板的RowDefinition、ColumnDefinition、Canvas面板的Left、Right。DockPanel面板的Dock都是附加属性。

附加属性的定义

1、声明数据属性变量。 public static 的DependencyProperty类型的变量。

2、在属性系统中进行注册,使用DependencyProperty.RegisterAttached()方法来注册,方法参数和注册依赖属性时Register()方法的参数一致。

3、调用静态方法设置和获取属性值。通过调用DependencyObject的SetValue()和GetValue()方法来设置和获取属性的值。两个方法应该命名为SetPropertyName()方法和GetPropertyName()方法。

附加属性 旨在用作可在任何对象上设置的一类全局属性。 在 Windows Presentation Foundation (WPF) 中,附加属性 通常定义为没有常规属性"包装"的一种特殊形式的依赖项属性 。也就是说,附加属性是说一个属性本来不属于某个对象,是根据某种特殊需求附加到该对象的。

举个形象的例子,去做火车,你就有了座位号这个属性;假如不做火车就没有这个属性。

附加属性的特点
  • 1、特殊的依赖属性
  • 2、用于非定义该属性的类 例如Grid面板的RowDefinition、ColumnDefinition、Canvas面板的Left、Right
  • DockPanel面板的Dock都是附加属性。
附加属性的定义
  1. 声明一个 public static readonlyDependencyProperty 类型的字段。 命名约定是 [ClassName].[PropertyName]Property
  2. 使用 DependencyProperty.RegisterAttached() 方法注册附加属性 。 这个方法接受的参数与 Register() 方法类似,但属性的所有者类型是定义附加属性的类而不是使用该属性的类。
  3. 提供 public static 的

Get[PropertyName](DependencyObject obj)

Set[PropertyName](DependencyObject obj, [PropertyType] value) 静态方法(访问器)。 这些方法是设置和获取附加属性值的标准方式。WPF/UWP 属性系统会通过这些静态方法来读写附加属性的值。

两个方法应该命名为SetPropertyName()方法和GetPropertyName()方法。

附加属性可以使用在Binding依赖在其他对象的数据上

说白了,所谓依赖,主要应用在以下地方:

1、双向绑定 。有了这个,依赖项属性不用写额的代码,也不用实现什么接口,它本身就俱备双向绑定的特性,比如,我把员工对象的姓名绑定到摇文本框,一旦绑定,只要文本框中的值发生改变,依赖项属性员工姓名也会跟着变化,反之亦然;

2、触发器 。这个东西在WPF中很重要,比如,一个按钮背景是红色,我想让它在鼠标停留在它上面是背景变成绿色,而鼠标一旦移开,按钮恢复红色

示例

窗体字体的大小随TextBox控件里面输入的值的大小而改变

xml 复制代码
<Grid>
  <StackPanel>
    <TextBox p:MyDependencyProperty.MyAttachedFontSize="{Binding Path=Text,RelativeSource={RelativeSource Mode=Self}}"/>
    <TextBlock>通过附加属性修改FontSize的大小</TextBlock>
  </StackPanel>
</Grid>

在MyDependencyProperty.xaml.cs文件里面添加附件属性,附件属性的名称为MyAttachedFontSize,使用快捷方式创建附件属性:输入propa,连续按两下Tab健。

csharp 复制代码
public partial class MyDependencyProperty : UserControl
{
        public MyDependencyProperty()
        {
            InitializeComponent();
        }

        public static int GetMyAttachedFontSize(DependencyObject obj)
        {
            return (int)obj.GetValue(MyAttachedFontSizeProperty);
        }

        public static void SetMyAttachedFontSize(DependencyObject obj, int value)
        {
            obj.SetValue(MyAttachedFontSizeProperty, value);
        }

        public static readonly DependencyProperty MyAttachedFontSizeProperty =
            DependencyProperty.RegisterAttached("MyAttachedFontSize", typeof(int), typeof(MyDependencyProperty),
            new PropertyMetadata((s, e) =>
            {
                //获取MyDependencyProperty用户控件,s代表Textbox,
                //MyDependencyProperty用户控件是TextBox的父级的父级的父级控件
                var mdp = (((s as FrameworkElement).Parent as FrameworkElement).Parent
                    as FrameworkElement).Parent as MyDependencyProperty;
                //更改用户控件的FontSize的值
                if (mdp != null && e.NewValue != null)
                {
                    var fontsize = 9;
                    int.TryParse(e.NewValue.ToString(), out fontsize);
                    mdp.FontSize = fontsize;
                }
            }));
}

附加属性就是对于一个对象而言, 本来它不具备这个属性, 但是由于附加给这个对象, 然后才有了这个属性,这种我们称之为附加属性。

注:附加属性也是依赖属性, 只是它的注册方式与表达方式略有不同。

如果仍然不理解, 我们可以找到WPF当中经常用到的例子, 如下所示, 当一个按钮处在不同的容器当中, 它就具有了不同的附加属性:

xml 复制代码
        <Grid>
            <!--在Grid当中,具备Row/Column附加属性-->
            <Button Grid.Row="0" Grid.Column="0" />
        </Grid>

        <DockPanel>
            <!--在DockPanel中,具备Dock附加属性-->
            <Button DockPanel.Dock="Left" />
        </DockPanel>

        <Canvas>
            <!--在Canvas中,具备Left/Top/Rifht/Bottom等附加属性-->
            <Button Canvas.Left="10"/>
        </Canvas>

propdp依赖属性

propa附加属性

声明附加属性的对象无需继承于DependencyObject,因为这时候DependencyObject对象作为方法参数传递。

csharp 复制代码
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));

依赖属性 : 当您需要单独创建控件时, 并且希望控件的某个部分能够支持数据绑定时, 你则可以使用到依赖属性。

附加属性 : 这种情况很多, 正因为WPF当中并不是所有的内容都支持数据绑定, 但是我们希望其支持数据绑定, 这样我们就可以创建基于自己声明的附加属性,添加到元素上 , 让其元素的某个原本不支持数据绑定的属性间接形成绑定关系

例如:为PassWord定义附加属性与PassWord进行关联。例如DataGrid控件不支持SelectedItems, 但是我们想要实现选中多个条目进行数据绑定, 这个时候也可以声明附加属性的形式让其支持数据绑定。


依赖属性与附加属性区别

核心区别:

  • 依赖属性 (Dependency Properties) 是定义在类自身上的属性。它们是 WPF 属性系统的核心,提供了高级特性(如数据绑定、样式、动画、属性继承、默认值、验证和属性更改通知)。自定义控件通常会使用依赖属性来暴露其可配置的行为和外观。
  • 附加属性 (Attached Properties) 是定义在一个 中,但旨在附加到其他类的对象 上的属性。它们允许一个类为不直接继承自它的其他类扩展功能。附加属性在 XAML 中通过 OwnerClass.PropertyName 的语法来设置。

1. 依赖属性 (Dependency Properties): 用于自定义控件自身的功能和外观

当你在创建一个自定义的 WPF 控件,并且希望该控件的属性能够支持 WPF 的各种特性(数据绑定、样式等)时,你应该使用依赖属性。

例子:自定义一个带圆角的 Border 控件

假设你想创建一个名为 RoundedBorder 的自定义控件,它继承自 Border 并添加一个 CornerRadius 属性来控制边框的圆角。

csharp 复制代码
// RoundedBorder.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

namespace MyCustomControls
{
    public class RoundedBorder : Border
    {
        // 声明 CornerRadius 依赖属性
        public static readonly DependencyProperty CornerRadiusProperty =
            DependencyProperty.Register(
                "CornerRadius",
                typeof(CornerRadius),
                typeof(RoundedBorder),
                new FrameworkPropertyMetadata(new CornerRadius(0),
                    FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender)
            );

        // 定义 CornerRadius CLR 包装器属性
        public CornerRadius CornerRadius
        {
            get { return (CornerRadius)GetValue(CornerRadiusProperty); }
            set { SetValue(CornerRadiusProperty, value); }
        }

        // (在 OnRender 或其他绘制逻辑中使用 CornerRadius 来绘制圆角)
    }
}

XAML 中使用:

xml 复制代码
<local:RoundedBorder CornerRadius="10" BorderThickness="1" BorderBrush="Black" Width="100" Height="50">
    <TextBlock Text="Hello" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</local:RoundedBorder>

在这个例子中,CornerRadiusRoundedBorder 控件自身的一个特性。我们希望它能够被数据绑定、在样式中设置等,因此我们将其实现为依赖属性。

2. 附加属性 (Attached Properties): 用于为现有控件扩展功能

当你想要为现有的控件(这些控件可能不是你创建的,或者你不想修改它们的类定义)添加新的行为或属性时,你应该使用附加属性。

例子:为一个 StackPanel 中的子元素添加对齐方式控制

WPF 的 StackPanel 自身并没有直接控制其子元素在堆叠方向上的对齐方式的属性(例如,让子元素在水平 StackPanel 中靠上、居中或靠下对齐)。我们可以使用附加属性来实现这个功能。

csharp 复制代码
// StackPanelExtensions.cs
using System.Windows;
using System.Windows.Controls;

namespace MyAttachedProperties
{
    public static class StackPanelExtensions
    {
        // 声明 VerticalContentAlignment 附加属性
        public static readonly DependencyProperty VerticalContentAlignmentProperty =
            DependencyProperty.RegisterAttached(
                "VerticalContentAlignment",
                typeof(VerticalAlignment),
                typeof(StackPanelExtensions),
                new FrameworkPropertyMetadata(VerticalAlignment.Stretch, FrameworkPropertyMetadataOptions.AffectsParentMeasure)
            );

        // 定义 GetVerticalContentAlignment 访问器
        public static VerticalAlignment GetVerticalContentAlignment(DependencyObject obj)
        {
            return (VerticalAlignment)obj.GetValue(VerticalContentAlignmentProperty);
        }

        // 定义 SetVerticalContentAlignment 访问器
        public static void SetVerticalContentAlignment(DependencyObject obj, VerticalAlignment value)
        {
            obj.SetValue(VerticalContentAlignmentProperty, value);
        }
    }
}

修改 StackPanel 的布局逻辑(这通常需要在自定义 Panel 中完成,这里为了简化概念):

为了让附加属性生效,通常需要在拥有这些子元素的父容器的布局逻辑中读取并应用这些附加属性的值。对于标准的 StackPanel,我们无法直接修改其布局逻辑。因此,这个例子更偏向于说明附加属性的定义和使用方式。要真正实现 StackPanel 子元素的对齐控制,你需要创建一个自定义的 Panel 派生类,并在其 ArrangeOverride 方法中读取 StackPanelExtensions.VerticalContentAlignmentProperty 的值来调整子元素的位置。

XAML 中使用:

xml 复制代码
<StackPanel Orientation="Horizontal" Height="50" Background="LightGray">
    <TextBlock Text="Left" Background="White" Margin="5"
               local:StackPanelExtensions.VerticalContentAlignment="Top"/>
    <TextBlock Text="Center" Background="White" Margin="5"
               local:StackPanelExtensions.VerticalContentAlignment="Center"/>
    <TextBlock Text="Bottom" Background="White" Margin="5"
               local:StackPanelExtensions.VerticalContentAlignment="Bottom"/>
</StackPanel>

在这个例子中,VerticalContentAlignment 是定义在 StackPanelExtensions 类上的附加属性,但我们将其"附加"到了 StackPanel 的子元素 TextBlock 上。虽然标准的 StackPanel 不知道这个属性,但如果有一个自定义的 Panel 能够读取并使用这个附加属性的值,它就能控制子元素的垂直对齐方式。

总结:

  • 使用依赖属性 来定义你自定义控件自身的特性,以便它们能够参与到 WPF 属性系统中。
  • 使用附加属性扩展现有控件的功能,允许一个类为其他不直接相关的类的实例提供额外的属性和行为。通常,你需要在一个父元素或服务中读取这些附加属性的值并应用相应的逻辑。

依赖属性就像是积木,每个积木都有自己的颜色、形状、大小,这些都是它自己的特点。如果你自己设计了一种新的特殊积木(比如圆角的),那么它的圆角就是它自己的特点,你可以直接说"这个圆角积木是红色的"、"这个圆角积木很大"。依赖属性就是你创造的新积木拥有的新特点,它们是这个积木自带的。

附加属性就像是贴纸,你可以把贴纸贴到任何形状的积木上,给它一个新的功能。比如,你可以贴一个"向上箭头"的贴纸在一个普通的方积木上,告诉大家这个积木要"向上"。这个"向上"的功能不是方积木自己带的,而是你用贴纸"附加"给它的。

举个例子:

  • 依赖属性: 你做了一个会发光的积木 。发光的颜色、亮度就是这个积木自己带的特点,你可以直接控制它的颜色是红色还是蓝色,亮度是亮一点还是暗一点。这些"发光颜色"和"亮度"就像依赖属性,是这个发光积木自己拥有的。
  • 附加属性: 你有很多普通的方积木,你想让它们在叠起来的时候,有些积木特别靠左边一点。普通的方积木自己不会知道"靠左边"这个事情。这时候,你可以想象你有一种特殊的"靠左贴纸",你可以把这个贴纸贴到你想靠左边的积木上。这个"靠左"的功能不是方积木自己有的,而是你用"贴纸"(附加属性)告诉它的。

简单来说:

  • 依赖属性 是你自己做的新积木自带的特殊功能
  • 附加属性 是你给任何积木贴上的新贴纸,让它有额外的功能。

绑定Binding

"问渠哪得清如许,为有源头活水来"

设问:这"半亩方塘"为什么这么清澈呢?并自答:因为有这源头活水不断地补充进来,才使得它这么清澈。------朱熹的《观书有感》正是因为有好的源头(水源),还有路径(水流的道路)。

Source(数据源)

Binding对数据源的要求并不是那么苛刻,只要它是一个对象,并且通过属性公开自己的数据(set;get;),它就能作为Binding的源。

就好像我们把Student的当作数据源,Student的通过公开属性(Name)让我们拿到信息。

Path(路径)

数据公开了,我们怎么拿到呢?

我们需要通往它的道路(Path),Binding的Path是一个PropertyPath类型,参数是一个string。也就是属性的名称(Name)。

我们把属性的名称来当作访问的路径。

Binding 怎么与控件关联

我们已经实例化好了一个Binding,并且设置了Source和Path。现在,我们需要将它与文本框关联。

BindingExpressionBase.SetBinding(DependencyObject target, DependencyProperty dp, BindingBase binding)

参数:

  • target:绑定的绑定目标。
  • dp:绑定的目标属性。
  • binding: 描述绑定的 System.Windows.Data.BindingBase 对象。

Binding 的源也就是数据的源头 。Binding对源的要求并不苛刻------只要它是一个对象 ,并且通过属性(Property)公开自己的数据,它就能作为Binding 的源

前面一个例子已经向大家证明,如果想让作为Binding源的对象具有自动通知 Binding自己属性值 已经已经变化 的能力,那么就需要让类实现INotifyChanged接口 并在属性的Set语句中激发PropertyChanged事件。(这个会在INotifyPropertyChanged里讲)在日常生活中,除了使用这种对象作为数据源之外,我们还有更多的选择,比如用一个控件做为另一个控件的数据源,把集合作为ItemControl的数据源、使用XML作为TreeView或Menu的数据源等。

把控件作为Binding源

大多数情况下Binding的源是逻辑层对象,但有的时候为了让UI产生联动效果也会使用Binding在控件间建立关联。

Binding在源与目标之间架起了沟通的桥梁,默认情况下数据即可以通过Binding送达目标,也可以通过目标回到源(收集用户对数据的修改)。有时候数据只需要展示给用户,不需要用户修改,这时候可以把Binding模式设置为从目标向源的单向沟通以及只在Binding关系确立时读取一次数据,这需要我们根据实际情况选择。

控制Binding数据流向的属性是Model ,它的类型是BindingModel的枚举 。BindingModel可以取值为TwoWay、OneWay、OneTime、OneWayToSource和Default。这里的Default指的是Binding的模式会根据目标是实际情况来确定,如是可以编辑的(TextBox的Text属性),Default就采用双向模式。如果是TextBlock,不可编辑,就使用单向模式。

拖动Slider手柄时,TextBox就会显示Slider的当前值(实际上这一块涉及到一个Double到String类型的转换,暂且忽略不计);如果我们在TextBox里面输入一个恰当的值按Tab键、让焦点离开TextBox,则Slider手柄就会跳转至相应的值那里。为什么一定要在TextBox失去焦点以后才改变值呢?这就引出了Binding的另外一个属性-----UpdateSourceTrigger,它的类型是UpdateSourceTrigger枚举,可取值为PropertyChanged、LostFous、Explicit和Default。显然,对于Text的Default行为与LostFocus一致,我们只需要把这个值改成PropertyChanged,则Slider就会随着输入值的变化而变化了。


UpdateSourceTrigger

UpdateSourceTrigger的作用是 当做改变的时候通知数据源,我们做了改变

直译为更新源触发器,相信在一些项目里,类似TextBox/TextBlock控件,后台数据已经更新,但是界面上还没有立即刷新,有时候可能是等失去焦点后才更新界面。

这种表现由绑定中的UpdateSourceTrigger 属性来控制。它的默认值是Default(LostFocuse),源会根据你绑定的属性来更新。写这篇文章的时候,除了Text属性之外的所有属性,源会随属性的改变而立即更新。Text属性不一样,它只有在目标元素失焦后才更新。

UpdateSourceTrigger有三个枚举:

Explicit,源不会更新除非你手动来操作。正因为这个原因,我在这个TextBox旁边添加了一个按钮,用于手动更新源。在后台代码中,我们看到点击事件处理方法里面只有两行代码,第一行获取目标控件的绑定,第二行调用UpdateSource()方法。

csharp 复制代码
private void btn_Click(object sender, RoutedEventArgs e)
{
   BindingExpression binding = txt1.GetBindingExpression(TextBox.TextProperty);
   binding.UpdateSource();
}

LostFocus,对于Text绑定来说其实就是一个默认值。也就是说一旦目标控件失去焦点,源就会被更新。失去焦点时候触发

PropertyChanged,一旦绑定的属性值改变,源会立即更新。本例中文本改变就产生这种效果。

绑定的UpdateSourceTrigger属性用来控制改变的值何时传递给源。

Mode

属性就是Mode,他有五个参数:

枚举类型 效果

OneWay 源发生变化,数据就会从源流向目标

OneTime 绑定会将数据从源发送到目标;但是,仅当启动了应用程序或 DataContext 发生更改时才会如此操作,因此,它不会侦听源中的更改通知。

OneWayToSource 绑定会将数据从目标发送到源

TwoWay 绑定会将源数据发送到目标,但如果目标属性的值发生变化,则会将它们发回给源

Default 绑定的模式根据实际情况来定,如果是可编辑的就是TwoWay,只读的就是OneWay


Binding的路径(Path)

作为Binding的源可能会有很多属性,通过这些属性Binding源可以把数据暴露给外界。那么,Binding到底需要关注哪个属性值呢?就需要用Binding的Path属性来指定了。例如前面这个例子,我们把Slider控件对象作为数据源,把它的Value属性作为路径。

"没有Path"的Binding

有的时候我们会在代码中我们看到Path是一个"."或者干脆没有Path的Binding,着实让人摸不着头脑。原来这是一种比较特殊的情况---Binding源本身就是一种数据且不需要Path来指明。典型的string,int等基本类型都是这样,他们是实例本身就是数据,我们无法指定通过那个属性来访问这个数据,这是我们只需要将这个数据设置为"."就可以了。在XAML中这个.可以忽略不写,但是在C#中编程必须要带上。下面请看下面这段代码:

csharp 复制代码
// 没有Path的Source
string myStr = "hfdahfdlh;ahdfhfsahdfhasdfa";
_txtBox6.SetBinding(TextBox.TextProperty, new Binding(".")
{
Source = myStr,
Mode = BindingMode.OneWay
});

Binding的简写方法

this.name.SetBinding()

new Binding(){}

XAML里的Binding

构造函数

我们这里使用文本框的DataContext赋值数据源,而不是用Binding的Source.

因为在xaml代码里去binding一个Source是很麻烦的事情。而且,一般只有静态资源,或者是以控件的数值为数据源时,我们才在xaml代码里去BindingSource

DataContext数据上下文
  • 当一个Binding没有Source时,会发生什么了?它会把当前对象的DataContext作为数据源。
  • DataContext属性定义在FrameworkElement类里,这个类是WPF控件的基类,这就意味着,所有控件都有这个属性。
  • 如果当前对像的DataContext为空,它就会像它的父容器"借"一下。

编写XAML代码

  • Text="{Binding Path=Name }"
  • 解读:并不是为Text赋了一个Binding的值。
  • 应该是为TextBox的属性Text关联(Binding)了一个数据源,可以把这看成一个 Binding的构造函数,相当于初始化Binding

XAML中Binding写法的解释

  • Text="{Binding Path=Name }"
  • 解读:并不是为Text赋了一个Binding的值。
  • 应该是为TextBox的属性Text关联(Binding)了一个数据源,可以把这看成一个 Binding的构造函数,相当于初始化Binding

还记得一开始的写法嘛

Binding静态资源和控件

关联静态资源

Resource

每个控件都有一个 Resource属性,我们可以在里面存放资源,方便访问。

在这里我们实例化了一个Student,并且设定了一个key="student"。

Source={StaticResource student}

通过StaticResource访问静态资源,通过key来确定关联哪个数据源。


我们会发现只有当文本框的值失去焦点时,进度条的显示才会变化。我们来设置一下: UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged(当值发生变化时就触发)

BindingPath的"/"用法

当我们数据源的一个属性是一个集合时,我们需要集合里的子元素。怎么去Path指定呢?

我们在创建一个班级类,班级类有一个Name属性,还有一个Student集合,代表有多个学生。

如果数据源的属性是一个集合,要把集合中的子元素当作Path,使用多斜线的语法,一路"斜下去"。或者是类似GridView里Source里设定绑定集合。

相关推荐
羊吖2 小时前
Vue文件预览组件实战:高性能懒加载
前端·javascript·vue.js
代码or搬砖2 小时前
如何创建一个vue项目(详细步骤)
前端·javascript·vue.js
赛贝维权申诉2 小时前
亚马逊爆款美国外观专利落地,家居/儿童/宠物等品类亚马逊卖家谨防侵权投诉!
前端·javascript·宠物
Macbethad2 小时前
工业设备配方管理程序技术方案
wpf
Howe~zZ2 小时前
mybatis 报错解决方案ORA-01795: maximum number of expressions in a list is 1000
java·服务器·前端
yuguo.im2 小时前
【译】Vuejs: 使用带有对象的 v-model 来创建自定义组件
前端·javascript·vue.js
我来变强了2 小时前
无法正确访问 Vue 实例的属性
前端·javascript·vue.js
qixingchao2 小时前
VUE Pinia 官方首推的数据状态管理库
前端·javascript·vue.js
凌波粒2 小时前
CSS基础详解(2)--Grid网格布局详解
前端·css·css3·html5