6

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

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

3 回答 3

3

我认为您应该使用预览事件,因为那些是从窗口到 Z-Order 中最高控制的 RoutingStrategy.Tunnel,而正常事件是 RoutingStrategy.Bubble。

在这个 RoutedEvents 中有一个属性 Handle 当它是真的系统将停止遍历可视化树,因为有人使用了这个事件。

于 2010-01-05T19:03:20.507 回答
0

正如@GuerreroTook 所说,您应该使用 WPF 的 RoutedEvents 来解决这个问题(更多信息在这里.

于 2010-01-08T09:59:30.067 回答
0

我发现你的代码示例很有趣,所以我试了一下......但我必须做一个小的修改才能让它在我的东西中正常工作。

我必须更改 HitTestFilter 方法中的第二个“if”,如下所示:

if (element == null || element.IsHitTestVisible)

如您所见,我在最后删除了无用的“元素!= this”(您已经在第一个“if”中测试了该条件)并在开头添加了“element == null”。

为什么?因为在过滤过程中的某个时刻,参数类型是 System.Windows.Media.ContainerVisual,它不从 UIElement 继承,因此元素将设置为 null 并返回 ContinueSkipSelfAndChildren。但我不想跳过孩子,因为我的 Canvas 包含在它的“Children”集合中,而我想要测试的 UIElements 包含在 Canvas 中。

于 2009-11-26T16:40:28.557 回答