下面介绍如何使用 C++、C# 或 Visual Basic 为 Windows 运行时应用定义和实现自己的依赖项属性。 我们列出了应用开发人员和组件作者可能想要创建自定义依赖项属性的原因。 我们介绍自定义依赖属性的实现步骤,以及一些可提高依赖属性的性能、可用性或多功能性的最佳做法。
先决条件
我们假设你已阅读 依赖项属性概述 ,并且从现有依赖属性使用者的角度了解依赖属性。 若要遵循本主题中的示例,还应了解 XAML 以及如何使用 C++、C# 或 Visual Basic 编写基本的 Windows 运行时应用。
什么是依赖属性?
若要支持属性的样式、数据绑定、动画和默认值,应将其实现为依赖属性。 依赖属性值不存储为类上的字段,它们由 xaml 框架存储,并使用键进行引用,当属性通过调用 DependencyProperty.Register 方法向 Windows 运行时属性系统注册时,将检索该键。 依赖属性只能由派生自 DependencyObject 的类型使用。 但在类层次结构中 ,DependencyObject 相当高,因此,用于 UI 和呈现支持的大多数类都支持依赖属性。 有关依赖项属性以及本文档中用于描述它们的一些术语和约定的详细信息,请参阅 依赖项属性概述。
Windows 运行时中依赖项属性的示例包括 :Control.Background、 FrameworkElement.Width 和 TextBox.Text 等。
约定是,由类公开的每个依赖属性都有一个相应的 公共静态只读 属性,该属性的类型为 DependencyProperty ,该属性在该类上公开,该属性提供依赖属性的标识符。 标识符的名称遵循以下约定:依赖属性的名称,并将字符串“Property”添加到名称末尾。 例如,Control.Background 属性的相应 DependencyProperty 标识符为 Control.BackgroundProperty。 标识符在注册时存储有关依赖属性的信息,然后可用于涉及依赖属性的其他作,例如调用 SetValue。
属性包装器
依赖属性通常具有包装器实现。 如果没有包装器,获取或设置属性的唯一方法是使用依赖属性实用工具方法 GetValue 和 SetValue ,并将标识符作为参数传递给它们。 表面看来这是一种属性,但这种用法相当不自然。 通过使用包装器,你的代码和引用依赖属性的其他代码都可以使用简单的对象属性语法,这种语法对于所用的语言而言是自然的。
如果自行实现自定义依赖属性,并希望它是公共的且易于调用,则还要定义属性包装器。 属性包装器还可用于向反射或静态分析进程报告有关依赖属性的基本信息。 具体而言,包装器是放置 ContentPropertyAttribute 等属性的位置。
何时将属性实现为依赖属性
每当在类上实现公共读/写属性时,只要类派生自 DependencyObject,就可以选择使属性充当依赖属性。 有时,使用专用字段支持属性的典型技术就足够了。 将自定义属性定义为依赖属性并不总是必要的或适当的。 选择将取决于您希望属性支持的场景。
如果希望属性支持 Windows 运行时或 Windows 运行时应用的一个或多个功能,则可以考虑将属性实现为依赖属性:
- 通过 Style 设置属性
- 作为使用 {Binding} 进行数据绑定的有效目标属性
- 通过Storyboard支持动画值
- 报告属性的值更改时间:
- 属性系统本身执行的操作
- 环境
- 用户操作
- 读取和写入样式
用于定义依赖属性的清单
可以将定义依赖属性视为一组概念。 这些概念不一定是过程步骤,因为可以在实现中的单个代码行中解决多个概念。 此列表仅提供快速概述。 我们将在本主题后面更详细地介绍每个概念,我们将用多种语言向你展示示例代码。
- 将属性名称注册到属性系统(调用 Register),并指定所有者类型和属性值的类型。
- Register 需要一个用于属性元数据的必需参数。 为此指定 null ,或者如果需要属性更改行为,或者可以通过调用 ClearValue 还原的基于元数据的默认值,请指定 PropertyMetadata 的实例。
- 将 DependencyProperty 标识符定义为所有者类型的 公共静态只读 属性成员。
- 定义包装器属性,遵循要实现的语言中使用的属性访问器模型。 包装属性名称应与在
Register 中使用的名称 字符串匹配。 实现 get 和 set 访问器,以便通过调用 GetValue 和 SetValue 并将自己的属性的标识符作为参数,将包装器与包装的依赖属性连接起来。 - (可选)将 ContentPropertyAttribute 等属性放在包装器上。
注释
如果要定义自定义附加属性,通常省略包装器。 您需要编写一种不同风格的访问器,以便 XAML 处理器能够使用。 请参阅 自定义附加属性。
登记财产
要使属性成为依赖属性,必须将该属性注册到由 Windows 运行时属性系统维护的属性存储中。 若要注册属性,请调用 Register 方法。
对于 Microsoft .NET 语言(C# 和 Microsoft Visual Basic),请在类的正文(类内部,但不属于任何成员定义)内调用 Register 。 标识符由 Register 方法调用提供,作为返回值。 Register 调用通常作为静态构造函数进行,或者作为类型 DependencyProperty 的公共静态只读属性初始化的一部分作为类的一部分进行。 此属性公开依赖属性的标识符。 下面是 Register 调用的示例。
注释
将依赖属性注册为标识符属性定义的一部分是典型的实现,但你也可以在类静态构造函数中注册依赖属性。 如果需要多个代码行来初始化依赖属性,此方法可能有意义。
对于 C++/CX,可以选择如何在标头和代码文件之间拆分实现。 典型的做法是将标识符本身在标头中声明为 公共静态 属性,包含 get 实现但不包含 set。 get 实现指向一个私有字段,该字段是一个未初始化的DependencyProperty实例。 还可以声明包装器,以及包装器的 get 和 set 实现。 在这种情况下,头文件包含一些最小的实现。 如果包装器需要 Windows 运行时属性,则也应在标头中添加该属性。 将 Register 调用置于代码文件中,该函数仅在应用首次初始化时才会运行。 使用 Register 的返回值填充在标头中声明的静态但未初始化的标识符,该标识符最初在实现文件的根范围内设置为 nullptr 。
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
nameof(Label),
typeof(String),
typeof(ImageWithLabelControl),
new PropertyMetadata(null)
);
Public Shared ReadOnly LabelProperty As DependencyProperty =
DependencyProperty.Register("Label",
GetType(String),
GetType(ImageWithLabelControl),
New PropertyMetadata(Nothing))
// ImageWithLabelControl.idl
namespace ImageWithLabelControlApp
{
runtimeclass ImageWithLabelControl : Windows.UI.Xaml.Controls.Control
{
ImageWithLabelControl();
static Windows.UI.Xaml.DependencyProperty LabelProperty{ get; };
String Label;
}
}
// ImageWithLabelControl.h
...
struct ImageWithLabelControl : ImageWithLabelControlT<ImageWithLabelControl>
{
...
public:
static Windows::UI::Xaml::DependencyProperty LabelProperty()
{
return m_labelProperty;
}
private:
static Windows::UI::Xaml::DependencyProperty m_labelProperty;
...
};
// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
Windows::UI::Xaml::DependencyProperty::Register(
L"Label",
winrt::xaml_typename<winrt::hstring>(),
winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
Windows::UI::Xaml::PropertyMetadata{ nullptr }
);
...
//.h file
//using namespace Windows::UI::Xaml::Controls;
//using namespace Windows::UI::Xaml::Interop;
//using namespace Windows::UI::Xaml;
//using namespace Platform;
public ref class ImageWithLabelControl sealed : public Control
{
private:
static DependencyProperty^ _LabelProperty;
...
public:
static void RegisterDependencyProperties();
static property DependencyProperty^ LabelProperty
{
DependencyProperty^ get() {return _LabelProperty;}
}
...
};
//.cpp file
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml.Interop;
DependencyProperty^ ImageWithLabelControl::_LabelProperty = nullptr;
// This function is called from the App constructor in App.xaml.cpp
// to register the properties
void ImageWithLabelControl::RegisterDependencyProperties()
{
if (_LabelProperty == nullptr)
{
_LabelProperty = DependencyProperty::Register(
"Label", Platform::String::typeid, ImageWithLabelControl::typeid, nullptr);
}
}
注释
对于 C++/CX 代码,您之所以拥有私有字段和一个显示 DependencyProperty 的公共只读属性,是为了使使用您的依赖属性的其他调用者也可以利用需要标识符为公共的属性系统工具 API。 如果使标识符保持私密,则用户无法使用这些实用工具 API。 此类 API 和方案的示例包括 GetValue 或 SetValue by choice、 ClearValue、 GetAnimationBaseValue、 SetBinding 和 Setter.Property。 不能为此使用公共字段,因为 Windows 运行时元数据规则不允许公共字段。
依赖属性名称约定
依赖项属性的命名有约定;在非特殊情况下应遵循这些约定。 依赖属性本身具有一个基本名称(前面示例中的“Label”),该名称作为 Register 的第一个参数提供。 该名称在每个注册类型中必须是唯一的,唯一性要求也适用于任何继承的成员。 通过基类型继承的依赖属性被视为已注册类型的一部分;无法再次注册继承属性的名称。
警告
虽然此处提供的名称可以是任何字符串标识符,这些标识符在编程中对所选语言有效,但通常也希望能够在 XAML 中设置依赖项属性。 若要在 XAML 中设置,选择的属性名称必须是有效的 XAML 名称。 有关详细信息,请参阅 XAML 概述。
创建标识符属性时,将属性的名称与后缀“Property”(例如“LabelProperty”)合并。 此属性是依赖属性的标识符,它用作在你自己的属性包装器中发出的 SetValue 和 GetValue 调用的输入。 属性系统和其他 XAML 处理器(如 {x:Bind})也使用它
实现封装器
属性包装器应在 get 实现中调用 GetValue,并在 set 实现中调用 SetValue。
警告
除非在特殊情况下,包装器实现应仅执行 GetValue 和 SetValue 操作。 否则,当属性是通过 XAML 设置的,而不是通过代码设置时,你将获得不同的行为。 为了提高效率,XAML 分析程序在设置依赖项属性时绕过包装器;并通过 SetValue 与后盾存储区交谈。
public String Label
{
get { return (String)GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
Public Property Label() As String
Get
Return DirectCast(GetValue(LabelProperty), String)
End Get
Set(ByVal value As String)
SetValue(LabelProperty, value)
End Set
End Property
// ImageWithLabelControl.h
...
winrt::hstring Label()
{
return winrt::unbox_value<winrt::hstring>(GetValue(m_labelProperty));
}
void Label(winrt::hstring const& value)
{
SetValue(m_labelProperty, winrt::box_value(value));
}
...
//using namespace Platform;
public:
...
property String^ Label
{
String^ get() {
return (String^)GetValue(LabelProperty);
}
void set(String^ value) {
SetValue(LabelProperty, value);
}
}
自定义依赖属性的属性元数据
将属性元数据分配给依赖属性时,对于属性所有者类型或其子类的每个实例,相同的元数据将应用于该属性。 在属性元数据中,可以指定两种行为:
- 属性系统分配给属性的所有实例的默认值。
- 一种静态回调方法,每当检测到属性值更改时,都会在属性系统中自动调用。
调用带有属性元数据的 Register
在前面的调用 DependencyProperty.Register 的示例中,我们传递了 propertyMetadata 参数的 null 值。 若要使依赖属性提供默认值或使用属性更改的回调,必须定义一个 PropertyMetadata 实例,该实例提供其中一项或两项功能。
通常,您可以在 DependencyProperty.Register 的参数中提供 PropertyMetadata 作为内联创建的实例。
注释
如果要定义 CreateDefaultValueCallback 实现,则必须使用实用工具方法 PropertyMetadata.Create ,而不是调用 PropertyMetadata 构造函数来定义 PropertyMetadata 实例。
下一个示例通过引用具有PropertyChangedCallback值的PropertyMetadata实例来修改前面显示的DependencyProperty.Register示例。 此部分稍后将展示“OnLabelChanged”回调的实现。
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
nameof(Label),
typeof(String),
typeof(ImageWithLabelControl),
new PropertyMetadata(null,new PropertyChangedCallback(OnLabelChanged))
);
Public Shared ReadOnly LabelProperty As DependencyProperty =
DependencyProperty.Register("Label",
GetType(String),
GetType(ImageWithLabelControl),
New PropertyMetadata(
Nothing, new PropertyChangedCallback(AddressOf OnLabelChanged)))
// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
Windows::UI::Xaml::DependencyProperty::Register(
L"Label",
winrt::xaml_typename<winrt::hstring>(),
winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
Windows::UI::Xaml::PropertyMetadata{ nullptr, Windows::UI::Xaml::PropertyChangedCallback{ &ImageWithLabelControl::OnLabelChanged } }
);
...
DependencyProperty^ ImageWithLabelControl::_LabelProperty =
DependencyProperty::Register("Label",
Platform::String::typeid,
ImageWithLabelControl::typeid,
ref new PropertyMetadata(nullptr,
ref new PropertyChangedCallback(&ImageWithLabelControl::OnLabelChanged))
);
默认值
可以为依赖属性指定默认值,以便在属性未设置时该属性始终返回特定的默认值。 此值可能与该属性类型的固有默认值不同。
如果未指定默认值,则依赖属性的默认值为引用类型的 null 或值类型或语言基元的类型默认值(例如,整数的默认值为 0 或字符串的默认值为空字符串)。 建立默认值的主要原因是在属性上调用 ClearValue 时会还原此值。 建立每个属性的默认值可能比在构造函数中建立默认值更方便,尤其是对于值类型。 但是,对于引用类型,请确保建立默认值不会创建无意的单一实例模式。 有关详细信息,请参阅本主题后面的最佳做法
// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
Windows::UI::Xaml::DependencyProperty::Register(
L"Label",
winrt::xaml_typename<winrt::hstring>(),
winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
Windows::UI::Xaml::PropertyMetadata{ winrt::box_value(L"default label"), Windows::UI::Xaml::PropertyChangedCallback{ &ImageWithLabelControl::OnLabelChanged } }
);
...
注释
请勿将UnsetValue设为默认值。 如果这样做,它将混淆属性使用者,并在属性系统中产生意外的后果。
CreateDefaultValueCallback
在某些情况下,你将为在多个 UI 线程上使用的对象定义依赖属性。 如果你正在定义多个应用使用的数据对象或你在多个应用中使用的控件,则可能是这种情况。 可以通过提供 CreateDefaultValueCallback 实现而不是与注册该属性的线程绑定的默认值实例来启用不同 UI 线程之间的对象交换。 基本上,CreateDefaultValueCallback 定义了一个用于创建默认值的工厂。 CreateDefaultValueCallback 返回的值始终与正在使用对象的当前 UI CreateDefaultValueCallback 线程相关联。
若要定义指定 CreateDefaultValueCallback 的元数据,必须调用 PropertyMetadata.Create 才能返回元数据实例; PropertyMetadata 构造函数没有包含 CreateDefaultValueCallback 参数的签名。
CreateDefaultValueCallback 的典型实现模式是创建新的 DependencyObject 类,将 DependencyObject 的每个属性的特定属性值设置为预期默认值,然后通过 CreateDefaultValueCallback 方法的返回值将新类作为对象引用返回。
属性变更回调方法
可以定义属性更改回调方法,以定义属性与其他依赖属性的交互,或者在属性发生更改时更新对象的内部属性或状态。 如果触发回调,则属性系统已确定属性值发生有效变化。 由于回调方法是静态的,所以回调的 d 参数很重要,因为它会告诉你该类的哪个实例报告了更改。 典型的实现使用事件数据的 NewValue 属性,并以某种方式处理该值,通常通过对作为 d 传递的对象执行一些其他更改。 对属性更改的其他响应是拒绝 NewValue 报告的值、还原 OldValue 或将该值设置为应用于 NewValue 的编程约束。
下一个示例演示 PropertyChangedCallback 实现。 它实现在前面的 Register 示例中引用的方法,作为 PropertyMetadata 构造参数的一部分。 此回调所处理的情形是,该类还具有一个计算而得的只读属性,名为“HasLabelValue” (其实现未显示)。 每当重新计算“Label”属性时,都会调用此回调方法,并且回调使依赖计算值能够与依赖属性的更改保持同步。
private static void OnLabelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
ImageWithLabelControl iwlc = d as ImageWithLabelControl; //null checks omitted
String s = e.NewValue as String; //null checks omitted
if (s == String.Empty)
{
iwlc.HasLabelValue = false;
} else {
iwlc.HasLabelValue = true;
}
}
Private Shared Sub OnLabelChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
Dim iwlc As ImageWithLabelControl = CType(d, ImageWithLabelControl) ' null checks omitted
Dim s As String = CType(e.NewValue,String) ' null checks omitted
If s Is String.Empty Then
iwlc.HasLabelValue = False
Else
iwlc.HasLabelValue = True
End If
End Sub
void ImageWithLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
auto iwlc{ d.as<ImageWithLabelControlApp::ImageWithLabelControl>() };
auto s{ winrt::unbox_value<winrt::hstring>(e.NewValue()) };
iwlc.HasLabelValue(s.size() != 0);
}
static void OnLabelChanged(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)
{
ImageWithLabelControl^ iwlc = (ImageWithLabelControl^)d;
Platform::String^ s = (Platform::String^)(e->NewValue);
if (s->IsEmpty()) {
iwlc->HasLabelValue=false;
}
}
结构和枚举的属性变化行为
如果 DependencyProperty 的类型是枚举或结构,则即使结构的内部值或枚举值没有更改,也可以调用回调。 这不同于系统基元,例如仅在值更改时调用它的字符串。 这是由于内部执行的对这些值的装箱和取消装箱操作引起的副作用。 如果某个属性的 PropertyChangedCallback 方法中使用的值是枚举或结构,您需要自行转换 OldValue 和 NewValue ,并使用转换后的值可用的重载比较运算符进行比较。 或者,如果没有此类运算符可用(可能是自定义结构的情况),则可能需要比较各个值。 如果结果是值未更改,通常会选择不采取任何行动。
private static void OnVisibilityValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if ((Visibility)e.NewValue != (Visibility)e.OldValue)
{
//value really changed, invoke your changed logic here
} // else this was invoked because of boxing, do nothing
}
Private Shared Sub OnVisibilityValueChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
If CType(e.NewValue,Visibility) != CType(e.OldValue,Visibility) Then
' value really changed, invoke your changed logic here
End If
' else this was invoked because of boxing, do nothing
End Sub
static void OnVisibilityValueChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
auto oldVisibility{ winrt::unbox_value<Windows::UI::Xaml::Visibility>(e.OldValue()) };
auto newVisibility{ winrt::unbox_value<Windows::UI::Xaml::Visibility>(e.NewValue()) };
if (newVisibility != oldVisibility)
{
// The value really changed; invoke your property-changed logic here.
}
// Otherwise, OnVisibilityValueChanged was invoked because of boxing; do nothing.
}
static void OnVisibilityValueChanged(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)
{
if ((Visibility)e->NewValue != (Visibility)e->OldValue)
{
//value really changed, invoke your changed logic here
}
// else this was invoked because of boxing, do nothing
}
}
最佳做法
在定义自定义依赖项属性时,请记住以下注意事项作为最佳做法。
DependencyObject 和线程处理
必须在与 Windows 运行时应用显示的当前窗口关联的 UI 线程上创建所有 DependencyObject 实例。 尽管必须在主 UI 线程上创建每个 DependencyObject,但可以从其他线程通过调用 调度器 来访问这些对象。
DependencyObject 的线程化方面是相关的,因为它通常意味着只有 UI 线程上运行的代码才能更改甚至读取依赖属性的值。 通常可以在典型的 UI 代码中避免线程处理问题,以便正确使用 异步 模式和后台工作线程。 如果定义自己的 DependencyObject 类型,并且尝试将其用于数据源或其他不一定合适的依赖对象,则通常只会遇到 DependencyObject 相关的线程问题。
避免无意的单一实例
当你声明一个采用引用类型的依赖属性,并在用于建立PropertyMetadata的代码中调用该引用类型的构造函数时,就可能会产生一个无意的单例。 会发生什么情况是,依赖属性的所有用法只共享 PropertyMetadata 的一个实例,从而尝试共享所构造的单个引用类型。 通过依赖属性设置的该值类型的任何子属性可能以未预期的方式传播到其他对象。
如果需要非 null 值,可以使用类构造函数为引用类型依赖属性设置初始值,但请注意,出于 依赖属性概述的目的,这被视为本地值。 如果您的类支持模板,那么使用模板可能更合适。 避免单一实例模式(但仍提供有用默认值)的另一种方法是公开引用类型的静态属性,该引用类型为该类的值提供适当的默认值。
集合类型依赖项属性
集合类型依赖项属性需要考虑一些其他实现问题。
在 Windows 运行时 API 中,集合类型的依赖项属性相对较为罕见。 在大多数情况下,可以使用项是 DependencyObject 子类的集合,但集合属性本身,是作为传统的 CLR 或 C++ 属性实现的。 这是因为集合不一定适合涉及依赖属性的一些典型方案。 例如:
- 通常不会对集合进行动画处理。
- 通常不会使用样式或模板预填充集合中的项。
- 尽管集合绑定是一个主要场景,但集合不需要是一个依赖属性才能作为绑定源。 对于绑定目标,更典型的是使用 ItemsControl 或 DataTemplate 的子类来支持集合项,或使用视图模型模式。 有关绑定到集合和从集合绑定的详细信息,请参阅 深入的数据绑定。
- 通过 INotifyPropertyChanged 或 INotifyCollectionChanged 等接口或从 ObservableCollection<T> 派生集合类型,可以更好地解决集合更改通知。
不过,集合类型依赖属性的方案确实存在。 接下来的三个部分提供了有关如何实现集合类型依赖属性的一些指导。
初始化集合
创建依赖属性时,可以通过依赖属性元数据建立默认值。 但请注意不要使用单例静态集合作为默认值。 相反,必须将集合值故意设置为唯一的(实例)集合,作为集合属性的所有者类的类构造函数逻辑的一部分。
// WARNING - DO NOT DO THIS
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register(
nameof(Items),
typeof(IList<object>),
typeof(ImageWithLabelControl),
new PropertyMetadata(new List<object>())
);
// DO THIS Instead
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register(
nameof(Items),
typeof(IList<object>),
typeof(ImageWithLabelControl),
new PropertyMetadata(null)
);
public ImageWithLabelControl()
{
// Need to initialize in constructor instead
Items = new List<object>();
}
DependencyProperty 及其 PropertyMetadata 的默认值是 DependencyProperty 的静态定义的一部分。 通过提供一个默认集合(或其他实例化的)值作为默认值,它将在类的所有实例间共享,而不是每个类拥有自己的集合,这是通常期望的。
更改通知
将集合定义为依赖属性不会通过调用“PropertyChanged”回调方法的属性系统自动为集合中的项提供更改通知。 如果要为集合及其项接收通知(例如数据绑定方案),请实现 INotifyPropertyChanged 或 INotifyCollectionChanged 接口。 有关详细信息,请参阅深入了解数据绑定。
依赖属性安全注意事项
将依赖属性声明为公共属性。 将依赖属性标识符声明为 公共静态只读 成员。 即使您尝试声明语言允许的其他访问级别(例如 受保护),依赖属性总是可以通过标识符结合属性系统 API 来进行访问。 将依赖属性标识符声明为内部或私有将不起作用,因为属性系统无法正常运行。
包装器属性实际上只是为了方便起见,可以通过调用 GetValue 或 SetValue 来绕过应用于包装器的安全机制。 因此,将包装器属性保留为公开,否则,您只是使合法的调用者更难使用您的属性,而不提供任何实质性的安全好处。
Windows 运行时不提供将自定义依赖属性注册为只读的方法。
依赖属性和类构造函数
类构造函数不应调用虚拟方法的一般原则。 这是因为可以调用构造函数来完成派生类构造函数的基初始化,并且当构造的对象实例尚未完全初始化时,可以通过构造函数输入虚拟方法。 当您从已继承 DependencyObject 的任何类派生时,请记住,属性系统本身内部调用并公开虚方法,作为其服务的一部分。 为了避免运行时初始化的潜在问题,请不要在类的构造函数中设置依赖属性值。
注册 C++/CX 应用的依赖项属性
在 C++/CX 中注册属性的实现比 C# 更棘手,这既是因为分离到标头和实现文件,又是因为实现文件的根范围内的初始化是一种不良做法。 (Visual C++ 组件扩展(C++/CX)将静态初始值设定项代码从根范围直接放入 DllMain,而 C# 编译器将静态初始值设定项分配给类,从而避免 DllMain 加载锁定问题。 此处的最佳做法是声明一个帮助程序函数,该函数对类执行所有依赖属性注册,每个类一个函数。 然后,对于应用使用的每个自定义类,您需要引用该自定义类公开的辅助注册函数。 在 应用程序构造函数(App::App())之前,调用每个助手注册函数一次。 例如,该构造函数仅在首次真正引用应用时运行,例如,如果暂停的应用恢复,该构造函数不会再次运行。 此外,如前面的C++注册示例所示,在每次调用 Register 时进行 nullptr 检查是很重要的:这样可以确保函数的调用方不会重复注册属性。 第二次注册调用可能会在没有此类检查的情况下崩溃你的应用,因为属性名称将是重复的。 如果查看示例C++/CX 版本的代码,可以在 XAML 用户和自定义控件示例中 查看此实现模式。