本文共 7474 字,大约阅读时间需要 24 分钟。
XAML天生就是用来呈现用户界面的,这是由于它具有层次化的特性。在WPF中,用户界面由一个对象树构建而成,这棵树叫作逻辑树
逻辑树的概念很直观,但是为什么要关注它呢?因为几乎WPF的每一方面(属性、事件、资源等)都有与逻辑树相关联的行为。例如,属性值有时会沿着树自动传递给子元素,而触发的事件可以自底向上或自顶向下遍历树.
可视树基本上是逻辑树的扩展,在可视树中,节点都被打散,分放到核心可视组件中。可视树提供了一些详细的可视化实现,而不是把每个元素当作一个“黑盒”。
逻辑树是静态的,不会受到程序员的干扰(例如动态添加/删除元素),但只要用户切换不同的Windows主题,可视树就会改变。
虽然在Window的构造函数中就可以遍历逻辑树,但可视树直到Window完成至少一次布局之后才会有节点,否则是空的. 要访问可视树需要在OnContentRendered,因为OnContentRendered是在布局完成之后才被调用的
WPF引入了一个新的属性类型叫作依赖属性,整个WPF平台中都会使用到它,用来实现样式化、自动数据绑定、动画等。
依赖属性在任何时刻都是依靠多个提供程序来判断它的值的。这些提供程序可以是一段一直在改变值的动画,或者一个父元素的属性值从上慢慢传递给子元素等。依赖属性的最大特征是其内建的传递变更通知(change notification)的能力。
下面是WPF中依赖属性的实现
public class Button : ButtonBase { // The dependency property public static readonly DependencyProperty IsDefaultProperty; static Button() { // Register the property Button.IsDefaultProperty = DependencyProperty.Register("IsDefault", typeof(bool), typeof(Button), new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsDefaultChanged))); ? } // A .NET property wrapper (optional) public bool IsDefault { get { return (bool)GetValue(Button.IsDefaultProperty); } set { SetValue(Button.IsDefaultProperty, value); } } // A property changed callback (optional) private static void OnIsDefaultChanged( DependencyObject o, DependencyPropertyChangedEventArgs e) { ? } ? }
依赖属性实现:
注意 在运行时,绕过了.NET属性包装器在XAML中设置依赖属性。虽然XAML编译器在编译时是依靠该属性包装器的,但在运行时WPF是直接调用GetValue和SetValue的!因此,为了让使用XAML设置属性与使用过程式代码设置属性保持一致,在属性包装器中除了GetValue/SetValue调用以外,不应该包含任何其他逻辑,这是至关重要的。如果需要添加自定义逻辑,应该在注册的回调函数中添加。所有WPF的内建属性包装器都应遵守这个规则,因此这个警告是针对那些打算写带有依赖属性的自定义类的人的。
依赖属性的功能特征:
A) 变更通知(callback)
无论何时,只要依赖属性的值改变了,WPF就会自动根据属性的元数据(metadata)触发一系列动作。这些动作可以重新呈现适当的元素、更新当前布局、刷新数据绑定等。内建的变更通知最有趣的特性之一是属性触发器,它可以在属性值改变时执行自定义动作,而不用更改任何过程式代码
B) 属性值继承
术语“属性值继承”(简称属性继承)并不是指传统的面向对象的类继承,而是指属性值自顶向下沿着元素树传递(类似于CSS样式的影响)
属性值的继承行为是由以下两种因素决定的:
C) 对多个提供程序的支持
WPF有许多强大的机制可以独立地去尝试设置依赖属性的值。如果没有设计良好的机制来处理这些完全不同的属性值提供程序,这个系统会变得混乱,属性值会变得不稳定。当然,正如它们的名字所表达的,依赖属性就是设计为以一致的、有序的方式依靠这些提供程序
(意思是对属性的影响存在于多个方面)
下面的代码清单显示了8个提供程序,它们可以设置大多数依赖属性的值,优先级顺序从高到低为:
本地值、样式触发器、模板触发器、样式设置程序、主题样式触发器、主题样式设置程序、属性值继承、默认值
如果第一步中的值是表达式(派生自System.Windows.Expression的一个对象),那么WPF会执行一种特殊的演算步骤——把表达式转换为具体的结果
如果一个或者多个动画在运行,它们有能力改变当前的属性值(使用第二步计算出来的值作为输入)或者完全替代当前的属性值。因此,动画(第13章的话题)胜过其他任何属性值提供程序——就连本地值也不是它的“对手”!
在所有属性值提供程序处理过之后,WPF将拿到一个几乎是终值的属性值,如果依赖属性已经注册了CoerceValueCallback,还会把这个属性值传递给CoerceValueCallback委托。在委托中,可能会对值进行限制和处理。
最后,如果依赖属性已经注册了ValidateValueCallback,之前的限制中的值将被传入ValidateValueCallback委托。如果输入值有效,该回调函数必须返回true;否则就返回false。返回false将会导致抛出一个异常,并使整个流程被取消。
附加属性是依赖属性的一种特殊形式,可以被有效地添加到任何对象中。(有些类似于C#中的扩展方法)
等价的C#代码
StackPanel panel = new StackPanel();TextElement.SetFontSize(panel, 30);TextElement.SetFontStyle(panel, FontStyle.Italic);
StackPanel中没有FontSize属性和FontStyle属性, 但是可以设置TextElement的附加属性。
附加属性设置了当前空间的附加属性的值, 如TextElement.FontSize, 它本身不会对当前元素产生影响。
但是会对当前元素的子元素产生影响。因为子元素的对应属性,如FontSize, 会从父元素设置的TextElemetn.FontSize中读取。
路由事件是专门设计用于在元素树中使用的事件。当路由事件触发后,它可以向上或向下遍历可视树和逻辑树,用一种简单而且持久的方式在每个元素上触发,而不需要使用任何定制代码。
例如,Button有一个Click事件,这是基于底层的MouseLeftButtonDown事件或者KeyDown事件实现的。当用户的鼠标指针位于标准按钮之上,且按下鼠标左键的时候,它们实际上是与ButtonChrome或者TextBlock可视子元素在交互。由于事件遍历了可视树,所以Button元素最终会发现这个事件,并处理该事件。
A) 路由事件的实现
路由事件也是由公共的静态RoutedEvent成员加上一个约定的Event后缀名构成的
路由事件的注册很像在静态构建器中注册依赖属性,它会定义一个普通的.NET事件或者一个事件包装器(event wrapper),这样可以保证在过程式代码中使用起来更加熟悉,并且可以在XAML中用事件特性语法(event attribute syntax)添加一个事件处理程序。与属性包装器一样,事件包装器在访问器中只能调用AddHandler和RemoveHandler,而不应该做其他事情。
public class Button : ButtonBase { // The routed event public static readonly RoutedEvent ClickEvent; static Button() { // Register the event Button.ClickEvent = EventManager.RegisterRoutedEvent("Click", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Button)); ? } // A .NET event wrapper (optional) public event RoutedEventHandler Click { add { AddHandler(Button.ClickEvent, value); } remove { RemoveHandler(Button.ClickEvent, value); } } protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { ? // Raise the event RaiseEvent(new RoutedEventArgs(Button.ClickEvent, this)); ? } ? }
B) 路由策略和事件处理程序
当注册完毕后,每个路由事件将选择3个路由策略中的一个。所谓路由策略就是事件触发遍历整棵元素树的方式,这些策略由RoutingStrategy枚举值提供。
路由事件处理程序
路由事件的事件处理程序有一个签名,它与通用.NET事件处理程序的模式匹配:第一个参数是一个System.Object对象,名为sender,第二个参数(一般命名为e)是一个派生自System.EventArgs的类。传递给事件处理程序的sender参数就是该处理程序被添加到的元素。参数e是RoutedEventArgs的一个实例(或者派生自RoutedEventArgs),RoutedEventArgs是EventArgs的一个子类,它提供了4个有用的属性:
C) 附加事件
类似于附加属性:
WPF提供了内建的命令支持,这是一个更为抽象且松耦合的事件版本。尽管事件是与某个用户动作(如点击一个Button或者选中一个ListBoxItem)相关联的,但命令表示的是那些与用户界面分离的动作。
大多数命令的能力来自于下面3种特性:
A) 内建命令
命令是任何一个实现了ICommand接口(位于System.Windows.Input命名空间)的对象,每个对象定义了3个简单的成员:
像Button、CheckBox和MenuItem这样的控件有相关的逻辑会与任何命令做交互。它们会有一个简单的Command属性(类型为ICommand),当设置了Command属性后,无论何时Click事件触发,这些控件会自动调用命令的Execute方法(只要CanExecute返回true时)。另外,它们会自动保持IsEnabled的值与CanExecute的值同步,这是通过CanExecuteChanged事件实现的
更加幸运的是,WPF甚至已经定义了一系列命令:
上面的每个属性并不会返回实现ICommand的独特类型,相反,它们都是RoutedUICommand的实例。RoutedUICommand类不仅实现了ICommand接口,还可以像路由事件一样支持冒泡。
这些命令还有一个默认的Text属性,可以用来显示到控件上。
这些命令还对于这默认的键盘快捷操作,不如F1对应于Help命令。
这些命令,只有执行内容,CanExecute和CanExecuteChanged还需要另外指定。
使用的一个例子, 把help button的
helpButtion.Command = ApplicationCommands.Help
本文转自JustRun博客园博客,原文链接:http://www.cnblogs.com/JustRun1983/archive/2012/08/13/2636608.html,如需转载请自行联系原作者