概述
除了路由事件,WPF中還有非路由事件,這個專題介紹一下WPF中所有的事件。
都有哪些事件
- 生命週期事件,元素的初始化,加載,卸載時發生
- 鼠標事件
- 鍵盤事件
- 手寫筆事件
- 多點觸控事件
下面就逐個介紹這些事件,大家可以先做一個簡單的瞭解,當要深入使用這些事件的時候查詢這篇文章即可
生命週期事件
一個元素創建流程大致是這樣:
- 首先需要解析Xaml,然後初始化,再渲染樣式,應用佈局和綁定。Xaml的解析需要調用BeginInit方法,解析完成需要調用EndInit方法。我們可以西想到,如果使用代碼創建控件,則不會調用這兩個方法,所以這兩個方法是Xaml的解析器進行調用的。
- 引發Initialized方法。這時候元素已經初始化,但是還沒有應用樣式和綁定。
- 引發Loaded事件。窗口就變得可見且樣式都渲染完成,也綁定完成了。如果要在初始化之後做某些操作,在Loaded事件處理程序中處理是極好的選擇。
需要注意的是,元素的渲染是自下而上的,所以在初始化的時候,貿然訪問其父元素是不可取的,因爲其可能還沒有初始化出來,而其子元素是可以被訪問的。
元素的生命週期事件如下:
窗口的聲明週期事件如下:
輸入事件
輸入事件的參數如下圖:
InputEventArgs是在RoutedEventArgs的基礎上添加了Timestamp和Device。Timestamp用於比較兩個事件引發時間誰更早,數字更大的是更近發生的。Device用於指示輸入設備的類型(鍵盤,鼠標,手寫筆)
鍵盤事件
如果想要實現不允許用戶輸入某些字符,我們除了在綁定的數據層進行校驗,也可以直接處理事件,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對象還有下面幾個方法:
上面的方法可以在觸發鍵盤事件的時候判斷按鍵是否按下,如果我們的觸發不是按鍵,而是後臺的一個事件,這時候我們就無法拿到KeyboardDevice,這時候應該怎麼判斷呢,答案是使用Keyboard靜態類來判斷
if (Keyboard.IsKeyDown(Key.LeftShift))
{
// 按下左邊Shift
}
鼠標事件
- 鼠標進入控件是MouseEnter 鼠標離開是MouseLeave,這兩個事件都不是路由事件,都是自己引發,自己處理,不進行傳播,一個StackPanel包裹一個Button,鼠標移入,先觸發StackPanel的移入事件,再觸發按鈕的移入事件,離開的時候先觸發按鈕的離開事件,再觸發StackPanel的離開事件。
- MouseMove和PreviewMouseMove是路由事件,可以傳播,這個方法用於監控鼠標的位置,在事件處理中可以使用e.GetPosition來獲取鼠標當前位置
- 可以使用IsMouseOver判斷當前鼠標是否位於一個元素或者其子元素上面
- 可以使用IsMouseDirectlyOver判斷當前鼠標是否位於一個元素而不在其子元素上面
- 單擊事件
- 使用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事件 <TextBox AllowDrop="True" Drop="TextBox_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來說,我們可以在各個層級上對這些事件進行處理。
- 原始觸控事件
原始觸控事件提供了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確定是不是一直按住案件觸發的結果。