5.1 属性与依赖
依赖属性是WPF引入的一个新的属性类型。
过去的属性很简单,如一个文件名、一个用户名和一个窗口的标题等都是属性,并且作为一个类的成员变量存储在类中。如下代码所示,其中_filename 是 mumu_File 的属性。
class mumu_File
{
private string _filename;
public string Filename
{
get
{
return _filename;
}
set
{
if(_filename != value)
{
_filename = value;
}
}
}
}
在 Main 函数中访问_filename属性如下代码所示:
class Program
{
static void Main(string[] args)
{
mumu_File file = new mumu_File();
file.Filename = "mumu_File.txt";
string s = file.Filename;
}
}
下面代码为 Button 中的一个 IsDefault 属性,注意它是一个依赖属性。
public class Button : ButtonBase
{
//依赖属性
public static readonly DependencyProperty IsDefaultProperty;
static Button()
{
//注册属性
Button.IsDefaultProperty = DependencyProperty.Register("IsDefault", typeof(bool), typeof(Button),
new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsDefaultChanged)));
}
//.NET属性包装器(可选)
public bool IsDefault
{
get{ return (bool)GetValue(Button.IsDefaultProperty);}
set{ SetValue(Button.IsDefaultProperty);}
}
//属性改变的回调(可选)
private static void OnIsDefaultChanged(DependencyObject o, DependencyPropertyChangedEventArgs e){ }
}
关于上述代码的详细解读:https://blog.csdn.net/Andrewniu/article/details/80438309
类型为DependencyProperty 的 IsDefaultProperty 是依赖属性,不过用户使用的是暴露后的 IsDefault 普通的.NET 属性,从中可以看出依赖属性是 WPF 在原来的属性基础上提供给用户功能更加丰富的一种类型的属性。
依赖属性是一种类型为DependencyProperty 的属性,其依赖属性标识(Dependency property identifier)则是依赖属性的实例。如 IsDefaultProperty 是依赖属性标识,其本身是一种依赖属性类型。但是大多数情况下不必区分是依赖属性还是其标识,从上下文即可得知。
与依赖属性相关的属于如下:
(1)DependencyObject:WPF中的一种类型,继承该类后才可以注册和拥有依赖属性。
(2)WPF 属性系统,WPF 通过提供一系列的服务扩展了普通的 .NET 属性,这些服务总称为“WPF属性系统”。
(3).NET属性包装器:指属性的 get 和 set的实现,如上面代码中的 IsDefault 的 get 和 set实现。 .NET属性包装器可以把依赖属性包装成普通的 .NET属性暴露给用户使用,在这个实现中均调用 DependencyObject 的 GetValue 和 SetValue方法。
依赖属性已经**不像简单的面向对象语言里的属性**,它更像一个计算过程,根据所输入的值经过一些计算最终得到另外一个值。整个计算过程“依赖”其他属性与内在和外在的多种因素,“依赖”正是由此而来,如下图所示:
5.2 认识依赖属性
5.2.1 分辨依赖属性
WPF中相当多的元素的属性是依赖属性,以按钮(Button)为例,宽度(Width)、背景色(Background)和字体大小(FontSize)等都是依赖属性。为分辨一个元素的属性,可以查阅MSDN文档。如果该属性是依赖属性,则该文档中必然会有依赖属性信息(Dependency Property Information)这一节。
5.2.2 引入依赖属性的原因
WPF主要的设计思想之一是侧重属性胜于方法和事件,即如果属性能解决问题,则坚决不使用方法和事件。
在过去不用方法和事件单单用属性是很难想象的,因为属性的功能太单一了,仅仅就是提供一个类型的值。因此WPF需要提供一个新的属性类型即依赖属性和与之配套的服务,让它能够做方法和事件所能做的事情。
具体来说依赖属性与以前的属性相比,提供了对资源引用、样式、动画、数据绑定、属性值继承、元数据重载及WPF设计器的集成支持功能的支持。这些功能都是WPF的重要特性,由此依赖属性在WPF平台中的重要地位可见一斑。
下面这个例子主要展示依赖属性(主要是按钮的背景色和字体大小属性)对上述功能的支持,程序运行结果如下图所示。图中“金色按钮”的背景色属性引用了一个画刷资源;“绿色按钮”通过样式使背景变成绿色;“动画按钮”通过动画使按钮的背景由红变绿,再由绿变红反复循环;“我被绑定成红色”按钮是通过和一个 .NET对象的属性绑定变成红色。最下面一行是属性继承支持。从左至右依次有3个按钮,单击“设置窗口字体:16”按钮,则所有标签和按钮的字体大小都会变成16;单击“设置按钮字体:8”按钮,则该按钮字体大小变成8;单击最后一个“重置字体:12”按钮,则所有的按钮字体大小恢复为初始状态大小。
<Window x:Class="mumu_Button01.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="mumu_Button01" Height="269" Width="572">
<Grid Name="Grid1">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!--资源支持-->
<Label HorizontalAlignment="Center" VerticalAlignment="Center">资源支持</Label>
<Button Grid.Row="0" Grid.Column="1" Name="resourceBtn" Margin="5">金色按钮</Button>
<!--样式支持-->
<Label Grid.Row="0" Grid.Column="2" HorizontalAlignment="Center" VerticalAlignment="Center">样式支持</Label>
<Button Grid.Row="0" Grid.Column="3" Name="styleBtn" Padding="0" Margin="5">绿色按钮</Button>
<!--动画支持-->
<Label Grid.Row="1" Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center">动画支持</Label>
<Button Grid.Row="1" Grid.Column="1" Name="animationBtn" Margin="5">动画按钮</Button>
<!--数据绑定支持-->
<Label Grid.Row="1" Grid.Column="2" HorizontalAlignment="Center" VerticalAlignment="Center">数据绑定支持</Label>
<Button Grid.Row="1" Grid.Column="3" Name="BindingBtn">我被绑定成红色!</Button>
<!--属性值继承-->
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center">属性继承支持</Label>
<Button Grid.Row="2" Grid.Column="1" Name="FontSizeWinBtn">设置窗口字体:16</Button>
<Button Grid.Row="2" Grid.Column="2" Name="FontSizeBtn">设置按钮字体:8</Button>
<Button Grid.Row="2" Grid.Column="3" Name="ResetFontSizeBtn">重置字体:12</Button>
</Grid>
</Window>
1、依赖属性对资源引用的支持
将金色按钮的背景色设置为金黄色,为了说明依赖属性对资源引用的支持,采用下面的做法(与上方代码保存在不同的文件夹下)。
(1)在App.xaml中定义一个 Key值为 MyBrush 的画刷资源,x:Key 属性用来唯一标识该资源,如下方代码所示。
<Application x:Class="mumu_Button01.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<SolidColorBrush x:Key="MyBrush" Color="Gold"/>
</Applcation.Resources>
</Application>
(2)在MainWindow.xaml文件中将金色按钮的背景色属性设置为该资源的引用,如下方代码所示:
<Button Grid.Row="0" Grid.Column="1" Name="resourceBtn" Margin="5"
Background="{DynamicResource MyBrush}">金色按钮</Button>
运行结果如下:
2、依赖属性对样式的支持
样式也是WPF当中的一个重要概念,详细看第13章。在这里我们先简单地认为样式是WPF为我们提供的能够方便的对多个控件应用同一个属性值的一种机制。
通过样式使绿色按钮背景为绿色的步骤如下(与上方代码保存在不同的文件夹下)。
(1)在App.xaml文件中添加一个新的样式“GreenButtonStyle”,作为应用程序的一个资源。其作用是将按钮的背景色属性值设置为绿色,如下方代码所示:
<Application.Resources>
<SolidColorBrush x:Key="MyBrush" Color="Gold"/>
<!--GreenButtonStyle将按钮的背景色设置为绿色-->
<Style x:Key="GreenButtonStyle">
<Setter Property="Control.Background" Value="Green"/>
</Style>
</Application.Resources>
(2)在MainWindow.xaml文件中绿色按钮同样通过关键字来引用该样式,将其Style属性值设置为“{StaticResource GreenButtonStyle}”,如下代码所示:
<Button Grid.Row="0" Grid.Column="3" Name="styleBtn" Padding="0" Margin="5"
Style="{StaticResource GreenButtonStyle}">绿色按钮</Button>
3、依赖属性对动画的支持
为动画按钮添加如下代码,使动画按钮的背景色从红变绿并反复循环。
<Button Grid.Row="1" Grid.Column="1" Name="animationBtn" Margin="5">
<Button.Background>
<SolidColorBrush x:Name="AnimBrush"/>
</Button.Background>
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Loading">
<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>
4、依赖属性对数据绑定的支持
数据绑定略微复杂一些。
(1)新建一个名为“BindingData”的类作为绑定的数据源,并绑定按钮的背景色和 BindingData 的 ColorName 属性,如下代码所示:
using System;
namespace mumu_Button01
{
class BindingData
{
public BindingData()
{
ColorName = "Red";
}
private string name = "Red";
public string ColorName
{
get
{
return name;
}
set
{
name = value;
}
}
}
}
(2)在App.xaml文件中添加BindingData对象的资源,代码如下所示:
<Application.Resources>
<!--引入一个.NET对象的资源-->
<local:BindingData x:Key="myDataSource"/>
<SolidColorBrush x:Key="MyBrush" Color="Gold"/>
<!--GreenButtonStyle将按钮的背景色设置为绿色-->
<Style x:Key="GreenButtonStyle">
<Setter Property="Control.Background" Value="Green"/>
</Style>
</Application.Resources>
如第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、依赖属性对属性值继承的支持
“属性值继承”也是一个新的概念,仍以按钮的字体大小属性(FontSize)为例。
(1)在MainWindow.xaml.cs 文件中为MainWindow类添加一个成员变量 _oldFontSize = 0,并在构造函数中保存当前窗口默认的字体大小值,其作用是将字体大小恢复为初始状态值。
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
_oldFontSize = FontSize;
}
private double _oldFontSize = 0;
}
(2)为“设置窗口字体:16”按钮添加一个单击事件处理函数,如下所示:
...
<Button Grid.Row="0" Grid.Column="0" Name="FontSizeWinBtn" Margin="5"
Click="FontSizeWinBtn_Click">设置窗口字体:16</Button>
(3)在MainWindow.xaml.cs文件中添加该事件的行为,**注意 FontSize 属性是整个窗口的属性**。即单击该按钮会将窗口的字体大小属性值赋为 16,代码如下所示:
...
private void FontSizeWinBtn_Click(object sender, RoutedEventArgs e)
{
FontSize = 16;
}
...
(4)为“设置按钮字体:8”的按钮添加事件处理函数,注意这里是**设置该按钮的字体大小**,如下代码所示:
...
<Button Grid.Row="1" Grid.Column="0" Name="FontSizeBtn" Margin="5"
Click="FontSizeBtn_Click">设置按钮字体:8</Button>
...
...
private void FontSizeBtn_Click(object sender, RoutedEventArgs e)
{
this.FontSizeBtn.FontSize = 8;
}
(5)为“重置字体:12”的按钮添加事件处理函数,这里将所有的字体大小都还原成初始的默认值,如下代码所示:
...
<Button Grid.Row="2" Grid.Column="0" Name="ResetFontSizeBtn" Margin="5"
Click="ResetFontSizeBtn_Click">重置字体</Button>
...
private void ResetFontSizeBtn_Click(object sender, RoutedEventArgs e)
{
FontSize = _oldFontSize;
this.FontSizeBtn.FontSize = _oldFontSize;
}
...
截至目前的运行结果如下:
6、依赖属性对元数据重载的支持
依赖属性和普通的.NET属性的区别之一就是有一个元数据对象,元数据和依赖属性是一对一的关系。通过设置元数据对象,可以改变依赖属性的状态和行为。如元数据会规定“你的默认值必须是红色”、“你这个整型值只能在3~10之间”或者“你发生了改变一定要通知某某”等。
一般用到的元数据类是 PropertyMetaData 和 FrameworkPropertyMetaData,前者是后者的基类,如下图所示:
一般依赖属性的元数据的类型为 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 的元数据特性如下图所示:
有了这个元数据对象,我们编写程序的思想也发生了一些变化。在过去,如果一个继承类需要一个属性A,这个属性A和基类的一个属性B“有点像又有点不像”的时候,左思右想,最后我们只好放弃代码优美,硬生生地在继承类里重新加上了一个属性A。但是现在在WPF里遇到了这种情况,我们仍然保留基类的这个属性B,但是将它的元数据对象换一个就可以满足要求,就被称之为元数据重载。如下图所示:
7、依赖属性对WPF设计器的集成支持
对 WPF 设计器的集成支持主要体现在自定义一个控件,如自定义一个按钮并为其添加了一个依赖属性,那么在 WPF 的属性窗口中会显示该项,而不显示普通属性。
5.2.3 依赖属性的组成部分
从上面的分析可知,依赖属性会涉及这几个部分:依赖属性变量(如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,用来控制按钮字符的间距。然后将该属性实现成依赖属性,使其具备属性值继承的特性,运行结果如下:
1、自定义 Button 按钮
自定义 SpaceButton 按钮,代码如下所示:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace mumu_Button02
{
public class SpaceButton:Button
{
//传统.NET做法 私有字段搭配一个公开属性
string txt;
public string Text
{
set
{
txt = value;
Content = SpaceOutText(txt);
}
get
{
return txt;
}
}
//依赖属性
public static readonly DependencyProperty SpaceProperty;
//.NET属性包装器
public int Space
{
set
{
SetValue(SpaceProperty, value);
}
get
{
return (int)GetValue(SpaceProperty);
}
}
//静态的构造函数
static SpaceButton()
{
//定义元数据
FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata();
metadata.DefaultValue = 0;
metadata.PropertyChangedCallback += OnSpacePropertyChanged;
//注册依赖属性
SpaceProperty = DependencyProperty.Register("Space", typeof(int), typeof(SpaceButton), metadata, ValidateSpaceValue);
}
//值验证的回调函数
static bool ValidateSpaceValue(object obj)
{
int i = (int)obj;
return i >= 0;
}
//属性值改变的回调函数
static void OnSpacePropertyChanged(DependencyObject obj,DependencyPropertyChangedEventArgs args)
{
SpaceButton btn = obj as SpaceButton;
string txt = btn.Content as string;
if (txt == null) return;
btn.Content = btn.SpaceOutText(txt);
}
//该方法为字符间隔添加空格
string SpaceOutText(string str)
{
if(str == null)
{
return null;
}
StringBuilder build = new StringBuilder();
//在其中添加 Space 个空格
foreach(char ch in str)
build.Append(ch + new string(' ', Space));
return build.ToString();
}
}
}
SpaceButton 继承 Button类,添加了一个普通属性 Text。这是传统的.NET做法,即一个私有字段 txt搭配一个 Text属性。一个依赖属性 SpaceProperty表示字符的空格间距,如下图所示:
为 SpaceProperty 实现一个.NET属性包装器,因为依赖属性本身是一个静态变量,所以在类的静态构造函数中创建和设置元数据,并注册依赖属性。这里元数据设置 Space的默认值 0,并且添加了一个实现的 OnSpacePropertyChanged 回调函数。只要 Space属性值发生改变,则调用这个回调函数。
在注册时还设置了一个实现的 ValidateSpaceValue值验证回调函数,用于检查 Space值是否大于等于 0。如果该值在外部设置小于 0,则程序会报告异常。注意两个回调函数都是静态方法。
SpaceOutText 方法主要为现有字符串添加空格,空格的个数由 Space变量决定。
2、外部调用自定义按钮
在MainWindow.xaml文件中调用SpaceButton按钮,如下代码所示:项目名与类名不要一样,否则会报错(我一开始都叫SpaceButton,总是报错)
<Window x:Class="mumu_Button02.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:mumu_Button02"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<local:SpaceButton x:Name="btnSpace" Grid.Column="0" Grid.Row="0" Margin="5" Click="btnSpace_Click" Text="设置按钮字符空格:2">
</local:SpaceButton>
<local:SpaceButton x:Name="winSpace" Grid.Column="0" Grid.Row="1" Margin="5" Click="winSpace_Click" Text="设置窗口字符空格:2">
</local:SpaceButton>
</Grid>
</Window>
注意:①在XAML中调用自定义类,需要声明 xmlns:local,然后调用时的标签是<local:SpaceButton>......
②自定义的普通.NET属性Text,也可以XAML文件中设置。
为第一个按钮添加事件处理器:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace mumu_Button02
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btnSpace_Click(object sender, RoutedEventArgs e)
{
this.btnSpace.Space = 2;
}
}
}
效果:
3、为依赖属性增加属性值继承的特性
为 Space 增加属性值继承特性,首先为窗口增加 Space 依赖属性,如下代码所示:
代码有问题暂时空着100页
5.4 所有规则大排队
5.4.1 按钮到底是什么颜色
首先查看代码:
该代码中有 3 处设置了按钮的背景色,那么按钮的背景色到底是什么颜色?红色,鼠标移动到按钮上的时候按钮颜色也没变。
这说明直接设置的值具有最高优先级,甚至能够屏蔽触发器设置属性的行为。
删除 Background=”Red”,运行结果:鼠标没有移动到按钮上时,按钮背景色为绿色,否则为蓝色。
如此说明优先级是“直接设置的值 > 样式中触发器设置的值 > 样式中 setter 设置的值”。
5.4.2 依赖属性设置优先级列表
一个依赖属性从设置到最终结果的完整流程如下图:
依赖属性经历了四个步骤:判断基础值、计算、动画,以及限制和验证。基础值是依赖属性在计算之前的值,主要相对计算和动画而言;本底值主要是在代码中直接通过.NET属性包装器或者在XAML中设置的值。如果依赖属性的值是一个资源(动态和静态)或者一个数据绑定的引用,也称为“本地值”。
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 的示例:
public static readonly DependencyProperty IsBubbleSourceProperty =
DependencyProperty.RegisterAttached("IsBubbleSource", typeof(Boolean), typeof(AquariumObject),
new FrameworkPropertyMetadata(false,FrameworkPropertyMetadataOptions.AffectsRender));
public static void SetIsBubbleSource(UIElement element, bool value)
{
element.SetValue(IsBubbleSourceProperty, value);
}
public static bool GetIsBubbleSource(UIElement element)
{
return (bool)element.GetValue(IsBubbleSourceProperty);
}
5.6 接下来做什么
109页~110页,问题的解答,省略。