WPF基础到企业应用系列7――深入剖析依赖属性三

八. 只读依赖属性

  我们以前在对简单属性的封装中,经常会对那些希望暴露给外界只读操作的字段封装成只读属性,同样在WPF中也提供了只读属性的概念,如一些 WPF控件的依赖属性是只读的,它们经常用于报告控件的状态和信息,像IsMouseOver等属性, 那么在这个时候对它赋值就没有意义了。 或许你也会有这样的疑问:为什么不使用一般的.Net属性提供出来呢?一般的属性也可以绑定到元素上呀?这个是由于有些地方必须要用到只读依赖属性,比如 Trigger等,同时也因为内部可能有多个提供者修改其值,所以用.Net属性就不能完成天之大任了。
那么一个只读依赖属性怎么创建呢?其实创建一个只读的依赖属性和创建一个一般的依赖属性大同小异(研究源码你会发现,其内部都是调用的同 一个Register方法)。仅仅是用DependencyProperty.RegisterReadonly替换了 DependencyProperty.DependencyProperty而已。和前面的普通依赖属性一样,它将返回一个 DependencyPropertyKey。该键值在类的内部暴露一个赋值的入口,同时只提供一个GetValue给外部,这样便可以像一般属性一样使 用了,只是不能在外部设置它的值罢了。

下面我们就用一个简单的例子来概括一下:

public partial class Window1 : Window
{ public Window1() { InitializeComponent(); //内部用SetValue的方式来设置值
DispatcherTimer timer = new DispatcherTimer(TimeSpan.FromSeconds(1),DispatcherPriority.Normal,(object sender,175);">EventArgs e)=> { int newValue = Counter == int.MaxValue ? 0 : Counter + 1; SetValue(counterKey,newValue); },Dispatcher); } //属性包装器,只提供GetValue,这里你也可以设置一个private的SetValue进行限制
public int Counter { get { return (int)GetValue(counterKey.DependencyProperty); } } //用RegisterReadOnly来代替Register来注册一个只读的依赖属性
private static readonly DependencyPropertyKey counterKey = DependencyProperty.RegisterReadOnly("Counter",typeof(int),typeof(Window1),new PropertyMetadata(0)); }


XAML中代码:

<Window x:Name="winThis" x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Read-Only Dependency Property" Height="300" Width="300">
<
Grid>
<
Viewbox>
<
TextBlock Text="{Binding ElementName=winThis,Path=Counter}" />
</
Viewbox>
</
Grid>
</
Window>

效果如下图所示:

ReadOnly_DPs

九. 附加属性

  前面我们讲了依赖属性。现在我们再继续探讨另外一种特殊的Dependency属性——附加属性。附加属性是一种特殊的依赖属性。他允许给一个对象添加一个值,而该对象可能对此值一无所知。

  最好的例子就是布局面板。每一个布局面板都需要自己特有的方式来组织它的子元素。如Canvas需要Top和left来布 局,DockPanel需要Dock来布局。当然你也可以写自己的布局面板(在上一篇文章中我们对布局进行了比较细致的探讨,如果有不清楚的朋友也可以再 回顾一下)。

下面代码中的Button 就是用了CanvasCanvas.TopCanvas.Left="20" 来进行布局定位,那么这两个就是传说中的附加属性。

<Canvas>
<
Button Canvas.Top="20" Canvas.Left="20" Content="Knights Warrior!"/>
</
Canvas>

  在最前面的小节中,我们是使用DependencyProperty.Register来注册一个依赖属性,同时依赖属性本身也对外提供了 DependencyProperty.RegisterAttached方法来注册附加属性。这个RegisterAttached的参数和 Register是完全一致的,那么Attached(附加)这个概念又从何而来呢?

  其实我们使用依赖属性,一直在Attached(附加)。我们注册(构造)一个依赖属性,然后在DependencyObject中通过 GetValue和SetValue来操作这个依赖属性,也就是把这个依赖属性通过这样的方法关联到了这个DependencyObject上,只不过是 通过封装CLR属性来达到的。那么RegisterAttached又是怎样的呢?

下面我们来看一个最简单的应用:首先我们注册(构造)一个附加属性

public class AttachedPropertyChildAdder
{ //通过使用RegisterAttached来注册一个附加属性
public static readonly DependencyProperty IsAttachedProperty = DependencyProperty.RegisterAttached("IsAttached",typeof(bool),175);">AttachedPropertyChildAdder),175);">FrameworkPropertyMetadata((bool)false)); //通过静态方法的形式暴露读的操作
public static bool GetIsAttached(DependencyObject dpo) { return (bool)dpo.GetValue(IsAttachedProperty); } //通过静态方法的形式暴露写的操作
public static void SetIsAttached(DependencyObject dpo,bool value) { dpo.SetValue(IsAttachedProperty,value); } }

在XAML中就可以使用刚才注册(构造)的附加属性了:

Attached_Properties

  在上面的例子中,AttachedPropertyChildAdder 中 并没有对IsAttached采用CLR属性形式进行封装,而是使用了静态SetIsAttached方法和GetIsAttached方法来存取 IsAttached值,当然如果你了解它内部原理,你就会看到实际上还是调用的SetValue与GetValue来进行操作(只不过拥有者不同而 已)。这里我们不继续深入下去,详细在后面的内容会揭开谜底。

十. 清除本地值

  在很多时候,由于我们的业务逻辑和UI操作比较复杂,所以一个庞大的页面会进行很多诸如动画、3D、多模板及样式的操作,这个时候页面的值已经 都被改变了,如果我们想让它返回默认值,可以用ClearValue 来清除本地值,但是遗憾的是,很多时候由于WPF依赖属性本身的设计,它往往会不尽如人意(详细就是依赖属性的优先级以及依赖属性EffectiveValueEntry 的 影响)。ClearValue 方法为在元素上设置的依赖项属性中清除任何本地应用的值提供了一个接口。但是,调用 ClearValue 并不能保证注册属性时在元数据中指定的默认值就是新的有效值。值优先级中的所有其他参与者仍然有效。只有在本地设置的值才会从优先级序列中移除。例如,如 果您对同时也由主题样式设置的属性调用 ClearValue,主题值将作为新值而不是基于元数据的默认值进行应用。如果您希望取消过程中的所有属性值,而将值设置为注册的元数据默认值,则可以 通过查询依赖项属性的元数据来最终获得默认值,然后使用该默认值在本地设置属性并调用 SetValue来实现,这里我们得感得PropertyMetadata类为我们提供了诸如DefaultValue这样的外部可访问的属性。

上面讲了这么多,现在我们就简单用一个例子来说明上面的原理(例子很直观,相信大家能很容易看懂)

XAML中代码如下:

<Window  x:Class="WpfApplication1.DPClearValue"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="400" Width="400">
<
StackPanel Name="root">
<
StackPanel.Resources>
<
Style TargetType="Button">
<
Setter Property="Height" Value="20"/>
<
Setter Property="Width" Value="250"/>
<
Setter Property="HorizontalAlignment" Value="Left"/>
</
Style>
<
Style TargetType="Ellipse">
<
Setter Property="Height" Value="50"/>
<
Setter Property="Width" Value="50"/>
<
Setter Property="Fill" Value="LightBlue"/>
</
Style TargetType="Rectangle">
<
Setter Property="Fill" Value="MediumBlue"/>
</
Style x:Key="ShapeStyle" TargetType="Shape">
<
Setter Property="Fill" Value="Azure"/>
</
Style>
</
StackPanel.Resources>
<
DockPanel Name="myDockPanel">
<
Ellipse Height="100" Width="100" Style="{StaticResource ShapeStyle}"/>
<
Rectangle Height="100" Width="100" Style="{StaticResource ShapeStyle}" />
</
DockPanel>
<
Button Name="RedButton" Click="MakeEverythingAzure" Height="39" Width="193">改变所有的值</Button>
<
Button Name="ClearButton" Click="RestoreDefaultProperties" Height="34" Width="192"> 清除本地值</Button>
</
StackPanel>
</
Window>

后台代码:

public partial class DPClearValue
{ //清除本地值,还原到默认值
void RestoreDefaultProperties(object sender,175);">RoutedEventArgs e) { UIElementCollection uic = myDockPanel.Children; foreach (Shape uie in uic) { LocalValueEnumerator locallySetProperties = uie.GetLocalValueEnumerator(); while (locallySetProperties.MoveNext()) { DependencyProperty propertyToClear = (DependencyProperty)locallySetProperties.Current.Property; if (!propertyToClear.ReadOnly) { uie.ClearValue(propertyToClear); } } } } //修改本地值
void MakeEverythingAzure(object sender,175);">Shape uie in uic) { uie.Fill = new SolidColorBrush(Colors.Azure); } } }

当按下”改变所有的值“按钮的时候,就会把之前的值都进行修改了,这个时候按下”清除本地值“就会使原来的所有默认值生效。

ClearLocallySetValues

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结