依赖系统
介绍
在构建声明性接口时使用的主要体系结构哲学之一是,对属性或方法的偏好。属性是声明性的,使您可以更轻松地指定意图而不是操作。这也支持用于显示用户界面内容的模型驱动或数据驱动的系统。这种理念的预期效果是创建了更多可以绑定的属性,以便更好地控制应用程序的行为。
为了使更多的系统由属性驱动,需要比反射提供的系统更丰富的属性系统。这种丰富性的一个简单示例是变更通知。为了启用双向绑定,您需要绑定的两侧都支持更改通知。为了使行为与属性值相关联,需要在属性值更改时得到通知。
UI提供了一个更丰富的属性系统,该系统派生自DependencyObject类型。属性系统是真正的“依赖项”属性系统,它跟踪属性表达式之间的依赖关系,并在依赖项发生更改时自动重新验证属性值。例如,如果您具有一个可继承的DependencyProperty(如FontSize),则如果属性在继承该值的元素的父元素上更改,则系统将自动更新。
属性系统中最重要的概念之一是属性表达式。通过表达式,可以以许多不同的形式来计算和更新属性值:表达式可以从ResourceDictionary中分配一个值,可以绑定到另一个对象属性或数据绑定中。
属性系统还提供属性值的稀疏存储。因为对象可以具有数十个(如果不是数百个)属性,并且大多数值处于其默认状态(继承,由样式设置等),则并非对象的每个实例都需要具有定义的每个属性的全部权重在上面。
属性系统的最终新功能是附加属性的概念。UI元素基于组成和组件重用的原理构建。通常,某些包含元素(例如Grid布局元素)需要有关子元素的其他数据来控制其行为(例如行/列信息)。除了将所有这些属性与每个元素相关联之外,任何对象都可以为任何其他对象提供属性定义。
依赖对象
介绍
DependencyObject基类使派生对象可以使用依赖项属性系统。
它还提供以下服务和特征:
- 依赖属性托管支持。您可以通过调用RegisterProperty或RegisterPropertyRO(只读属性)方法来注册依赖项属性,并将创建的属性存储为类中的公共静态字段。
- 附加的财产托管支持。您可以通过调用RegisterProperty方法来注册附加属性,并将创建的属性存储为类中的公共静态字段。然后,可以在从DependencyObject派生的任何类上设置您附加的属性。
- 获取,设置和清除DependencyObject上存在的任何依赖项属性的值的实用程序方法。
- 元数据,强制值支持,属性更改通知以及对依赖项属性或附加属性的覆盖回调。同样,DependencyObject类简化了依赖项属性的每个所有者属性元数据。
它是从Visual,UIElement或Freezable派生的类的通用基类。
实作
假设我们有一个从DependencyObject派生的类:
/// A button that raises Click event after a delay time
class DelayedButton: public Button
{
NS_DECLARE_REFLECTION(DelayedButton, Button)
};
继承者必须注意有关DependencyObject的NoesisGUI实现的一些重要事项:
- DependencyObject是IComponentInitializer类。在初始化对象之前,不会通知所有值修改,也不对表达式进行求值(这意味着不会解析资源和绑定)。派生类必须初始化不是依赖属性重写OnInit函数的实例成员。继承者必须调用父实现以不破坏依赖对象的初始化:
void DelayedButton::OnInit()
{
ParentClass::OnInit();
InitComponent(mOtherInfo);
}
- DependencyObject提供一个OnPropertyChanged函数,以将属性更改通知继承者。继承者必须调用父实现以不破坏通知系统:
<pre style="box-sizing: inherit; overflow: auto; font-family: Consolas, "Courier New", Courier, monospace; font-size: 0.9em; background: rgb(244, 244, 244); border: 1px solid rgb(230, 230, 230); color: rgb(0, 0, 0); margin: 0px; padding: 0.25em;">NsBool DelayedButton :: OnPropertyChanged (const的 DependencyPropertyChangedEventArgs & ë )
{
NsBool 处理 = 父类:: OnPropertyChanged (É );如果 (!处理) { 如果 (ē 。道具 == DelayProperty ) { //属性更改管理这里 返回 真; } } 退货 处理;
}
</pre>
依赖属性
介绍
依赖项属性的目的是提供一种基于其他输入值来计算属性值的方法。这些其他输入可能包括系统属性(例如主题和用户首选项),即时属性确定机制(例如数据绑定和动画/故事板),多次使用模板(例如资源和样式)或通过与父子关系已知的值元素树中的其他元素。
此外,可以实现依赖项属性以提供独立的验证,默认值,监视对其他属性的更改的回调,以及可以根据潜在的运行时信息强制改变属性值的系统。
派生类还可以通过覆盖依赖项属性元数据来更改现有属性的某些特定特征,而不是覆盖现有属性的实际实现或创建新属性。
实作
NoesisGUI通过反射元数据支持实现依赖项属性系统。每个依赖项对象都将其依赖项属性存储在反射TypeMetaData派生类DependencyData中。此类提供注册与反射类型关联的依赖项属性的功能。
为了创建一个新的派生的DependencyObject类,我们开始定义它将具有的公共依赖项属性:
<pre style="box-sizing: inherit; overflow: auto; font-family: Consolas, "Courier New", Courier, monospace; font-size: 0.9em; background: rgb(244, 244, 244); border: 1px solid rgb(230, 230, 230); color: rgb(0, 0, 0); margin: 0px; padding: 0.25em;">// DelayedButton.h
命名空间 教程
{
class DelayedButton : public Button
{
public :
///依赖属性
// @ {
静态 const DependencyProperty * DelayProperty ;
// @}
};
}
</pre>
然后,我们必须在依赖系统中注册该属性:
<pre style="box-sizing: inherit; overflow: auto; font-family: Consolas, "Courier New", Courier, monospace; font-size: 0.9em; background: rgb(244, 244, 244); border: 1px solid rgb(230, 230, 230); color: rgb(0, 0, 0); margin: 0px; padding: 0.25em;">// DelayedButton.cpp
NS_IMPLEMENT_REFLECTION (教程:: DelayedButton )
{
NsMeta < TypeId > (“ Tutorials.DelayedButton” );
DependencyData * 数据 = NsMeta < DependencyData > (TypeOf < SelfClass > ());
数据-> RegisterProperty < float > (DelayProperty , “ Delay” , PropertyMetadata :: 创建(1.0f ));
}
</pre>
前面的代码执行三件事:
- 分配用于在ComponentFactory中注册该类的TypeId,以便XAML解析器能够在解析XAML文件时创建DelayedButton实例。
- NsMeta创建与DelayedButton类的反射关联的元数据,依赖系统将使用该元数据来查找此类注册的依赖属性。
- RegisterProperty在依赖系统中创建一个名为“ Delay”的属性,默认值为1.0f,并且该属性与DelayedButton类相关联。
- RegisterProperty还将创建的属性分配给DelayProperty公共成员。
之后,我们可以从xaml使用该类,如下所示:
<pre style="box-sizing: inherit; overflow: auto; font-family: Consolas, "Courier New", Courier, monospace; font-size: 0.9em; background: rgb(244, 244, 244); border: 1px solid rgb(230, 230, 230); color: rgb(0, 0, 0); margin: 0px; padding: 0.25em;"><local:DelayedButton Delay = “ 1.5” />
</pre>
或在代码中:
<pre style="box-sizing: inherit; overflow: auto; font-family: Consolas, "Courier New", Courier, monospace; font-size: 0.9em; background: rgb(244, 244, 244); border: 1px solid rgb(230, 230, 230); color: rgb(0, 0, 0); margin: 0px; padding: 0.25em;">Ptr < 教程:: DelayedButton > btn = Noesis :: MakePtr < 教程:: DelayedButton > ();
BTN - > 的SetValue < 浮动> (教程:: DelayedButton :: DelayProperty , 1.5F );
</pre>
函数SetValue是DependencyObject的模板化方法,用于设置对象中指定的依赖项属性的本地值。为了使修改和访问依赖属性值更容易,我们通常在所有者类中将它们与getter / setter方法包装在一起(我们省略了上面的代码以使其更加清楚):
<pre style="box-sizing: inherit; overflow: auto; font-family: Consolas, "Courier New", Courier, monospace; font-size: 0.9em; background: rgb(244, 244, 244); border: 1px solid rgb(230, 230, 230); color: rgb(0, 0, 0); margin: 0px; padding: 0.25em;">// DelayedButton.h
命名空间 教程
{
class DelayedButton : public Button
{
public :
///获取或设置按钮单击延迟,以秒为单位
// @ {
float GetDelay () const ;
无效的 SetDelay (浮动 延迟);
// @}
};
}
// DelayedButton.cpp
命名空间 教程
{
float DelayedButton :: GetDelay () const
{
return GetValue < float > (DelayProperty );
}
void DelayedButton :: SetDelay (float delay )
{
SetValue < float > (DelayProperty , delay );
}
}
//正在使用...
Ptr < 教程:: DelayedButton > btn = Noesis :: MakePtr < 教程:: DelayedButton > ();
BTN - > SetDelay (1.5F );
</pre>
正如我们在示例中看到的那样,依赖项属性可以具有与反射类型关联的额外信息。此元数据存储在PropertyMetadata对象中。
属性元数据
注册依赖项属性时,我们使用PropertyMetadata对象设置一些与该属性关联的值。
默认值
首先,我们可以定义其默认值。该值可能存储在属性中,但是默认值(以及其他信息将在后面说明)存储在元数据中,因为依赖项属性可以具有与不同类型关联的不同元数据。这使我们有可能在派生类中覆盖属性的默认值。例如:
<pre style="box-sizing: inherit; overflow: auto; font-family: Consolas, "Courier New", Courier, monospace; font-size: 0.9em; background: rgb(244, 244, 244); border: 1px solid rgb(230, 230, 230); color: rgb(0, 0, 0); margin: 0px; padding: 0.25em;">// DerivedDelayedButton.cpp
NS_IMPLEMENT_REFLECTION (教程:: DerivedDelayedButton )
{
DependencyData * data = NsMeta < DependencyData > (TypeOf < SelfClass > ());
数据-> OverrideMetadata < float > (DelayedButton :: DelayProperty , “ Delay” ,
PropertyMetadata :: Create (5.0f ));
}
</pre>
派生类的实例将具有不同的默认值:
<pre style="box-sizing: inherit; overflow: auto; font-family: Consolas, "Courier New", Courier, monospace; font-size: 0.9em; background: rgb(244, 244, 244); border: 1px solid rgb(230, 230, 230); color: rgb(0, 0, 0); margin: 0px; padding: 0.25em;">Ptr < 教程:: DelayedButton > btn1 = Noesis :: MakePtr < 教程:: DelayedButton > ();
浮动 delayBtn1 = btn1- > GetDelay (); //->返回1.0f
Ptr < 教程:: DerivedDelayedButton > btn2 = Noesis :: MakePtr < 教程:: DerivedDelayedButton > ();
浮动 delayBtn2 = btn2- > GetDelay (); //->返回5.0f
</pre>
此外,其他尚未修改(覆盖)的元数据值将与与祖先类关联的元数据合并。其他元数据值包括:属性更改回调和强制值回调。但是PropertyMetadata派生类可以使用更多值扩展属性信息。
属性更改回调
该属性更改回调是所谓的每次财产的实际价值为DependencyObject发生改变时,静态函数。该函数的签名必须为以下内容:
<pre style="box-sizing: inherit; overflow: auto; font-family: Consolas, "Courier New", Courier, monospace; font-size: 0.9em; background: rgb(244, 244, 244); border: 1px solid rgb(230, 230, 230); color: rgb(0, 0, 0); margin: 0px; padding: 0.25em;">void PropertyChangedCallback (DependencyObject * d , const DependencyPropertyChangedEventArgs & e );
</pre>
该函数接收更改了属性的对象,以及带有更改信息的结构:
- e.prop:已更改的属性
- e.oldValue:更改前依赖对象中的值
- e.newValue:在依赖项对象中设置的新值
使用此信息,类可以管理其依赖项属性的更改并更新其他值或启动事件。
强制值回调
所述裹胁值回调是静态函数被称为每当一个依赖属性值被重新评估,或强迫通过专门请求CoerceValue方法。该函数的签名必须为以下内容:
<pre style="box-sizing: inherit; overflow: auto; font-family: Consolas, "Courier New", Courier, monospace; font-size: 0.9em; background: rgb(244, 244, 244); border: 1px solid rgb(230, 230, 230); color: rgb(0, 0, 0); margin: 0px; padding: 0.25em;">布尔 CoerceValueCallback (const DependencyObject * d , const void * baseValue , void * coercedValue );
</pre>
强制与依赖项属性的基值进行交互,以确保应用某些约束,因为当时存在这些约束,但基值仍然保留。强制回调的最常见用法是确保一个值在最小值和最大值之间。继续前面的示例,我们可以在我们的DelayedButton类中定义另外两个属性MinimumDelay和MaximumDelay,并将CoerceValueCallback关联到Delay属性:
<pre style="box-sizing: inherit; overflow: auto; font-family: Consolas, "Courier New", Courier, monospace; font-size: 0.9em; background: rgb(244, 244, 244); border: 1px solid rgb(230, 230, 230); color: rgb(0, 0, 0); margin: 0px; padding: 0.25em;">// DelayedButton.h
class DelayedButton : public Button
{
private :
静态 布尔 CoerceDelay (const DependencyObject * d , const void * baseValue , void * coercedValue );
};
// DelayedButton.cpp
bool DelayedButton :: CoerceDelay (const DependencyObject * d , const void * baseValue ,
void * coercedValue )
{
const DelayedButton * btn = static_cast < const DelayedButton *> (d );
float newValue = * static_cast < const float *> (baseValue );
浮动和 强制 = * static_cast < float *> (coercedValue );
威吓 = STD :: 最大值(BTN - > GetMinimumDelay (), STD :: 分钟(NEWVALUE , BTN - > GetMaximumDelay ()));
返回 true ;
}
NS_IMPLEMENT_REFLECTION (教程:: DelayedButton )
{
DependencyData * data = NsMeta < DependencyData > (TypeOf < SelfClass > ());
数据-> RegisterProperty < float > (DelayProperty , “ Delay” ,
PropertyMetadata :: 创建(1.0f , &CoerceDelay ));
数据-> RegisterProperty < float >(MinimumDelayProperty , “ MinimumDelay” ,
PropertyMetadata :: 创建(0.0f ));
数据-> RegisterProperty < float > (MaximumDelayProperty , “ MaximumDelay” ,
PropertyMetadata :: 创建(10.0f ));
}
</pre>
但是我们还需要确保最大值大于最小值,因此我们也将强制函数应用于该属性:
<pre style="box-sizing: inherit; overflow: auto; font-family: Consolas, "Courier New", Courier, monospace; font-size: 0.9em; background: rgb(244, 244, 244); border: 1px solid rgb(230, 230, 230); color: rgb(0, 0, 0); margin: 0px; padding: 0.25em;">// DelayedButton.h
class DelayedButton : public Button
{
private :
静态 布尔 CoerceMax (const DependencyObject * d , const void * baseValue , void * coercedValue );
};
// DelayedButton.cpp
bool DelayedButton :: CoerceMax (const DependencyObject * d , const void * baseValue ,
void * coercedValue )
{
const DelayedButton * btn = static_cast < const DelayedButton *> (d );
float newValue = * static_cast < const float *> (baseValue );
浮动和 强制 = * static_cast < float *> (coercedValue );
威吓 = STD :: 最大值(BTN - > GetMinimumDelay (), NEWVALUE );
返回 true ;
}
NS_IMPLEMENT_REFLECTION (教程:: DelayedButton )
{
DependencyData * data = NsMeta < DependencyData > (TypeOf < SelfClass > ());
数据-> RegisterProperty < float > (DelayProperty , “ Delay” ,
PropertyMetadata :: 创建(1.0f , &CoerceDelay ));
数据-> RegisterProperty < float >(MinimumDelayProperty , “ MinimumDelay” ,
PropertyMetadata :: 创建(0.0f ));
数据-> RegisterProperty < float > (MaximumDelayProperty , “ MaximumDelay” ,
PropertyMetadata :: 创建(10.0f , &CoerceMax ));
}
</pre>
最后,我们必须检查最小值和最大值是否更改以更新当前延迟值:
<pre style="box-sizing: inherit; overflow: auto; font-family: Consolas, "Courier New", Courier, monospace; font-size: 0.9em; background: rgb(244, 244, 244); border: 1px solid rgb(230, 230, 230); color: rgb(0, 0, 0); margin: 0px; padding: 0.25em;">// DelayedButton.h
class DelayedButton : public Button
{
private :
静态 void OnMinChanged (DependencyObject * d , const DependencyPropertyChangedEventArgs & e );
静态 无效 OnMaxChanged (DependencyObject * d , const DependencyPropertyChangedEventArgs & e );
};
// DelayedButton.cpp
void DelayedButton :: OnMinChanged (DependencyObject * d , const DependencyPropertyChangedEventArgs & e )
{
DelayedButton * btn = static_cast < DelayedButton *> (d );
d- > CoerceValue < float > (MaximumDelayProperty );
d- > CoerceValue < float > (DelayProperty );
}
void DelayedButton :: OnMaxChanged (DependencyObject * d , const DependencyPropertyChangedEventArgs & e )
{
DelayedButton * btn = static_cast < DelayedButton *> (d );
d- > CoerceValue < float > (DelayProperty );
}
NS_IMPLEMENT_REFLECTION (DelayedButton )
{
DependencyData * data = NsMeta < DependencyData > (TypeOf < SelfClass > ());
数据-> RegisterProperty < float > (DelayProperty , “ Delay” ,
PropertyMetadata :: 创建(1.0f , &CoerceDelay ));
数据-> RegisterProperty < float > (MinimumDelayProperty , “ MinimumDelay” ,
PropertyMetadata :: 创建(0.0f , &OnMinChanged ));
数据-> RegisterProperty < float > (MaximumDelayProperty , “ MaximumDelay” ,
PropertyMetadata :: 创建(10.0f , &OnMaxChanged , &CoerceMax )));
}</pre>
网友评论