【WPF】如何让TreeView实现右键选中的功能

这儿还有更简单的
WPF 中TreeView 右键选中实现

有时候我们需要在TreeView中实现这样的功能:

在TreeView上点击右键弹出 菜单,同时鼠标点击处的TreeViewItem被选中,然后我们针对选中的数据进行处理。

不过,WPF的TreeView并没有提供右键单击选中的功能。我们需要自己去实现。

【思路】

最基本的思路是,在TreeView右键点击的事件发生时,我们遍历它所有的Item,包括子Item,获取Item所对应的TreeViewItem 控件的边界Rect,然后调用Rect的Contains方法判断鼠标是否在该范围内。(另外一种更简单的方式见后面的补充)

【特殊点】

需要注意的是,TreeViewItem是一个ItemsControl,当我们选中它的某个子项时,它本身的Rect也包含鼠标,如下图所示,因此,我们需要一直往下找,直到最后一个包含鼠标的TreeViewItem。显然,这个是递归的过程。

TreeViewRightClick_ParentAndChildren.png(14.31 K)
1/13/2009 2:21:34 PM


当Level_3_1被选中时,Root,Level_1_1,Level_2_1的Rect都会被认为是包含了鼠标位置。

【实现】

代码很简单:
  1.         public static TreeViewItem SelectItemByRightClick(ItemsControl source)
  2.         {
  3.             /
  4.             //
  5.             // Note: 对于TreeViewItem来说,如果被选中了,那肯定是它的父节点也被选中了
  6.             //
  7.             /
  8.             if (!(source is TreeView) && !(source is TreeViewItem))
  9.             {
  10.                 throw new ArgumentException("只支持参数为TreeView或者TreeViewItem", "source");
  11.             }

  12.             foreach (object item in source.Items)
  13.             {
  14.                 TreeViewItem currentItem = source.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
  15.                 Point mousePosition = Mouse.GetPosition(currentItem);

  16.                 Rect itemRect = VisualTreeHelper.GetDescendantBounds(currentItem);

  17.                 // 可能是选中的项,也可能是选中项的父节点
  18.                 if (itemRect.Contains(mousePosition))
  19.                 {
  20.                     // 看看是不是它的孩子被选中了,否则就是它自己被选中了             
  21.                     if (currentItem.IsExpanded)
  22.                     {
  23.                         // 只判断展开的项
  24.                         TreeViewItem selectedItem = SelectItemByRightClick(currentItem);
  25.                         if (selectedItem != null)
  26.                         {
  27.                             selectedItem.IsSelected = true;
  28.                             return selectedItem;
  29.                         }
  30.                     }
  31.                     currentItem.IsSelected = true;
  32.                     return currentItem;
  33.                 }
  34.             }
  35.             return null;
  36.         }
复制代码
为了方便使用,我还定义了一个AttachedProperty,这样可以通过一句简单的xaml语句来开启右键选中功能。比如:
  1.           local:TreeViewHelper.EnableRightClickSelection="True"
复制代码
【注意事项】

在使用AttachedProperty来开启右键选中功能时,需要特别注意是事件的处理顺序。

首先,TreeView会发生PreviewMouseRightButtonDown事件,然后TreeViewHelper中的代码会开始处理,接着是MouseRightButtonDown事件。这个现象的原因在于,在我写的TreeViewHelper里面,是通过监听PreviewMouseRightButtonDown来处理的,而在控件初始化的时候,首先会加上我们自己写的PreviewMouseRightButtonDown事件处理方法,然后才设置附加属性的值,这样导致我们自定义的事件处理发生在TreeViewHelper事件处理之前。

因此,如果是通过附加属性开启的,最好是在MouseRightButtonDown处理方法中写其他的代码。如果是想在PreviewMouseRightButtonDown中处理,则不要使用附加属性,而是手动调用TreeViewHelper.SelectItemByRightClick(treeView)。然后再写其他处理逻辑。这在我的示例代码中有说明。



代码下载: 附件: TreeViewRightClick.rar (下载 50 次)

【附】

另外一个小问题,关于WPF中调用Message.Show()方法时需要注意:

如果是在非 UI 线程调用该方法,则需要通过Dispatcher.Invoke()来调用,否则,对话框会阻塞这个非UI线程,而不是UI线程,造成一个看上去“非模态”的对话框。

示例: 附件: MessageBoxTricks.rar (下载 18 次)


【补充】

昨天晚上又想起来,可以利用RoutedEvent的一些特性来更简单的实现TreeView的鼠标右键选中。

我们知道,在WPF里面,我们可以在元素的Parent上监听该元素的事件,诸如:

复制代码
这样,当TreeViewItem发生PreviewMouseRightButtonDown事件时,该事件将会被TreeView所截获,交由我们定义的事件处理方法去处理。

但是,跟普通的事件注册处理有所不同的是,在方法
  1. private void TreeViewItem_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
复制代码
里面,sender并不是TreeViewItem,而是TreeView。为了拿到TreeViewItem,我们需要利用e里面的OriginalSource属性。

然而,如果我们就直接写  TreeViewItem item = e.OriginalSource as TreeViewItem 是拿不到的。跟踪断点我们可以发现,e.OriginalSource原来是TextBlock。不是TreeView和TreeViewItem么,怎么莫名其妙跑出来个TextBlock?其实,这个TextBlock是WPF给我们提供一个默认的TreeView的ItemTemplate中的东东,所以,我们只要沿着它的TemplatedParent往上找,就会找到TreeViewItem。

示例代码如下:
  1.         private static void TreeViewItem_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
  2.         {
  3.             // 注意,这里的sender是TreeView
  4.             // 我们需要从e.OriginalSource拿到TreeViewItem
  5.             TreeViewItem item = GetTemplatedAncestor(e.OriginalSource as FrameworkElement);
  6.             item.IsSelected = true;
  7.         }

  8.         private static T GetTemplatedAncestor(FrameworkElement element) where T : FrameworkElement
  9.         {
  10.             if (element is T)
  11.             {
  12.                 return element as T;
  13.             }

  14.             FrameworkElement templatedParent = element.TemplatedParent as FrameworkElement;
  15.             if (templatedParent != null)
  16.             {
  17.                 return GetTemplatedAncestor(templatedParent);
  18.             }

  19.             return null;
  20.         }
复制代码
为了方便使用,还是觉得搞成AttachedProperty更合适,那么,如果在代码中让TreeView监听TreeViewItem的事件呢?其实也很简单:
  1.         private static void OnEnableRightClickSelectionChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
  2.         {
  3.             TreeView treeView = sender as TreeView;
  4.             if (treeView != null)
  5.             {
  6.                 // 利用RoutedEvent的特性,让TreeView处理TreeViewItem的PreviewMouseRightButtonDown事件
  7.                 if ((bool)e.NewValue)
  8.                 {
  9.                     treeView.AddHandler(
  10.                         TreeViewItem.PreviewMouseRightButtonDownEvent,
  11.                         new MouseButtonEventHandler(TreeViewItem_PreviewMouseRightButtonDown));
  12.                 }
  13.                 else
  14.                 {
  15.                     treeView.RemoveHandler(
  16.                         TreeViewItem.PreviewMouseRightButtonDownEvent,
  17.                         new MouseButtonEventHandler(TreeViewItem_PreviewMouseRightButtonDown));
  18.                 }
  19.             }
  20.         }
复制代码
使用的时候,在xaml里面描述一下就可以了:
  1.           local:TreeViewHelper2.EnableRightClickSelection="True"
复制代码
并且,这时候,不再跟TreeView的事件有任何冲突了,我们可以放心的在TreeView的PreviewMouseRightButtonDown中写自己想写的逻辑。

你可能感兴趣的:(技术)