2010-02-17 6 views
18

XAMLWarum wird das MouseDoubleClick-Ereignis von TreeViewItem mehrmals per Doppelklick ausgelöst?

<TreeView Name="GroupView" ItemsSource="{Binding Documents}"> 
      <TreeView.ItemContainerStyle> 
       <Style TargetType="{x:Type TreeViewItem}"> 
        <EventSetter Event="MouseDoubleClick" Handler="OnTreeNodeDoubleClick"/> 
       </Style> 
      </TreeView.ItemContainerStyle> 
      .... 
</TreeView> 

-Code-Behind

private void OnTreeNodeDoubleClick(object sender, MouseButtonEventArgs mouseEvtArgs) 
     { 
      Console.WriteLine("{3} MouseDoubleClick Clicks={0} ChangedButton={1} Source={2} Handled={4} ButtonState={5}", 
       mouseEvtArgs.ClickCount, mouseEvtArgs.ChangedButton, mouseEvtArgs.OriginalSource, 
       mouseEvtArgs.Timestamp, mouseEvtArgs.Handled, mouseEvtArgs.ButtonState); 
     } 

finde ich, dass für einen Doppelklick, werden die Event-Handler mehrmals aufgerufen. Ich versuche, ein Dokument in der Registerkarte mit einem Doppelklick auf den entsprechenden Baumknoten zu öffnen; also müsste ich die zusätzlichen Anrufe herausfiltern.

23479156 MouseDoubleClick Clicks=1 ChangedButton=Left Source=System.Windows.Controls.TextBlock Handled=False ButtonState=Pressed 
23479156 MouseDoubleClick Clicks=1 ChangedButton=Left Source=System.Windows.Controls.TextBlock Handled=False ButtonState=Pressed 

In meiner etwas komplizierten App wird es 4 Mal pro Doppelklick ausgelöst. Auf einer einfachen Repro-App wird sie zweimal pro Doppelklick erhöht. Auch alle Parameter des Ereignisarguments sind gleich, daher kann ich den letzten Parameter nicht unterscheiden.

Irgendwelche Ideen, warum das so ist?

+0

Verwenden Sie die Strukturansicht in UpdatePanel? – Kangkan

+0

@Kangkan: Nein. Dies ist keine Web-App; eine einfache Desktop-App. – Gishu

+1

Ich hatte das gleiche Problem einmal, habe es nie herausgefunden. Ich habe den doubleclick-Ereignishandler in der Baumansicht installiert (statt in den treeviewitems) und nur die selecteditem-Eigenschaft verwendet ... –

Antwort

-1

Der wahrscheinlichste Grund ist, dass der doubleclick-Handler mehrfach installiert ist, sodass jede Instanz des Handlers einmal für jeden Klick aufgerufen wird.

+0

@John - hat in allen Dateien gefunden, der einzige Ort, an dem OnTreeNodeDoubleClick in der Style-Definition – Gishu

0

Dies ist die wunderbare Welt der Veranstaltung sprudelt. Das Ereignis sprudelt die Knotenhierarchie Ihres TreeViews auf und Ihr Handler wird einmal für jeden Knoten im Hierarchiepfad aufgerufen.

Verwenden Sie einfach so etwas wie

 // ... 
     if (sender != this) 
     { 
      return; 
     } 
     // Your handler code goes here ... 
     args.Handled = true; 
     // ... 

in Ihrem Handler-Code.

+0

Bubbling erwähnt wird würde * verschiedene Handler * veranlassen, die Benutzeroberflächenhierarchie für ein Ereignis aufzurufen. Es sollte ** nicht ** dazu führen, dass derselbe Handler mehrmals pro Ereignis aufgerufen wird. – Gishu

+0

Eine andere Sache zu Ihren identischen Handler-Parametern. Es spielt keine Rolle, welches Ereignis Sie abonnieren, der Sender ist immer das TreeView. Der echte Absender ist im Parameter RoutedEvent.OriginalSource verborgen. Je nach Ereignis ist es manchmal das TreeViewItem, aber für Mausereignisse ist es der Kontrolltyp, der in der TreeViewItem (Hierarchical) DataTemplate definiert ist, auf den der Benutzer geklickt hat. Wahrscheinlich hat MS die FrameworkElement-Implementierung nicht überschrieben. – banzai

4

Dies ist nicht wirklich ein sprudelndes Problem. Ich habe das schon mal gesehen. Selbst wenn du dem Ereignis mitteilst, dass du es gehandhabt hast, brodelt es weiter. Außer, dass ich nicht glaube, dass es tatsächlich sprudelt, sondern eher den eigenen Doppelklick-Event des Knotens auslöst. Da könnte ich mich total irren. Aber in jedem Fall ist es wichtig, zu wissen, dass zu sagen:

e.handled = true; 

Ist dies nichts zu stoppen passiert.

Eine Möglichkeit, dieses Verhalten zu verhindern, besteht darin, zu beachten, dass Sie beim ersten Doppelklicken zuerst klicken und das ausgewählte Ereignis zuerst ausgelöst werden soll. Obwohl Sie die Double-Click-Ereignisse nicht stoppen können, sollten Sie innerhalb des Handlers überprüfen können, ob die Ereignislogik ausgeführt werden soll. Dieses Beispiel nutzt die:

TreeViewItem selectedNode; 

private void MouseDoubleClickEventHandler(object sender, MouseButtonEventArgs e) 
{ 
    if(selectedNode = e.Source) 
    { 
     //do event logic 
    } 
} 

private void TreeViewSelectedEventHandler(object sender, RoutedEventArgs e) 
{ 
    selectedNode = (TreeViewItem)e.Source; 
} 

Manchmal aber Sie Situationen, wo die Knoten von anderen Bohnen als durch das TreeView SelectedItemChanged Ereignis ausgewählt werden. In diesem Fall können Sie so etwas tun. Wenn Sie ein TreeView mit einem einzigen deklarierten obersten Knoten haben passieren, können Sie diesen Knoten einen bestimmten Namen geben und dann so etwas tun:

bool TreeViewItemDoubleClickhandled; 

private void MouseDoubleClickEventHandler(object sender, MouseButtonEventArgs e) 
{ 
    if (!TreeViewItemDoubleClickhandled) 
    { 
     //do logic here 

     TreeViewItemDoubleClickhandled = true; 
    } 

    if (e.Source == tviLoadTreeTop) 
    { 
     TreeViewItemDoubleClickhandled = false; 
    } 
    e.Handled = true; 
} 

Unabhängig von der Methode, die Sie verwenden, ist die wichtige Sache zu beachten, dass aus irgendeinem Grund mit TreeViewItem Doppelklick, dass Sie nicht stoppen können die Ereignisse aus dem Baum auslösen. Zumindest habe ich keinen Weg gefunden.

+0

Spot auf! Die Tatsache, dass "e.Handled" auf "true" gesetzt wurde, hat keinen Effekt. Es ist ein Fehler in WPF. Ich werde meine eigene Antwort mit weiteren Details hinzufügen. –

15

Wenn ein TreeViewItem doppelt geklickt wird, wird dieses Element als Teil des Steuerverhaltens ausgewählt.In Abhängigkeit vom jeweiligen Szenario könnte es möglich sein, zu sagen:

... 
TreeViewItem tviSender = sender as TreeViewItem; 

if (tviSender.IsSelected) 
    DoAction(); 
... 
20

Ich weiß, das ist eine alte Frage, aber wie ich über sie in meiner Suche nach der Lösung kam, hier sind meine Erkenntnisse für zukünftige Besucher!

TreeViewItem s sind rekursiv ineinander enthalten. TreeViewItem ist ein HeaderedContentControl (siehe msdn), mit den untergeordneten Knoten als Content. Die Grenzen aller TreeViewItem umfassen also alle untergeordneten Elemente. Dies kann mit dem ausgezeichneten WPF Inspector überprüft werden, indem ein TreeViewItem im visuellen Baum ausgewählt wird, der die Grenzen des TreeViewItem hervorhebt.

Im Beispiel des OP ist das Ereignis MouseDoubleClick unter Verwendung des Styles auf jedem TreeViewItem registriert. Daher wird das Ereignis für die TreeViewItem s, auf die Sie doppelklickten - und jedes seiner übergeordneten Elemente - separat ausgelöst. Dies kann in Ihrem Debugger verifiziert werden, indem Sie einen Haltepunkt in Ihren Doppelklick-Ereignishandler setzen und eine Überwachung auf die Source-Eigenschaft der Ereignisargumente setzen - Sie werden feststellen, dass sie sich jedes Mal ändert, wenn der Event-Handler aufgerufen wird. Übrigens, wie zu erwarten ist, bleibt die OriginalSource des Ereignisses gleich.

dieses unerwartete Verhalten zu begegnen, ob die Quelle Überprüfung TreeViewItem ausgewählt wird, wie von Pablo in seiner Antwort vorgeschlagen hat, das Beste für mich gearbeitet.

6
private void TreeView_OnItemMouseDoubleClick(object sender, MouseButtonEventArgs e) 
{ 
    if (e.Source is TreeViewItem 
     && (e.Source as TreeViewItem).IsSelected) 
    { 
     // your code 
     e.Handled = true; 
    } 
} 
1

Ich habe etwas elegantere Lösung als für die Auswahl überprüft oder das Erstellen von Fahnen:

Eine Hilfsmethode:

public static object GetParent(this DependencyObject obj, Type expectedType) { 

    var parent = VisualTreeHelper.GetParent(obj); 
    while (parent != null && parent.GetType() != expectedType) 
     parent = VisualTreeHelper.GetParent(parent); 

    return parent; 
} 

Und Ihre Handler dann:

+0

Sie sollten angeben, dass "OriginalSource" die ursprüngliche Berichtsquelle ist, die durch reine Treffertests ermittelt wurde. Wenn also der Ereignissender mit dem übergeordneten 'TreeViewItem' identisch ist, werden wir direkt auf den Knoten geklickt. – Maxence

7

Ich habe etwas Debuggen gemacht und es scheint ein Bug in WPF zu sein. Die meisten Antworten sind bereits korrekt und die Problemumgehung besteht darin, zu überprüfen, ob das Baumansichtselement ausgewählt ist.

@ Antwort ristogod das ist die am nächsten an der Wurzel Problem - es wird erwähnt, dass e.Handled = true Einstellung das erste Mal Handler aufgerufen wird, nicht den gewünschten Effekt haben und das Ereignis weiter sprudeln, Eltern TreeViewItem s'Handler aufrufen (wo e.Handled ist wieder false).

Der Fehler scheint in diesem Code in WPF zu sein: http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Control.cs,5ed30e0aec6a58b2

Er empfängt das MouseLeftButtonDown Ereignis (die bereits durch das Kind Kontrolle behandelt wird), aber es funktioniert nicht überprüfen, ob e.Handled bereits auf true gesetzt ist. Dann fährt es fort, einen neuen MouseDoubleClick Ereignisargus zu erstellen (mit e.Handled == false) und ruft das immer auf.

Die Frage bleibt auch, warum, nachdem es zum ersten Mal behandelt wurde, das Ereignis weiter blase? Da in dieser Linie, wenn wir die Handler registrieren Control.HandleDoubleClick: http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Control.cs,40

wir wahr, wie das letzte Argument zu RegisterClassHandler passieren: http://referencesource.microsoft.com/#PresentationCore/Core/CSharp/System/Windows/EventManager.cs,161 das ist handledEventsToo.

Also das unglückliche Verhalten ist ein Zusammenfluss von zwei Faktoren ab:

  1. Control.HandleDoubleClick immer genannt wird (für behandelte Ereignisse zu), und
  2. Control.HandleDoubleClick nicht überprüfen, ob das Ereignis bereits
  3. behandelt worden war

Ich werde das WPF-Team benachrichtigen, aber ich bin mir nicht sicher, ob dieser Fehler behoben werden kann, da er bestehende Apps zerstören kann (die auf das aktuelle Verhalten von Event-Handlern angewiesen sind, selbst wenn Handled se war) t von einem vorherigen Handler zu true).

0

Es gibt einige ziemlich große Probleme mit dieser Lösung, aber es könnte funktionieren, falls jemand dieses Problem an mehreren Stellen lösen muss und ich ein Szenario gefunden habe, in dem die akzeptierte Lösung nicht funktioniert (Doppelklick auf einen Schalter) dass öffnet sich ein Popup, wo der Umschaltknopf in einem anderen Element ist, das Doppelklick Griffe)

public class DoubleClickEventHandlingTool 

{ Privat const string DoubleClickEventHandled = "DoubleClickEventHandled.";

public static void HandleDoubleClickEvent() 
{ 
    Application.Current.Properties[DoubleClickEventHandled] = DateTime.Now.AddSeconds(1); 
} 

public static bool IsDoubleClickEventHandled() 
{ 
    var doubleClickWasHandled = Application.Current.Properties[DoubleClickEventHandled] as DateTime?; 

    return doubleClickWasHandled.HasValue && !IsDateTimeExpired(doubleClickWasHandled.Value); 
} 

private static bool IsDateTimeExpired(DateTime value) 
{ 
    return value < DateTime.Now; 
} 

public static void EnableDoubleClickHandling() 
{ 
    Application.Current.Properties[DoubleClickEventHandled] = null; 
} 

public static bool IsDoubleClickEventHandledAndEnableHandling() 
{ 
    var handled = IsDoubleClickEventHandled(); 
    EnableDoubleClickHandling(); 

    return handled; 
} 

}

Verwenden DoubleClickEventHandlingTool.HandleDoubleClickEvent() innerhalb des inneren/Low Level-Element zB:

private void OnPreviewMouseDown(object sender, MouseButtonEventArgs e) 
{if (e.ClickCount == 2) DoubleClickEventHandlingTool.HandleDoubleClickEvent();} 

Hohe Doppelklick Ereignis führt dann nur es Aktion, wenn:

if (!DoubleClickEventHandlingTool.IsDoubleClickEventHandledAndEnableHandling()) 
Verwandte Themen