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

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

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

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

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

使用“item.Focus();” 似乎不工作100%,使用“item.IsSelected = true;” 确实。

如果你想要一个只有XAML的解决scheme,你可以使用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将select树中相应的节点。

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

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

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

使用来自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,所以修复了这个问题。

几乎是正确的 ,但是你需要注意树中的非视觉效果(比如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; } 

我认为注册一个类处理程序应该做的伎俩。 只需在您的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; } } 

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

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

第1项
– 孩子1
– 孩子2
– 子项目1
– 子项目2

如果我select了Subitem2,事件将会启动三次,并select项目1。 我用一个布尔和一个asynchronous调用来解决这个问题。

 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)。 这意味着下一个通过它将试图向上移动的树会跳过,留下您select正确的节点。 它似乎工作到目前为止:-)

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