Select TreeView Node on right click before displaying ContextMenu

后端 未结 11 1961
鱼传尺愫
鱼传尺愫 2020-11-29 19:39

I would like to select a WPF TreeView Node on right click, right before the ContextMenu displayed.

For WinForms I could use code like this Find node clicked under co

相关标签:
11条回答
  • 2020-11-29 20:00

    I think registering a class handler should do the trick. Just register a routed event handler on the TreeViewItem's PreviewMouseRightButtonDownEvent in your app.xaml.cs code file like this:

    /// <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;
        }
    }
    
    0 讨论(0)
  • 2020-11-29 20:01

    Using the original idea from alex2k8, correctly handling non-visuals from Wieser Software Ltd, the XAML from Stefan, the IsSelected from Erlend, and my contribution of truly making the static method Generic:

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

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

    Edit: The previous code always worked fine for this scenario, but in another scenario VisualTreeHelper.GetParent returned null when LogicalTreeHelper returned a value, so fixed that.

    0 讨论(0)
  • 2020-11-29 20:02

    Another way to solve it using MVVM is bind command for right click to your view model. There you can specify other logic as well as source.IsSelected = true. This uses only xmlns:i="http://schemas.microsoft.com/expression/2010/intera‌​ctivity" from System.Windows.Interactivity.

    XAML for view:

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

    View model:

        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;
            }
        }
    
    0 讨论(0)
  • 2020-11-29 20:08

    I was having a problem with selecting children with a HierarchicalDataTemplate method. If I selected the child of a node it would somehow select the root parent of that child. I found out that the MouseRightButtonDown event would get called for every level the child was. For example if you have a tree something like this:

    Item 1
       - Child 1
       - Child 2
          - Subitem1
          - Subitem2

    If I selected Subitem2 the event would fire three times and item 1 would be selected. I solved this with a boolean and an asynchronous call.

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

    It feels a little cludgy but basically I set the boolean to true on the first pass through and have it reset on another thread in a few seconds (3 in this case). This means that the next passes through where it would try to move up the tree will get skipped leaving you with the correct node selected. It seems to work so far :-)

    0 讨论(0)
  • 2020-11-29 20:09

    If you want a XAML-only solution you can use Blend Interactivity.

    Assume the TreeView is data bound to a hierarchical collection of view-models having a Boolean property IsSelected and a String property Name as well as a collection of child items named 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>
    

    There are two interesting parts:

    1. The TreeViewItem.IsSelected property is bound to the IsSelected property on the view-model. Setting the IsSelected property on the view-model to true will select the corresponding node in the tree.

    2. When PreviewMouseRightButtonDown fires on the visual part of the node (in this sample a TextBlock) the IsSelected property on the view-model is set to true. Going back to 1. you can see that the corresponding node that was clicked on in the tree becomes the selected node.

    One way to get Blend Interactivity in your project is to use the NuGet package Unofficial.Blend.Interactivity.

    0 讨论(0)
  • 2020-11-29 20:19

    Using "item.Focus();" doesn't seems to work 100%, using "item.IsSelected = true;" does.

    0 讨论(0)
提交回复
热议问题