2010-02-17 5 views
18

XAMLPerché l'evento MouseDoubleClick di TreeViewItem viene generato più volte per doppio clic?

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

codice sottostante

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); 
     } 

trovo che per un doppio click, il gestore di eventi viene chiamato più volte. Sto cercando di aprire un documento in una scheda con un doppio clic sul nodo dell'albero corrispondente; quindi avrei bisogno di filtrare le chiamate extra.

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 

Nella mia app leggermente complicata, viene generato 4 volte per doppio clic. Su una semplice ripro-app, viene sollevato 2 volte per doppio clic. Anche tutti i parametri degli argomenti degli eventi sono uguali, quindi non riesco a distinguere l'ultimo di un set.

Qualche idea del perché è così?

+0

Stai usando la visualizzazione ad albero all'interno di UpdatePanel? – Kangkan

+0

@Kangkan: No. Questa non è una web-app; una semplice app per desktop. – Gishu

+1

Ho avuto lo stesso problema una volta, non l'ho mai capito. Ho installato il gestore di eventi doubleclick su treeview (invece che su treeviewitems) e ho appena utilizzato la proprietà selecteditem ... –

risposta

-1

Il motivo più probabile è che il gestore doubleclick sia installato più volte, quindi ogni istanza del gestore viene chiamata una volta per ogni clic.

+0

@John - ha effettuato una ricerca in tutti i file, l'unico posto in cui OnTreeNodeDoubleClick è menzionato nella definizione di stile – Gishu

0

Questo è il meraviglioso mondo dell'evento che bolle. L'evento sta risalendo la gerarchia dei nodi del tuo TreeView e il tuo gestore è chiamato una volta per ogni nodo nel percorso della gerarchia.

Proprio usare qualcosa come

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

nel codice gestore.

+0

Bubbling causerebbe * gestori diversi * sulla gerarchia degli elementi dell'interfaccia utente da invocare per un evento. Dovrebbe ** non ** far sì che lo stesso gestore venga chiamato più volte per evento. – Gishu

+0

Un'altra cosa per i parametri identici del gestore. Non importa a quale evento ti iscrivi, il mittente è sempre TreeView. Il vero mittente è nascosto nel parametro RoutedEvent.OriginalSource. A seconda dell'evento, a volte è TreeViewItem ma per gli eventi del mouse è il tipo di controllo definito nel DataTemplate TreeViewItem (Hierarchical) su cui l'utente ha fatto clic. Probabilmente MS non ha sovrascritto l'implementazione FrameworkElement. – banzai

4

Questo non è in realtà un problema bubbling. L'ho visto prima. Anche quando dici che l'evento è stato gestito, continua a continuare a ribollire. Tranne che non penso che stia davvero esplodendo, ma piuttosto sparando al doppio click dell'evento sopra il nodo. Potrei essere totalmente sbagliato su questo. Ma in entrambi i casi, è importante sapere che dire:

e.handled = true; 

non fa nulla per evitare che questo succeda.

Un modo per evitare questo comportamento è notare che quando si fa doppio clic, si esegue il primo clic singolo e l'evento selezionato deve essere attivato per primo. Pertanto, anche se non è possibile interrompere il verificarsi degli eventi Double Click, è possibile controllare all'interno del gestore per verificare se la logica evento deve essere eseguita. Questo esempio fa leva che:

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; 
} 

volte però avete situazioni in cui i nodi vengono selezionati da altri fagioli che attraverso l'evento TreeView SelectedItemChanged. In tal caso puoi fare qualcosa di simile. Se vi capita di avere un controllo TreeView con un singolo nodo principale dichiarato, si può dare quel nodo un nome specifico e quindi fare qualcosa del genere:

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; 
} 

Indipendentemente dal metodo utilizzato, la cosa importante è notare che per qualsiasi ragione con il doppio click di TreeViewItem non è possibile impedire agli eventi di sparare all'albero. Almeno non ho trovato un modo.

+0

Spot on! Il fatto che l'impostazione e.Handled su true non abbia alcun effetto previsto è un bug in WPF. Aggiungerò la mia risposta con maggiori dettagli. –

15

Quando si fa doppio clic su TreeViewItem, quell'elemento viene selezionato come parte del comportamento di controllo.A seconda del particolare scenario potrebbe essere possibile dire:

... 
TreeViewItem tviSender = sender as TreeViewItem; 

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

So che questa è una vecchia questione, ma come mi sono imbattuto nelle mie ricerche per la soluzione, qui sono i miei risultati per tutti i futuri visitatori!

TreeViewItem s sono ricorsivamente contenuti l'uno nell'altro. TreeViewItem è un HeaderedContentControl (vedere msdn), con i nodi secondari come Content. Quindi, ogni limite di TreeViewItem include tutti i suoi elementi figli. Questo può essere verificato usando l'eccellente WPF Inspector selezionando uno TreeViewItem nell'albero visivo, che evidenzi i limiti dello TreeViewItem.

Nell'esempio dell'OP, l'evento MouseDoubleClick è registrato su ogni TreeViewItem utilizzando lo stile. Pertanto, l'evento verrà innalzato per gli TreeViewItem s su cui si è fatto doppio clic, e ciascuno dei relativi elementi principali, separatamente. Questo può essere verificato nel tuo debugger inserendo un breakpoint nel tuo gestore di eventi con doppio clic e mettendo un watch sulla proprietà Source degli argomenti degli eventi - noterai che cambia ogni volta che viene chiamato il gestore di eventi. Per inciso, come ci si può aspettare, lo OriginalSource dell'evento rimane lo stesso.

Per contrastare questo comportamento imprevisto, verificare se la sorgente TreeViewItem è selezionata, come suggerito da Pablo nella sua risposta, ha funzionato al meglio per me.

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

devo soluzione un po 'più elegante di controllo per la selezione o la creazione di bandiere:

un metodo di supporto:

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; 
} 

E poi il tuo gestore:

+0

È necessario specificare che 'OriginalSource' è l'origine dei rapporti originale determinata dal puro test di hit, quindi se il mittente dell'evento è uguale al' TreeViewItem padre ', si fa clic sul nodo direttamente. – Maxence

7

Ho fatto un po 'di debug e sembra che ci sia un bug in WPF. La maggior parte delle risposte già fornite sono corrette e la soluzione è controllare se l'elemento della vista ad albero è selezionato.

@ risposta di ristogod è più vicino al problema alla radice - si parla che l'impostazione e.Handled = true il primo gestore volta che viene invocato non ha l'effetto desiderato e l'evento continua a ribollire, chiamando i gestori genitore TreeViewItem s'(dove e.Handled è false di nuovo).

Il bug sembra essere in questo codice in WPF: http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Control.cs,5ed30e0aec6a58b2

che riceve l'evento MouseLeftButtonDown (che è gestito dal controllo figlio già), ma non riesce a controllare se e.Handled è già impostato a true. Quindi procede alla creazione di un nuovo argomento evento MouseDoubleClick (con e.Handled == false) e lo invoca sempre.

La domanda rimane anche perché dopo averlo impostato per gestire la prima volta l'evento continua a scoppiare? Perché in questa linea, quando si registra il gestore Control.HandleDoubleClick: http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Control.cs,40

passiamo vero come ultimo argomento a RegisterClassHandler: http://referencesource.microsoft.com/#PresentationCore/Core/CSharp/System/Windows/EventManager.cs,161 quali è handledEventsToo.

Così il comportamento spiacevole è una confluenza di due fattori:

  1. Control.HandleDoubleClick si chiama sempre (per eventi gestiti troppo), e
  2. Control.HandleDoubleClick non riesce a controllare se l'evento era già stato maneggiato

Informerò il team WPF ma non sono sicuro che valga la pena di correggere questo bug perché potrebbe interrompere le app esistenti (che si affidano al comportamento corrente dei gestori di eventi chiamati anche se Handled t a vero da un precedente gestore).

0

Ci sono alcuni problemi piuttosto importanti con questa soluzione, ma potrebbe funzionare nel caso qualcuno abbia bisogno di risolvere questo problema in più punti e ho trovato uno scenario in cui la soluzione accettata non funziona (doppio clic su un pulsante di attivazione/disattivazione che apre un popup, in cui il pulsante di commutazione è all'interno di un altro elemento che gestisce doppio clic)

public class DoubleClickEventHandlingTool 

{ const string privato 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; 
} 

}

Usa DoubleClickEventHandlingTool.HandleDoubleClickEvent() all'interno della interno/basso elemento di livello ad esempio:

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

alto livello doppio click evento, allora esegue solo è azione quando:

if (!DoubleClickEventHandlingTool.IsDoubleClickEventHandledAndEnableHandling()) 
Problemi correlati