这儿还有更简单的
WPF 中TreeView 右键选中实现
有时候我们需要在TreeView中实现这样的功能:
在TreeView上点击右键弹出
菜单,同时鼠标点击处的TreeViewItem被选中,然后我们针对选中的数据进行处理。
不过,WPF的TreeView并没有提供右键单击选中的功能。我们需要自己去实现。
【思路】
最基本的思路是,在TreeView右键点击的事件发生时,我们遍历它所有的Item,包括子Item,获取Item所对应的TreeViewItem
控件的边界Rect,然后调用Rect的Contains方法判断鼠标是否在该范围内。(另外一种更简单的方式见后面的补充)
【特殊点】
需要注意的是,TreeViewItem是一个ItemsControl,当我们选中它的某个子项时,它本身的Rect也包含鼠标,如下图所示,因此,我们需要一直往下找,直到最后一个包含鼠标的TreeViewItem。显然,这个是递归的过程。
当Level_3_1被选中时,Root,Level_1_1,Level_2_1的Rect都会被认为是包含了鼠标位置。
【实现】
代码很简单:
- public static TreeViewItem SelectItemByRightClick(ItemsControl source)
- {
- /
- //
- // Note: 对于TreeViewItem来说,如果被选中了,那肯定是它的父节点也被选中了
- //
- /
- if (!(source is TreeView) && !(source is TreeViewItem))
- {
- throw new ArgumentException("只支持参数为TreeView或者TreeViewItem", "source");
- }
- foreach (object item in source.Items)
- {
- TreeViewItem currentItem = source.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
- Point mousePosition = Mouse.GetPosition(currentItem);
- Rect itemRect = VisualTreeHelper.GetDescendantBounds(currentItem);
- // 可能是选中的项,也可能是选中项的父节点
- if (itemRect.Contains(mousePosition))
- {
- // 看看是不是它的孩子被选中了,否则就是它自己被选中了
- if (currentItem.IsExpanded)
- {
- // 只判断展开的项
- TreeViewItem selectedItem = SelectItemByRightClick(currentItem);
- if (selectedItem != null)
- {
- selectedItem.IsSelected = true;
- return selectedItem;
- }
- }
- currentItem.IsSelected = true;
- return currentItem;
- }
- }
- return null;
- }
复制代码
为了方便使用,我还定义了一个AttachedProperty,这样可以通过一句简单的xaml语句来开启右键选中功能。比如:
-
- 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所截获,交由我们定义的事件处理方法去处理。
但是,跟普通的事件注册处理有所不同的是,在方法
- 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。
示例代码如下:
- private static void TreeViewItem_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
- {
- // 注意,这里的sender是TreeView
- // 我们需要从e.OriginalSource拿到TreeViewItem
- TreeViewItem item = GetTemplatedAncestor(e.OriginalSource as FrameworkElement);
- item.IsSelected = true;
- }
- private static T GetTemplatedAncestor(FrameworkElement element) where T : FrameworkElement
- {
- if (element is T)
- {
- return element as T;
- }
- FrameworkElement templatedParent = element.TemplatedParent as FrameworkElement;
- if (templatedParent != null)
- {
- return GetTemplatedAncestor(templatedParent);
- }
- return null;
- }
复制代码
为了方便使用,还是觉得搞成AttachedProperty更合适,那么,如果在代码中让TreeView监听TreeViewItem的事件呢?其实也很简单:
- private static void OnEnableRightClickSelectionChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
- {
- TreeView treeView = sender as TreeView;
- if (treeView != null)
- {
- // 利用RoutedEvent的特性,让TreeView处理TreeViewItem的PreviewMouseRightButtonDown事件
- if ((bool)e.NewValue)
- {
- treeView.AddHandler(
- TreeViewItem.PreviewMouseRightButtonDownEvent,
- new MouseButtonEventHandler(TreeViewItem_PreviewMouseRightButtonDown));
- }
- else
- {
- treeView.RemoveHandler(
- TreeViewItem.PreviewMouseRightButtonDownEvent,
- new MouseButtonEventHandler(TreeViewItem_PreviewMouseRightButtonDown));
- }
- }
- }
复制代码
使用的时候,在xaml里面描述一下就可以了:
-
- local:TreeViewHelper2.EnableRightClickSelection="True"
复制代码
并且,这时候,不再跟TreeView的事件有任何冲突了,我们可以放心的在TreeView的PreviewMouseRightButtonDown中写自己想写的逻辑。