2011-06-08 107 views
16

我的場景簡化了:我有一個包含Employees行的ListView,並且在每個Employee行中都有按鈕「Increase」和「Decrease」來調整他的薪水。MouseDoubleClick事件不會冒泡

假裝在我的程序中,雙擊一個Employee行意味着「激發這個人」。

問題是當我點擊「增加」時,這會觸發ListViewItem上的雙擊事件。當然,我只是在增加薪水時不想解僱人員。

根據所有其他事件的工作方式,我希望能夠通過設置Handled=true來解決這個問題。但是,這不起作用。在我看來,WPF會生成兩個單獨的,完全不關聯的雙擊事件。

以下是重現我的問題的最小示例。可見組件:

<ListView> 
    <ListViewItem MouseDoubleClick="ListViewItem_MouseDoubleClick"> 
      <Button MouseDoubleClick="Button_MouseDoubleClick"/> 
    </ListViewItem> 
</ListView> 

而且處理代碼:

private void Button_MouseDoubleClick(object s, MouseButtonEventArgs e) { 
    if (!e.Handled) MessageBox.Show("Button got unhandled doubleclick."); 
    e.Handled = true; 
} 

private void ListViewItem_MouseDoubleClick(object s, MouseButtonEventArgs e) { 
    if (!e.Handled) MessageBox.Show("ListViewItem got unhandled doubleclick."); 
    e.Handled = true; 
} 

發射了這個程序,雙擊上市按鈕後,消息框依次顯示出來。 (另外,按鈕被卡在向下的位置在這之後。)

的是「固定」我可以,在ListViewItem的處理程序,檢查連接到該事件的可視化樹,並檢查「有一個按鈕某處「,因此放棄該事件,但這是最後的手段。我想在編寫這樣一個kludge之前至少了解這個問題。

有沒有人知道爲什麼 WPF做到了這一點,並採用了優雅的慣用方法來避免這個問題?

回答

10

我想你會發現MouseDoubleClick事件是MouseDown事件之上的抽象事件。也就是說,如果兩個MouseDown事件發生的速度足夠快,MouseDoubleClick事件也將被提出。 ButtonListViewItem似乎都有這個邏輯,所以這就解釋了爲什麼你會看到兩個不同的事件MouseDoubleClick

MSDN

雖然這個路由事件似乎 遵循冒泡的路線,通過一個 元素樹,它實際上是由每個的UIElement沿 元素樹提出了一個直接 路由事件。 如果您在 MouseDoubleClick事件處理程序 中將Handled屬性設置爲true,則沿路線的後續MouseDoubleClick事件 將與 一起發生設置爲false。

你可以嘗試在Button處理MouseDown和設置,要處理,使得它不會傳播到ListViewItem

希望我可以自己驗證一下,但我現在沒有.NET。

+0

謝謝,但我無法完全符合這個要求。 Button上的MouseDown/MouseLeftButtonDown處理函數(或Button上的容器包裝函數)永遠不會被觸發,所以看起來事件已經被按鈕處理了。另外,我無法從ListViewItem的MouseDown/MouseLeftButtonDown中推斷出雙擊,因爲這些事件是由ListViewItem內容(如文本框)中的其他可視組件處理和吞食的。 – Deestan 2011-06-08 14:22:47

3

由於沒有出現過明確回答這個問題,這是我最後使用的解決方法:

protected override void ListViewItem_MouseDoubleClick(MouseButtonEventArgs e) { 
    var originalSource = e.OriginalSource as System.Windows.Media.Visual; 
    if (originalSource.IsDescendantOf(this)) { 
     // Test for IsDescendantOf because other event handlers can have changed 
     // the visual tree such that the actually clicked original source 
     // component is no longer in the tree. 
     // You may want to handle the "not" case differently, but for my 
     // application's UI, this makes sense. 
     for (System.Windows.DependencyObject depObj = originalSource; 
      depObj != this; 
      depObj = System.Windows.Media.VisualTreeHelper.GetParent(depObj)) 
     { 
      if (depObj is System.Windows.Controls.Primitives.ButtonBase) return; 
     } 
    } 

    MessageBox.Show("ListViewItem doubleclicked."); 
} 

的類名在這裏用不必要的文檔目的全命名空間類型。

4

那麼它可能不是優雅或習慣,但你可能會喜歡它比你目前的解決方法更好:

int handledTimestamp = 0; 

    private void ListViewItem_MouseDoubleClick(object sender, MouseButtonEventArgs e) 
    { 
     if (e.Timestamp != handledTimestamp) 
     { 
      System.Diagnostics.Debug.WriteLine("ListView at " + e.Timestamp); 
      handledTimestamp = e.Timestamp; 
     } 
     e.Handled = true; 
    } 

    private void Button_MouseDoubleClick(object sender, MouseButtonEventArgs e) 
    { 
     if (e.Timestamp != handledTimestamp) 
     { 
      System.Diagnostics.Debug.WriteLine("Button at " + e.Timestamp); 
      handledTimestamp = e.Timestamp; 
     } 
     e.Handled = true; 
    } 

奇怪的是,如果你不設置e.Handled = true這不起作用。如果你沒有設置e.Handled並在按鈕的處理程序中放置一個斷點或一個Sleep,你將在ListView的處理程序中看到延遲。 (即使沒有明確的延遲,仍然會有一些小的延遲,足以破壞它。)但是,一旦設置了e.Handled,延遲有多長時間並不重要,它們將具有相同的時間戳。我不確定這是爲什麼,我不確定這是否是您可以依賴的記錄行爲。

+0

不漂亮,但有趣。 :)由於這兩個點擊事件都來自完全相同的MouseDown操作系統窗口消息,因此時間戳可能會與此綁定。如果這在某個地方確實是有記錄的行爲,這是一個很好的解決方案。可悲的是,MSDN在這裏並沒有多少亮點。 – Deestan 2011-06-14 07:05:57

+0

測試過了。它通常工作,但是隨時某些代碼由於某種原因觸及事件處理隊列時,它會中斷。我不會推薦使用這個。 – Deestan 2011-06-18 22:59:47

+0

感謝您採取這種解決方法 - 最終使用了一個變體。正如Deestan指出的那樣,在某些情況下,時間戳是不同的(除了未標記爲已處理的情況之外)。將連續事件之間的時間間隔設置爲幾百毫秒,對我來說工作得很好。 – alexei 2012-06-15 16:55:43

8

MSDN documentation爲MouseDoubleClick確實就如何冒泡保持MouseDoubleClick事件的建議:誰想要處理 鼠標雙擊應使用 MouseLeftButtonDown事件時 ClickCount的是

控制作者等於兩個。這將 導致處理狀態 在 樹中處理該事件的元素 中的情況下適當地傳播。

因此,如果ClickCount爲2,則可以處理MouseLeftButtonDown事件並將掛起設置爲true。但是這在按鈕上失敗,因爲它們已經處理了MouseLeftButtonDown並且不會引發該事件。

但仍然存在PreviewMouseLeftButtonDown事件。請使用您的按鈕時ClickCount的等於如下兩集處理,以真:

private void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { 
    if (e.ClickCount == 2) 
     e.Handled = true; 
} 
+0

「但是仍然存在PreviewMouseLeftButtonDown事件。在按鈕上使用它來處理爲true」 - 這不起作用,因爲Preview-events首先通過ListViewItem * *。 – Deestan 2011-06-14 06:55:27

+0

@Deestan,是的,但PreviewMouseLeftButtonDown將在PreviewMouseDoubleClick之前。 ListViewItem只是尋找後者,所以你可以處理前者來阻止它。 – 2011-06-17 19:21:24

+0

@Robert Levy:啊,我明白了。是的,那種工作。不過,它會產生視覺故障,因爲如果在雙擊後按住按鈕,則按鈕不會熄滅。此外,它只會偶然地運作* - 我們沒有遵循任何記錄的行爲。 – Deestan 2011-06-18 22:49:06

2

Control.MouseDoubleClick是不是泡沫事件,而是一個直接的事件。

由於檢查與Snoop這個問題,這是用於瀏覽視覺樹和路由事件的工具,我看到「ListView的」和「一個ListBoxItem」的Control.MouseDoubleClick事件在同一時間發射。你可以用這個Snoop工具來檢查。


首先,找到一個答案,它是需要檢查MouseDoublClick的兩個事件參數相同的對象。你會期望它們是同一個對象。如果它是真的,那麼你的問題就很奇怪,但它們不是同一個實例。我們可以用以下代碼檢查它。

RoutedEventArgs _eventArg; 
private void Button_MouseDoubleClick(object s, RoutedEventArgs e) 
{ 
    if (!e.Handled) Debug.WriteLine("Button got unhandled doubleclick."); 
    //e.Handled = true; 
    _eventArg = e; 
} 

private void ListViewItem_MouseDoubleClick(object s, RoutedEventArgs e) 
{ 
    if (!e.Handled) Debug.WriteLine("ListViewItem got unhandled doubleclick."); 
    e.Handled = true; 
    if (_eventArg != null) 
    { 
     var result = _eventArg.Equals(e); 
     Debug.WriteLine(result); 
    } 
} 

這意味着MouseDoublClick的事件參數是在某處新建的,但它爲什麼是我不深刻理解。

爲了更清楚起見,讓我們檢查BottonBase.Click的事件參數。它將返回檢查相同實例的真實情況。

<ListView> 
     <ListViewItem ButtonBase.Click="ListViewItem_MouseDoubleClick"> 
      <Button Click="Button_MouseDoubleClick" Content="click"/> 
     </ListViewItem> 
    </ListView> 

如果您只關注如上所述的執行過程,將會有很多解決方案。如上所述,我認爲使用國旗(_eventArg)也是不錯的選擇。

-1
  1. 您不能輕易改變雙擊事件被觸發的方式,因爲它們取決於用戶設置,並且延遲是在控制面板中自定義的。
  2. 您應該結賬RepeatButton,它允許您按下按鈕並在按下按鈕時按常規順序生成多個點擊事件。
  3. 如果你想自定義事件冒泡,那麼你應該搜索預覽事件,它允許你阻止事件的傳播。 What are WPF Preview Events?
+1

不回答這個問題 – 2011-06-17 19:19:29

1

我剛剛遇到了同樣的問題。有一個簡單但不明顯的解決方案。

這裏是點擊雙重如何通過控制提出....

private static void HandleDoubleClick(object sender, MouseButtonEventArgs e) 
{ 
    if (e.ClickCount == 2) 
    { 
     Control control = (Control)sender; 
     MouseButtonEventArgs mouseButtonEventArgs = new MouseButtonEventArgs(e.MouseDevice, e.Timestamp, e.ChangedButton, e.StylusDevice); 
     if (e.RoutedEvent == UIElement.PreviewMouseLeftButtonDownEvent || e.RoutedEvent == UIElement.PreviewMouseRightButtonDownEvent) 
     { 
      mouseButtonEventArgs.RoutedEvent = Control.PreviewMouseDoubleClickEvent; 
      mouseButtonEventArgs.Source = e.OriginalSource; 
      mouseButtonEventArgs.OverrideSource(e.Source); 
      control.OnPreviewMouseDoubleClick(mouseButtonEventArgs); 
     } 
     else 
     { 
      mouseButtonEventArgs.RoutedEvent = Control.MouseDoubleClickEvent; 
      mouseButtonEventArgs.Source = e.OriginalSource; 
      mouseButtonEventArgs.OverrideSource(e.Source); 
      control.OnMouseDoubleClick(mouseButtonEventArgs); 
     } 
     if (mouseButtonEventArgs.Handled) 
     { 
      e.Handled = true; 
     } 
    } 
} 

所以,如果你處理PreviewMouseDoubleClick對孩子控制MouseDoubleClick設置e.Handled = true將不會觸發父控件。