WPF之属性

属性

.NET Framework中的属性又称为CLR属性(CLR,Common Language Runtime),既可以说CLR属性是private字段的安全访问包装(Get/Set方法),也可以说一个private字段在后台支持(back)一个CLR属性。
C#代码中的属性的编译结果是两个方法,再多实例方法也只有一个拷贝,CLR属性并不会增加内存的负担。属性仅仅是个语法糖衣(Syntax Sugar)

相关资料参考:属性(C# 编程指南)

依赖属性(Dependency Property)

在WPF中,微软推出了“依赖属性”这个新概念。依赖属性就是一种可以自己没有值,并能通过使用Binding从数据源获得值(依赖在别人身上)的属性。拥有依赖属性的对象被称为“依赖对象”。

与传统的CLR属性和面向对象思想相比依赖属性有很多新颖之处,其中包括:

  • 节省实例对内存的开销。
  • 属性值可以通过Binding依赖在其他对象上。

依赖属性对内存的使用方式

传统的.NET开发中,一个对象所占用的内存空间在调用new操作符进行实例化的时候就已经决定了,而WPF允许对象在被创建的时候并不包含用于存储数据的空间(即字段所占用的空间)、只保留在需要用到数据时能够获得默认值、借用其他对象数据或实时分配空间的能力一一这种对象就称为依赖对象(Dependency Object)而它这种实时获取数据的能力则依靠依赖属性(Dependency Property)来实现。

在WPF开发中,必须使用依赖对象作为依赖属性的宿主,使二者结合起来,才能形成完整的Binding目标被数据所驱动。

在WPF系统中,依赖对象的概念被DependencyObject类所实现,依赖属性的概念则由DependencyProperty类所实现

DependencyObject具有GetValue和SetValue两个方法:

public class DependencyObject : DispatcherObject
{
    public void ClearValue(DependencyProperty dp)
    {
        //...
    }
    public void SetValue(DependencyProperty dp, object value);
    {
        //...
    }
}

这两个方法都以DependencyProperty对象为参数,GetValue方法通过DependencyProperty对象获取数据;SetValue通过DependencyProperty对象存储值——正是这两个方法把DependencyObject和DependencyProperty紧密结合在一起。

DependencyObject是WPF系统中相当底层的一个基类,继承树如下所示:

WPF的所有UI控件都是依赖对象,WPF的类库在设计时充分利用了依赖属性的优势,UI控件的绝大多数属性都已经依赖化了。

声明和使用依赖属性

准备好一个界面,如下所示:

<StackPanel>
    <TextBox x:Name="textBox1" BorderBrush="Black" Margin="5"/>
    <TextBox x:Name="textBox2" BorderBrush="Black" Margin="5"/>
    <Button Content="OK" Margin="5" Click="Button_Click"/>
</StackPanel>

声明依赖属性

DependencyProperty必须以DependencyObject为宿主、借助它的SetValue和GetValue 方法进行写入与读取。因此,**想使用自定义的DependencyProperty,宿主一定是DependencyObject 的派生类。

DependencyProperty实例的声明特点很鲜明——引用变量由public static readonly三个修饰符修饰,实例并非使用new操作符得到而是使用DependencyProperty.Register方法生成,代码如下:

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

命名约定:成员变量的名字需要加上Property后缀以表明它是一个依赖属性

上面使用的是DependencyProperty.Register方法参数最少、最简单的一个重载,分析一下这3个参数:

  • 第1个参数为string类型,用这个参数来指明以哪个CLR属性作为这个依赖属性的包装器,或者说此依赖属性支持(back)的是哪个CLR属性。将来会使用名为Name的CLR属性来包装它,所以这个参数被赋值为Name。
  • 第2个参数用来指明此依赖属性用来存储什么类型的值,学生的姓名是string类型,所以这个参数被赋值为typeof(string)。
  • 第3个参数用来指明此依赖属性的宿主是什么类型,或者说DependencyProperty.Register方法将把这个依赖属性注册关联到哪个类型上。本例中的意图是为Student类准备一个可依赖的名称属性,所以这个参数被赋值为typeof(Student)。

这里有三点需要注意:

  • 依赖属性的包装器(Wrapper)是一个CLR属性,实际上的依赖属性是那个由public static readonly修体的Dependency Property实例,有没有包装器这个依赖属性都存在
  • 包装器的作用是以“实例属性”的形式向外界暴露依赖属性,这样一个依赖属性才能成为数据源的一个Path。
  • 注册依赖属性时使用的第二个参数是一个数据类型,这个数据类型也是包装器的数据类型,它的全称应该是“依赖属性的注册类型”,一般情况下也会把这个类型类型称为“依赖属性的类型”。

使用依赖属性

理解了依赖属性声明变量和创建实例的过程,就可以尝试使用它了。

依赖属性的“属性”

依赖属性首先是属性,尝试用依赖属性来存储值并把值顺利读取出来
UI中OK按钮的Click事件处理器代码如下:

private void Button_Click(object sender, RoutedEventArgs e)
{
    //创建一个Student 实例并使用变量stu引用
    Student stu = new Student(); 
    //调用SetValue方法把textBox1.Text 属性的值存储进依赖属性
    stu.SetValue(Student.NameProperty, this. textBox1.Text); 
    //使用GetValue方法把值读取出来,注意SetValue的返回值是object类型,要进行适当的类型转换
    textBox2.Text = (string)stu.GetValue(Student.NameProperty);
}

Student类的SetValue和GetValue方法继承自DependencyObject类,效果如下所示:

依赖属性的“依赖”性

让textBox1作为数据来源,把Student实例作为数据的目标,让Student实例依赖在textBoxl上(仅仅是为了展示依赖属性的“依赖”功能,现实工作中几乎从来不这么做),代码如下:

Student stu;
public MainWindow()
{
    InitializeComponent();

    stu = new Student();
    //创建一个Binding的实例,让textBox1作为数据源对象并从其Text属性中获取数据
    Binding binding = new Binding("Text") { Source = textBox1 };
    //使用BindingOperations类的SetBinding方法指定将stu对象借助刚刚声明的Binding实例依赖在textBox1上
    BindingOperations.SetBinding(stu, Student.NameProperty, binding);
}

private void Button_Click(object sender, RoutedEventArgs e)
{
    //弹出对话框显示依赖属性的值
    MessageBox.Show(stu.GetValue(Student.NameProperty).ToString());
}

注:依赖属性即使没有CLR属性作为其外包装也可以很好地工作。

如果想把textBox1和textBox2关联起来,代码如下:

Binding binding = new Binding("Text") { Source = textBox1 };
textBox2.SetBinding(TextBox.TextProperty, binding);

DependencyObject类(Student类的基类)没有SetBinding方法,SetBinding 方法是FrameworkElement类的方法。FrameworkElement是个相当高层的类(比UIElement类还高),这从侧面表明微软希望能够SetBinding(即作为数据目标)的对象是UI元素。

FrameworkElement类的SetBinding方法仅仅对BindingOperations的SetBinding方法做了一个简单的封装,代码如下:

public class FrameworkElement : UIElement //…
{
    //…
    public BindingExpressionBase SetBinding(DependencyProperty dp, BindingBase binding)
    {
        return BindingOperations.SetBinding(this, dp, binding);
    }
    //…
}

添加CRL属性外包装:

现在使用的依赖属性依靠SetValue和GetValue两个方法进行对外界的暴露,而且在使用GetValue的时候还需要进行一次数据类型的转换,所以大多数情况下会为依赖属性添加一个CRL属性外包装,代码如下:

public class Student : DependencyObject
{
    //CLR属性包装器
    public string Name
    {
        get { return (string)GetValue(NameProperty); }
        set { SetValue(NameProperty, value); }
    }
    
    public static readonly DependencyProperty NameProperty=DependencyProperty.Register("Name", typeof(string), typeof(Student));
}

通过CLR属性包装访问依赖属性,代码如下:

private void Button_Click(object sender, RoutedEventArgs e)
{
    Student stu = new Student(); 
    stu.Name = this.textBox1.Text; 
    this.textBox2.Text = stu.Name;
}

依赖对象可以通过Binding依赖在其他对象上,即依赖对象是作为数据的目标而存在的。为依赖对象的依赖属性添加CLR属性包装就相当于为依赖对象准备了用于暴露数据的Binding Path,现在的依赖对象已经具备了扮演数据源和数据目标双重角色的能力。尽管Student类没有实现INotifyPropertyChanged接口,当属性的值发生改变时与之关联的Binding对象依然可以得到通知,依赖属性默认带有这样的功能,天生就是合格的数据源

使用依赖属性形成Binding链

向FrameworkElement类借用一下它的SetBinding方法、升级一下Student类,代码如下:

public class Student : DependencyObject
{
    //CLR属性包装器
    public string Name
    {
        get { return (string)GetValue(NameProperty); }
        set { SetValue(NameProperty, value); }
    }
    
    //依赖属性
    public static readonly DependencyProperty NameProperty=DependencyProperty.Register("Name", typeof(string), typeof(Student));

    //SetBinding 包装
    public BindingExpressionBase SetBinding(DependencyProperty dp, BindingBase binding)
    {
        return BindingOperations.SetBinding(this,dp,binding);
    }
}

使用Binidng把Student对象关联到textBox1上,再把textBox2关联到Student对象上形成Binding链,代码如下:

Student stu;
public MainWindow()
{
    InitializeComponent();
    stu = new Student(); 
    stu.SetBinding(Student.NameProperty, new Binding("Text") { Source = textBox1 }); 
    textBox2.SetBinding(TextBox.TextProperty, new Binding("Name") { Source = stu });
}
private void Button_Click(object sender, RoutedEventArgs e)
{
    //便于监视依赖属性的值
    MessageBox.Show(stu.GetValue(Student.NameProperty).ToString());
}

运行程序,当在textBoxl中输入字符的时候,textBox2就会同步显示,此时Student对象的Name属性值也同步变化了。

注:在一个类中声明依赖属性时并不需要手动进行声明、注册并使用CLR属性封装,只需要输入propdp连按两次Tab键,一个标准的依赖属性(带CLR属性包装)就声明好了,继续按动Tab键,可以在提示环境中修改依赖属性的各个参数。

依赖属性的DefaultMetadata属性

在自动生成的代码中,DependencyProperty.Register使用的是带4个参数的重载,前3个参数与前面介绍的一致,第4个参数的类型是PropertyMetadata类

第4个参数的作用是给依赖属性的DefaultMetadata属性赋值,DefaultMetadata的作用是向依赖属性的调用者提供一些基本信息,信息包括:

  • CoerceValueCallback:依赖属性值被强制改变时此委托会被调用,此委托可关联一个影响函数。
  • DefaultValue:依赖属性未被显式赋值时,若读取之则获得此默认值,不设此值会抛出异常。
  • IsSealed:控制PropertyMetadata的属性值是否可以更改,默认值为true
  • PropertyChangedCallback:依赖属性的值被改变之后此委托会被调用,此委托可关联一个影响函数。

依赖属性的DefaultMetadata只能通过Register方法的第4个参数进行赋值,而且一旦赋值就不能改变(DefaultMetadata是个只读属性)。如果想用新的PropertyMetadata 替换这个默认的Metadata,需要使用DependencyProperty.OverrideMetadata方法

依赖属性值存取的秘密

依赖对象的依赖属性是一个static对象,调用依赖对象的SetValue方法时值不可能是保存在static对象里,重点分析DependencyProperty.Register方法和DependencyObject.SetValue方法和DependencyObject.GetValue方法

DependencyProperty.Register方法

DependencyProperty.Register方法由名称可知,不仅要创建DependencyProperty实例,还要对它进行“注册”。

阅读源码会发现DependencyProperty类具有这样一个成员:

private static Hashtable PropertyFromName = new Hashtable();

一旦程序运行,就会有这样一个全局的Hashtable存在,这个Hashtable就是用来注册DependencyProperty实例的地方

在源码中,所有的DependencyProperty.Register方法重载最后都归结为对DependencyProperty.RegisterCommon方法的调用(可以把RegisterCommon理解为Register方法的“完整版”),RegisterCommon方法的源码如下:

/*
 * RegisterCommon方法的前4个参数与前面分析过的Register方法一致
 */
private static DependencyProperty RegisterCommon(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback)
{
    /*
     * FromNameKey 是一个,NET Framework内部数据类型。它的构造器代码如下:
     * public FromNameKey(string name,Type ownerType) 
     * {
     *     _name = name;
     *     _ownerType = ownerType;
     *     hashCode = _name.GetHashCode()^_ownerType.GetHashCode();
     * }
     * 
     * 并且override有其GetHashCode方法:
     * public override int GetHashCode()
     * {
     *     return _hashCode;
     * }
     * 
     * 
     * 由上面的代码可知:FromNamekey对象(也就是变量key)的hash code实际上是RegisterCommon第1个参数(CLR属性名字符串)的hash code与第3个参数(宿主类型)的hash code做异或运算得来的。
     * 
     */
    FromNameKey key = new FromNameKey(name, ownerType);
    lock (Synchronized)
    {
        /*
         * 每对“CLR属性名一宿主类型”所决定的DependencyProperty实例是唯一的。
         * 如果尝试使用同一个CLR属性名字和同一个宿主类型进行注册,程序会抛出异常。
         */
        if (PropertyFromName.Contains(key))
        {
            throw new ArgumentException(SR.Get(SRID.PropertyAlreadyRegistered, name, ownerType.Name));
        }
    }

    /*
     * RegisterCommon 检查程序员是否提供了PropertyMetadate,如果没有提供则为之准备一个默认的PropertyMetadate实例。
     */

    // 为所有类型建立默认metadata(如果未提供)
    if (defaultMetadata == null)
    {
        defaultMetadata = AutoGeneratePropertyMetadata(propertyType, validateValueCallback, name, ownerType);
    }
    else // Metadata对象已经提供
    {
        // 如果未指定defaultValue,则自动生成一个
        if (!defaultMetadata.DefaultValueWasSet())
        {
            defaultMetadata.DefaultValue = AutoGenerateDefaultValue(propertyType);
        }

        ValidateMetadataDefaultValue(defaultMetadata, propertyType, name, validateValueCallback);
    }

    // 创建DependencyProperty的实例
    DependencyProperty dp = new DependencyProperty(name, propertyType, ownerType, defaultMetadata, validateValueCallback);

    // Seal (null means being used for default metadata, calls OnApply)
    defaultMetadata.Seal(dp, null);

    if (defaultMetadata.IsInherited)
    {
        dp._packedData |= Flags.IsPotentiallyInherited;
    }

    if (defaultMetadata.UsingDefaultValueFactory)
    {
        dp._packedData |= Flags.IsPotentiallyUsingDefaultValueFactory;
    }


    // Map owner type to this property
    // Build key
    lock (Synchronized)
    {
        //DependencyProperty实例被注册进Hashtable中(Hashtable会自动调用key的GetHashcode 方法获取其hash code):
        PropertyFromName[key] = dp;
    }


    if (TraceDependencyProperty.IsEnabled)
    {
        TraceDependencyProperty.TraceActivityItem(
            TraceDependencyProperty.Register,
            dp,
            dp.OwnerType);
    }

    //生成的DependencyProperty实例被当作返回值交还
    return dp;
}

用一句话概括DependencyProperty对象的创建与注册,那就是:创建一个DependencyProperty 实例并用它的CLR属性名和宿主类型名生成hash code,最后把hash code和DependencyProperty 实例作为Key-Value对存入全局的、名为PropertyFromName的Hashtable中。这样,WFP属性系统通过CLR属性名和宿主类型名就可以从这个全局的Hashtable中检索出对应的DependencyProperty实例。

把DependencyProperty实例注册进全局Hashtable时使用的key由CLR属性名哈希值和宿主类型哈希值经过运算得到,但这并不是DependencyProperty实例的哈希值。每个DependencyProperty实例都具有一个名为GilobalIndex的int 类型属性,GlobalIndex的值是经过一些算法处理得到的,确保了每个DependencyProperty实例的Giloballndex是唯一的

DependencyProperty的GetHashCode方法亦被重写:

public override int GetHashCode() 
{
    return GlobaIndex;
}

所以,GlobalIndex属性值也就是DependencyProperty实例的哈希值——这一点非常重要,因为通过这个值就可以直接检索到某个DependencyProperty实例

DependencyObject.GetValue方法

GetValue方法源码如下:

public object GetValue(DependencyProperty dp)
{
    this.VerifyAccess();

    if (dp == null)
    {
        throw new ArgumentNullException("dp");
    }
    
    return GetValueEntry(
            LookupEntry(dp.GlobalIndex),
            dp,
            null,
            RequestFlags.FullyResolved).Value;
}

方法的前几行是为了校验传入参数的有效性,只有return一句才是核心内容,return语句展开可以写成这样:

Entrylndex entrylndex = LookupEntry(dp.Globallndex);
EffectiveValueEntry vaueEnry = GetValueEntry(entrylndex, dp,null, RequestFlags.FullyResolved);
return valueEntry.Value;

Entry是“入口”的意思,WPF的依赖属性系统在存放值的时候会把每个有效值存放在EffectiveValueEntryy类的实例里,每个实例都有自己的入口——检索算法只要找到这个入口、走进入口就能拿到依赖属性的值。
EffectiveValueEntry的所有构造器都包含一个DependencyProperty类型的参数,每个EectiveValueEntry都关联着一个DependencyProperty
EffectiveValueEntry类具有一个名为PropertyIndex的属性,这个属性的值实际上就是与之关联的Dependencyroperty的GlobalIndex属性值

在DependencyObject类的源码中可以找到这样一个成员变量:

//此DependencyObject的有效值缓存
//这是一个使用DP.GlobalIndex排序的数组,这种排序是通过插入排序算法来维持的
private EffectiveValueEntry[] _effectiveValues;

这个数组向我们提示了依赖属性存储值的秘密——每个Dependencyobject实例都自带一个EffectiveValueEntry类型数组,当某个依赖属性的值要被读取时,算法就会从这个数组中去检索值,如果数组中没有包含这个值,算法会返回依赖属性的默认值(这个值由依赖属性的DefaultMetadata来提供)。

由上可知,被static关键字所修饰的依赖属性对象其作用是用来检索真正的属性值而不是存储值;被用做检索键值的实际上是依赖属性的GlobalIndex属性(本质是其hash code,而 hash code又由其CLR包装器名和宿主类型名共同决定),为了保证GlobalIndex属性值的稳定性,声明的时候又使用了readonly关键字进行修饰

实际工作中,依赖属性的值除了可能存储在EffectiveValueEntry数组或由默认值提供外,还有很多途径可以获得,如元素的Style或Theme。WPF对依赖属性值的读取优先级由先到后依次是:
(1)WPF属性系统强制值。
(2)由动画过程控制的值。
(3)本地变量值(存储在EffectiveValueEntry数组中)。
(4)由上级元素的Template设置的值。
(5)由隐式样式(Implicit Style)设置的值。
(6)由样式之触发器(Style Trigger)设置的值。
(7)由模板之触发器(Template Trigger)设置的值。
(8)由样式之设置器(Style Setter)设置的值。
(9)由默认样式(Default Style)设置的值,默认模式其实就是由主题(Theme)指定的模式。
(10)由上级元素继承而来的值。
(11)默认值,来源于依赖属性的元数据(metadata)。

DependencyObject.SetValue方法

SetValue方法源码如下:

public void SetValue(DependencyProperty dp, object value)
{
    // 验证调用线程是否有权访问此对象,只有dispatcher线程可以访问DispatcherObject
    this.VerifyAccess();

    // 缓存此方法无论如何需要获取的metadata对象
    PropertyMetadata metadata = SetupPropertyChange(dp);

    // 进行标准属性设置
    SetValueCommon(dp, value, metadata, false /* coerceWithDeferredReference */, false /* coerceWithCurrentValue */, OperationType.Unknown, false /* isInternal */);
}

赋值流程主要有这样几个操作:

  • 检查值是不是DependencyProperty.UnsetValue,是则说明调用者的意图是清空现有的值,程序会调用ClearValueCommon方法来清空现有的值。
  • 检查EffectiveValueEntry数组中是否已经存在相应依赖属性的位置,有则把旧值改写为新值,没有则新建EffectiveValueEntry对象并存储新值。只有被用到的值才会被放进这个列表,WPF系统用算法(时间)换取了对内存(空间)的节省
  • 调用UpdateEffectiveValue对新值做一些相应处理。

附加属性(Attached Properties)

附加属性的含义

一个属性本来不属于某个对象,但由于某种需求而被后来附加上,也就是把对象放入一个特定环境后对象才具有的属性(表现出来就是被环境赋予的属性)就称为附加属性(Attached Properties)。附加属性的作用是将属性与数据类型(宿主)解耦,让数据类型的设计更加灵活。

在Grid里对一个TextBox定位,XAML代码如下:

<Grid ShowGridLines="True">
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <TextBox Background="Lime" Grid.Column="1" Grid.Row="1" />
</Grid>

如果TextBox被放置在Canvas里,XAML代码如下:

<Canvas Margin="10">
    <TextBox Background="Lime" Width="200" Canvas.Top="0"/>
    <TextBox Background="Lime" Width="200" Canvas.Top="30"/>
    <TextBox Background="Lime" Width="200" Canvas.Top="60"/>
</Canvas>

如果TextBox被放在DockPanel里,XAML代码如下:

<DockPanel LastChildFill="False">
    <TextBox Background="Orange" DockPanel.Dock="Top"/>
    <TextBox Background="Orange" DockPanel.Dock="Bottom"/>
    <TextBox Background="Green" Width="80" DockPanel.Dock="Left"/>
    <TextBox Background="Green" Width="80" DockPanel.Dock="Right"/>
</DockPanel>

放在StackPanel里最简单,XAML代码如下:

<StackPanel Margin="10,5">
    <TextBox Background="LightBlue" Margin="0,5"/>
    <TextBox Background="LightBlue" Margin="0,5"/>
    <TextBox Background="LightBlue" Margin="0,5"/>
</StackPanel>

TextBox控件的设计者不可能知道控件发布后程序员是把它放在Grid里还是Canvas里(甚至是以后版本将推出的新布局里),所以也不可能为TextBox准备诸如Column、Row或者Left、Top这类属性。直接让布局来决定一个TextBox用什么属性来设置它的位置,放在Grid里就让Grid为它附加上Column和Row属性,放在Canvas 里就让Canvas为它附加上Top、Left等属性,放在DockPanel里就让DockPanel为它附加Dock属性。

声明、注册和使用

附加属性的本质就是依赖属性,二者仅在注册和包装器上有一点区别。前面用于快速创建依赖属性的snippet是propdp,这里用于快速创建附加属性的snippet是propa

自定义类之间使用附加属性

以人在学校里会获得年级和班级两个属性为例,人放在学校里会获得年级和班级两个属性说明年级和班级两个属性是由学校附加给人的,这两个属性的真实所有者(宿主)应该是学校。

准备一个名为School的类,并让它继承DependencyObject类,完成附加属性的框架,代码如下:

class School : DependencyObject
{
    public static int GetGrade(DependencyObject obj)
    {
        return (int)obj.GetValue(GradeProperty); 
    }

    public static void SetGrade(DependencyObject obj, int value) 
    {
        obj.SetValue(GradeProperty, value);
    }

    public static readonly DependencyProperty GradeProperty = 
        DependencyProperty.RegisterAttached("Grade", typeof(int), typeof(School), new UIPropertyMetadata(0));
} 

可明显看出,GradeProperty就是一个DependencyProperty类型成员变量,声明时一样使用public static readonly 三个关键字共同修饰,唯一的不同就是注册附加属性使用的是名为RegisterAttached的方法,但参数却与使用Register方法无异
附加属性的包装器也与依赖属性不同——依赖属性使用CLR属性对GetValue和SetValue两个方法进行包装,附加属性则使用两个方法分别进行包装(为了在使用的时候保持语句行文上的通畅)。

准备一个派生自DependencyObject、名为Human的类:

class Human:DependencyObject
{
   
}

在UI上准备一个Button,Click事件的处理器代码如下:

private void Button_Click(object sender, RoutedEventArgs e)
{
    Human human = new Human();
    School.SetGrade(human, 6); 
    int grade = School.GetGrade(human); 
    MessageBox.Show(grade.ToString());
}

点击按钮,效果如下:

这一过程与前面依赖属性保存值的过程别无二致——值仍然被保存在Human实例的EffectiveValueEntry数组里,只是用于在数组里检索值的依赖属性(即附加属性)并不以Human类为宿主而是寄宿在School类里——反正CLR属性名和宿主类型名只用来生成hash code和Globallndex。

如何在XAML和C#代码中直接为附加属性赋值

XAML代码如下:

<Grid ShowGridLines="True">
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <Button Content="OK" Grid.Column="1" Grid.Row="1"/>
</Grid>

等效C#代码如下:

Grid grid = new Grid() { ShowGridLines = true }; 

grid.ColumnDefinitions.Add(new ColumnDefinition()); 
grid.ColumnDefinitions.Add(new ColumnDefinition()); 
grid.ColumnDefinitions.Add(new ColumnDefinition()); 

grid.RowDefinitions.Add(new RowDefinition()); 
grid.RowDefinitions.Add(new RowDefinition()); 
grid.RowDefinitions.Add(new RowDefinition());

Button button = new Button(){ Content = "OK"}; 
Grid.SetColumn(button, 1); 
Grid.SetRow(button, 1); 

grid.Children.Add(button);
this.Content = grid;

使用Binding依赖在其他对象的数据上

附加属性的本质是依赖属性一—附加属性也可以使用Binding依赖在其他对象的数据上

窗体使用Canvas布局,两个Slider用来控制矩形在Canvas中的横纵坐标,效果如下:

XAML代码如下:

<Canvas>
    <Slider x:Name="sliderX" Canvas.Top="10" Canvas.Left="10" Width="260" Minimum="50" Maximum="200"/>
    <Slider x:Name="sliderY" Canvas.Top="40" Canvas.Left="10" Width="260" Minimum="50" Maximum="200"/>
    <Rectangle x:Name="rect" Fill="Blue" Width="30" Height="30" Canvas.Left="{ Binding ElementName=sliderX, Path=Value}" Canvas.Top="{ Binding ElementName=sliderY, Path=Value}"/>
</Canvas>

等效C#代码(仅Binding部分)如下:

// 设置Binding 
this.rect.SetBinding(Canvas.LeftProperty,new Binding("Value") { Source = sliderX });
this.rect.SetBinding(Canvas.TopProperty, new Binding("Value") { Source = sliderY });

由此可见,在使用Binding时除了宿主类型稍有不同外没有任何区别

posted @ 2021-02-22 22:25  time-flies  阅读(37)  评论(0编辑  收藏
……