我正在构建一个画布控件。这个根画布有几个重叠的孩子(画布也是如此)。这样做是为了让每个孩子都可以处理自己的绘图,然后我可以使用任何孩子组合来组合最终结果以获得所需的行为。
就渲染而言,这非常有效。但是,这不适用于鼠标事件。鼠标事件的工作方式如下(以 previewmousemove 为例):
1-如果根画布在鼠标下,则触发事件 2-检查所有子级,如果有一个在鼠标下,则触发事件并停止
因此,只有我添加的第一个孩子会收到鼠标移动事件。该事件不会传播到所有孩子,因为它们重叠。
为了克服这个问题,我尝试了以下操作: 1- 覆盖根画布中的鼠标事件 2- 对于每个事件,找到所有想要使用 VisualTreeHelper.HitTest 处理事件的子级 3- 对于返回有效命中测试结果的所有子级(即:在鼠标下并愿意处理事件(IsHitTestVisible == true)),???
这就是我卡住的地方,我不知何故需要将鼠标事件发送给所有孩子,并停止事件的正常流程以确保第一个孩子不会收到两次(通过事件中的handled = true)。
通过将 RaiseEvent 与传递给子级的相同事件一起使用,事情似乎可以正常工作,但不知何故它也会在父级(根画布)上引发事件。为了绕过这一点,我需要创建事件的副本并设置强制设置源,尽管它似乎更像是一种 hack 而不是解决方案。有没有合适的方法来做我想做的事情?代码示例如下。
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;
}