Wählen Sie mit der rechten Maustaste TreeView Node aus, bevor Sie ContextMenu anzeigen

Antworten:

130

Abhängig von der Art und Weise, wie der Baum gefüllt wurde, können der Absender und die e.Source-Werte variieren .

Eine der möglichen Lösungen besteht darin, e.OriginalSource zu verwenden und TreeViewItem mithilfe von VisualTreeHelper zu finden:

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;
}
alex2k8
quelle
Ist dieses Ereignis für TreeView oder TreeViewItem?
Louis Rhys
1
a Haben Sie eine Idee, wie Sie die Auswahl aufheben können, wenn Sie mit der rechten Maustaste auf eine leere Stelle klicken?
Louis Rhys
Die einzige Antwort, die von 5 anderen geholfen hat ... Ich mache wirklich etwas falsch mit der Treeview-Population, danke.
3
Als Antwort auf Louis Rhys 'Frage: if (treeViewItem == null) treeView.SelectedIndex = -1oder treeView.SelectedItem = null. Ich glaube, beides sollte funktionieren.
James M
24

Wenn Sie eine Nur-XAML-Lösung wünschen, können Sie Blend Interactivity verwenden.

Angenommen, die TreeViewDaten sind an eine hierarchische Sammlung von Ansichtsmodellen mit einer BooleanEigenschaft IsSelectedund einer StringEigenschaft Namesowie eine Sammlung von benannten untergeordneten Elementen gebunden 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>

Es gibt zwei interessante Teile:

  1. Die TreeViewItem.IsSelectedEigenschaft ist an die IsSelectedEigenschaft im Ansichtsmodell gebunden . Wenn Sie die IsSelectedEigenschaft im Ansichtsmodell auf true setzen, wird der entsprechende Knoten im Baum ausgewählt.

  2. Bei PreviewMouseRightButtonDownBränden im visuellen Teil des Knotens (in diesem Beispiel a TextBlock) wird die IsSelectedEigenschaft im Ansichtsmodell auf true gesetzt. Zurück zu 1. Sie können sehen, dass der entsprechende Knoten, auf den im Baum geklickt wurde, zum ausgewählten Knoten wird.

Eine Möglichkeit, Blend Interactivity in Ihr Projekt aufzunehmen, ist die Verwendung des NuGet-Pakets Unofficial.Blend.Interactivity .

Martin Liversage
quelle
2
Tolle Antwort, danke! Es wäre hilfreich zu zeigen, in was die Zuordnungen iund die eiNamespace-Zuordnungen aufgelöst werden und in welchen Assemblys sie gefunden werden können. Ich gehe davon aus, dass: xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"und xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"welche in den Assemblys System.Windows.Interactivity bzw. Microsoft.Expression.Interactions enthalten sind.
Prlc
Dies hat nicht geholfen, da ChangePropertyActionversucht wird, eine IsSelectedEigenschaft des gebundenen Datenobjekts festzulegen, das nicht Teil der Benutzeroberfläche ist, sodass es keine IsSelectedEigenschaft hat. Mache ich etwas falsch?
Antonín Procházka
@ AntonínProcházka: Meine Antwort erfordert, dass Ihr "Datenobjekt" (oder Ansichtsmodell) eine IsSelectedEigenschaft hat, wie im zweiten Absatz meiner Antwort angegeben: Angenommen, die TreeViewDaten sind an eine hierarchische Sammlung von Ansichtsmodellen mit einer booleschen EigenschaftIsSelected gebunden ... (meine Betonung).
Martin Liversage
16

Verwenden von "item.Focus ();" scheint nicht 100% zu funktionieren, wenn "item.IsSelected = true;" tut.

Erlend
quelle
Danke für diesen Tipp. Half mir.
i8abug
Guter Tipp. Ich rufe zuerst Focus () auf und setze dann IsSelected = true.
Jim Gomes
12

Fügen Sie in XAML einen PreviewMouseRightButtonDown-Handler in XAML hinzu:

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

Behandeln Sie das Ereignis dann folgendermaßen:

    private void TreeViewItem_PreviewMouseRightButtonDown( object sender, MouseEventArgs e )
    {
        TreeViewItem item = sender as TreeViewItem;
        if ( item != null )
        {
            item.Focus( );
            e.Handled = true;
        }
    }
Stefan
quelle
2
Es funktioniert nicht wie erwartet, ich bekomme immer das Root-Element als Absender. Ich habe eine ähnliche Lösung gefunden, die unter den so hinzugefügten Ereignishandlern wie erwartet funktioniert. Irgendwelche Änderungen an Ihrem Code, um ihn zu akzeptieren? :-)
alex2k8
Es hängt anscheinend davon ab, wie Sie die Baumansicht füllen. Der Code, den ich gepostet habe, funktioniert, da dies genau der Code ist, den ich in einem meiner Tools verwende.
Stefan
Hinweis: Wenn Sie hier einen Debug-Punkt festlegen, können Sie sehen, um welchen Typ es sich bei Ihrem Absender handelt. Dieser hängt natürlich davon ab, wie Sie den Baum eingerichtet haben
Dies scheint die einfachste Lösung zu sein, wenn es funktioniert. Es hat bei mir funktioniert. Tatsächlich sollten Sie den Absender einfach als TreeViewItem umwandeln, denn wenn dies nicht der Fall ist, ist dies ein Fehler.
Craftworkgames
12

Unter Verwendung der ursprünglichen Idee von alex2k8, des korrekten Umgangs mit nicht visuellen Elementen von Wieser Software Ltd, der XAML von Stefan, der IsSelected von Erlend und meinem Beitrag, die statische Methode wirklich generisch zu machen:

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>

C # -Code dahinter:

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

Bearbeiten: Der vorherige Code hat in diesem Szenario immer einwandfrei funktioniert, aber in einem anderen Szenario hat VisualTreeHelper.GetParent null zurückgegeben, wenn LogicalTreeHelper einen Wert zurückgegeben hat.

Sean Hall
quelle
1
Um dies zu fördern
Terrence
7

Fast richtig , aber Sie müssen auf nicht visuelle Elemente im Baum achten (wie Runz. B. a).

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; 
}
Anthony Wieser
quelle
Diese generische Methode scheint ein bisschen seltsam, wie ich sie verwenden kann, wenn ich TreeViewItem schreibe. treeViewItem = VisualUpwardSearch <TreeViewItem> (e.OriginalSource als DependencyObject); es gibt mir Konvertierungsfehler
Rati_Ge
TreeViewItem treeViewItem = VisualUpwardSearch <TreeViewItem> (e.OriginalSource als DependencyObject) als TreeViewItem;
Anthony Wieser
6

Ich denke, die Registrierung eines Klassenhandlers sollte den Trick machen. Registrieren Sie einfach einen gerouteten Ereignishandler im PreviewMouseRightButtonDownEvent von TreeViewItem in Ihrer app.xaml.cs-Codedatei wie folgt:

/// <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;
    }
}
Nathan Swannet
quelle
Hat für mich gearbeitet! Und einfach auch.
Dvallejo
2
Hallo Nathan. Es klingt so, als ob der Code global ist und sich auf jede TreeView auswirkt. Wäre es nicht besser, eine Lösung zu haben, die nur lokal ist? Es könnte Nebenwirkungen verursachen?
Eric Ouellet
Dieser Code ist in der Tat global für die gesamte WPF-Anwendung. In meinem Fall war dies ein erforderliches Verhalten, sodass es für alle in der Anwendung verwendeten Baumansichten konsistent war. Sie können dieses Ereignis jedoch in einer Treeview-Instanz selbst registrieren, sodass es nur für diese Treeview gilt.
Nathan Swannet
2

Eine andere Möglichkeit, das Problem mit MVVM zu lösen, ist der Befehl bind, mit der rechten Maustaste auf Ihr Ansichtsmodell zu klicken. Dort können Sie auch andere Logik angeben source.IsSelected = true. Dies wird nur xmlns:i="http://schemas.microsoft.com/expression/2010/intera‌​ctivity"von verwendet System.Windows.Interactivity.

XAML zur Ansicht:

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

Modell anzeigen:

    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;
        }
    }
Benderto
quelle
1

Ich hatte ein Problem mit der Auswahl von Kindern mit einer HierarchicalDataTemplate-Methode. Wenn ich das Kind eines Knotens auswählen würde, würde es irgendwie das Stammelternteil dieses Kindes auswählen. Ich fand heraus, dass das MouseRightButtonDown-Ereignis für jedes Level aufgerufen wird, in dem sich das Kind befindet. Zum Beispiel, wenn Sie einen Baum wie diesen haben:

Punkt 1
   - Kind 1
   - Kind 2
      - Unterpunkt1
      - Unterpunkt2

Wenn ich Subitem2 auswählen würde, würde das Ereignis dreimal ausgelöst und Element 1 würde ausgewählt. Ich habe dies mit einem booleschen und einem asynchronen Aufruf gelöst.

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

Es fühlt sich etwas ungeschickt an, aber im Grunde habe ich den Booleschen Wert beim ersten Durchgang auf true gesetzt und ihn in wenigen Sekunden auf einen anderen Thread zurückgesetzt (in diesem Fall 3). Dies bedeutet, dass der nächste Durchgang, bei dem versucht wird, den Baum nach oben zu bewegen, übersprungen wird und Sie den richtigen Knoten ausgewählt haben. Es scheint soweit zu funktionieren :-)

Zoey
quelle
Die Antwort ist zu Satz MouseButtonEventArgs.Handledzu true. Da ist das Kind das erste, das gerufen wird. Wenn Sie diese Eigenschaft auf true setzen, werden andere Aufrufe des übergeordneten Elements deaktiviert.
Basit Anwer
0

Sie können es mit dem Ereignis "Mit der Maus nach unten" auswählen. Dadurch wird die Auswahl ausgelöst, bevor das Kontextmenü aktiviert wird.

Scott Thurlow
quelle
0

Wenn Sie innerhalb des MVVM-Musters bleiben möchten, können Sie Folgendes tun:

Aussicht:

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

Code dahinter:

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

Jetzt können Sie entweder auf die Änderung der ClickedTreeElement-Eigenschaft reagieren oder einen Befehl verwenden, der intern mit dem ClickedTreeElement funktioniert.

Erweiterte Ansicht:

<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>
RonnyR
quelle