zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    dpkg 删除 百度网盘 程序
    ubuntu 安装go
    解决 swap file “*.swp”already exists!问题
    ROS Topic 常用指令
    正交概念
    vim 永久显示行号 & 临时显示行号
    awk、grep、sed
    Keil中使用Astyel进行C语言的格式化
    红黑树学习
    802.11 对于multicast 和 broadcast的处理
  • 原文地址:https://www.cnblogs.com/JustRun1983/p/2636608.html
Copyright © 2011-2022 走看看