【问题标题】:Firing MouseDown events for overlapping Rectangles on a Canvas为 Canvas 上的重叠矩形触发 MouseDown 事件
【发布时间】:2019-03-11 18:05:34
【问题描述】:

我有一个包含 Canvas 的 WPF 窗口,该窗口在代码中填充了旋转的矩形。每个矩形都有一个 MouseDown 事件,它们的位置将根据用户提供的坐标分布。通常两个或更多会重叠,部分遮挡下方的矩形。

我需要在按下鼠标时为鼠标下方的每个矩形触发 MouseDown 事件,即使该矩形被另一个矩形阻挡,但我只获得最顶部矩形的 MouseDown 事件。

我尝试为单击的矩形设置 e.Handled,并通过 Canvas 路由事件但没有运气,甚至尝试根据坐标定位鼠标下方的对象,但是矩形使计算变得困难。

    public MainWindow()
    {
        InitializeComponent();

        Rectangle r1 = new Rectangle() {Width = 80, Height = 120, Fill = Brushes.Blue };
        r1.MouseDown += r_MouseDown;
        RotateTransform rt1 = new RotateTransform(60);
        r1.RenderTransform = rt1;
        Canvas.SetLeft(r1, 150);
        Canvas.SetTop(r1, 50);
        canvas1.Children.Add(r1);

        Rectangle r2 = new Rectangle() { Width = 150, Height = 50, Fill = Brushes.Green };
        r2.MouseDown += r_MouseDown;
        RotateTransform rt2 = new RotateTransform(15);
        r2.RenderTransform = rt2;
        Canvas.SetLeft(r2, 100);
        Canvas.SetTop(r2, 100);
        canvas1.Children.Add(r2);
    }


    private void r_MouseDown(object sender, MouseButtonEventArgs e)
    {
        Console.WriteLine("Rectangle Clicked");
    }
}

【问题讨论】:

标签: c# wpf canvas rectangles


【解决方案1】:

还有另一个与此类似的问题,但它没有公认的答案,而且对于解决此问题的最终解决方案应该是什么也很不清楚。让我们看看我们是否可以更清楚一点。

首先,下面概述的解决方案将使用VisualTreeHelper.HitTest 方法来识别鼠标是否单击了您的矩形。 VisualTreeHelper 允许我们找到矩形,即使它们由于 Canvas.SetTop 和各种 .RenderTransform 操作等原因而移动。

其次,我们将在您的画布元素上而不是在单个矩形上捕获点击事件。这使我们能够在画布级别处理事物并一次检查所有矩形。

public MainWindow()
    {
        InitializeComponent();
        //Additional rectangle for testing.
        Rectangle r3 = new Rectangle() { Width = 175, Height = 80, Fill = Brushes.Goldenrod };
        Canvas.SetLeft(r3, 80);
        Canvas.SetTop(r3, 80);
        canvas1.Children.Add(r3);

        Rectangle r1 = new Rectangle() { Width = 80, Height = 120, Fill = Brushes.Blue };
        RotateTransform rt1 = new RotateTransform(60);
        r1.RenderTransform = rt1;
        Canvas.SetLeft(r1, 100);
        Canvas.SetTop(r1, 100);
        canvas1.Children.Add(r1);

        Rectangle r2 = new Rectangle() { Width = 150, Height = 50, Fill = Brushes.Green };
        RotateTransform rt2 = new RotateTransform(15);
        r2.LayoutTransform = rt2;
        Canvas.SetLeft(r2, 100);
        Canvas.SetTop(r2, 100);
        canvas1.Children.Add(r2);
        //Mouse 'click' event.
        canvas1.PreviewMouseDown += canvasMouseDown;
    }

    //list to store the hit test results
    private List<HitTestResult> hitResultsList = new List<HitTestResult>();

使用的HitTest 方法更复杂,因为该方法的最简单版本只返回“最顶层”项。最上面的意思是第一个绘制的项目,所以它实际上在视觉上是矩形堆栈底部的那个。为了得到所有的矩形,我们需要使用下面所示的 HitTest 方法的复杂版本。

    private void canvasMouseDown(object sender, MouseButtonEventArgs e)
    {
        if (canvas1.Children.Count > 0)
        {
            // Retrieve the coordinates of the mouse position.
            Point pt = e.GetPosition((UIElement)sender);

            // Clear the contents of the list used for hit test results.
            hitResultsList.Clear();

            // Set up a callback to receive the hit test result enumeration.
            VisualTreeHelper.HitTest(canvas1,
                                new HitTestFilterCallback(MyHitTestFilter),
                                new HitTestResultCallback(MyHitTestResult),
                                new PointHitTestParameters(pt));

            // Perform actions on the hit test results list.
            if (hitResultsList.Count > 0)
            {
                string msg = null;
                foreach (HitTestResult htr in hitResultsList)
                {
                    Rectangle r = (Rectangle)htr.VisualHit;
                    msg += r.Fill.ToString() + "\n";
                }
                //Message displaying the fill colors of all the rectangles 
                //under the mouse when it was clicked.
                MessageBox.Show(msg);
            }
        }
    }

    // Filter the hit test values for each object in the enumeration.
    private HitTestFilterBehavior MyHitTestFilter(DependencyObject o)
    {
        // Test for the object value you want to filter.
        if (o.GetType() == typeof(Label))
        {
            // Visual object and descendants are NOT part of hit test results enumeration.
            return HitTestFilterBehavior.ContinueSkipSelfAndChildren;
        }
        else
        {
            // Visual object is part of hit test results enumeration.
            return HitTestFilterBehavior.Continue;
        }
    }

   // Add the hit test result to the list of results.
    private HitTestResultBehavior MyHitTestResult(HitTestResult result)
    {
        //Filter out the canvas object.
        if (!result.VisualHit.ToString().Contains("Canvas"))
        {
            hitResultsList.Add(result);
        }
        // Set the behavior to return visuals at all z-order levels.
        return HitTestResultBehavior.Continue;
    }

上面的测试示例只是显示了一个消息框,显示了单击鼠标指针下所有矩形的填充颜色;验证 VisualTreeHelper 确实检索了堆栈中的所有矩形。

【讨论】:

  • 这个解决方案效果很好。我曾希望存在一些更简单的东西,但这可以完成工作,我可以从中获得我需要的信息。谢谢@Stewbob。
  • 是的,当我看到你的问题时,我想,“哦,我知道该怎么做”,然后花了接下来的半天时间才意识到这并不容易。多种形状,尤其是对象的变换,需要使用 VisualTreeHelper,这使得它有点麻烦。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-07-18
  • 2011-02-20
  • 1970-01-01
  • 1970-01-01
  • 2013-03-19
  • 2023-03-17
  • 1970-01-01
相关资源
最近更新 更多