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


Answers:


130

根据树的填充方式,发送者和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;
}

是TreeView或TreeViewItem的事件?
路易·里斯

1
如果在空的位置单击鼠标右键,如何取消选择所有内容?
路易·瑞斯

唯一的一个答案对其他5个问题有所帮助...谢谢,我确实对树状视图人口做错了什么。

3
回答路易斯·瑞斯的问题:if (treeViewItem == null) treeView.SelectedIndex = -1treeView.SelectedItem = null。我相信任何一个都应该起作用。
James M

24

如果您只需要XAML解决方案,则可以使用Blend Interactivity。

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

有两个有趣的部分:

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

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

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


2
好答案,谢谢!显示iand和ei名称空间映射可以解析到哪些内容以及可以在其中找到哪些程序集将很有帮助。我假设:xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions",分别在System.Windows.Interactivity和Microsoft.Expression.Interactions程序集中找到。
prlc 2015年

这无济于事,因为ChangePropertyAction尝试设置IsSelected绑定数据对象的属性,该属性不是UI的一部分,因此它没有IsSelected属性。难道我做错了什么?
安东宁·普罗哈兹卡

@AntonínProcházka:我的答案要求您的“数据对象”(或视图模型)具有IsSelected如我的答案第二段所述的属性:假设TreeViewis数据绑定到具有布尔属性IsSelected的视图模型的分层集合中...(我的重点)。
Martin Liversage

16

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


感谢您的提示。帮助过我。
i8abug

好提示。我先调用Focus(),然后设置IsSelected = true。
Jim Gomes 2015年

12

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

2
它不能按预期工作,我总是将root元素作为发送者。我已经找到了一种类似的解决方案social.msdn.microsoft.com/Forums/en-US/wpf/thread / ...以这种方式添加的事件处理程序可以按预期工作。您对代码进行了任何更改以接受吗?:-)
alex2k8

显然,这取决于您如何填充树视图。我发布的代码行得通,因为那是我在一种工具中使用的确切代码。
Stefan

请注意,如果您在此处设置调试点,则可以看到发件人的类型,这当然取决于设置树的方式

这似乎是最简单的解决方案。它为我工作。实际上,您应该只将发件人转换为TreeViewItem,因为如果不是,那就是一个错误。
craftworkgames 2014年

12

使用alex2k8的原始思想,正确处理Wieser Software Ltd的非可视内容,Stefan的XAML,Erlend的IsSelected,以及我真正使静态方法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#代码:

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,因此进行了修复。


1
为了进一步解决这个问题,此答案在DependencyProperty扩展中实现了这一点:stackoverflow.com/a/18032332/84522
Terrence

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

当我写TreeViewItem treeViewItem = VisualUpwardSearch <TreeViewItem>(e.OriginalSource as DependencyObject)时,该通用方法似乎有点奇怪。它给了我转换错误
Rati_Ge 2012年

TreeViewItem treeViewItem = VisualUpwardSearch <TreeViewItem>(e.OriginalSource作为DependencyObject)作为TreeViewItem;
安东尼·威瑟

6

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

为我工作!而且也很简单。
dvallejo,2012年

2
你好内森。听起来代码是全局的,并且会影响每个TreeView。有一个仅本地解决方案会更好吗?它会产生副作用吗?
埃里克·厄勒

对于整个WPF应用程序,此代码确实是全局的。就我而言,这是必需的行为,因此它对于应用程序中使用的所有树视图都是一致的。但是,您可以在树视图实例本身上注册此事件,因此它仅适用于该树视图。
内森·斯旺内(Nathan Swannet)'16

2

使用MVVM解决此问题的另一种方法是将右键单击视图模型的bind命令。在这里您还可以指定其他逻辑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)。这意味着下一遍将尝试向上移动的树将被跳过,从而为您选择了正确的节点。到目前为止似乎工作正常:-)


答案是设置MouseButtonEventArgs.Handledtrue。因为孩子是第一个被呼唤的孩子。将此属性设置为true将禁用对父级的其他调用。
巴斯特·安维尔

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

ViewModel:

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>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.