做過.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的樣子也寫個了路由事件的,名字無所謂,只要不沖突就好,代碼如下:

<?xml version="1.0" encoding="utf-8"?> <CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"> <CodeSnippet Format="1.0.0"> <Header> <Title>定義一個 Routed Event</Title> <Shortcut>propre</Shortcut> <Description>將 RoutedEvent 用作后備存儲的路由事件的代碼段</Description> <Author>Jello Chen</Author> <SnippetTypes> <SnippetType>Expansion</SnippetType> </SnippetTypes> </Header> <Snippet> <Declarations> <Literal> <ID>Click</ID> <ToolTip>事件類型</ToolTip> <Default>Click</Default> </Literal> <Literal> <ID>newEvent</ID> <ToolTip>路由事件參數</ToolTip> <Default>newEvent</Default> </Literal> </Declarations> <Code Language="csharp"> <![CDATA[ //Add CLR Event wrapper for Routed Event public event RoutedEventHandler $Click$ { add {this.AddHandler($Click$Event,value);} remove {this.RemoveHandler($Click$Event,value);} } //State and Register Routed Event public static readonly RoutedEvent $Click$Event = EventManager.RegisterRoutedEvent("$Click$",RoutingStrategy.Bubble,typeof(RoutedEventHandler),typeof(ButtonBase)); //Trigger Method of Routed Event protected virtual void On$Click$ () { RoutedEventArgs $newEvent$ = new RoutedEventArgs($Click$Event,this); this.RaiseEvent($newEvent$); } $end$]]> </Code> </Snippet> </CodeSnippet> </CodeSnippets>
看起來是不是和依賴屬性的結構很像?首先是聲明注冊了一個RoutedEvent類型的MyClickEvent,也由static readonly修飾,也是通過一個靜態方法來獲得實例,方法多了第二個事件路由策略的參數;然后是為其添加CLR事件包裝器MyClick,分別通過AddHandler和RemoveHandler來向路由事件添加和移除一個委托,這兩個方法不是在DependencyObject中定義的,而是在更高層的UIElement類中定義的;最后定義了一個路由事件的觸發方法,實例化一個該路由事件相關的路由事件參數,將其作為參數傳入RaiseEvent激發事件方法中,這個方法也是定義在UIElement類中的。
我們先來使用上面的代碼:
Xaml代碼:
<local:MyButton x:Name="btnTest" Content="Press me" Width="80" Height="30"/>
C#代碼:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.btnTest.MyClick +=new RoutedEventHandler(btnTest_MyClick); } private void btnTest_MyClick(object sender, RoutedEventArgs e) { MessageBox.Show("I am Pressed!"); } }
這里,我們在構造器中使用過程式代碼來進行路由事件的訂閱,當然也可以在Xaml中使用,由VS來自動生成這個訂閱關系。看起來和我們在Webform和Winform中使用的沒什么差別,這應該歸功於事件包裝器(Event Wrapper)。下面來詳細說下路由事件的注冊方法EventManager.RegisterRoutedEvent(string name, RoutingStrategy routingStrategy, Type handlerType, Type ownerType):
- 參數name:路由事件的名稱。該名稱在所有者類型中必須是唯一的,並且不能為 null 或空字符串。
- 參數routingStrategy:作為枚舉值的事件的路由策略。有三種路由策略:
- Tunnel隧道方式:路由事件使用隧道策略,以便事件實例通過樹向下路由(從根到源元素)。
- Bubble冒泡方式:路由事件使用冒泡策略,以便事件實例通過樹向上路由(從事件元素到根)。
- Direct直接方式:路由事件不通過元素樹路由,僅在源元素發生,與.net事件類似,但其支持其他路由事件功能,例如類處理、System.Windows.EventTrigger 或 System.Windows.EventSetter。
- 參數handlerType: 事件處理程序的類型。該類型必須為委托類型,並且不能為 null。
- 參數ownerType:路由事件的所有者類類型。該類型不能為 null。
再來看下路由事件處理程序的簽名,它與.net事件處理程序簽名相匹配。第一個參數是Object類型,指該處理程序被添加到的元素;第二個參數是RoutedEventArgs類型,它是EventArgs的子類,它提供了四個屬性:
Source屬性:邏輯樹中一開始觸發該事件的元素。
OriginalSource屬性:可視樹中一開四觸發該事件的元素。
Handled屬性:將事件標記為是否已處理,true時,Tunnel隧道方式和Bubble冒泡方式將不再繼續,否則繼續。
RoutedEvent屬性:指真正的路由事件對象,主要用於當一個事件處理程序同時被多個路由事件。
來看個冒泡事件的例子:
Xaml代碼:
<Window x:Class="RoutedEventDemo.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="300" MouseDown="Image_MouseDown" x:Name="window1"> <Grid MouseDown="Image_MouseDown" x:Name="grid1"> <StackPanel x:Name="sp1" MouseDown="Image_MouseDown"> <TextBlock x:Name="tb1" MouseDown="Image_MouseDown" Width="60" Height="60"> <Image x:Name="img1" Source="Images/photo.png" MouseDown="Image_MouseDown"/> </TextBlock> <CheckBox Content="Handler Setting" x:Name="cb"/> </StackPanel> </Grid> </Window>
這里的CheckBox是用來設置Handler屬性。
過程式代碼:
private int count = 0; private void Image_MouseDown(object sender, MouseButtonEventArgs e) { count++; string msg = "#" + count.ToString() + ":\r\n" + "sender:" + sender.ToString() + "\r\n" + "Source:" + e.Source + "\r\n" + "OriginalSource:" + e.OriginalSource + "\r\n"; e.Handled = (bool)this.cb.IsChecked; MessageBox.Show(msg); }
CheckBox未選中時,會觸發5次,依次為Image--TextBlock--StackPanel--Grid--Window;選中時只會冒泡一次到Image。需要注意的是:Button繼承自UIElement的MouseDown事件(真正定義在Mouse類)在類內部已經處理(事件被掛起),一般不會觸發附加到Mouse類的其它實例的事件處理,了解詳細點擊MSDN,里面提到了兩種解決辦法,更推薦使用相對應的Preview隧道事件來處理。
1.2附加事件
附加事件的思想,其實和附加屬性是一樣,某一元素沒有某一種事件,需要將該事件附加在該元素上以實現相應的事件監聽處理功能。先來看這樣的場景:
Xaml代碼:
<Window x:Class="RoutedEventDemo.Window2" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window2" Height="300" Width="300"> <Grid> <StackPanel> <Button Content="1" /> <Button Content="2" /> <Button Content="3" /> </StackPanel> </Grid> </Window>
現在需要用一個事件處理程序來處理StackPanel中的所有按鈕的單擊事件。自然而然,我們會想到監聽StackPanel的Click事件。然而,Click事件是Button特有的事件(繼承自ButtonBase),這時候就需要使用附加事件來解決了。寫成這樣<StackPanel ButtonBase.Click="StackPanel_Click">......</StackPanel>。這里沒寫成Button.Click是為了智能提示的方便,當然在寫出Button.Click后Xaml已經能識別出這是Button的Click事件。當然,這兩種是由區別的,Button.Click只能監聽Button類型的單擊事件,而ButttonBase.Click能監聽ButtonBase類型的單擊事件,例如Button、RadioButton和CheckBox等。
過程式代碼:
this.sp.AddHandler(Button.ClickEvent, new RoutedEventHandler(StackPanel_Click));
UIElement.AddHandler還有一個重載版本:public void AddHandler(RoutedEvent routedEvent, Delegate handler, bool handledEventsToo);需要注意的是第三個參數,如果為 true,則將按以下方式注冊處理程序:即使路由事件在其事件數據中標記為已處理,也會調用該處理程序;如果為 false,則使用默認條件注冊處理程序,即當路由事件被標記為已處理時,將不調用處理程序。默認值為false。前面說過這不是一種好的方式,因為事件應在第一時間被處理,而應該盡可能地避免處理已經處理過的事件。由這也能看出來,當將事件標記為已處理時,隧道傳遞和冒泡仍然會繼續,只不過在默認情況下事件處理程序只處理未處理的事件。
2.WPF事件
在WPF框架中,已經為我們封裝了許多的事件,主要分為這么幾類:
- 生命周期事件:在元素被初始化、加載和卸載時發生。
- 輸入事件:
- 鼠標事件
- 鍵盤事件
- 手寫筆事件
- 多點觸控事件
2.1生命周期事件
當首次創建或釋放元素時都會觸發一些事件,這些事件就是生命周期事件。
來看下這些事件的執行時機。在FrameworkElement中實現了ISupportInitialize接口,該接口中有兩個方法BeginInit()和EndInit(),前者是用信號通知對象初始化即將開始,當元素被實例化后調用該方法,然后開始屬性設置;后者是用信號通知對象初始化已完成,當初始化完成后調用該方法,然后觸發Initialized事件。實現這個接口的目的是保證初始化工作的原子性。當初始化窗口時,是從下到上進行的,也就是從葉子節點開始的,這保證了當某一元素需要內容時其內容都已初始化。當所有的元素都已初始化完成后,然后開始布局、應用樣式及可能的數據綁定等。Initialized事件后,開始觸發Loaded事件,Load的順序和Initialized的順序相反,是從上到下進行的,當所有的元素都Load完成后,窗口顯示出來。
對於Window窗口還有一些特有事件:
當希望在窗口首次加載時做一些額外的初始化工作時就可以通過在其Loaded事件的處理程序中完成。正常情況下,也可以在窗口構造器中的InitializeComponent()方法后來處理。
2.2輸入事件
用戶通過一些外設如鼠標、鍵盤、手寫筆和多點觸控屏等來進行輸入操作時觸發的事件,就是輸入事件。輸入事件可以通過繼承自InputEventArgs自定義事件參數類來附加額外的信息,看下繼承關系圖:
InputEventArgs事件參數類在RoutedEventArgs類基礎上只增加了Timestamp和Device兩個屬性,Timestamp屬性表示事件何時發生的毫秒數,用於用於比較各事件之間的發生順序,值越大越是最近發生;Device屬性表示觸發事件的設備的對象,設備對象是繼承自抽象類System.Windows.Input.InputDevice的子類的實例。
2.2.1鍵盤事件
當用戶按下一個鍵,就會觸發一系列的事件,這里按順序依次列出公共的事件:
- PreviewKeyDown事件:隧道事件,按下一個鍵觸發
- KeyDown事件:冒泡事件,按下一個鍵觸發
- PreviewTextInput事件:隧道事件,當按鍵完成並且元素正在接受文本輸入時發生。對於一些像Ctrl、Shift等不會產生文本輸入的鍵不會觸發該事件
- TextInput事件:冒泡事件,當按鍵完成並且元素正在接受文本輸入時發生。對於一些像Ctrl、Shift等不會產生文本輸入的鍵不會觸發該事件
- PreviewKeyUp事件:隧道事件,當釋放一個鍵時發生
- KeyUp事件:冒泡事件,當釋放一個鍵時發生
上面是一些公共的事件,不同控件可能還有一些自己特有的事件,為了不沖突,還會將上面的導致沖突的事件掛起。例如TextBox控件擁有TextChanged事件,而掛起了TextInpute事件。
PreviewKeyDown事件、KeyDown事件、PreviewKeyUp事件和KeyUp事件都是通過KeyEventArgs對象來提供相應的信息。該對象有一個Key屬性,是一個System.Windows.Input.Key的枚舉類型。當我們在檢查文本框的輸入的內容時,通常需要監聽PreviewTextInput事件(可以接受文本輸入元素)和PreviewKeyDown事件(不可以接受文本輸入元素)。看下面的例子:
Xaml代碼:

<StackPanel UIElement.PreviewTextInput="StackPanel_PreviewTextInput" UIElement.PreviewKeyDown="StackPanel_PreviewKeyDown"> <TextBox /> <TextBox /> <TextBox /> </StackPanel>
cs代碼:

//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"); } */ }
運行效果:
在上面例子中,通過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框架中的生命周期事件、鼠標事件、鍵盤事件、手寫筆事件和多點觸控事件,細節很多,但都有跡可循。