WPF学习(2) – 逻辑树和可视树、依赖属性、附加属性、路由事件、命令

1. 逻辑树和可视树

XAML天生就是用来呈现用户界面的,这是由于它具有层次化的特性。在WPF中,用户界面由一个对象树构建而成,这棵树叫作逻辑树

逻辑树的概念很直观,但是为什么要关注它呢?因为几乎WPF的每一方面(属性、事件、资源等)都有与逻辑树相关联的行为。例如,属性值有时会沿着树自动传递给子元素,而触发的事件可以自底向上或自顶向下遍历树.

可视树基本上是逻辑树的扩展,在可视树中,节点都被打散,分放到核心可视组件中。可视树提供了一些详细的可视化实现,而不是把每个元素当作一个“黑盒”。

 逻辑树是静态的,不会受到程序员的干扰(例如动态添加/删除元素),但只要用户切换不同的Windows主题,可视树就会改变。

 虽然在Window的构造函数中就可以遍历逻辑树,但可视树直到Window完成至少一次布局之后才会有节点,否则是空的. 要访问可视树需要在OnContentRendered,因为OnContentRendered是在布局完成之后才被调用的

2. 依赖属性

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) { ? } 
    ? 
}

依赖属性实现:

  • 必须是DependencyProperty类型
  • 必须是Public、Static, 并且有一个Property作为后缀
  • 调用DependencyProperty.Register静态方法创建的,这样的方法需要一个名称(依赖属性名称)、一个属性类型以及拥有这个属性的类
  • 通过不同的Register方法重载,你可以传入metadata(元数据)来告诉WPF如何处理该属性、如何处理属性值改变的回调、如何处理强制值转换,以及如何验证值
  • System.Windows.DependencyObject是底层基类,这是拥有依赖属性的类必须继承的。

注意 在运行时,绕过了.NET属性包装器在XAML中设置依赖属性。虽然XAML编译器在编译时是依靠该属性包装器的,但在运行时WPF是直接调用GetValue和SetValue的!因此,为了让使用XAML设置属性与使用过程式代码设置属性保持一致,在属性包装器中除了GetValue/SetValue调用以外,不应该包含任何其他逻辑,这是至关重要的。如果需要添加自定义逻辑,应该在注册的回调函数中添加。所有WPF的内建属性包装器都应遵守这个规则,因此这个警告是针对那些打算写带有依赖属性的自定义类的人的。

依赖属性的功能特征:

A) 变更通知(callback)

无论何时,只要依赖属性的值改变了,WPF就会自动根据属性的元数据(metadata)触发一系列动作。这些动作可以重新呈现适当的元素、更新当前布局、刷新数据绑定等。内建的变更通知最有趣的特性之一是属性触发器,它可以在属性值改变时执行自定义动作,而不用更改任何过程式代码

B) 属性值继承

术语“属性值继承”(简称属性继承)并不是指传统的面向对象的类继承,而是指属性值自顶向下沿着元素树传递(类似于CSS样式的影响)

属性值的继承行为是由以下两种因素决定的:

  • 并不是每个依赖属性都参与属性值继承的。(从其内部来讲,依赖属性会通过传递FrameworkPropertyMetadataOptions.Inherits给DependencyProperty.Register方法来完成继承。)
  • 有其他一些优先级更高的源来设置这些属性值

C) 对多个提供程序的支持

WPF有许多强大的机制可以独立地去尝试设置依赖属性的值。如果没有设计良好的机制来处理这些完全不同的属性值提供程序,这个系统会变得混乱,属性值会变得不稳定。当然,正如它们的名字所表达的,依赖属性就是设计为以一致的、有序的方式依靠这些提供程序

(意思是对属性的影响存在于多个方面)

image

  • 判断基础值

    下面的代码清单显示了8个提供程序,它们可以设置大多数依赖属性的值,优先级顺序从高到低为:

    本地值、样式触发器、模板触发器、样式设置程序、主题样式触发器、主题样式设置程序、属性值继承、默认值

  • 计算

    如果第一步中的值是表达式(派生自System.Windows.Expression的一个对象),那么WPF会执行一种特殊的演算步骤——把表达式转换为具体的结果

  • 应用动画

    如果一个或者多个动画在运行,它们有能力改变当前的属性值(使用第二步计算出来的值作为输入)或者完全替代当前的属性值。因此,动画(第13章的话题)胜过其他任何属性值提供程序——就连本地值也不是它的“对手”!

  • 限制

    在所有属性值提供程序处理过之后,WPF将拿到一个几乎是终值的属性值,如果依赖属性已经注册了CoerceValueCallback,还会把这个属性值传递给CoerceValueCallback委托。在委托中,可能会对值进行限制和处理。

  • 验证

   最后,如果依赖属性已经注册了ValidateValueCallback,之前的限制中的值将被传入ValidateValueCallback委托。如果输入值有效,该回调函数必须返回true;否则就返回false。返回false将会导致抛出一个异常,并使整个流程被取消。

3. 附加属性

附加属性是依赖属性的一种特殊形式,可以被有效地添加到任何对象中。(有些类似于C#中的扩展方法)

<StackPanel TextElement.FontSize="30" TextElement.FontStyle="Italic" 
      Orientation="Horizontal" HorizontalAlignment="Center"> 
      <Button MinWidth="75" Margin="10">Help</Button> 
      <Button MinWidth="75" Margin="10">OK</Button> 
</StackPanel>

等价的C#代码

StackPanel panel = new StackPanel();
TextElement.SetFontSize(panel, 30);
TextElement.SetFontStyle(panel, FontStyle.Italic);

StackPanel中没有FontSize属性和FontStyle属性, 但是可以设置TextElement的附加属性。

附加属性设置了当前空间的附加属性的值, 如TextElement.FontSize, 它本身不会对当前元素产生影响。

但是会对当前元素的子元素产生影响。因为子元素的对应属性,如FontSize, 会从父元素设置的TextElemetn.FontSize中读取。

4. 路由事件

路由事件是专门设计用于在元素树中使用的事件。当路由事件触发后,它可以向上或向下遍历可视树和逻辑树,用一种简单而且持久的方式在每个元素上触发,而不需要使用任何定制代码。

例如,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枚举值提供。

  • Tunneling(管道传递)——事件首先在根元素上被触发,然后从每一个元素向下沿着树传递,直到到达源元素为止(或者直到处理程序把事件标记为已处理为止)。
  • Bubbling(冒泡)——事件首先在源元素上被触发,然后从每一个元素向上沿着树传递,直到到达根元素为止(或者直到处理程序把事件标记为已处理为止)。
  • Direct(直接)——事件仅在源元素上触发。这与普通.NET事件的行为相同,不同的是这样的事件仍然会参与一些路由事件的特定机制,如事件触发器。

路由事件处理程序

路由事件的事件处理程序有一个签名,它与通用.NET事件处理程序的模式匹配:第一个参数是一个System.Object对象,名为sender,第二个参数(一般命名为e)是一个派生自System.EventArgs的类。传递给事件处理程序的sender参数就是该处理程序被添加到的元素。参数e是RoutedEventArgs的一个实例(或者派生自RoutedEventArgs),RoutedEventArgs是EventArgs的一个子类,它提供了4个有用的属性:

  • Source——逻辑树中一开始触发该事件的元素。
  • OriginalSource——可视树中一开始触发该事件的元素(例如,TextBlock或者标准Button元素的ButtonChrome子元素)。
  • Handled——布尔值,设置为true表示标记事件为已处理,这就是用于停止Tunneling或Bubbling的标记。
  • RoutedEvent——真正的路由事件对象(如Button.ClickEvent),当一个事件处理程序同时被用于多个路由事件时,它可以有效地识别被触发的事件。

C) 附加事件

类似于附加属性:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
x:Class="AboutDialog" ListBox.SelectionChanged="ListBox_SelectionChanged" 
Button.Click="Button_Click" 
Title="About WPF Unleashed" SizeToContent="WidthAndHeight" 
Background="OrangeRed">
</Window>

5. 命令

WPF提供了内建的命令支持,这是一个更为抽象且松耦合的事件版本。尽管事件是与某个用户动作(如点击一个Button或者选中一个ListBoxItem)相关联的,但命令表示的是那些与用户界面分离的动作。

大多数命令的能力来自于下面3种特性:

  • WPF定义了许多内建命令。
  • 命令自动支持输入手势(input gesture),如键盘快捷方式。
  • 有些WPF控件有一些与不同命令关联的内建行为。

A) 内建命令

命令是任何一个实现了ICommand接口(位于System.Windows.Input命名空间)的对象,每个对象定义了3个简单的成员:

  • Execute——执行特定命令的逻辑的方法。
  • CanExecute——如果命令允许被执行,则该方法返回true;如果不允许执行,则返回false。
  • CanExecuteChanged——无论何时,只要CanExecute的值改变,该事件就会触发

像Button、CheckBox和MenuItem这样的控件有相关的逻辑会与任何命令做交互。它们会有一个简单的Command属性(类型为ICommand),当设置了Command属性后,无论何时Click事件触发,这些控件会自动调用命令的Execute方法(只要CanExecute返回true时)。另外,它们会自动保持IsEnabled的值与CanExecute的值同步,这是通过CanExecuteChanged事件实现的

更加幸运的是,WPF甚至已经定义了一系列命令:

  • ApplicationCommands——Close、Copy、Cut、Delete、Find、Help、New、Open、Paste、Print、PrintPreview、Properties、Redo、Replace、Save、SaveAs、SelectAll、Stop、Undo等。
  • ComponentCommands——MoveDown 、MoveLeft 、MoveRight 、MoveUp 、ScrollByLine 、ScrollPageDown、ScrollPageLeft、ScrollPageRight、ScrollPageUp、SelectToEnd、SelectToHome、SelectToPageDown、SelectToPageUp等。
  • MediaCommands——ChannelDown 、ChannelUp 、DecreaseVolume 、FastForward 、IncreaseVolume、MuteVolume、NextTrack、Pause、Play、PreviousTrack、Record、Rewind、Select、Stop等。
  • NavigationCommands——BrowseBack 、BrowseForward 、BrowseHome 、BrowseStop 、Favorites、FirstPage、GoToPage、LastPage、NextPage、PreviousPage、Refresh、Search、Zoom等。
  • EditingCommands——AlignCenter、AlignJustify、AlignLeft、AlignRight、Correct-SpellingError、DecreaseFontSize、DecreaseIndentation、EnterLineBreak、EnterParagraphBreak、IgnoreSpellingError、IncreaseFontSize、IncreaseIndentation、MoveDown-
    ByLine、MoveDownByPage、MoveDownByParagraph、MoveLeftByCharacter、MoveLeftByWord、MoveRightByCharacter、MoveRightByWord等。

上面的每个属性并不会返回实现ICommand的独特类型,相反,它们都是RoutedUICommand的实例。RoutedUICommand类不仅实现了ICommand接口,还可以像路由事件一样支持冒泡。

这些命令还有一个默认的Text属性,可以用来显示到控件上。

这些命令还对于这默认的键盘快捷操作,不如F1对应于Help命令。

这些命令,只有执行内容,CanExecute和CanExecuteChanged还需要另外指定。

使用的一个例子, 把help button的

helpButtion.Command = ApplicationCommands.Help

原文地址:https://www.cnblogs.com/JustRun1983/p/2636608.html