WPFの命中测试

概述: 

WPF中的Canvas是常用的一个绘图控件,可以方便地在Canvas中添加我们需要处理的各种元素如:图片、文字等。但Canvas中元素增加到一定数量,并且有重合的时候,我们如何通过在Canvas中点击鼠标,获得我们想要的元素,然后再对该元素做出相应的控制?

命中测试,可以很好地解决这个问题

本文目的: 

使用命中测试,选取Canvas中相应Element。

正文: 

可视化树如何影响命中测试

可视化树中的起始点确定在对象的命中测试枚举过程中返回哪些对象。如果有多个要执行命中测试的对象,则可视化树中用作起始点的可视化对象必须是所有相关对象的公共上级。例如,如果您希望对以下关系图中的按钮元素和绘图可视化对象执行命中测试,则必须将可视化树中的起始点设置为两者的公共上级。在本例中,画布元素是按钮元素和绘图可视化对象的公共上级。

可视化树层次结构的关系图

WPFの命中测试

即要在命中测试的结果中,包含Button及Drawing Visual,就必须在他们的公共上级Canvas中做命中测试。

示例

Canvas中放置了底层放置了一张图片,顶层放置了一个文本框,我们想实现点击文本框区域时,命中测试的返回结果为下层的Image,先贴上代码,然后再解释。

private void canvasMain_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
GetCurrentElement(e.GetPosition(canvasMain));
} private void GetCurrentElement(Point point)
{
PointHitTestParameters parameters = new PointHitTestParameters(point);
VisualTreeHelper.HitTest(canvasMain, HitTestFilter, HitTestCallback, parameters);
} private HitTestResultBehavior HitTestCallback(HitTestResult result)
{
Image image = result.VisualHit as Image;
if (image != null)
   {
_currentElement = image;
return HitTestResultBehavior.Stop;
} else
return HitTestResultBehavior.Continue;
} private HitTestFilterBehavior HitTestFilter(DependencyObject o)
{
Type type = o.GetType();
switch (type.Name)
{
case "Canvas":
return HitTestFilterBehavior.ContinueSkipSelf;
default:
return HitTestFilterBehavior.Continue;
}
}

其中VisualTreeHelper.HitTest方法是最主要的方法,其有三种重载形式,我们使用的是第二种重载形式,参数及其含义如下:

参数名称

类型

说明

reference

System.Windows.Media.Visual

要进行命中测试的 Visual

filterCallback

System.Windows.Media.HitTestFilterCallback

表示命中测试筛选回调值的方法

resultCallback

System.Windows.Media.HitTestResultCallback

表示命中测试结果回调值的方法

hitTestParameters

System.Windows.Media.HitTestParameters

要进行命中测试的参数值

1. reference

这个参数不用多说,就是我们要进行命中测试元素的公共上级,即他们在WPF树结构的上级,在这里为“canvasMain”;

2. filterCallback

使用命中测试筛选回调函数可以枚举呈现内容包含指定坐标的所有可视化对象。但是,您可能要忽略不希望在命中测试结果回调函数中处理的可视化树的某些分支。命中测试筛选回调函数的返回值确定可视化对象的枚举应执行的操作类型。例如,如果返回值 ContinueSkipSelfAndChildren,则可从命中测试结果枚举中移除当前可视化对象及其子对象。 这意味着命中测试结果回调函数在其枚举中将看不到这些对象。修剪可视化对象树会减少命中测试结果枚举过程中的处理量。其效果如下所示:

WPFの命中测试

示例代码中我们并不想对Canvas进行命中测试,所以我们就判断如果枚举的命中测试结果为Canvas,则返回HitTestFilterBehavior.ContinueSkipSelf,这样将忽略它,而对其子对象进行命中测试。

下面是HitTestFilterBehavior的成员列表,我们可以根据需要,返回不用的值,以筛选命中测试结果。

成员名称

说明

ContinueSkipChildren

针对当前的 Visual(但不包括其子代)进行命中测试。

ContinueSkipSelfAndChildren

不要针对当前的 Visual 或其子代进行命中测试。

ContinueSkipSelf

不要针对当前的 Visual 进行命中测试,但要针对其子代进行命中测试。

Continue

针对当前的 Visual 及其子代进行命中测试。

Stop

在当前 Visual 处停止命中测试。

3. resultCallback

表示命中测试结果回调值的方法。result.VisualHit即为本次命中测试的结果,可以根据其类型判断其是否符合我们的测试要求,如果不符合则返回HitTestResultBehavior.Continue,继续枚举命中测试的下一个结果,如果符合要求则可以使用变量来接受这个结果,然后直接调用HitTestResultBehavior.Stop来结束整个命中测试。

4. hitTestParameters

定义命中测试的参数。从此公共基类派生出来的可用于实际命中测试的类包括 PointHitTestParameters 和 GeometryHitTestParameters。根据名称亦可以看出PointHitTestParameters主要用于点测试,而GeometryHitTestParameters则可以用于区域内的命中测试,该类型的详细信息请查询MSDN。

PointHitTestParameters的初始化也很简单,只需传入我们需要进行命中测试的点(Point)即可。

OK,主要的方法及其含义都解释完毕,想必大家都已能看明白示例代码,对命中测试也有了一个初步的了解。实际情况中可以根据情况,灵活运用filterCallback和resultCallback这两个回调函数,做到复杂的命中测试。

注意事项:

1. WPF大多控件都有IsHitTestVisible 属性,其可获取或设置一个值,该值声明某个 UIElement 派生对象是否可以作为其呈现内容某部分的命中测试结果返回。 这样,您便可以选择性地更改可视化树,以确定命中测试中涉及哪些可视化对象。

2. 在命中测试结果枚举过程中,不应执行修改可视化树的任何操作。在遍历可视化树的过程中,在可视化树中添加或移除对象会导致不可预知的行为。您可以在 HitTest 方法返回之后安全地修改可视化树。 您可能想要提供一个数据结构(例如 ArrayList),以便在命中测试结果枚举期间存储值。

3. 命中的可视化对象按 Z 顺序进行枚举。Z 顺序中位于最顶层的可视化对象最先进行枚举。所有其他可视化对象按递减的 Z 顺序级别进行枚举。此枚举顺序对应于可视化对象的呈现顺序。

(非原创)

上一篇:LeetCode 31. Next Permutation (下一个排列)


下一篇:trigger click 和 click 的区别??