c#wpf重叠控件不接收鼠标事件
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/740799/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
c# wpf overlapping controls not receiving mouse events
提问by
I am building a canvas control. This root canvas has several overlapping children (canvas as well). This is done so each child can handle its own drawing and I can then compose the final result with any combination of children to get the desired behavior.
我正在构建一个画布控件。这个根画布有几个重叠的子画布(也是画布)。这样做是为了让每个孩子都可以处理自己的绘图,然后我可以用任何孩子的组合来组成最终结果以获得所需的行为。
This is working very well as far as rendering is concerned. This does not work so well with mouse events however. The way mouse events works are as follow (using previewmousemove as an example):
就渲染而言,这非常有效。然而,这对于鼠标事件效果不佳。鼠标事件的工作方式如下(以 previewmousemove 为例):
1- If root canvas is under mouse, fire event 2- Check all children, if one is under mouse, fire event and stop
1- 如果根画布在鼠标下,则触发事件 2- 检查所有子项,如果有一个在鼠标下,则触发事件并停止
As such, only the first child I add will receive the mouse move event. The event is not propagated to all children because they overlap.
因此,只有我添加的第一个孩子才会收到鼠标移动事件。该事件不会传播到所有子项,因为它们重叠。
To overcome this, I attempted the following: 1- Override mouse events in the root canvas 2- For every event, find all children that want to handle the event using VisualTreeHelper.HitTest 3- For all children that returned a valid hit test result (ie: under mouse and willing to handle the event (IsHitTestVisible == true)), ???
为了克服这个问题,我尝试了以下方法: 1- 覆盖根画布中的鼠标事件 2- 对于每个事件,使用 VisualTreeHelper.HitTest 找到所有想要处理该事件的子级 3- 对于返回有效命中测试结果的所有子级(即:在鼠标下并愿意处理事件(IsHitTestVisible == true)),???
This is where I am stuck, I somehow need to send the mouse event to all children, and stop the normal flow of the event to make sure the first child doesn't receive it twice (via handled = true in the event).
这是我被卡住的地方,我需要以某种方式将鼠标事件发送给所有孩子,并停止事件的正常流程以确保第一个孩子不会收到它两次(在事件中通过handled = true)。
By using RaiseEvent with the same event passed on the children, things seem to work but somehow it raises the event on the parent (root canvas) as well. To bypass this I needed to create a copy of the event and set force set the source though it appears to be more of a hack than a solution. Is there a proper way to do what I am trying to do? Code example follows.
通过将 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;
}
回答by SuperOli
I found your code example interesting so I gave it a try... but I had to make a small modification for it to work properly in my stuff.
我发现你的代码示例很有趣,所以我试了一下……但我必须做一些小的修改才能让它在我的东西中正常工作。
I had to change the 2nd "if" in the HitTestFilter method as follow:
我不得不更改 HitTestFilter 方法中的第二个“if”,如下所示:
if (element == null || element.IsHitTestVisible)
As you can see I removed the useless "element != this" at the end (you already tested that condition in the 1st "if") and I added "element == null" at the beginning.
如您所见,我在末尾删除了无用的“元素!= this”(您已经在第一个“if”中测试了该条件),并在开头添加了“element == null”。
Why? Because at some point during the filtering the parameter type was System.Windows.Media.ContainerVisual which doesn't inherit from UIElement and so element would be set to null and ContinueSkipSelfAndChildren would be returned. But I don't want to skip the children because my Canvas is contained inside its "Children" collection and UIElements I want to hittest with are contained in Canvas.
为什么?因为在过滤过程中的某个时候,参数类型是 System.Windows.Media.ContainerVisual,它不是从 UIElement 继承的,因此元素将被设置为 null 并返回 ContinueSkipSelfAndChildren。但我不想跳过孩子,因为我的 Canvas 包含在它的“Children”集合中,而我想要测试的 UIElements 包含在 Canvas 中。
回答by GuerreroTook
I think you should use the preview events because those are RoutingStrategy.Tunnel from Window to the most high control in Z-Order, and normal events are RoutingStrategy.Bubble.
我认为你应该使用预览事件,因为那些是从Window到Z-Order最高控制的RoutingStrategy.Tunnel,而正常事件是RoutingStrategy.Bubble。
In this RoutedEvents there are a property Handle when it's true the system will stop to traverse the visual tree because someone used this event.
在这个 RoutedEvents 中有一个属性 Handle 当它为真时系统将停止遍历可视化树,因为有人使用了这个事件。