2009年9月18日 星期五

在WPF中滑鼠事件(Event)傳遞 - 按鈕(Button)篇

在WPF中滑鼠事件(Event)傳遞中我們提到基本的滑鼠事件傳遞
但是如果這時候視窗中有一個按鈕(Button),例如

<Window ... Name="Win">
<Grid Name="Grd">
<Button Name="Btn">
<Rectangle Name="Rec" />
</Button>
</Grid>
</Window>

同樣將所有的滑鼠事件都套用到相同的事件處理函式
依照在WPF中滑鼠事件(Event)傳遞所作的設定
出來的結果會是

Win : PreviewMouseLeftButtonDown
Win : PreviewMouseDown
Grd : PreviewMouseLeftButtonDown
Grd : PreviewMouseDown
Btn : PreviewMouseLeftButtonDown
Btn : PreviewMouseDown
Rec : PreviewMouseLeftButtonDown
Rec : PreviewMouseDown
Rec : MouseLeftButtonDown
Rec : MouseDown
Win : PreviewMouseLeftButtonUp
Win : PreviewMouseUp
Grd : PreviewMouseLeftButtonUp
Grd : PreviewMouseUp
Btn : PreviewMouseLeftButtonUp
Btn : PreviewMouseUp
Btn : Click

我們這時候會觀察到一個奇怪的現像
就是在Down的過程時,preview的事件都還是出來了
不過在Rec回傳給Btn之後,MouseDown事件就不見了
而Up的地方更奇怪
Btn在preview完後,竟然直接出現了Click
而Rec甚至連preview都沒有收到

這邊的解釋是,在Down的時候,preview的事件順利的傳遞到了Rec
而在回傳的時候,因為Btn發現這是左鍵的Down
所以將這個事件標示為已處理(handled)再回傳
而之後Grd和Win再收到的就是已處理過的事件,所以他們就不會再做動作

而在Up的階段,preview的事件傳到了Btn的時候
他就已經發現現在應該是Click發生的時候了
所以他就處理Click的事件,然後一樣標示成已處理並直接回傳
所以Rec就什麼都收不到,而上層的Grd和Win則是收到已處理過的事件
所以它們也不再做動作了

如果仍然希望已處理的事件可以觸發事件處理
那就要用UIElement.AddHandler()來加入事件處理,例如

Btn.AddHandler(
/// the event we want to handle
Button.MouseLeftButtonDownEvent,
/// the event handler be set
new MouseButtonEventHandler(MouseEH),
/// handle the evnet even if it is handled
true);

這裡第三個參數是handledEventsToo,是否需要處理已處理事件
在一般的加入事件處理(不論XAML或C#)會採用預設值false
而我們因為希望即使是已處理事件也要觸發事件,所以設成true

如果將所有元件的MouseLeftButtonDown, MouseDown,
MouseLeftButtonUp, MouseUp都利用這種方式來加入事件處理
那輸出結果會變成

Win : PreviewMouseLeftButtonDown
Win : PreviewMouseDown
Grd : PreviewMouseLeftButtonDown
Grd : PreviewMouseDown
Btn : PreviewMouseLeftButtonDown
Btn : PreviewMouseDown
Rec : PreviewMouseLeftButtonDown
Rec : PreviewMouseDown
Rec : MouseLeftButtonDown
Rec : MouseDown
Btn : MouseLeftButtonDown
Btn : MouseDown
Grd : MouseLeftButtonDown
Grd : MouseDown
Win : MouseLeftButtonDown
Win : MouseDown
Win : PreviewMouseLeftButtonUp
Win : PreviewMouseUp
Grd : PreviewMouseLeftButtonUp
Grd : PreviewMouseUp
Btn : PreviewMouseLeftButtonUp
Btn : PreviewMouseUp
Btn : Click
Btn : MouseLeftButtonUp
Btn : MouseUp
Grd : MouseLeftButtonUp
Grd : MouseUp
Win : MouseLeftButtonUp
Win : MouseUp

記得,如果這時候原本的事件處理仍然有寫的話
那就會變成同時加入只處理未處理事件,以及處理所有事件的兩個事件處理
所以Rec的Down就會重複兩次(因為每次加入都會視為不同的delegate)

--
參考資料
wpf button 事件的觸發順序
UIElement.AddHandler 方法 (RoutedEvent, Delegate, Boolean)

沒有留言:

張貼留言