【问题标题】:Select TreeView Node on right click before displaying ContextMenu在显示 ContextMenu 之前右键单击选择 TreeView 节点
【发布时间】:2009-02-26 20:49:13
【问题描述】:

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

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

【问题讨论】:

    标签: wpf treeview contextmenu


    【解决方案1】:

    取决于树的填充方式,the sender and the e.Source values may vary

    一种可能的解决方案是使用 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?
    • an 如果右键单击空白位置,知道如何取消选择所有内容吗?
    • 唯一一个帮助了其他 5 个答案的答案......我真的在树视图人口方面做错了,谢谢。
    • 回答 Louis Rhys 的问题:if (treeViewItem == null) treeView.SelectedIndex = -1treeView.SelectedItem = null。我相信两者都应该有效。
    【解决方案2】:

    如果您想要一个仅限 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 属性。将 view-model 上的 IsSelected 属性设置为 true 将选择树中的相应节点。

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

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

    【讨论】:

    • 很好的答案,谢谢!显示 iei 命名空间映射解析为什么以及可以在哪些程序集中找到它们会很有帮助。我假设:xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions",它们位于 System.Windows.Interactivity和 Microsoft.Expression.Interactions 程序集。
    • 这没有帮助,因为 ChangePropertyAction 试图设置绑定数据对象的 IsSelected 属性,这不是 UI 的一部分,因此它没有 IsSelected 属性.我做错了吗?
    • @AntonínProcházka:我的回答要求您的“数据对象”(或视图模型)具有IsSelected 属性,如我的回答第二段所述:假设TreeView 是数据绑定到具有 布尔属性 IsSelected... 的视图模型的分层集合(我的重点)。
    【解决方案3】:

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

    【讨论】:

    • 感谢您的提示。帮助了我。
    • 好提示。我先调用 Focus(),然后设置 IsSelected = true。
    【解决方案4】:

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

    【讨论】:

    • 它没有按预期工作,我总是将根元素作为发件人。我找到了一个类似的解决方案,social.msdn.microsoft.com/Forums/en-US/wpf/thread/… 以这种方式添加的事件处理程序按预期工作。对您的代码进行任何更改以接受它吗? :-)
    • 这显然取决于您如何填充树视图。我发布的代码有效,因为这是我在我的一个工具中使用的确切代码。
    • 请注意,如果您在此处设置调试点,您可以看到您的发件人是什么类型,这当然会根据您设置树的方式而有所不同
    • 这似乎是最简单的解决方案。它对我有用。实际上,您应该将 sender 转换为 TreeViewItem,因为如果不是,那就是一个错误。
    【解决方案5】:

    使用来自 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,因此修复了该问题。

    【讨论】:

    【解决方案6】:

    Almost Right,但您需要注意树中的非视觉对象(例如 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(e.OriginalSource as DependencyObject);它给了我转换错误
    • TreeViewItem treeViewItem = VisualUpwardSearch(e.OriginalSource as DependencyObject) as TreeViewItem;
    【解决方案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;
        }
    }
    

    【讨论】:

    • 为我工作!也很简单。
    • 你好,内森。听起来代码是全局的,会影响每个 TreeView。有一个仅限本地的解决方案不是更好吗?它会产生副作用吗?
    • 这段代码对于整个 WPF 应用程序来说确实是全局的。就我而言,这是必需的行为,因此它对于应用程序中使用的所有树视图都是一致的。但是,您可以在树视图实例本身上注册此事件,因此它仅适用于该树视图。
    【解决方案8】:

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

    用于查看的 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;
            }
        }
    

    【讨论】:

      【解决方案9】:

      我在使用 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.Handled设置为true。因为孩子是第一个被召唤的。将此属性设置为 true 将禁用对父级的其他调用。
      【解决方案10】:

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

      【讨论】:

        【解决方案11】:

        如果您想保持 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>
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多