2
votes

I have a WPF window containing a Canvas which is populated with rotated Rectangles in code. The rectangles each have a MouseDown event and their positions will be distributed according to coordinates provided by the user. Often two or more will overlap, partially obstructing the rectangle beneath it.

I need the MouseDown event to fire for each rectangle that is under the mouse when it is pressed, even if that rectangle is obstructed by another rectangle, but I am only getting the MouseDown event for the topmost rectangle.

I have tried setting e.Handled for the clicked rectangle, and routing the events through the Canvas with no luck, and even gone as far as trying to locate the objects beneath the mouse based on their coordinates, but the rotation of the rectangles make that difficult to calculate.

    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");
    }
}

enter image description here

1
This question is very similar to what you are asking.DonBoitnott

1 Answers

0
votes

There is another question that is similar to this, but it has no accepted answer and it is quite unclear as to what the final solution should be to resolve this issue. Let's see if we can be a little more clear.

First off, the solution outlined below will use the VisualTreeHelper.HitTest method in order to identify if the mouse has clicked your rectangles. The VisualTreeHelper allows us to find the rectangles even if they have moved around due to things like Canvas.SetTop and various .RenderTransform operations.

Secondly, we are going to be capturing the click event on your canvas element rather than on the individual rectangles. This allows us to handle things at the canvas level and check all the rectangles at once, as it were.

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>();

The HitTest method being used is the more complicated one, because the simplest version of that method only returns "the topmost" item. And by topmost, they mean the first item drawn, so it's actually visually the one on the bottom of the stack of rectangles. In order to get all of the rectangles, we need to use the complicated version of the HitTest method shown below.

    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;
    }

The test example above just displays a message box showing the fill colors of all rectangles under the mouse pointer when it was clicked; verifying that VisualTreeHelper did in fact retrieve all the rectangles in the stack.

enter image description here