四,路由事件

  1. 定义、注册和包装路由事件
    WPF事件模型和属性模型非常类似,和依赖属性一样,路由属性由只读的静态字段表示,在一个静态构造函数中注册,并且通过一个标准的.Net事件定义进行包装。例如,WPF的Button类提供了熟悉的Click事件,该事件继承自ButtonBase基类。
    public abstract class ButtonBase : ContentControl
    {
        public static readonly RoutedEvent ClickEvent;
    
        static ButtonBase()
        {
            ButtonBase.ClickEvent = EventManager.RegisterRoutedEvent("Click", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ButtonBase));
        }
    
        public event RoutedEventHandler Click
        {
            add
            {
                base.AddHandler(ButtonBase.ClickEvent, value);
            }
            remove
            {
                base.RemoveHandler(ButtonBase.ClickEvent, value);
            }
        }
    }
  2. 共享路由事件
    与依赖属性一样,可以在类之间共享路由事件的定义。例如,两个基类:UIElement类(该类的所有普通WPF元素的起点)和ContentElement类(该类是所有内容元素的起点),都使用了MouseUp事件。MouseUp事件是由System.Windows.Input.Mouse类定义的。UIElement类和ContentElement类只是通过RoutedEvent.AddOwner()方法重用MouseUp事件。
    UIElement.MouseUpEvent = Mouse.MouseUpEvent.AddOwner(typeof(UIElement));
  3. 引发路由事件
    路由事件不是通过传统的.Net事件包装器引发的,而是使用RaiseEvent()方法引发事件,所有元素都继承了该方法,下面是来自ButtonBase类深层的代码 :
    RoutedEventArgs e = new RoutedEventArgs(ButtonBase.MouseUpEvent, this);
    base.RaiseEvent(e);
  4. 使用多种方式为路由事件关联事件处理程序:
    1)  为XAML标记添加事件特性,如:<Image Source="happyface.jpg" Stretch="None" MouseUp="img_MouseUp" />
    2)  使用代码:img.MouseUp += new MouseButtonEventHandler(img_MouseUp);
    3)  通过UIElement.AddHandler()方法直接连接事件:img.AddHandler(Image.MouseUpEvent,new MouseButtonEventHandler(img_MouseUp));
  5. 事件路由
    WPF路由事件模型提供的路由事件的三种路由策略:
    1)  直接路由事件(Direct Event):它们起源于一个元素,并且不传递给其它元素,如MouseEnter事件是一个直接路由事件。
    2)  冒泡路由事件(Bubbling Event):如MouseDown事件是一个冒泡路由事件。它首先由被单击的元素引发,再被该元素的父元素引发,再被父元素的父元素引发,直接元素树的项部。
    3)  隧道路由事件(Tunneling Event):在包含层次中向下传递。
    四,路由事件_第1张图片
  6. 处理挂起事件的方法
    使用AddHandler()方法提供的一个重载版本,该重载版本可以接收一个Boolean值作为它的第三个参数,如果将该参数设置为true,哪么即使设置了Handled标志,也将接收到事件。通常这不是一个正确的设计决策,为了防止可能会造成的困惑,按钮被设计为会挂起MouseUp事件,因为如果为一个按钮错误地处理了MouseUp事件,而没有处理Click事件,那么事件处理代码就只能对鼠标单击做出响应,而不能对相应的键盘操作做出响应。
  7. 附加事件
    假设在StackPanel面板中包装了一堆按钮,并且希望在一个事件处理程序中处理所有这些按钮的单击事件,粗略的方法是将每个按钮的Click事件关联到同一个事件处理程序,但是Click事件支持事件冒泡,从而提供了一种更好的选择,可以通过以“类名.事件名”的形式把按钮的Click事件附加到更高层次的元素上(如包含按钮的StackPanel面板,它没有定义Click事件)来处理所有按钮的单击事件。如:
    <StackPanel Button.Click="DoSomething" Margin="5">
        <Button Name="btn1" Content="Command 1"></Button>
        <Button Name="btn2" Content="Command 2"></Button>
        <Button Name="btn3" Content="Command 3"></Button>
    </StackPanel>

    Click事件实际上是在ButtonBase类中定义的,且Button类继承了该事件,如果为ButtonBase.Click事件关联事件处理程序,那么当任何继承自ButtonBase的控件(包括Button类、RadioButton类以及CheckBox类)被单击时,都会调用该事件处理程序,如果只为Button事件关联事件处理程序,那么事件处理程序就只能被Button对象使用。
    可以在代码中关联附加事件,但是需要使用UIElement.AddHandler()方法,而不能使用+=运算符语法:

    StackPanelTest.AddHandler(Button.ClickEvent, new RoutedEventHandler(DoSomething));
  8. 隧道路由事件
    隧道路由事件的工作方式和冒泡路由事件相同,但方向相反,很容易识别隧道路由事件,因为它们都以单词Preview开头,而且,WPF通常成对地定义冒泡路由事件和隧道路由事件。这意味着如果发现一个冒泡的MouseUp事件,就还可以找到一个PreviewMouseUp隧道事件,隧道路由事件总是在冒泡路由事件之前触发。如果将隧道路由事件标记为已处理过,那么冒泡路由事件就不会发生,这是因为两个事件共享同一个RoutedEventArgs类的实例。

  9. 生命周期事件
    四,路由事件_第2张图片
    FrameworkElement类实现了ISupportInitialize接口,该接口提供了两个用于控制初始化过程的方法,第一个是BiginInit()方法,在实例化元素后会立即调用该方法,BeginInit()方法被调用之后,XAML解析器设置所有元素的属性并添加内容。第二个是EndInit()方法,当初始化完成后,该方法被调用,此时引发Initialized事件。
    当创建窗口时,每个元素都以自下而上的方式被初始化,这意味着位于深层的嵌套元素在它们的容器之前先被初始化。当引发初始化事件时,可以确保元素树中当前元素以下的元素已经全部完成了初始化。但是包含当前元素的元素可能还没有初始化,并且不能假设窗口的任何其它部分已经初始化。在每个元素都已经初始化之后,还需要在它们的容器中进行布局、应用样式、绑定数据源。一旦初始化过程完成,就会引发Loaded事件,Loaded事件和Initialized事件发生的过程相反,也就是包含所有元素的窗口首先引发Loaded事件,然后才是更深层次的嵌套元素,当为所有元素都引发了Loaded事件之后,窗口就变的可见了,并且元素都已被呈现。
    四,路由事件_第3张图片

  10. 输入事件
    输入事件是当用户使用某些种类的外设硬件进行交互时发生的事件,例如鼠标、键盘、手写笔或多点触摸屏。输入事件可以通过继承自InputEventArgs的自定义事件参数类传递额外的信息。InputEventArgs增加了两个属性:Timestamp和Device。Timestamp属性提供了一个指示事件何时发生的毫秒数。Device属性返回一个对象,该对象提供与触发事件的设备相关的更多信息。设备可以是鼠标、键盘、手写笔或多点触摸屏,它们由各自不同的类表示,所有这些类都继承自抽象类 System.Windows.Input.InputDevice
    四,路由事件_第4张图片
    当持续按下一个键的时候,会重复引发按键事件,此时KeyEventArgs.IsRepeat属性的值为true。
    可以使用KeyConverter类将Key值转换为更有用的字符串,例如:使用KeyConverter.ConverterToString()将Key.D9和Key.NumPad9都转换成字符串“9”。
    在进行文本框验证时最好的方法是同时处理PreviewTextInput事件(负责处理大多数验证)和PreviewKeyDown事件(用于哪些在文本框中不会引发PreviewTextInput的按键,如空格键),如:

    private void textBlock1_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        short val;
        if (!Int16.TryParse(e.Text, out val))
        {
            e.Handled = true;
        }
    }
    
    private void textBlock1_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Space)
        {
            e.Handled = true;
        }
    }
    View Code
  11. 焦点
    为了让一个控件能够接受焦点,必须将Focusable属性设置为true,此属性是在UIElement类中定义的。
    为了将焦点从一个元素移动到另一个元素,用户可以单击鼠标或使用Tab键和箭头键,在以前的开发框架中,强制编程人员确保Tab键以合理方式移动焦点(通常是从左到右,从上到下),并且确保在窗口第一次显示时正确的控件获得焦点。在WPF中,而些额外的工作几乎不再需要,因为WPF使用层次结构的元素布局实现了Tab键切换焦点的顺序。当按下Tab键时会将焦点移动到当前元素的第一个子元素,如果当前元素没有子元素,会将焦点移动到同级的下一个子元素。例如:如果在具有两个StackPanel容器的窗口中使用Tab键转移焦点,焦点首先会通过第一个StackPanel面板中的所有控件,然后通过第二个容器中的所有控件。
    我们还可以通过TabIndex(TabIndex属性是在Control类中定义的,默认所有控件的TabIndex属性都被设置为Int32.MaxValue)控制Tab焦点转移的顺序,TabIndex为0的元素首先获得焦点,然后是1,2,3…。如果多个元素具有相同的的TabIndex值,WPF将使用自动顺序。
    可以通过将IsTabStop属性(此属性也是在Control类中定义)设置为false来阻止一个控件被包围进Tab键的焦点顺序。

  12. 获取键盘状态
    KeyEventArgs.KeyStates属性:反映触发事件的键的属性。
    KeyEventArgs.KeyboardDevice属性:它提供了KeyboardDivice类的一个实例,它的属性包括当前是哪个元素具有焦点(FocusedElement)以及当事件发生时按下了哪些修饰键,它还提供了同个简便的方法,对于这些方法,需要传递一个Key枚举值:
    四,路由事件_第5张图片
    可以使用KeyBoard类在非键盘事件中获取键盘状态信息,它与KeyboardDevice类非常类似,只是KeyBoard类由静态成员构成。

  13. 鼠标输入
    UIElement类定义了两个用于鼠标命中测试的属性:IsMouseOver(确定当前鼠标是否位于一个元素及其子元素的上面),IsMouseDirectlyOver(检查鼠标是否位于一个元素上面但是没有位于其子元素上面)。

  14. 多点触控输入
    多点触控(Multitouch)

你可能感兴趣的:(事件)