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

问题描述 投票:0回答:11

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

对于 WinForms,我可以使用这样的代码 查找在上下文菜单下单击的节点,WPF 替代方案是什么?

wpf treeview contextmenu
11个回答
142
投票

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

可能的解决方案之一是使用 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;
}

27
投票

如果您想要仅 XAML 的解决方案,您可以使用 Blend Interactivity。

假设

TreeView
是绑定到视图模型的分层集合的数据,该视图模型具有
Boolean
属性
IsSelected
String
属性
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>

有两个有趣的部分:

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

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

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


17
投票

使用

item.Focus();
似乎不能 100% 工作,使用
item.IsSelected = true;
可以。


14
投票

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

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#代码:

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

编辑:以前的代码对于这种情况总是工作得很好,但在另一种情况下,当 LogicalTreeHelper 返回值时,VisualTreeHelper.GetParent 返回 null,因此修复了该问题。


13
投票

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

然后像这样处理事件:

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

7
投票

几乎正确,但您需要注意树中的非视觉对象(例如,

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

7
投票

我认为注册一个类处理程序应该可以解决问题。 只需在 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;
    }
}

2
投票

使用 MVVM 解决此问题的另一种方法是右键单击视图模型的绑定命令。您可以在那里指定其他逻辑以及

source.IsSelected = true
。 这仅使用
xmlns:i="http://schemas.microsoft.com/expression/2010/intera‌​ctivity"
中的
System.Windows.Interactivity

用于视图的 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>

查看模型:

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

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

第 1 项
- 儿童 1
- 孩子 2
- 子项目1
- 子项目2

如果我选择 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; });
    }

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


0
投票

您可以通过鼠标按下事件来选择它。这将在上下文菜单启动之前触发选择。


0
投票

如果您想保持 MVVM 模式,您可以执行以下操作:

查看:

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

背后代码:

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

视图模型:

private YourTreeElementClass _clickedTreeElement;

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

现在您可以对 ClickedTreeElement 属性更改做出反应,也可以使用在内部与 ClickedTreeElement 配合使用的命令。

扩展视图:

<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>
© www.soinside.com 2019 - 2024. All rights reserved.