5.1 属性与依赖

依赖属性是WPF引入的一个新的属性类型。

过去的属性很简单,如一个文件名、一个用户名和一个窗口的标题等都是属性,并且作为一个类的成员变量存储在类中。如下代码所示,其中_filename 是 mumu_File 的属性。

  1. class mumu_File
  2. {
  3. private string _filename;
  4. public string Filename
  5. {
  6. get
  7. {
  8. return _filename;
  9. }
  10. set
  11. {
  12. if(_filename != value)
  13. {
  14. _filename = value;
  15. }
  16. }
  17. }
  18. }
  1. Main 函数中访问_filename属性如下代码所示:
  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. mumu_File file = new mumu_File();
  6. file.Filename = "mumu_File.txt";
  7. string s = file.Filename;
  8. }
  9. }
  1. 下面代码为 Button 中的一个 IsDefault 属性,注意它是一个依赖属性。
  1. public class Button : ButtonBase
  2. {
  3. //依赖属性
  4. public static readonly DependencyProperty IsDefaultProperty;
  5. static Button()
  6. {
  7. //注册属性
  8. Button.IsDefaultProperty = DependencyProperty.Register("IsDefault", typeof(bool), typeof(Button),
  9. new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsDefaultChanged)));
  10. }
  11. //.NET属性包装器(可选)
  12. public bool IsDefault
  13. {
  14. get{ return (bool)GetValue(Button.IsDefaultProperty);}
  15. set{ SetValue(Button.IsDefaultProperty);}
  16. }
  17. //属性改变的回调(可选)
  18. private static void OnIsDefaultChanged(DependencyObject o, DependencyPropertyChangedEventArgs e){ }
  19. }

关于上述代码的详细解读:https://blog.csdn.net/Andrewniu/article/details/80438309

类型为DependencyProperty 的 IsDefaultProperty 是依赖属性,不过用户使用的是暴露后的 IsDefault 普通的.NET 属性,从中可以看出依赖属性是 WPF 在原来的属性基础上提供给用户功能更加丰富的一种类型的属性。

依赖属性是一种类型为DependencyProperty 的属性,其依赖属性标识(Dependency property identifier)则是依赖属性的实例。如 IsDefaultProperty 是依赖属性标识,其本身是一种依赖属性类型。但是大多数情况下不必区分是依赖属性还是其标识,从上下文即可得知。

与依赖属性相关的属于如下:

(1)DependencyObject:WPF中的一种类型,继承该类后才可以注册和拥有依赖属性。

第5章 依赖属性——木木的“汗血宝马” - 图1

(2)WPF 属性系统,WPF 通过提供一系列的服务扩展了普通的 .NET 属性,这些服务总称为“WPF属性系统”。

(3).NET属性包装器:指属性的 get 和 set的实现,如上面代码中的 IsDefault 的 get 和 set实现。 .NET属性包装器可以把依赖属性包装成普通的 .NET属性暴露给用户使用,在这个实现中均调用 DependencyObject 的 GetValue 和 SetValue方法。

依赖属性已经**不像简单的面向对象语言里的属性**,它更像一个计算过程,根据所输入的值经过一些计算最终得到另外一个值。整个计算过程“依赖”其他属性与内在和外在的多种因素,“依赖”正是由此而来,如下图所示:

第5章 依赖属性——木木的“汗血宝马” - 图2

5.2 认识依赖属性

5.2.1 分辨依赖属性

WPF中相当多的元素的属性是依赖属性,以按钮(Button)为例,宽度(Width)、背景色(Background)和字体大小(FontSize)等都是依赖属性。为分辨一个元素的属性,可以查阅MSDN文档。如果该属性是依赖属性,则该文档中必然会有依赖属性信息(Dependency Property Information)这一节。

第5章 依赖属性——木木的“汗血宝马” - 图3

5.2.2 引入依赖属性的原因

WPF主要的设计思想之一是侧重属性胜于方法和事件,即如果属性能解决问题,则坚决不使用方法和事件。

在过去不用方法和事件单单用属性是很难想象的,因为属性的功能太单一了,仅仅就是提供一个类型的值。因此WPF需要提供一个新的属性类型即依赖属性和与之配套的服务,让它能够做方法和事件所能做的事情。

具体来说依赖属性与以前的属性相比,提供了对资源引用、样式、动画、数据绑定、属性值继承、元数据重载及WPF设计器的集成支持功能的支持。这些功能都是WPF的重要特性,由此依赖属性在WPF平台中的重要地位可见一斑。

下面这个例子主要展示依赖属性(主要是按钮的背景色和字体大小属性)对上述功能的支持,程序运行结果如下图所示。图中“金色按钮”的背景色属性引用了一个画刷资源;“绿色按钮”通过样式使背景变成绿色;“动画按钮”通过动画使按钮的背景由红变绿,再由绿变红反复循环;“我被绑定成红色”按钮是通过和一个 .NET对象的属性绑定变成红色。最下面一行是属性继承支持。从左至右依次有3个按钮,单击“设置窗口字体:16”按钮,则所有标签和按钮的字体大小都会变成16;单击“设置按钮字体:8”按钮,则该按钮字体大小变成8;单击最后一个“重置字体:12”按钮,则所有的按钮字体大小恢复为初始状态大小。

第5章 依赖属性——木木的“汗血宝马” - 图4

  1. <Window x:Class="mumu_Button01.MainWindow"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. Title="mumu_Button01" Height="269" Width="572">
  5. <Grid Name="Grid1">
  6. <Grid.RowDefinitions>
  7. <RowDefinition/>
  8. <RowDefinition/>
  9. <RowDefinition/>
  10. </Grid.RowDefinitions>
  11. <Grid.ColumnDefinitions>
  12. <ColumnDefinition/>
  13. <ColumnDefinition/>
  14. <ColumnDefinition/>
  15. <ColumnDefinition/>
  16. </Grid.ColumnDefinitions>
  17. <!--资源支持-->
  18. <Label HorizontalAlignment="Center" VerticalAlignment="Center">资源支持</Label>
  19. <Button Grid.Row="0" Grid.Column="1" Name="resourceBtn" Margin="5">金色按钮</Button>
  20. <!--样式支持-->
  21. <Label Grid.Row="0" Grid.Column="2" HorizontalAlignment="Center" VerticalAlignment="Center">样式支持</Label>
  22. <Button Grid.Row="0" Grid.Column="3" Name="styleBtn" Padding="0" Margin="5">绿色按钮</Button>
  23. <!--动画支持-->
  24. <Label Grid.Row="1" Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center">动画支持</Label>
  25. <Button Grid.Row="1" Grid.Column="1" Name="animationBtn" Margin="5">动画按钮</Button>
  26. <!--数据绑定支持-->
  27. <Label Grid.Row="1" Grid.Column="2" HorizontalAlignment="Center" VerticalAlignment="Center">数据绑定支持</Label>
  28. <Button Grid.Row="1" Grid.Column="3" Name="BindingBtn">我被绑定成红色!</Button>
  29. <!--属性值继承-->
  30. <Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center">属性继承支持</Label>
  31. <Button Grid.Row="2" Grid.Column="1" Name="FontSizeWinBtn">设置窗口字体:16</Button>
  32. <Button Grid.Row="2" Grid.Column="2" Name="FontSizeBtn">设置按钮字体:8</Button>
  33. <Button Grid.Row="2" Grid.Column="3" Name="ResetFontSizeBtn">重置字体:12</Button>
  34. </Grid>
  35. </Window>

1、依赖属性对资源引用的支持

将金色按钮的背景色设置为金黄色,为了说明依赖属性对资源引用的支持,采用下面的做法(与上方代码保存在不同的文件夹下)。

(1)在App.xaml中定义一个 Key值为 MyBrush 的画刷资源,x:Key 属性用来唯一标识该资源,如下方代码所示。

  1. <Application x:Class="mumu_Button01.App"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. StartupUri="MainWindow.xaml">
  5. <Application.Resources>
  6. <SolidColorBrush x:Key="MyBrush" Color="Gold"/>
  7. </Applcation.Resources>
  8. </Application>
  1. 2)在MainWindow.xaml文件中将金色按钮的背景色属性设置为该资源的引用,如下方代码所示:
  1. <Button Grid.Row="0" Grid.Column="1" Name="resourceBtn" Margin="5"
  2. Background="{DynamicResource MyBrush}">金色按钮</Button>
  1. 运行结果如下:

第5章 依赖属性——木木的“汗血宝马” - 图5

2、依赖属性对样式的支持

样式也是WPF当中的一个重要概念,详细看第13章。在这里我们先简单地认为样式是WPF为我们提供的能够方便的对多个控件应用同一个属性值的一种机制。

通过样式使绿色按钮背景为绿色的步骤如下(与上方代码保存在不同的文件夹下)。

(1)在App.xaml文件中添加一个新的样式“GreenButtonStyle”,作为应用程序的一个资源。其作用是将按钮的背景色属性值设置为绿色,如下方代码所示:

  1. <Application.Resources>
  2. <SolidColorBrush x:Key="MyBrush" Color="Gold"/>
  3. <!--GreenButtonStyle将按钮的背景色设置为绿色-->
  4. <Style x:Key="GreenButtonStyle">
  5. <Setter Property="Control.Background" Value="Green"/>
  6. </Style>
  7. </Application.Resources>
  1. 2)在MainWindow.xaml文件中绿色按钮同样通过关键字来引用该样式,将其Style属性值设置为“{StaticResource GreenButtonStyle}”,如下代码所示:
  1. <Button Grid.Row="0" Grid.Column="3" Name="styleBtn" Padding="0" Margin="5"
  2. Style="{StaticResource GreenButtonStyle}">绿色按钮</Button>

第5章 依赖属性——木木的“汗血宝马” - 图6

第5章 依赖属性——木木的“汗血宝马” - 图7

3、依赖属性对动画的支持

为动画按钮添加如下代码,使动画按钮的背景色从红变绿并反复循环。

  1. <Button Grid.Row="1" Grid.Column="1" Name="animationBtn" Margin="5">
  2. <Button.Background>
  3. <SolidColorBrush x:Name="AnimBrush"/>
  4. </Button.Background>
  5. <Button.Triggers>
  6. <EventTrigger RoutedEvent="Button.Loading">
  7. <BeginStoryboard>
  8. <Storyboard>
  9. <ColorAnimation Storyboard.TargetName="AnimBrush"
  10. Storyboard.TargetProperty="{SolidColorBrush.Color}"
  11. From="Red" To="Green" Duration="0:0:5"
  12. AutoReverse="True" RepeatBehavior="Forever"/>
  13. </Storyboard>
  14. </BeginStoryboard>
  15. </EventTrigger>
  16. </Button.Triggers>
  17. 动画按钮
  18. </Button>

4、依赖属性对数据绑定的支持

数据绑定略微复杂一些。

(1)新建一个名为“BindingData”的类作为绑定的数据源,并绑定按钮的背景色和 BindingData 的 ColorName 属性,如下代码所示:

  1. using System;
  2. namespace mumu_Button01
  3. {
  4. class BindingData
  5. {
  6. public BindingData()
  7. {
  8. ColorName = "Red";
  9. }
  10. private string name = "Red";
  11. public string ColorName
  12. {
  13. get
  14. {
  15. return name;
  16. }
  17. set
  18. {
  19. name = value;
  20. }
  21. }
  22. }
  23. }
  1. 2)在App.xaml文件中添加BindingData对象的资源,代码如下所示:
  1. <Application.Resources>
  2. <!--引入一个.NET对象的资源-->
  3. <local:BindingData x:Key="myDataSource"/>
  4. <SolidColorBrush x:Key="MyBrush" Color="Gold"/>
  5. <!--GreenButtonStyle将按钮的背景色设置为绿色-->
  6. <Style x:Key="GreenButtonStyle">
  7. <Setter Property="Control.Background" Value="Green"/>
  8. </Style>
  9. </Application.Resources>
  1. 如第4章中所述,如果需要在XAML文件中引用一个自定义的类,则需要像上例中那样,声明`xmlns:c="clr-namespace:mumu_Button01"`后才能使用该自定义类。

(3)在MainWindow.xaml 文件中将该.NET对象绑定到“我被绑定成红色”按钮的背景色上,如下代码所示:

<Button Grid.Row="1" Grid.Column="3" Name="BindingBtn"

Background="{Binding Source={StaticResource myDataSource},Path=ColorName}">我被绑定成红色!</Button>

运行结果:

第5章 依赖属性——木木的“汗血宝马” - 图8

5、依赖属性对属性值继承的支持

“属性值继承”也是一个新的概念,仍以按钮的字体大小属性(FontSize)为例。

(1)在MainWindow.xaml.cs 文件中为MainWindow类添加一个成员变量 _oldFontSize = 0,并在构造函数中保存当前窗口默认的字体大小值,其作用是将字体大小恢复为初始状态值。

  1. public partial class MainWindow : Window
  2. {
  3. public MainWindow()
  4. {
  5. InitializeComponent();
  6. _oldFontSize = FontSize;
  7. }
  8. private double _oldFontSize = 0;
  9. }
  1. 2)为“设置窗口字体:16”按钮添加一个单击事件处理函数,如下所示:
  1. ...
  2. <Button Grid.Row="0" Grid.Column="0" Name="FontSizeWinBtn" Margin="5"
  3. Click="FontSizeWinBtn_Click">设置窗口字体:16</Button>
  1. 3)在MainWindow.xaml.cs文件中添加该事件的行为,**注意 FontSize 属性是整个窗口的属性**。即单击该按钮会将窗口的字体大小属性值赋为 16,代码如下所示:
  1. ...
  2. private void FontSizeWinBtn_Click(object sender, RoutedEventArgs e)
  3. {
  4. FontSize = 16;
  5. }
  6. ...
  1. 4)为“设置按钮字体:8”的按钮添加事件处理函数,注意这里是**设置该按钮的字体大小**,如下代码所示:
  1. ...
  2. <Button Grid.Row="1" Grid.Column="0" Name="FontSizeBtn" Margin="5"
  3. Click="FontSizeBtn_Click">设置按钮字体:8</Button>
  4. ...
  1. ...
  2. private void FontSizeBtn_Click(object sender, RoutedEventArgs e)
  3. {
  4. this.FontSizeBtn.FontSize = 8;
  5. }
  1. 5)为“重置字体:12”的按钮添加事件处理函数,这里将所有的字体大小都还原成初始的默认值,如下代码所示:
  1. ...
  2. <Button Grid.Row="2" Grid.Column="0" Name="ResetFontSizeBtn" Margin="5"
  3. Click="ResetFontSizeBtn_Click">重置字体</Button>
  1. ...
  2. private void ResetFontSizeBtn_Click(object sender, RoutedEventArgs e)
  3. {
  4. FontSize = _oldFontSize;
  5. this.FontSizeBtn.FontSize = _oldFontSize;
  6. }
  7. ...
  1. 截至目前的运行结果如下:

F5运行后单击“设置窗口字体:16”按钮

单击“设置按钮字体:8”单击“重置字体:12”

6、依赖属性对元数据重载的支持

依赖属性和普通的.NET属性的区别之一就是有一个元数据对象,元数据和依赖属性是一对一的关系。通过设置元数据对象,可以改变依赖属性的状态和行为。如元数据会规定“你的默认值必须是红色”、“你这个整型值只能在3~10之间”或者“你发生了改变一定要通知某某”等。

一般用到的元数据类是 PropertyMetaData 和 FrameworkPropertyMetaData,前者是后者的基类,如下图所示:

第5章 依赖属性——木木的“汗血宝马” - 图13

一般依赖属性的元数据的类型为 PropertyMetaDta,而大部分控件的依赖属性,如按钮的 Width 及 Background 这样的依赖属性就会用到 FrameworkPropertyMetaData 元数据对象。

一般元数据对象包括如下类型的信息:

(1)默认值:如 Background 的默认值是红色,Width 的默认值是 30 等。

(2)引用回调函数:其中 PropertyChangedCallback,在属性值发生改变时调用;CoerceValueCallback 用于限制属性值,如一个进度条的当前值需要限制在最小值和最大值之间。如果该值小于最小值minimum,则为 minimum;如果大于最大值maximum,则为maximum。

(3)如果是框架级别的一些属性,如按钮的Width、Background等,则会有一些告知 WPF 该属性的某些状态信息,这是 FrameworkPropertyMetaData 类型的元数据的特点。如前例中的 FontSize属性,AffectsMeasure、AffectsRender 和 Inherits 即此类信息,其中 Inherits 为 true 意味着该属性具有属性值继承的特性。FontSize 的元数据特性如下图所示:

第5章 依赖属性——木木的“汗血宝马” - 图14

有了这个元数据对象,我们编写程序的思想也发生了一些变化。在过去,如果一个继承类需要一个属性A,这个属性A和基类的一个属性B“有点像又有点不像”的时候,左思右想,最后我们只好放弃代码优美,硬生生地在继承类里重新加上了一个属性A。但是现在在WPF里遇到了这种情况,我们仍然保留基类的这个属性B,但是将它的元数据对象换一个就可以满足要求,就被称之为元数据重载。如下图所示:

第5章 依赖属性——木木的“汗血宝马” - 图15

7、依赖属性对WPF设计器的集成支持

对 WPF 设计器的集成支持主要体现在自定义一个控件,如自定义一个按钮并为其添加了一个依赖属性,那么在 WPF 的属性窗口中会显示该项,而不显示普通属性。

5.2.3 依赖属性的组成部分

第5章 依赖属性——木木的“汗血宝马” - 图16

从上面的分析可知,依赖属性会涉及这几个部分:依赖属性变量(如IsDefaultProperty)、普通的.NET属性(IsDefault)、.NET属性包装器、元数据和值验证函数。

5.3 自定义依赖属性

5.3.1 何时需要自定义一个依赖属性

如果我们在自定义一个属性的时候,需要让该属性支持哪些功能,那么我们就必须把它实现成依赖属性。

实现一个依赖属性必须满足以下条件:

(1)该类必须继承自 DependencyObject 类,只有 DependencyObject 类可以注册和拥有依赖属性。

(2)该类中必须定义一个 public static readonly 成员变量,类型为 DependencyProperty,如“public static readonly DependencyProperty IsDefaultProperty;”。

(3)该依赖属性名必须以“属性名+Property”命名,如 Button 的 IsDefault 属性,命名为“IsDefaultProperty”。

(4)必须调用 DependencyProperty 的注册方法(Register 及 RegisterReadOnly)在WPF属性系统中注册该依赖属性或者使用依赖属性的 AddOwner 方法。两种方法均返回一个 DependencyProperty 类型的标识并将其保存在定义的 DependencyProperty 成员变量中。

(5)为依赖属性实现一个.NET属性包装器。

5.3.2 自定义一个依赖属性示例

该类继承一个按钮类,实现一个自定义按钮。为该按钮添加一个依赖属性 Space,用来控制按钮字符的间距。然后将该属性实现成依赖属性,使其具备属性值继承的特性,运行结果如下:

第5章 依赖属性——木木的“汗血宝马” - 图17

1、自定义 Button 按钮

自定义 SpaceButton 按钮,代码如下所示:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using System.Windows;
  7. using System.Windows.Controls;
  8. namespace mumu_Button02
  9. {
  10. public class SpaceButton:Button
  11. {
  12. //传统.NET做法 私有字段搭配一个公开属性
  13. string txt;
  14. public string Text
  15. {
  16. set
  17. {
  18. txt = value;
  19. Content = SpaceOutText(txt);
  20. }
  21. get
  22. {
  23. return txt;
  24. }
  25. }
  26. //依赖属性
  27. public static readonly DependencyProperty SpaceProperty;
  28. //.NET属性包装器
  29. public int Space
  30. {
  31. set
  32. {
  33. SetValue(SpaceProperty, value);
  34. }
  35. get
  36. {
  37. return (int)GetValue(SpaceProperty);
  38. }
  39. }
  40. //静态的构造函数
  41. static SpaceButton()
  42. {
  43. //定义元数据
  44. FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata();
  45. metadata.DefaultValue = 0;
  46. metadata.PropertyChangedCallback += OnSpacePropertyChanged;
  47. //注册依赖属性
  48. SpaceProperty = DependencyProperty.Register("Space", typeof(int), typeof(SpaceButton), metadata, ValidateSpaceValue);
  49. }
  50. //值验证的回调函数
  51. static bool ValidateSpaceValue(object obj)
  52. {
  53. int i = (int)obj;
  54. return i >= 0;
  55. }
  56. //属性值改变的回调函数
  57. static void OnSpacePropertyChanged(DependencyObject obj,DependencyPropertyChangedEventArgs args)
  58. {
  59. SpaceButton btn = obj as SpaceButton;
  60. string txt = btn.Content as string;
  61. if (txt == null) return;
  62. btn.Content = btn.SpaceOutText(txt);
  63. }
  64. //该方法为字符间隔添加空格
  65. string SpaceOutText(string str)
  66. {
  67. if(str == null)
  68. {
  69. return null;
  70. }
  71. StringBuilder build = new StringBuilder();
  72. //在其中添加 Space 个空格
  73. foreach(char ch in str)
  74. build.Append(ch + new string(' ', Space));
  75. return build.ToString();
  76. }
  77. }
  78. }
  1. SpaceButton 继承 Button类,添加了一个普通属性 Text。这是传统的.NET做法,即一个私有字段 txt搭配一个 Text属性。一个依赖属性 SpaceProperty表示字符的空格间距,如下图所示:

第5章 依赖属性——木木的“汗血宝马” - 图18

为 SpaceProperty 实现一个.NET属性包装器,因为依赖属性本身是一个静态变量,所以在类的静态构造函数中创建和设置元数据,并注册依赖属性。这里元数据设置 Space的默认值 0,并且添加了一个实现的 OnSpacePropertyChanged 回调函数。只要 Space属性值发生改变,则调用这个回调函数。

在注册时还设置了一个实现的 ValidateSpaceValue值验证回调函数,用于检查 Space值是否大于等于 0。如果该值在外部设置小于 0,则程序会报告异常。注意两个回调函数都是静态方法。

SpaceOutText 方法主要为现有字符串添加空格,空格的个数由 Space变量决定。

2、外部调用自定义按钮

在MainWindow.xaml文件中调用SpaceButton按钮,如下代码所示:项目名与类名不要一样,否则会报错(我一开始都叫SpaceButton,总是报错)

  1. <Window x:Class="mumu_Button02.MainWindow"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6. xmlns:local="clr-namespace:mumu_Button02"
  7. mc:Ignorable="d"
  8. Title="MainWindow" Height="300" Width="300">
  9. <Grid>
  10. <Grid.RowDefinitions>
  11. <RowDefinition/>
  12. <RowDefinition/>
  13. </Grid.RowDefinitions>
  14. <local:SpaceButton x:Name="btnSpace" Grid.Column="0" Grid.Row="0" Margin="5" Click="btnSpace_Click" Text="设置按钮字符空格:2">
  15. </local:SpaceButton>
  16. <local:SpaceButton x:Name="winSpace" Grid.Column="0" Grid.Row="1" Margin="5" Click="winSpace_Click" Text="设置窗口字符空格:2">
  17. </local:SpaceButton>
  18. </Grid>
  19. </Window>
  1. 注意:①在XAML中调用自定义类,需要声明 xmlns:local,然后调用时的标签是<local:SpaceButton>......
  2. ②自定义的普通.NET属性Text,也可以XAML文件中设置。

为第一个按钮添加事件处理器:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using System.Windows;
  7. using System.Windows.Controls;
  8. using System.Windows.Data;
  9. using System.Windows.Documents;
  10. using System.Windows.Input;
  11. using System.Windows.Media;
  12. using System.Windows.Media.Imaging;
  13. using System.Windows.Navigation;
  14. using System.Windows.Shapes;
  15. namespace mumu_Button02
  16. {
  17. /// <summary>
  18. /// MainWindow.xaml 的交互逻辑
  19. /// </summary>
  20. public partial class MainWindow : Window
  21. {
  22. public MainWindow()
  23. {
  24. InitializeComponent();
  25. }
  26. private void btnSpace_Click(object sender, RoutedEventArgs e)
  27. {
  28. this.btnSpace.Space = 2;
  29. }
  30. }
  31. }
  1. 效果:

运行时点击第一个按钮后

3、为依赖属性增加属性值继承的特性

为 Space 增加属性值继承特性,首先为窗口增加 Space 依赖属性,如下代码所示:

代码有问题暂时空着100页

5.4 所有规则大排队

5.4.1 按钮到底是什么颜色

首先查看代码:

  1. 该代码中有 3 处设置了按钮的背景色,那么按钮的背景色到底是什么颜色?红色,鼠标移动到按钮上的时候按钮颜色也没变。

这说明直接设置的值具有最高优先级,甚至能够屏蔽触发器设置属性的行为。

删除 Background=”Red”,运行结果:鼠标没有移动到按钮上时,按钮背景色为绿色,否则为蓝色。

如此说明优先级是“直接设置的值 > 样式中触发器设置的值 > 样式中 setter 设置的值”。

5.4.2 依赖属性设置优先级列表

一个依赖属性从设置到最终结果的完整流程如下图:

第5章 依赖属性——木木的“汗血宝马” - 图21

依赖属性经历了四个步骤:判断基础值、计算、动画,以及限制和验证。基础值是依赖属性在计算之前的值,主要相对计算和动画而言;本底值主要是在代码中直接通过.NET属性包装器或者在XAML中设置的值。如果依赖属性的值是一个资源(动态和静态)或者一个数据绑定的引用,也称为“本地值”。

第5章 依赖属性——木木的“汗血宝马” - 图22

5.4.3 验证优先级的示例

1、设置本地值>模板父类>样式

什么是 TemplatedParent?比如一个按钮按照模板创建的,并且这个模板中有一个椭圆元素,那么这个椭圆的 TemplatedParent 是按钮。104~107页跳过。

5.5 附加属性和“等餐号”

附加属性(Attached Property),打个比方进行说明:

一些很火爆的餐厅,门口会有人给每个等待的顾客写上一个号码,然后依次叫号,叫到号的顾客会被带到不同的餐桌上。附加属性就好比“等餐号”,明明是餐厅的属性,但是任何一个来餐厅的顾客都可以设置这个属性值(比如 1,2,3……)。服务员再根据顾客的这个属性值安排合适的位置。

在WPF中,最典型的附加属性就是各种布局里面的属性,比如 Grid的Row,Column或者是 DockPanel 里的 Dock属性等。Row 和 Column 也像“等餐号”一样,让每个元素去设置其属性值,再由 Grid 按照属性值把元素安排在合适的位置。

5.5.2 附加属性的本质

附加属性本质上是一个依赖属性,与普通的依赖属性相比有以下不同:

(1)注册不再是通过 Register方法注册,而是通过 RegisterAttached 方法注册。

(2)没有普通的.NET属性包装器,而是通过 Get 和 Set 属性名来实现属性包装。

(3)没有普通的.NET属性。

以下是一个附加属性 IsBubbleSource 的示例:

  1. public static readonly DependencyProperty IsBubbleSourceProperty =
  2. DependencyProperty.RegisterAttached("IsBubbleSource", typeof(Boolean), typeof(AquariumObject),
  3. new FrameworkPropertyMetadata(false,FrameworkPropertyMetadataOptions.AffectsRender));
  4. public static void SetIsBubbleSource(UIElement element, bool value)
  5. {
  6. element.SetValue(IsBubbleSourceProperty, value);
  7. }
  8. public static bool GetIsBubbleSource(UIElement element)
  9. {
  10. return (bool)element.GetValue(IsBubbleSourceProperty);
  11. }

5.6 接下来做什么

109页~110页,问题的解答,省略。