1. 程式人生 > >WPF RoutedEvent and HitTest - 簡書

WPF RoutedEvent and HitTest - 簡書

知識點 開源 graphic param ref lin sta edev orien

原文:WPF RoutedEvent and HitTest - 簡書

學習的時候切忌心浮氣躁,慢慢的過每一個知識點,不要漏掉任何細節。不然當遇到細節問題的時候,會惱,會鬧,會悔不該當初——花一下午調bug最後只改了一個參數有感。

相信很多用過WPF的人都知道WPF中的路由事件。一般看書的話,這個知識點也會在前幾章講到。總的來說,也就是

  • WPF的控件都存在於一顆Visual tree當中。
  • 事件在控件中的傳遞,其實也就是事件在Visual tree中的傳遞
  • 隧道事件從上往下,由樹根開始向葉子傳播
  • 冒泡事件從下往上,由子節點開始向根傳播

假設我們有一個Visual tree長這樣:

MainWindow    
|_Border
  |_Grid
    |_TextBlock

那麽如果用戶點擊了TextBlock。那麽會產生什麽事件,然後會怎麽傳遞呢?
答案是

  1. 會產生PreviewMouseDownMouseDown事件
  2. PreviewMouseDown是隧道事件,事件的順序是MainWindow->Border->Grid->TextBlock
  3. MouseDown是冒泡事件,事件的順序與之前相反,是TextBlock->Grid->Border->MainWindow

Tips:
如何查看WPF中的事件?有一個開源工具snoop可以幫助你。下圖是一個實際示例,UI結構以及操作和上述一致。

技術分享圖片 example

好,我們再看一個例子。

MainWindow    
|_Border
  |_Grid
    |_TextBlock(Margin="32")

和上個例子不同的地方在於,我們把TextBlock的邊距擴大了。這就意味著,我們可以點擊在TextBlock的邊距上,那麽會發生什麽呢?先自己想想哦。

技術分享圖片 正確答案

註意這裏的border是window中自帶的,不是我們自己聲明的。所以正確答案是,只傳播到了MainWindow

。為了區別,我給聲明的Border隨便起了個名字。

要是答對了的同學,那不是一般的棒!
我們這裏有兩個問題:

  1. PreviewMouseDown只傳遞到了MainWindow。作為一個隧道事件,沒有繼續往下傳遞。
  2. 觸發事件的是MainWindow,不是Border也不是Grid

先解答第一個問題。路由事件的準確觸發順序應該是

  1. 隧道事件從根開始,傳播到產生事件的控件為止。如果中間有控件處理(e.Handled = true)掉,就停止傳播。
  2. 冒泡事件從產生事件的控件開始,傳播到根節點為止。如果中間有控件處理(e.Handled = true)掉,就停止傳播。
  3. 系統提供的Preview事件先觸發。如果被處理(e.Handled = true)掉,不會在產生對應的冒泡事件。

第二個問題就很惱人了。總的來說就是

  • 如果沒有控件沒有被渲染,那麽該控件不能被HitTest,也不能被路由事件觸發。(參見這裏)
    也就是說,BorderGrid沒能觸發,是因為他們沒有Background,沒有被渲染。如果加上,即使你加的是TransParent,也會有效。
    技術分享圖片 添加了TransParent為Border的背景
    技術分享圖片 添加了TransParent為Grid的背景

也就是說,如果你希望下面的控件能夠觸發事件。那麽讓上面的控件不能被HitTest就可以了。我今天遇到的坑是,上面一層自己畫的一個框,用的函數是

dc.DrawGeometry(Brushes.Transparent, new Pen(brush, GraphicsLineWidth), PathGeometry);

改成

dc.DrawGeometry(null, new Pen(brush, GraphicsLineWidth), PathGeometry);

就好了。

WPF RoutedEvent and HitTest - 簡書