我正在构建一个画布控件。此根画布具有多个重叠的子画面(画布也是如此)。这样做是为了让每个孩子都可以处理自己的绘图,然后我可以用任何子组合来组合最终结果,以获得所需的行为。
就渲染而言,这非常有效。但是,对于鼠标事件,这不能很好地工作。鼠标事件的工作方式如下(使用previewmousemove作为示例):
1-如果root画布在鼠标下,则触发事件 2-检查所有孩子,如果有孩子,请点击火灾事件并停止
因此,只有我添加的第一个孩子才会收到鼠标移动事件。事件不会传播给所有孩子,因为它们重叠。
为了解决这个问题,我尝试了以下方法: 1-在根画布中覆盖鼠标事件 2-对于每个事件,使用VisualTreeHelper.HitTest查找要处理事件的所有子项 3-对于所有返回有效命中测试结果的孩子(即:在鼠标下并且愿意处理事件(IsHitTestVisible == true)),???
这是我被卡住的地方,我不知何故需要将鼠标事件发送给所有孩子,并停止事件的正常流程以确保第一个孩子没有收到它两次(通过事件中的processed = true) )。
通过使用RaiseEvent并在子项上传递相同的事件,事情似乎有效,但不知何故它也会在父(根画布)上引发事件。为了绕过这个,我需要创建事件的副本并设置强制设置源,尽管它看起来更像是一个黑客而不是解决方案。有没有正确的方法来做我想做的事情?代码示例如下。
public class CustomCanvas : Canvas
{
private List<object> m_HitTestResults = new List<object>();
public new event MouseEventHandler MouseMove;
public CustomCanvas()
{
base.PreviewMouseMove += new MouseEventHandler(CustomCanvas_MouseMove);
}
private void CustomCanvas_MouseMove(object sender, MouseEventArgs e)
{
// Hack here, why is the event raised on the parent as well???
if (e.OriginalSource == this)
{
return;
}
Point pt = e.GetPosition((UIElement)sender);
m_HitTestResults.Clear();
VisualTreeHelper.HitTest(this,
new HitTestFilterCallback(OnHitTest),
new HitTestResultCallback(OnHitTest),
new PointHitTestParameters(pt));
MouseEventArgs tmpe = new MouseEventArgs(e.MouseDevice, e.Timestamp, e.StylusDevice);
tmpe.RoutedEvent = e.RoutedEvent;
tmpe.Source = this;
foreach (object hit in m_HitTestResults)
{
UIElement element = hit as UIElement;
if (element != null)
{
// This somehow raises the event on us as well as the element here, why???
element.RaiseEvent(tmpe);
}
}
var handlers = MouseMove;
if (handlers != null)
{
handlers(sender, e);
}
e.Handled = true;
}
private HitTestFilterBehavior OnHitTest(DependencyObject o)
{
UIElement element = o as UIElement;
if (element == this)
{
return HitTestFilterBehavior.ContinueSkipSelf;
}
else if (element != null && element.IsHitTestVisible && element != this)
{
return HitTestFilterBehavior.Continue;
}
return HitTestFilterBehavior.ContinueSkipSelfAndChildren;
}
private HitTestResultBehavior OnHitTest(HitTestResult result)
{
// Add the hit test result to the list that will be processed after the enumeration.
m_HitTestResults.Add(result.VisualHit);
// Set the behavior to return visuals at all z-order levels.
return HitTestResultBehavior.Continue;
}
答案 0 :(得分:3)
我认为您应该使用预览事件,因为这些是从Window到Routing-Z-Order中最高控件的RoutingStrategy.Tunnel,而正常事件是RoutingStrategy.Bubble。
在这个RoutedEvents中有一个属性Handle,当系统将停止遍历可视树时,因为有人使用了这个事件。
答案 1 :(得分:0)
我发现你的代码示例很有趣所以我试了一下......但是我必须做一个小修改才能在我的东西中正常工作。
我必须在HitTestFilter方法中更改第二个“if”,如下所示:
if (element == null || element.IsHitTestVisible)
正如你所看到的那样,我在最后删除了无用的“元素!= this”(你已经在第一个“if”中测试了这个条件)并且我在开头添加了“element == null”。
为什么呢?因为在过滤期间的某个时刻参数类型是System.Windows.Media.ContainerVisual,它不从UIElement继承,因此元素将被设置为null并且将返回ContinueSkipSelfAndChildren。但我不想跳过孩子,因为我的Canvas包含在其“Children”系列中,而我想要测试的UIElements包含在Canvas中。
答案 2 :(得分:0)
正如@GuerreroTook所说,你应该通过使用WPF的RoutedEvents来解决这个问题(更多信息here。