c#wpf重叠控件没有接收鼠标事件

时间:2009-04-11 21:22:33

标签: c# wpf controls mouse z-order

我正在构建一个画布控件。此根画布具有多个重叠的子画面(画布也是如此)。这样做是为了让每个孩子都可以处理自己的绘图,然后我可以用任何子组合来组合最终结果,以获得所需的行为。

就渲染而言,这非常有效。但是,对于鼠标事件,这不能很好地工作。鼠标事件的工作方式如下(使用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;
        }

3 个答案:

答案 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