wpf 在显示 ContextMenu 之前右键单击选择 TreeView 节点

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/592373/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 20:05:04  来源:igfitidea点击:

Select TreeView Node on right click before displaying ContextMenu

wpftreeviewcontextmenu

提问by alex2k8

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

我想在 ContextMenu 显示之前右键单击选择一个 WPF TreeView 节点。

For WinForms I could use code like this Find node clicked under context menu, what are the WPF alternatives?

对于 WinForms,我可以使用类似Find node clicked under context menu 的代码,WPF 的替代方案是什么?

回答by alex2k8

Depending on the way the tree was populated, the sender and the e.Source values may vary.

根据填充树的方式,发件人和 e.Source 值可能会有所不同

One of the possible solutions is to use e.OriginalSource and find TreeViewItem using the VisualTreeHelper:

一种可能的解决方案是使用 e.OriginalSource 并使用 VisualTreeHelper 查找 TreeViewItem:

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

回答by Martin Liversage

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

如果您想要仅 XAML 的解决方案,您可以使用混合交互。

Assume the TreeViewis data bound to a hierarchical collection of view-models having a Booleanproperty IsSelectedand a Stringproperty Nameas well as a collection of child items named Children.

假设TreeView数据绑定到具有Boolean属性IsSelectedString属性Name以及名为 的子项集合的视图模型的分层集合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.IsSelectedproperty is bound to the IsSelectedproperty on the view-model. Setting the IsSelectedproperty on the view-model to true will select the corresponding node in the tree.

  2. When PreviewMouseRightButtonDownfires on the visual part of the node (in this sample a TextBlock) the IsSelectedproperty 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.

  1. TreeViewItem.IsSelected属性绑定到IsSelected视图模型上的属性。将IsSelected视图模型上的属性设置为 true 将选择树中的相应节点。

  2. PreviewMouseRightButtonDown在节点的可视部分(在此示例中 a TextBlockIsSelected上触发时,视图模型上的属性设置为 true。回到1.可以看到树中被点击的对应节点变成了选中的节点。

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

在项目中获得 Blend Interactivity 的一种方法是使用 NuGet 包Unofficial.Blend.Interactivity

回答by Erlend

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

使用“item.Focus();” 使用 "item.IsSelected = true;" 似乎不能 100% 工作 做。

回答by Stefan

In XAML, add a PreviewMouseRightButtonDown handler in XAML:

在 XAML 中,在 XAML 中添加 PreviewMouseRightButtonDown 处理程序:

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

Then handle the event like this:

然后像这样处理事件:

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

回答by Sean Hall

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:

使用 alex2k8 的原始想法,正确处理来自 Wieser Software Ltd 的非视觉对象、来自 Stefan 的 XAML、来自 Erlend 的 IsSelected,以及我对真正使静态方法通用的贡献:

XAML:

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:

后面的 C# 代码:

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.

编辑:以前的代码在这种情况下始终可以正常工作,但在另一种情况下,VisualTreeHelper.GetParent 在 LogicalTreeHelper 返回值时返回 null,因此修复了该问题。

回答by Anthony Wieser

Almost Right, but you need to watch out for non visuals in the tree, (like a Run, for instance).

几乎是对的,但是您需要注意树中的非视觉对象(Run例如 )。

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

回答by Nathan Swannet

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:

我认为注册一个类处理程序应该可以解决问题。只需在 app.xaml.cs 代码文件中的 TreeViewItem 的 PreviewMouseRightButtonDownEvent 上注册一个路由事件处理程序,如下所示:

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

回答by benderto

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.

使用 MVVM 解决它的另一种方法是绑定命令,用于右键单击您的视图模型。在那里您可以指定其他逻辑以及source.IsSelected = true. 这仅使用xmlns:i="http://schemas.microsoft.com/expression/2010/intera??ctivity"来自System.Windows.Interactivity.

XAML for view:

用于查看的 XAML:

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

回答by Zoey

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:

我在使用 HierarchicalDataTemplate 方法选择孩子时遇到了问题。如果我选择了一个节点的子节点,它会以某种方式选择该子节点的根父节点。我发现 MouseRightButtonDown 事件会为孩子所在的每个级别调用。例如,如果你有一棵树是这样的:

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

第1项
   -儿童1
   - 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.

如果我选择 Subitem2,则事件将触发 3 次,并选择项目 1。我用布尔值和异步调用解决了这个问题。

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

感觉有点笨拙,但基本上我在第一次通过时将布尔值设置为 true 并在几秒钟内在另一个线程上重置它(在这种情况下为 3 )。这意味着下一次通过它试图向上移动树的地方将被跳过,让您选择正确的节点。到目前为止它似乎工作:-)

回答by Scott Thurlow

You can select it with the on mouse down event. That will trigger the select before the context menu kicks in.

您可以使用 on mouse down 事件选择它。这将在上下文菜单启动之前触发选择。