Search code examples
c#wpfcanvasselectionrectangles

A selection-rectangle (drawn by mouse on canvas) does not recognize rectangles, which are inside that selection-rectangle (C# WPF)


I am now really desperate. I just can't get any further and in principle it shouldn't be that difficult. I tried solving it the last 2 weeks. The following task/problem: I have a canvas in my C# WPF project called “MarkPointsMap”, on which there are different kind of mark points (3 different kind of rectangles with different colors). One MarkPointsMap can contain round about 3000-4000 different MarkPoints (Children of that canvas) all over across the MarkPointsMap. The positions of that MarkPoints are loaded from a .tab-file, which is converted into a list called “MarkPoints” and added as Children on the “MarkPointsMap” canvas after loading the respective .tab-file.

This a screenshot of the MarkPointsMap: This a screenshot of the MarkPointsMap:

There are e.g. mostly small rectangles (the green ones), a few middle rectangles (the yellow ones) and two big rectangles (the red ones). Mulitple markpoints should be selected with a selection-rectangle. In my project I can already draw the selection rectangle, but the “if this.selectRect.Contains(MarkPointsMap.Children)” (see below) part does not work.

The selection-rectangle looks as follows:

The selection-rectangle looks as follows:

The following method “drawMarkPoints”, which is called after loading the .tab file in the program, draws the markpoints on the canvas “MarkPointsMap”:

public void drawMarkPoints()
    {
        MarkPointsMap.Children.Clear();

        foreach (MarkPoint markpoint in this.Marks.markpoints)
        {
            if (markpoint.type.relevant)
            {
                Rectangle markpointArea = new Rectangle();
                markpointArea.Tag = markpoint;
                //!!
                SolidColorBrush mySolidColorBrush = new SolidColorBrush();
                mySolidColorBrush.Color = markpoint.fillColor;
                markpointArea.Fill = mySolidColorBrush;
                markpointArea.StrokeThickness = 0.2;
                markpointArea.Stroke = Brushes.Black;

                markpointArea.Width = markpoint.symbol.Width;
                markpointArea.Height = markpoint.symbol.Height;
                //set the markpoints in canvas 
                Canvas.SetLeft(markpointArea, getCanvasCoordinates(markpoint.center).X - 0.5 * markpoint.symbol.Width); 
                Canvas.SetTop(markpointArea, getCanvasCoordinates(markpoint.center).Y - 0.5 * markpoint.symbol.Height);

                MarkPointsMap.Children.Add(markpointArea);
          
            }
        }
    }

The selection-rectangle is drawn in the canvas “MarkPointsMap” with the three Mouse-Events (MarkPointsMap_MouseDown, MarkPointsMap_MouseMove, MarkPointsMap_MouseUp):

public Rect itemRect;
public Rect selectRect;
private point anchorPoint; 
private Rectangle manualDragRect = new Rectangle(); 

private void MarkPointsMap_MouseDown(object sender, MouseButtonEventArgs e)
    {
        if (e.ChangedButton == MouseButton.Left)
        {
            isLeftMouseButtonDownOnWindow = true;
            anchorPoint = e.MouseDevice.GetPosition(MarkPointsMap);
            MarkPointsMap.CaptureMouse();
            e.Handled = true;
            manualDragRect = new Rectangle
            {
                Stroke = Brushes.Red,
                StrokeThickness = 2
            };
            MarkPointsMap.Children.Add(manualDragRect); 
        }       
    }

private void MarkPointsMap_MouseMove(object sender, MouseEventArgs e)
    {
        if (!MarkPointsMap.IsMouseCaptured)
            return;

        Point location = e.MouseDevice.GetPosition(MarkPointsMap);
        double minX = Math.Min(location.X, anchorPoint.X);
        double minY = Math.Min(location.Y, anchorPoint.Y);
        double maxX = Math.Max(location.X, anchorPoint.X);
        double maxY = Math.Max(location.Y, anchorPoint.Y);

        Canvas.SetTop(manualDragRect, minY);
        Canvas.SetLeft(manualDragRect, minX);

        double height = maxY - minY;
        double width = maxX - minX;

        manualDragRect.Height = Math.Abs(height);
        manualDragRect.Width = Math.Abs(width);
        Point xy_2 = new Point((Canvas.GetLeft(manualDragRect) + 0.5 * width), (Canvas.GetTop(manualDragRect) + 0.5 * height));
        this.selectRect = new Rect(xy_2.X, xy_2.Y, width, height);
    }


private void MarkPointsMap _MouseUp(object sender, MouseButtonEventArgs e)
    {
        MarkPointsMap.ReleaseMouseCapture();

        foreach (MarkPoint markpoint in this.Marks.markpoints) 
        {
            Rect itemRect = new Rect(markpoint.center.X, markpoint.center.Y, markpoint.symbol.Width, markpoint.symbol.Height);
            if (selectRect.Contains(itemRect))
            {
                MessageBox.Show("Test!"); // it does not reach this code, if I circle several markpoints with the red selection-rectangle called “selectRect”
                
            }

        }
    }

Why doesn’t this work? I guess it has to do with the converting from rectangle (System.Windows.Shapes using derictive) to struct Rect : IFormattable. The “if (selectRect.Contains(itemRect)”, which is not reached should in perspective color all mark points, which are inside the selection-rectangle in red, then compute the Point (x-Coordinate, y-Coordinate) of the middle-point of the selection-rectangle and add this middle point back to the original .tab-file.
Any ideas or hints, how I could continue? Thanks in advance. Best regards!


Solution

  • It looks like your calculations are wrong. You are offsetting the selectRect. You are centering it relative to the rendered manualDragRect. This way you are testing a totally different area than the one you have defined by the rendered selection bounds.

    Additionally, you are capturing the mouse on the Canvas but release the capture on some different control. It should be released on the MarkPointsMap too. Also be careful with marking mouse events as handled. If the input handling is not part of a custom control, you should generally avoid it.

    You should consider to use an ItemsControl with a Canvas as panel. You can then move the drawing to the markup and define a DataTemplate for the MarkPoint data.

    I recommend to use the original Rect returned from the manualDragRect shape. This means you should remove the selectRect field and related computations:

    private void MarkPointsMap_MouseUp(object sender, MouseButtonEventArgs e)
    {
      MarkPointsMap.ReleaseMouseCapture();
    
      foreach (MarkPoint markpoint in this.Marks.markpoints)
      {
        Rect itemRect = new Rect(markpoint.center.X, markpoint.center.Y, markpoint.symbol.Width, markpoint.symbol.Height);
    
        Rect selectionBounds = manualDragRect.RenderedGeometry.Bounds;
        selectionBounds.Offset(Canvas.GetLeft(manualDragRect), Canvas.GetTop(manualDragRect));
        if (selectionBounds.Contains(itemRect))
        {
          MessageBox.Show("Test!");
        }
    
        // TODO::Calculate center of selectionBounds
        Point selectionBoundsCenter = ...;
      }
    }