Selezionare TreeView Node facendo clic con il pulsante destro del mouse prima di visualizzare ContextMenu


Risposte:


130

A seconda del modo in cui l'albero è stato popolato, i valori del mittente e di e.Source possono variare .

Una delle possibili soluzioni è utilizzare e.OriginalSource e trovare TreeViewItem utilizzando VisualTreeHelper:

private void OnPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
    TreeViewItem treeViewItem = VisualUpwardSearch(e.OriginalSource as DependencyObject);

    if (treeViewItem != null)
    {
        treeViewItem.Focus();
        e.Handled = true;
    }
}

static TreeViewItem VisualUpwardSearch(DependencyObject source)
{
    while (source != null && !(source is TreeViewItem))
        source = VisualTreeHelper.GetParent(source);

    return source as TreeViewItem;
}

è questo evento per TreeView o TreeViewItem?
Louis Rhys

1
an Qualche idea su come deselezionare tutto se il clic destro è su una posizione vuota?
Louis Rhys

L'unica risposta che ha aiutato altri 5 ... Sto davvero facendo qualcosa di sbagliato con la popolazione treeview, grazie.

3
In risposta alla domanda di Louis Rhys: if (treeViewItem == null) treeView.SelectedIndex = -1o treeView.SelectedItem = null. Credo che entrambi dovrebbero funzionare.
James M,

24

Se desideri una soluzione solo XAML, puoi utilizzare Blend Interactivity.

Si supponga che i TreeViewdati siano legati a una raccolta gerarchica di modelli di visualizzazione aventi una Booleanproprietà IsSelectede una Stringproprietà Name, nonché una raccolta di elementi figlio denominati Children.

<TreeView ItemsSource="{Binding Items}">
  <TreeView.ItemContainerStyle>
    <Style TargetType="TreeViewItem">
      <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
    </Style>
  </TreeView.ItemContainerStyle>
  <TreeView.ItemTemplate>
    <HierarchicalDataTemplate ItemsSource="{Binding Children}">
      <TextBlock Text="{Binding Name}">
        <i:Interaction.Triggers>
          <i:EventTrigger EventName="PreviewMouseRightButtonDown">
            <ei:ChangePropertyAction PropertyName="IsSelected" Value="true" TargetObject="{Binding}"/>
          </i:EventTrigger>
        </i:Interaction.Triggers>
      </TextBlock>
    </HierarchicalDataTemplate>
  </TreeView.ItemTemplate>
</TreeView>

Ci sono due parti interessanti:

  1. La TreeViewItem.IsSelectedproprietà è vincolata alla IsSelectedproprietà sul modello di visualizzazione. Impostando la IsSelectedproprietà sul modello di visualizzazione su true si selezionerà il nodo corrispondente nell'albero.

  2. Quando PreviewMouseRightButtonDownsi attiva sulla parte visiva del nodo (in questo esempio a TextBlock) la IsSelectedproprietà sul modello di visualizzazione è impostata su true. Tornando a 1. si può vedere che il nodo corrispondente su cui si è fatto clic nell'albero diventa il nodo selezionato.

Un modo per ottenere Blend Interactivity nel progetto consiste nell'usare il pacchetto NuGet Unofficial.Blend.Interactivity .


2
Ottima risposta, grazie! Sarebbe utile mostrare a cosa risolvono i mapping dello spazio dei nomi ie eie in quali assembly possono essere trovati. Presumo: xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"e xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions", che si trovano rispettivamente negli assembly System.Windows.Interactivity e Microsoft.Expression.Interactions.
prlc

Ciò non ha aiutato poiché ChangePropertyActionsta cercando di impostare una IsSelectedproprietà dell'oggetto dati associato, che non fa parte dell'interfaccia utente, quindi non ha IsSelectedproprietà. Sto facendo qualcosa di sbagliato?
Antonín Procházka

@ AntonínProcházka: La mia risposta richiede che il tuo "oggetto dati" (o modello di visualizzazione) abbia una IsSelectedproprietà come indicato nel secondo paragrafo della mia risposta: supponiamo che i TreeViewdati siano legati a una raccolta gerarchica di modelli di visualizzazione aventi una proprietà booleanaIsSelected ... (la mia enfasi).
Martin Liversage

16

Utilizzando "item.Focus ();" non sembra funzionare al 100%, utilizzando "item.IsSelected = true;" fa.


Grazie per questo suggerimento. Mi ha aiutato.
i8abug

Bel consiglio. Chiamo prima Focus (), quindi imposto IsSelected = true.
Jim Gomes,

12

In XAML, aggiungi un gestore PreviewMouseRightButtonDown in XAML:

    <TreeView.ItemContainerStyle>
        <Style TargetType="{x:Type TreeViewItem}">
            <!-- We have to select the item which is right-clicked on -->
            <EventSetter Event="TreeViewItem.PreviewMouseRightButtonDown" Handler="TreeViewItem_PreviewMouseRightButtonDown"/>
        </Style>
    </TreeView.ItemContainerStyle>

Quindi gestisci l'evento in questo modo:

    private void TreeViewItem_PreviewMouseRightButtonDown( object sender, MouseEventArgs e )
    {
        TreeViewItem item = sender as TreeViewItem;
        if ( item != null )
        {
            item.Focus( );
            e.Handled = true;
        }
    }

2
Non funziona come previsto, ottengo sempre l'elemento radice come mittente. Ho trovato una soluzione simile a social.msdn.microsoft.com/Forums/en-US/wpf/thread/… I gestori di eventi aggiunti in questo modo funzionano come previsto. Qualche modifica al tuo codice per accettarlo? :-)
alex2k8

Apparentemente dipende da come si popola la visualizzazione ad albero. Il codice che ho pubblicato funziona, perché è il codice esatto che uso in uno dei miei strumenti.
Stefan,

Nota se imposti un punto di debug qui puoi vedere di che tipo è il tuo mittente che ovviamente differirà in base a come

Questa sembra la soluzione più semplice quando funziona. Ha funzionato per me. In effetti, dovresti semplicemente eseguire il cast del mittente come TreeViewItem perché se non lo è, è un bug.
giochi di artigianato

12

Utilizzando l'idea originale di alex2k8, gestendo correttamente le immagini non visive di Wieser Software Ltd, XAML di Stefan, IsSelected di Erlend e il mio contributo per rendere veramente generico il metodo statico:

XAML:

<TreeView.ItemContainerStyle> 
    <Style TargetType="{x:Type TreeViewItem}"> 
        <!-- We have to select the item which is right-clicked on --> 
        <EventSetter Event="TreeViewItem.PreviewMouseRightButtonDown"
                     Handler="TreeViewItem_PreviewMouseRightButtonDown"/> 
    </Style> 
</TreeView.ItemContainerStyle>

Codice C # dietro:

void TreeViewItem_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
    TreeViewItem treeViewItem = 
              VisualUpwardSearch<TreeViewItem>(e.OriginalSource as DependencyObject);

    if(treeViewItem != null)
    {
        treeViewItem.IsSelected = true;
        e.Handled = true;
    }
}

static T VisualUpwardSearch<T>(DependencyObject source) where T : DependencyObject
{
    DependencyObject returnVal = source;

    while(returnVal != null && !(returnVal is T))
    {
        DependencyObject tempReturnVal = null;
        if(returnVal is Visual || returnVal is Visual3D)
        {
            tempReturnVal = VisualTreeHelper.GetParent(returnVal);
        }
        if(tempReturnVal == null)
        {
            returnVal = LogicalTreeHelper.GetParent(returnVal);
        }
        else returnVal = tempReturnVal;
    }

    return returnVal as T;
}

Modifica: il codice precedente ha sempre funzionato correttamente per questo scenario, ma in un altro scenario VisualTreeHelper.GetParent ha restituito null quando LogicalTreeHelper ha restituito un valore, quindi è stato risolto.


1
Per favorire questo, questa risposta lo implementa in un'estensione DependencyProperty: stackoverflow.com/a/18032332/84522
Terrence

7

Quasi giusto , ma devi fare attenzione alle immagini non visive nell'albero, (come un Run, per esempio).

static DependencyObject VisualUpwardSearch<T>(DependencyObject source) 
{
    while (source != null && source.GetType() != typeof(T))
    {
        if (source is Visual || source is Visual3D)
        {
            source = VisualTreeHelper.GetParent(source);
        }
        else
        {
            source = LogicalTreeHelper.GetParent(source);
        }
    }
    return source; 
}

questo metodo generico sembra un po 'strano come posso usarlo quando scrivo TreeViewItem treeViewItem = VisualUpwardSearch <TreeViewItem> (e.OriginalSource as DependencyObject); mi dà un errore di conversione
Rati_Ge

TreeViewItem treeViewItem = VisualUpwardSearch <TreeViewItem> (e.OriginalSource as DependencyObject) as TreeViewItem;
Anthony Wieser

6

Penso che la registrazione di un gestore di classi dovrebbe fare il trucco. Basta registrare un gestore di eventi indirizzati su PreviewMouseRightButtonDownEvent di TreeViewItem nel file di codice app.xaml.cs in questo modo:

/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        EventManager.RegisterClassHandler(typeof(TreeViewItem), TreeViewItem.PreviewMouseRightButtonDownEvent, new RoutedEventHandler(TreeViewItem_PreviewMouseRightButtonDownEvent));

        base.OnStartup(e);
    }

    private void TreeViewItem_PreviewMouseRightButtonDownEvent(object sender, RoutedEventArgs e)
    {
        (sender as TreeViewItem).IsSelected = true;
    }
}

Ha funzionato per me! E anche semplice.
dvallejo

2
Ciao Nathan. Sembra che il codice sia globale e influenzerà ogni TreeView. Non sarebbe meglio avere una soluzione solo locale? Potrebbe creare effetti collaterali?
Eric Ouellet

Questo codice è effettivamente globale per l'intera applicazione WPF. Nel mio caso, questo era un comportamento richiesto, quindi era coerente per tutte le visualizzazioni ad albero utilizzate all'interno dell'applicazione. È tuttavia possibile registrare questo evento su un'istanza treeview stessa in modo che sia applicabile solo per quella treeview.
Nathan Swannet

2

Un altro modo per risolverlo utilizzando MVVM è il comando bind per fare clic con il pulsante destro del mouse sul modello di visualizzazione. Lì puoi specificare altra logica e source.IsSelected = true. Questo utilizza solo xmlns:i="http://schemas.microsoft.com/expression/2010/intera‌​ctivity"da System.Windows.Interactivity.

XAML per la visualizzazione:

<TreeView ItemsSource="{Binding Items}">
  <TreeView.ItemContainerStyle>
    <Style TargetType="TreeViewItem">
      <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
    </Style>
  </TreeView.ItemContainerStyle>
  <TreeView.ItemTemplate>
    <HierarchicalDataTemplate ItemsSource="{Binding Children}">
      <TextBlock Text="{Binding Name}">
        <i:Interaction.Triggers>
          <i:EventTrigger EventName="PreviewMouseRightButtonDown">
            <i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.TreeViewItemRigthClickCommand}" CommandParameter="{Binding}" />
          </i:EventTrigger>
        </i:Interaction.Triggers>
      </TextBlock>
    </HierarchicalDataTemplate>
  </TreeView.ItemTemplate>
</TreeView>

Visualizza modello:

    public ICommand TreeViewItemRigthClickCommand
    {
        get
        {
            if (_treeViewItemRigthClickCommand == null)
            {
                _treeViewItemRigthClickCommand = new RelayCommand<object>(TreeViewItemRigthClick);
            }
            return _treeViewItemRigthClickCommand;
        }
    }
    private RelayCommand<object> _treeViewItemRigthClickCommand;

    private void TreeViewItemRigthClick(object sourceItem)
    {
        if (sourceItem is Item)
        {
            (sourceItem as Item).IsSelected = true;
        }
    }

1

Ho riscontrato un problema con la selezione dei bambini con un metodo HierarchicalDataTemplate. Se selezionassi il figlio di un nodo, in qualche modo selezionerei il genitore radice di quel figlio. Ho scoperto che l'evento MouseRightButtonDown veniva chiamato per ogni livello del bambino. Ad esempio, se hai un albero simile a questo:

Elemento 1
   - Bambino 1
   - Bambino 2
      - Sottoelemento1
      - Sottoelemento2

Se selezionassi Subitem2, l'evento si sarebbe attivato tre volte e sarebbe stato selezionato l'elemento 1. Ho risolto questo problema con una chiamata booleana e una chiamata asincrona.

private bool isFirstTime = false;
    protected void TaskTreeView_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
    {
        var item = sender as TreeViewItem;
        if (item != null && isFirstTime == false)
        {
            item.Focus();
            isFirstTime = true;
            ResetRightClickAsync();
        }
    }

    private async void ResetRightClickAsync()
    {
        isFirstTime = await SetFirstTimeToFalse();
    }

    private async Task<bool> SetFirstTimeToFalse()
    {
        return await Task.Factory.StartNew(() => { Thread.Sleep(3000); return false; });
    }

Sembra un po 'rumoroso, ma fondamentalmente ho impostato il valore booleano su true al primo passaggio e l'ho resettato su un altro thread in pochi secondi (3 in questo caso). Ciò significa che il prossimo passaggio attraverso il punto in cui tenterebbe di salire sull'albero verrà saltato lasciandoti con il nodo corretto selezionato. Finora sembra funzionare :-)


La risposta è di serie MouseButtonEventArgs.Handleda true. Poiché il bambino è il primo a essere chiamato. L'impostazione di questa proprietà su true disabiliterà altre chiamate al genitore.
Basit Anwer

0

Puoi selezionarlo con l'evento su mouse down. Ciò attiverà la selezione prima che il menu contestuale si attivi.


0

Se vuoi rimanere all'interno del pattern MVVM puoi fare quanto segue:

Visualizza:

<TreeView x:Name="trvName" ItemsSource="{Binding RootElementListView}" Tag="{Binding ClickedTreeElement, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate DataType="{x:Type models:YourTreeElementClass}" ItemsSource="{Binding Path=Subreports}">
            <TextBlock Text="{Binding YourTreeElementDisplayProperty}" PreviewMouseRightButtonDown="TreeView_PreviewMouseRightButtonDown"/>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

Codice dietro:

private void TreeView_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
    if (sender is TextBlock tb && tb.DataContext is YourTreeElementClass te)
    {
        trvName.Tag = te;
    }
}

ViewModel:

private YourTreeElementClass _clickedTreeElement;

public YourTreeElementClass ClickedTreeElement
{
    get => _clickedTreeElement;
    set => SetProperty(ref _clickedTreeElement, value);
}

Ora puoi reagire alla modifica della proprietà ClickedTreeElement o puoi usare un comando che funziona internamente con ClickedTreeElement.

Visualizzazione estesa:

<UserControl ...
             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
    <TreeView x:Name="trvName" ItemsSource="{Binding RootElementListView}" Tag="{Binding ClickedTreeElement, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="MouseRightButtonUp">
                <i:InvokeCommandAction Command="{Binding HandleRightClickCommand}"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>
        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate DataType="{x:Type models:YourTreeElementClass}" ItemsSource="{Binding Path=Subreports}">
                <TextBlock Text="{Binding YourTreeElementDisplayProperty}" PreviewMouseRightButtonDown="TreeView_PreviewMouseRightButtonDown"/>
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
    </TreeView>
</UserControl>
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.