WPF路由事件

1.1、注册路由事件

  在WPF中,注册路由事件是通过EventManager.RegisterRoutEvent()方法注册的。当注册一个事件时,需要指定事件的名称、路由类型、定义事件处理程序语法的委托、以及拥有该事件的类。通常,路由事件通过普通的.NET事件进行包装,从而使所有.net语法都能够访问他们事件包装器都可以使用AddHandler()和RemoveHandler()方法进行添加和删除已注册的调用程序,这两个方法都是在FrameworkElement基类中定义的,并被每一个WPF元素继承。

1.2、共享路由事件

  在WPF中也可以共享路由事件。例如,UIElement类(该类是所有普通WPF元素的起点)和ContentElement类(所有内容元素的起点),都是用了MouseUp事件。MouseUp事件是由System.Windows.Input.Mouse类定义的。UIElement类和ContentElement类只是通过RouteEvent.AddOwner()方法重用MouseUp事件。UIElement.MouseUp=Mouse.MouseUpEvent.AddOwner(typeof(UIElement));

1.3、引发路由事件

  在WPF中事件不是通过传统的.NET事件包装器引发的。而是使用RaiseEvent()方法引发事件,所有元素都从UIElement类继承了该方法。下面是ButtonBase类深层的代码

RoutedEventArgs e =new RoutedEventArgs(ButtonBase.ClickEvent,this);
Base.RaiseEvent(e);
View Code

 RaiseEvent()方法负责为每一个已经通过AddHndler()方法注册的调用程序引发事件。因为AddHandler()方法是公有的,所以调用程序可以访问该方法——他们能够通过直接调用AddHandler()方法注册它们自己,或者也可以使用事件包装器。不管使哪一种方法,当调用RaiseEvent()方法时都会通知它们。

  所有WPF事件都为事件签名使用熟悉的.NET约定。每一个事件处理程序的第一个参数(sender参数)都提供了引发该事件的对象的引用。第二个参数是一个EventArgs对象,该对象与其他所有可能很重要的附加细节绑定在一起。例如:MouseUp事件提供了一个MouseEventAgrs对象,用于指示当事件发生时按下了哪些鼠标键。

  在WPF中,如果一个事件不需要传递任何额为的细节,可以使用RoutedEventArgs类,该类包含了有关如何传递事件的一些细节。如果一个事件确实需要传递额外的信息,则需要使用更特殊的继承自RoutedEventArgs的对象(如:MouseButtonEventArgs)。因为每一个WPF事件参数类都继承自RoutedEventArgs类,所以每个WPF事件处理程序都可访问与事件路由相关的信息。

1.4处理路由事件

  在WPF中可以使用多种方法关联时间程序。最常用的方法是为XAML标记添加事件特性。例如

<Button Click="事件名称">Ok</Button>

也可以使用代码连接事件,如下为Imgae添加MouseUp事件

img.MouseUp+=new MouseButtonEventHandler(img_MouseUp);

C#还允许隐式地创建合适的委托对象:img.MouseUp+=Img_MouseUp;

以上方法都是依赖于事件包装器,事件包装器调用UIElement.AddHandler()方法,也可以直接调用UIElemement.AddHandler()方法直接连接对象。如下:

//使用引发事件的类的名称
img.AddHandler(Image.MouseUpEvent,new MouseButtonEventHandler(img_MouseUp));

//使用定义事件的类的命长
img.AddHandler(UIElement.MouseUpEvent,new MouseButtonEventHandler(img_MouseUp));

如果想要断开事件处理程序的话,如下

img_MouseUp -=img_MouseUp;

//img.RemoveHandler(Image.MouseUpEvent,new MouseButtonEventHandler(img_MouseUp));

 2事件路由

  路由事件实际上以下列三种方式出现:

1、与普通的.NET事件类似的直接路由事件。他们起源于一个元素,并且不传递给其他元素。

2、在包含层次中向上传递的冒泡路由事件。他首先由被该元素引发,接下来被该元素的父元素引发,然后被该父元素的父元素引发,以此类推,知道元素输定段。

3、在包含层次中向下传递的隧道路由事件。隧道路由事件在事件到达恰当的空间之前为预览事件提供了机会。

当使用EventManager.RegisterEvent()方法注册一个路由事件时,需要传递一个RoutingStrategy枚举值,该值用于只是希望应用于实践的事件行为。

2.1 RoutedEventArgs类

  我们可以从RoutedEventArgs类获取事件的一些其他细节信息,由于所有WPF事件参数都继承自RoutedEventArgs,任何事件处理程序都可以使用这些属性。

RoutedEventArgs类的属性
Source 指示引发了事件的对象
OriginalSource 指示最初是什么对象引发了事件。通常OriginalSource属性值与Source属性值是相同的,但在某些情况下,OriginalSource属性指向对象书中更深的层次,疑惑的作为更高一级元素的一部分的后台元素。
RoutedEvent 通过事件处理程序为触发的事件提供RoutedEvent对象。如果使用同一个事件处理程序处理不同的事件,这一信息是非常有用的。
Handled 该属性允许终止事件的冒泡或隧道过程。如果一个控件将Handled属性设置为true,那么事件就不会继续传递,并且也不会再为任何其他元素引发该事件。

2.2 处理挂起事件

  有一个方法可以接受被标记为处理过的事件。不是通过XAML关联事件处理程序,而是必须使用前面介绍的AddHandler()方法。AddHandle()方法提供了一个重载版本,该版本可以接受一个Boolean值作为他的第三个参数。如果该参数设置为True,那么及时设置了Handled标志,也将接收到事件。

2.3 附加事件

  假设在StackPannel面板中包装了一堆按钮,并且希望在一个事件处理程序中处理所有这些按钮的单击事件。粗略的方法是将每一个按钮都关联到Click上,WPF提供了一种方便的方法,可以通过处理高层次元素的Click事件来处理所有按钮的单击事件。如果高一层的元素没有Click事件,可以通过"类名.事件名"的方式进行关联,如下:

//错误的方式,因为StackPannel没有Click事件
<StackPannel/ Click="DoSomething">
    <Button Name="cmd1">Command1</Button>
    <Button Name="cmd2">Command2</Button>
    <Button Name="cmd3">Command3</Button>
</StackPannel>



//正确的方式
<StackPannel/ Button.Click="DoSomething">
    <Button Name="cmd1">Command1</Button>
    <Button Name="cmd2">Command2</Button>
    <Button Name="cmd3">Command3</Button>
</StackPannel>
View Code

 2.4 隧道事件

  隧道路由事件都以单词Preview开头。而且,WPF通常成对的定义冒泡路由事件和隧道路由事件。如果将隧道路由事件标记为已处理过,那么冒泡路由事件就不会发生,因为两个事件共享一个RoutedEventArgs类的实例。隧道路由事件是从最上层的元素开始向下层传递。


3.0 WPF事件

  WPF为每一个元素都提供了许多事件,但是看最重要的事件通常包括以下5类:

  1. 生命周期事件:这些事件将在元素被初始化、加载或卸载时发生。
  2. 鼠标事件:这些事件是鼠标动作的结果
  3. 键盘事件:这些事件是键盘动作的结果
  4. 手写笔事件:这些事件是使用类似钢笔的手写笔的结果,在平板电脑上使用手写笔代替鼠标。
  5. 多点触控事件:这些事件是一个或多个手指在多点触控屏幕的结果。

3.1 生命周期事件

  当首次创建以及释放所有元素时都会引发事件。可以使用这些事件初始化窗口。它们是在FrameworkElement类中定义的。

所有元素的生命周期事件
Initialized 当元素被实例化,并且已经根据XAML标记设置了元素的属性之后发生。这时元素已经初始化,但是窗口的其他部分可能还没有初始化。此外,还没有应用样式和数据绑定。这时,IsInitialized属性为true。
Loaded 当整个窗口已经初始化,并且也已经应用样式和数据绑定时,该事件发生。这是在元素被呈现之前的最后一站。
Unloaded 当元素被释放时,该事件发生,原因是包含元素的窗口被关闭或特定的元素被从窗口中删除。

FrameworkElement类实现了ISupportInitialize接口,该接口提供了两个用于控制初始化过程的方法。第一个是BeginInit()方法,在实例化元素后会立即调用该方法。BeginInit()方法被调用之后,XAML解析器设置所有元素的属性并添加内容。第二个是EndInit()方法,当初始化完成后,该方法被调用,此事引发Initialized事件,然后是Loaded事件,之后所用元素被呈现。如果只是对执行控件的第一次初始化感兴趣,完成这一任务的最好时机是在触发Loaded事件时。通常,可以在同一个位置进行所有初始化,这个位置一般是Window.Loaded事件处理程序。以上只是列出了部分生命周期事件,窗口还有它自己更特殊的生命周期事件。

Window类的生命周期事件
   
   
   
   
   
   

3.2 输入事件

  输入事件可以通过继承自InputEventArgs的自定义事件参数类传递额外的信息。InputEventArgs类只增加了两个属性:Timestamp和Device。Timestamp属性提供了一个指示事件何时发生的毫秒数。Device属性返回一个对象,该对象提供于触发事件的设备相关的更多信息,设备可以是鼠标、键盘、手写笔,这三种可能来自不同的类表示,所有这些类都继承自System.Windows.Input.InputDevice。

键盘输入

  当用户按下键盘上的一个键时,就会发生一系列事件,下面根据他们发生的顺序列出这些事件

所有元素的键盘事件
PreviewKeyDown 当按下一个键时发生
KeyDown 当按下一个键时发生
PreviewTextInput 当按键完成并且元素正在接受文本输入时发生。对于那些不会产生文本“输入”的按键,不会引发该事件
TextInput 当按键完成并且元素正在接受文本输入时发生。对于那些不会产生文本“输入”的按键,不会引发该事件
PreviewKeyUp 当释放一个按键时发生
KeyUp 当释放一个按键时发生
TextChanged 在一个按键导致文本框中的文本发生改变之后会立即引发的事件。

 

 4.1 焦点

  在Windows中,为了让一个空间能够接受焦点,必须将Focusable属性设置为true,对于所有控件这是一个默认值。Focusable属性石在UIEelemet类中定义的。这意味着其他非控件元素也能获得焦点。通常,非控件元素Focusable属性设置为false,但也可以设置为true,当它们获得焦点时,面板边缘会显示一个点划线边框。在按Tab键时可以切换元素,如果想转移Tab键的转换顺序,则需要对元素的TextIndex属性进行设置。

4.2 获取键盘状态

  当发生按键事件时,经常需要知道更多的信息,而不仅仅是知道按下的哪个键。而且还要知道是否还按下了其他键。对于键盘事件,获取这些信息比较容易。KeyEventArgs对象包含一个KeyStates属性,该属性反映触发事件的键的属性,KeyboardDevice属性为键盘上的所有键提供了相同的信息。KeyboardDevice属性提供了KeyboardDevice类的一个实例。它的属性包括当前是哪个元素具有焦点以及当事件发生时按下了哪些修饰键。修饰键包括Shift、Ctrl、Alt,并且可以使用位逻辑来检查他们的状态,如下所示:

            if ((e.KeyboardDevice.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
            {
                lblInfo.Text += "\r\nYou held the Control key.";                   
            }

KeyboardDevice对象还提供了几个简便的方法,对于这些方法中的每个方法,需要传递一个Key枚举值。

IsKeyDown() 当事件发生时,通知是否按下了该键
IsKeyUp() 当事件发生时,通知是否释放了该键
IsKeyToggled() 当事件发生时,通知该键是否处于“打开”状态,只有对于那些能够开、关的键,该方法才有意义,如:CapsLock键、ScrollLock键、NumLock键
GetKeyStates() 返回一个多多哥KeyStates枚举值,指明该建当前是否被释放了、按下了或处于切换状态。该方法本质上和为同一个键同时调用IsKeyDown()方法和IsKeyToggled()方法相同

没有限制在键盘事件中获取键的信息,也可以在任何时候获取键盘状态信息,我们可以使用KeyBoard类,该类和KeyboardDevice类非常类似,只是Keyboard类由静态成员构成下面的例子使用Keyboard类检查左边Shift键的当前状态:

            if (Keyboard.IsKeyDown(Key.LeftShift))
            {
                lblInfo.Text = "The left Shift is held down.";                   
            }

5.1 鼠标输入——鼠标单击

  鼠标事件执行几个相关联的任务。当鼠标移动到一个元素上面时,可以通过最基本的鼠标事件进行响应。这些事件是MouseEnter事件和MoseLeave事件。这两个事件都是直接事件,这意味着他们不适用冒泡和隧道过程,而是源自一个元素并且只被该元素引发。还有PreviewMouseMove事件和MouseMove事件只要移动鼠标就会引发这两个事件。所有这些事件都提供了形同的信息:MouseEventArgs对象。MouseEventArgs对象包含当事件发生时表示鼠标键状态的属性,以及一个GetPosition()方法,该方法返回相对于所选元素的鼠标坐标。下面是一个示例:

        private void MouseMoved(object sender, MouseEventArgs e)
        {
            Point pt = e.GetPosition(this);
            lblInfo.Text = 
                String.Format("You are at ({0},{1}) in window coordinates",
                pt.X, pt.Y);            
        }

鼠标单击事件的引发方法和按键事件的引发方式有些类似。区别是对于鼠标左键和鼠标右键引发不同的事件。除了鼠标左右键的按下和释放的事件外还有PreviewMouseWheel和MouseWheel,鼠标滚轮动作的事件。所有鼠标键事件都提供一个MouseButtonEventArgs对象。MouseButtonEventArgs类继承自MouseEventArgs类(这意味着该类包含相同的坐标和按钮状态信息),并且添加了几个成员。这些成员中相对不重要的是MouseButton(通知是哪一个鼠标键引发的事件)和ButtonState(该成员用于通知当事件发生时鼠标键是出于按下状态还是释放状态)。ClickCount属性,该属性用于通知鼠标键被单击多少次,从而可以区分是单机还是双击。

5.2 鼠标拖放

  拖放操作时一种拖动信息使其离开窗口中的某个位置并将其放置到其他位置的技术,通常是将它用作为高级用户提供的一个快捷方式。拖放操作的方法和事件被集中到了System.Windows.DragDrop类中,然后供其他类使用。本质上拖放操作通过以下三个步骤进行:1、用户单击一个元素,并保持鼠标键为按下状态。这时,某些信息被搁置起来,并且拖放操作开始。2、用户将鼠标移动到其他元素上。如果该元素可以接受正在拖动的内容,鼠标指针会变成拖放图标。3、当鼠标释放鼠标键时,元素接受信息并决定如何处理接收到的信息。可以通过按下ESC键取消操作。对于拖放操作有两个方面:源和目标。为了创建拖放源,需要在某个位置调用DragDrop.DoDragDrop()方法初始化拖放操作。此时确定拖动操作的源,个只希望移动的内容,并指明允许什么样的拖放效果。

  通常在响应MouseDown或PreviewMouseDown事件时,调用DoDragDrop()方法。下面是当单击一个标签时该示例初始化拖放操作。

        private void lblSource_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Label lbl = (Label)sender;
            DragDrop.DoDragDrop(lbl, lbl.Content, DragDropEffects.Copy);
        }

接收数据的元素需要将它的AllowDrop属性设置为true。此外,他还需要通过处理Drop事件来处理数据:

    <Label Grid.Row="1" Grid.ColumnSpan="2" Background="LightGoldenrodYellow"
           VerticalAlignment="Center" HorizontalAlignment="Center" Padding="20"
      AllowDrop="True" Drop="lblTarget_Drop">To this Label</Label>

如果希望有选择的接受内容,可以处理DragEnter事件。这时,可以检查正字拖动的内容的数据类型,然后确定所允许的操作类型。下面的示例只允许文本内容——如果拖动的内容不能被转换为文本,就不允许进行拖放操作,并且鼠标指针会变成一个具有一条线的圆形光标,表示禁止操作:

        private void lblTarget_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.Text))
                e.Effects = DragDropEffects.Copy;
            else
                e.Effects = DragDropEffects.None;
        }

最后,当操作完成后就可以检索并处理数据了。

        private void lblTarget_Drop(object sender, DragEventArgs e)
        {
            ((Label)sender).Content = e.Data.GetData(DataFormats.Text);
        }

 

posted on 2013-09-08 22:18  松竹柏柳  阅读(722)  评论(0编辑  收藏  举报

导航