WPF高级教程(八)专题:事件

概述

除了路由事件,WPF中还有非路由事件,这个专题介绍一下WPF中所有的事件。

都有哪些事件

  • 生命周期事件,元素的初始化,加载,卸载时发生
  • 鼠标事件
  • 键盘事件
  • 手写笔事件
  • 多点触控事件

下面就逐个介绍这些事件,大家可以先做一个简单的了解,当要深入使用这些事件的时候查询这篇文章即可

生命周期事件

一个元素创建流程大致是这样:

  1. 首先需要解析Xaml,然后初始化,再渲染样式,应用布局和绑定。Xaml的解析需要调用BeginInit方法,解析完成需要调用EndInit方法。我们可以西想到,如果使用代码创建控件,则不会调用这两个方法,所以这两个方法是Xaml的解析器进行调用的。
  2. 引发Initialized方法。这时候元素已经初始化,但是还没有应用样式和绑定。
  3. 引发Loaded事件。窗口就变得可见且样式都渲染完成,也绑定完成了。如果要在初始化之后做某些操作,在Loaded事件处理程序中处理是极好的选择。

需要注意的是,元素的渲染是自下而上的,所以在初始化的时候,贸然访问其父元素是不可取的,因为其可能还没有初始化出来,而其子元素是可以被访问的。

元素的生命周期事件如下:
WPF高级教程(八)专题:事件_第1张图片
窗口的声明周期事件如下:
WPF高级教程(八)专题:事件_第2张图片

输入事件

输入事件的参数如下图:
WPF高级教程(八)专题:事件_第3张图片
InputEventArgs是在RoutedEventArgs的基础上添加了Timestamp和Device。Timestamp用于比较两个事件引发时间谁更早,数字更大的是更近发生的。Device用于指示输入设备的类型(键盘,鼠标,手写笔)

键盘事件

WPF高级教程(八)专题:事件_第4张图片
WPF高级教程(八)专题:事件_第5张图片
如果想要实现不允许用户输入某些字符,我们除了在绑定的数据层进行校验,也可以直接处理事件,PreviewTextInput比PreviewKeyDown优势的点在于这时候取出来的值不是按键信息而是输入信息,但是他的缺点是 空格键不会触发这个事件,所以,要做到按键处理,需要用以下的逻辑联合处理

private void Window_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
    short val;
    if (!Int16.TryParse(e.Text, out val))
    {
        // 不循序输入数字
        e.Handled = true;
    }
}

private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
    if (e.Key == Key.Space)
    {
        // 不允许输入空格
        // 这个按键不会引发PreviewTextInput事件
        e.Handled = true;
    }
}

如果需要获取键盘状态,比如是否多个键按下,是否打开了大写锁定开关,就需要使用KeyboardDevice属性

// 判断是否按住contrl键
// e.KeyboardDevice.Modifiers获取的是当前按下的所有键的实例,通过位运算可以计算出这个键是否被按下
if ((e.KeyboardDevice.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
{
    // 按下Control
}

KeyboardDevice对象还有下面几个方法:
WPF高级教程(八)专题:事件_第6张图片
上面的方法可以在触发键盘事件的时候判断按键是否按下,如果我们的触发不是按键,而是后台的一个事件,这时候我们就无法拿到KeyboardDevice,这时候应该怎么判断呢,答案是使用Keyboard静态类来判断

if (Keyboard.IsKeyDown(Key.LeftShift))
{
    // 按下左边Shift
}

鼠标事件

  • 鼠标进入控件是MouseEnter 鼠标离开是MouseLeave,这两个事件都不是路由事件,都是自己引发,自己处理,不进行传播,一个StackPanel包裹一个Button,鼠标移入,先触发StackPanel的移入事件,再触发按钮的移入事件,离开的时候先触发按钮的离开事件,再触发StackPanel的离开事件。
  • MouseMove和PreviewMouseMove是路由事件,可以传播,这个方法用于监控鼠标的位置,在事件处理中可以使用e.GetPosition来获取鼠标当前位置
  • 可以使用IsMouseOver判断当前鼠标是否位于一个元素或者其子元素上面
  • 可以使用IsMouseDirectlyOver判断当前鼠标是否位于一个元素而不在其子元素上面
  • 单击事件
    WPF高级教程(八)专题:事件_第7张图片
  • 使用ButtonState判断用于通知的时候鼠标是在按下还是释放状态(不常用)
  • 使用MouseButton判断哪个鼠标键引发的事件(不常用)
  • 使用Clickcount判断单机还是双击(双击为2)
  • 通常情况下,我们要点击之后响应,是对Up事件进行处理,这是Windows的做法
  • Control类拥有MouseDoubleClick来响应双击事件,Button类拥有Click事件,这些都是更高级的鼠标事件
  • 鼠标事件与键盘事件一样,也可以使用静态类在事件处理程序之外获取鼠标的状态,这个静态类是Mouse类
  • 鼠标捕获是一种不太常用的技术,我们之后会举个例子,简单说就是一个元素可以捕获鼠标事件,屏蔽其他元素,使得只有自己接受和处理鼠标元素,这种技术主要用于短时间的操作,比如拖放。
    // 使得element1捕获鼠标
    Mouse.Capture(element1);
    // 解除捕获
    Mouse.Capture(null);
    // 很多事件会导致元素失去捕获,比如弹出新的窗口,element在丢失鼠标捕获的时候会触发LostMouseCapture方法
    

特殊1:焦点控制

  • Focusable属性控制这个元素是否能够获取焦点,这个属性是在UIElement中定义的,但是Contrl类的默认值是true,其他的默认值为false,比如StackPanel的默认值是false
  • WPF的层级结构拥有默认的Tab切换顺序
  • 可以设置TabIndex控制tab顺序,顺序是0-1-2-3
  • TabIndex默认都是最大值,所以设置开始TabIndex需要设置为0
  • 不想让Tab获取焦点可以设置IsTabStop=“False”,这个只是针对使用Tab键,使用代码仍然可以使其获取焦点,如果完全不允许其获取焦点,可以设置Focusable为False

特殊2:鼠标拖放

  • 拖放的相关事件集中到System.Windows.DragDrop中
  • TextBox原生支持拖放,可以拖放word中的文字到TextBox中
  • 不支持拖放的控件想要拖放实现逻辑,必须下面几步
    // 不支持拖放的控件想要支持拖放需要下面的步骤
    // 1. 在源控件的MouseDown事件中调用DoDragDrop表明要对源做什么(要把源里面的什么数据做什么操作)
    private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e)
    {
        DragDrop.DoDragDrop(lbl_source, lbl_source.Text, DragDropEffects.Copy);
    }
    // 2. 把目标控件的AllowDrop设置为True
    // 3. 设置目标控件的Drop事件
    
    // 4. 处理目标的DragEnter事件,用来过滤不合法的数据
    private void TextBox_DragEnter(object sender, DragEventArgs e)
    {
        if (e.Data.GetDataPresent(DataFormats.Text))
            e.Effects = DragDropEffects.Copy;
        else
            e.Effects = DragDropEffects.None;
    }
    // 5. 处理目标的Drag事件
    private void TextBox_Drop(object sender, DragEventArgs e)
    {
        ((TextBox)sender).Text = (string)e.Data.GetData(DataFormats.Text);
    }
    
  • 通过上面的例子我们发现,可以通过拖放交换任意类型的对象而不仅限于string
  • 可以通过拖放在不同应用程序之间交换数据,需要交换的数据类型可以是基本数据类型,int,string等,也可以是实现了ISerializable接口或者IDataObject接口的对象,这种对象可以在其他应用程序中重新构造。对于WPF对象,可以使用XamlReader将其转换为XAML,在其他应用程序中重新构造为WPF对象。
  • 在不同程序中传递数据需要检测一下Clipboard类,系统剪切板中也可以复制各种类型的数据。

特殊3:多点触控(这一个功能只介绍,不举例说明,用到的时候再着重研究)

  • 多点触控在不同的层级上表现的事件也不尽相同,在底层,多点触控就是一系列MouseDown和MouseUp的叠加,而这些操作经过组合,就变成了抽象的操作,更高级的手势(比如,旋转,放大),而对于元素内部,也有一些更高级的抽象,比如拖动条等,对于WPF来说,我们可以在各个层级上对这些事件进行处理。
  • 原始触控事件
    WPF高级教程(八)专题:事件_第8张图片
    原始触控事件提供了TouchEventArgs,可以使用GetTouchPoint来获取触控发生的时候的屏幕左边。另一个参数TouchDevice,这个参数提供了触摸的设备ID,需要注意的是,多点触控的时候,每个手指都是一个独立的ID
  • 高级手势操作。使用高级手势操作需要将元素的IsManipulationEnabled设置为True。能够接收的事件为 ManipulationStarting(开始触摸操作)ManipulationStarted,ManipulationDelta(正在触摸操作,这个事件将被不断触发一直到触摸结束,参数为触摸过程中变换的具体参数),ManipulationCompleted(触摸操作完成)。
  • 触摸操作一些对象,可能会过于生硬,这时候我们可以给触摸对象加上惯性,在释放的时候缓慢减速,在碰到边界的时候弹回,这些操作需要我们处理 ManipulationInertiaStarting事件。

一些技巧

  • 初始化控件的数据推荐在Loaded事件中进行
  • 尽量不要在窗口的构造函数中初始化数据,虽然这样也有效果,但是如果一旦出现异常,则该异常会由Xaml解析器抛出,抛出XamlParseException,不好调试和跟踪错误。
  • 原生控件有可能会挂起一些事件,引发自己特有的事件,例如TextBox挂起了TextInput事件,但是他自己可以引发TextChanged事件。
  • 如果一直按住shift+A不动,会打出一串A,这时候事件的调用是A键一个KeyDown而Shift键一系列KeyDown,这时候可以判断KeyEventArgs.IsRepeat确定是不是一直按住案件触发的结果。

你可能感兴趣的:(WPF,C#)