您的位置:首页 > 其它

WPF学习(6)路由事件

2013-12-15 21:29 666 查看
做过.net开发的朋友对于事件应该都不陌生。追溯历史,事件(Event)首先应用在Com和VB上,它是对在MFC中使用的烦琐的消息机制的一个封装,然后.net又继承了这种事件驱动机制,这种事件也叫.net事件。正如WPF在简单的.net属性概念上添加了许多基础的东西一样,它也为.net事件添加了许多基础的东西。路由事件(RoutedEvent)是专门设计用于在元素树中使用的事件。当路由事件触发后,它可以向上或向下遍历逻辑树和可视树,用一种简单而且持久的方式在每个元素上触发,而不需要使用任何定制代码。下面我们就来学习下路由事件,本节主要包括以下几个方面内容:路由事件和WPF事件(包括键盘输入、鼠标输入和多点触控输入等)。

1.路由事件

1.1自定义路由事件

路由事件的实现和行为与依赖属性有很多相同的地方。我们还是先举个例子来说明下路由事件的实现方式,然后再来讲下路由事件的一些特性。拿最常用的Button的Click事件(继承自ButtonBase抽象类)来说明:

class MyButton:Button
{

//Add CLR Event wrapper for Routed Event
public event RoutedEventHandler MyClick
{
add { this.AddHandler(MyClickEvent, value); }
remove { this.RemoveHandler(MyClickEvent, value); }
}

//State and Register Routed Event
public static readonly RoutedEvent MyClickEvent =
EventManager.RegisterRoutedEvent("MyClick", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyButton));

//Trigger Method of Routed Event
protected override void OnClick()
{
base.OnClick();
RoutedEventArgs newEvent = new RoutedEventArgs(MyClickEvent, this);
this.RaiseEvent(newEvent);
}
}


我照着依赖属性的Code Snippet的样子也写个了路由事件的,名字无所谓,只要不冲突就好,代码如下:

//PreviewTextInput事件处理
private void StackPanel_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
long num = 0;
if (!long.TryParse(e.Text, out num))
{
MessageBox.Show(e.Text + "键不是数字");
e.Handled = true;
}
}
//PreviewKeyDown事件处理
private void StackPanel_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Space)
{
KeyConverter cvt = new KeyConverter();//这里只是为了演示KeyConverter的用法,此处并不合适
MessageBox.Show(cvt.ConvertToString(e.Key) + "键不是数字");
e.Handled = true;
}
/*
//获取键盘对象来判断事件发生时是否打开的大小写键
if (e.KeyboardDevice.IsKeyToggled(Key.CapsLock))
MessageBox.Show("CapsLock is opened");
else
MessageBox.Show("CapsLock is closed");
*/
/*
//事件触发时键盘状态和实际的键盘状态可能会不一致
//通过Keyboard.IsKeyToggled()静态方法来实时获取键盘状态
if (Keyboard.IsKeyToggled(key: Key.CapsLock))
{
MessageBox.Show("CapsLock is opened");
}
else
{
MessageBox.Show("CapsLock is closed");
}
*/
}


View Code
运行效果:



在上面例子中,通过PreviewTextInput事件处理哪些可以触发PreviewTextInput事件的按键,通过PreviewKeyDown事件处理剩下的哪些不能触发PreviewTextInput事件的按键(上面的空格键),从而让输入只能为数字。

2.2.2鼠标事件

鼠标操作会触发一系列相关的事件触发。主要有移入移出事件、单击双击事件、捕获鼠标事件和鼠标拖放事件。

2.2.2.1移入移出事件

最基本的移入移出事件是MouseEnter和MouseLeave事件,这两个事件是直接事件(Direct Event),也就是说不会冒泡会隧道传输。还有几个冒泡和隧道事件为PreviewMouseMove/MouseMove。

private void Window_MouseMove(object sender, MouseEventArgs e)
{
Point p = e.GetPosition(this);//获取鼠标相对于窗口的坐标
MessageBox.Show("Relative to Window,x is " + p.X + ",y is " + p.Y);
Point p1 = PointToScreen(p);//获取鼠标相对于屏幕的坐标
MessageBox.Show("Relative to Screen,x is " + p1.X + ",y is " + p1.Y);
}


可以通过MouseEventArgs的GetPosition(IInputElement element)方法来获取触发事件时鼠标相对于控件的坐标,然后可以通过Visual的PointToScreen(Point p)方法将相对坐标转化为相对于屏幕的坐标,这个在一些交互中经常用到,当然也可以通过P/Invoke方法调用Win32 API来获取相对于屏幕坐标。

2.2.2.2单击双击事件

鼠标单击事件和键盘按键事件类似,区别是鼠标单击分左右键。下面按顺序列出事件:



这些事件都提供了MouseButtonEventArgs对象,它继承自MouseEventArgs(有判断鼠标是按下还是释放的MouseButtonState属性,有获取相对位置的GetPosition方法),MouseButtonEventArgs对象自身又增加了两个属性,一个是MouseButton属性,用于判断事件是由鼠标那个键触发的;另一个是ClickCount,用于判断点击次数,可以用来判断单击双击。

某些元素又添加了更高级的鼠标事件,例如ButtonBase添加了Click事件,Control类添加了PreviewMouseDoubleClick事件和MouseDoubleClick事件。

另外,还提供了PreviewMouseWheel和MouseWheel鼠标滚轮滚动事件,它们提供了MouseWheelEventArgs对象,这个对象有个Delta的属性,用于获取指示鼠标滚轮变更量的值,如果鼠标滚轮朝上旋转(背离用户的方向),则该值为正;如果鼠标滚轮朝下旋转(朝着用户的方向),则该值为负。

可以通过Mouse类来实时地获取鼠标的相关信息。

2.2.2.3捕获鼠标事件

通常情况下,按下事件和释放事件是成对被触发的,但是也有另外,如单击某个元素,保持按下状态,然后移动鼠标指针离开该元素,这种情况就不会触发释放的事件。当我们想要在鼠标离开了元素后触发仍然触发释放事件执行其它操作,就要先判断该元素是否可用(IsEnabled="true",禁用的元素是无法获取捕获鼠标的),然后通过UIElement.CaptureMouse()方法来捕获鼠标,成功捕获返回true,否则返回false,如果返回true,就会触发GotMouseCapture和IsMouseCaptureChanged事件,并将事件数据中的RoutedEventArgs.Source报告为调用 CaptureMouse 方法的元素。 如果强制执行捕获,则可能会干扰现有捕获,特别是与鼠标拖放有关的捕获。若要从所有元素中清除鼠标捕获,请用值为 null 的 element 参数调用Mouse.Capture()方法。在UIElment和Mouse类中都有GotMouseCapture事件,它们存在这样的关系:当UIElement作为基元素继承时,此事件会为该类的Mouse.GotMouseCapture附加事件创建一个别名,以便 GotMouseCapture 包含在该类的成员列表中。 附加到 GotMouseCapture 事件的事件处理程序将附加到基础Mouse.GotMouseCapture附加事件上,并接收同一事件数据实例。UIElement.LostMouseCapture事件也类似。

2.2.2.4鼠标拖放事件

鼠标拖放是作为一种快捷方便的方式来使用的,例如将垃圾文件拖放到回收站,将一个doc文档拖放到打开的Word窗口来打开该doc文档等。

一般,拖放操作分为三个步骤:

1)用户单击某个元素,并保持鼠标键按下状态,这时某些信息被搁置,拖放操作开始。

2)用户将鼠标将鼠标移动到其它元素上,如果该元素可以接受拖放的内容,则鼠标指针变为拖放图标,否则变为拒绝操作图标。

3)用户释放鼠标键时,该元素接受信息。按ESC可取消操作。

某些控件内部已经内置了拖放逻辑,例如TextBox,你可以选中TextBox中的文本,将其拖放到另一个TextBox中。

对于那些没有内置拖放逻辑的控件来说,想要实现拖放,实现我们来处理控件的拖放事件。下面来举个例子:

Xaml代码:

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="138*" />
<ColumnDefinition Width="140*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="99*" />
<RowDefinition Height="162*" />
</Grid.RowDefinitions>
<TextBox Height="30" />
<TextBox Grid.Column="1" Height="30" />
<StackPanel x:Name="sp" Grid.Row="1" Button.PreviewMouseDown="StackPanel_PreviewMouseDown">
<Button Content="1" Background="Green"/>
<Button Content="2" Background="Red" />
<Button Content="3" Background="Orange" />
<TextBlock Text="4" Background="AliceBlue" />
</StackPanel>
<StackPanel x:Name="sp1" Grid.Row="1" Grid.Column="1" Background="Gray" AllowDrop="True" Drop="StackPanel_Drop">

</StackPanel>
</Grid>


cs代码:

//在PreviewMouseDown事件中调用DragDrop.DoDragDrop方法初始化拖放操作(创建源)
private void StackPanel_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Button button = e.Source as Button;
if (button != null)
DragDrop.DoDragDrop(button, button, DragDropEffects.Copy);
}
//将目标的AllowDrop设为true,监听其Drop事件
private void StackPanel_Drop(object sender, DragEventArgs e)
{
Button button = e.Data.GetData(typeof(Button)) as Button;
this.sp.Children.Remove(button);//由于拖放的Button已经是sp的Child,故需要断开与原容器的连接
this.sp1.Children.Add(button);
}


效果如下:



重点是DragEventArgs对象的Data属性,它是IDataObject接口类型,用于封装拖放对象相关的信息,里面有一些经常要的方法,如GetData/SetData方法,GetDataPresent方法。

如果想要达到过滤拖放内容的目标,应该使用DragEnter事件来判断,最后交给Drop事件处理。

如果拖放操作时是在应用程序间进行的,而且源是复杂的对象,一种做法是通过序列化反序列化方式,另一种是通过使用XamlWriter方法将WPF对象转化为Xaml,然后通过XamlReader方法再转化为WPF对象,实质也是第一种。

2.2.3手写笔事件

手写笔是一种在平板电脑或其它触屏设备上使用的类似笔的设备,它的行为很像鼠标,可以触发MosueMove、MouseDown和MouseUp等事件,同时它还具有对应的事件StylusMove、StylusDown和StylusUp事件及它们的Preview事件,另外它还有一些特有的事件,例如:StylusInAirMove、StylusInRange、StylusOutOfRange和StylusSystemGesture事件,这些事件是手写笔特有的事件。在InkCanvas中应用手写笔事件,经常可以实现不错的效果。关于这些事件的详细信息,可查看MSDN

2.2.4多点触控事件

多点触控和手写笔不同的是,多点触控支持同时多个手指操作甚至手势(Gesture),win7上标准的手势可查看MSDN,当前支持触控的硬件列表可查看MSDN

正如鼠标事件有高低层次一样(Click及MouseDoubleClick等事件属于高层次事件,而MouseDown及MouseUp等事件属于低层次事件),多点触控也有高低层次事件的区分。

1)原始触控(raw touch):这是触控的低级支持,都是一些单独的触控事件,不支持手势。

2)操作(manipulation):这是一个对原始触控的抽象层,支持手势。WPF支持的通用的手势包括移动(pan)、缩放(zoom)、旋转(rotate)和轻按(tap)。

3)内置的元素支持(built-in element support):有些控件已经对多点触控提供了内置支持,例如可滚动的控件支持触控移动,如ListBox、ListView、DataGrid、TextBox和ScrollViewer。

2.2.4.1原始触控

原始触控事件也像低级的鼠标键盘事件一样,被封装在UIElement和ContentElement之中。如下图所示:



上面这些事件都提供了TouchEventArgs对象,这个对象有两个重要的成员,一个GetTouchPoint方法(获取触控事件发生时触控点的坐标);一个是TouchDevice属性。内部是将每个触点都看成是单独的设备,会为每个触点分配唯一的设备ID,根据TouchDevice.Id来区分触点(手指)。

2.2.4.2操作

对于那些直接简明的触控,使用原始触控就足够了。但是,如果要方便地支持触控手势,例如旋转,则需要触控操作。通过将元素的IsManipulationEnabled属性(定义在UIElement中)设为true,则该元素可以使用触控操作,然后可以响应四个事件:ManipulationStarting、ManipulationStarted、ManipulationDelta和ManipulationCompleted。它们都提供了不同的事件参数对象,每个对象都有自己独特的属性和方法。具体可以查看MSDN

2.2.4.3惯性

WPF中的惯性(Inertia)也是构建在低级事件之上的,它使得用户体验更好。例如,当用手指在划动一张图片的时候,正常情况下,当手指离开的时候图片会立即停止。而当启用了惯性属性后,手指离开时图片还会减速划动一段距离,而且,当图片到边界时还可以产生一个反弹的效果。这需要监听ManipulationInertiaStarting事件,这个事件提供了ManipulationInertiaStartingEventArgs对象,这个对象提供了延伸惯性行为ExpansionBehavior、线性惯性行为TranslationBehavior和旋转惯性行为RotationBehavior,通过设置相应Behavior的InitialVelocity初始速率和DesiredDeceleration预期减速度来实现相应惯性效果。另外,为了使其接触边界时,能够自然弹回,需要在ManipulationDelta事件中调用ReportBoundaryFeedback()方法,将会触发UIElement.ManipulationBoundaryFeedback事件,在该事件中,使应用程序或组件能够在对象到达边界时提供可视反馈。还可以调用事件参数的Cancel方法来取消操作,将不再引发操作事件并且对于触控将会触发鼠标事件。

3总结

本节主要讲了WPF的路由事件及附加事件,然后描述了WPF框架中的生命周期事件、鼠标事件、键盘事件、手写笔事件和多点触控事件,细节很多,但都有迹可循。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: