views:

90

answers:

5

In my C# (.NET 2) app I'd like to determine which control is closet to the mouse.

I can think of a few ways to do this that won't quite work right. I could use the Control.Location property, but that just gives me top/left, and the mouse might be on the other side of the control. I could calculate the center point of a control, but large controls would skew this (being near the edge of a control counts as being close to the control).

So basically I have a bunch of rectangles on a canvas and a point. I need to find the rectangle nearest to the point.

(Ideally I'd like to actually know the distance between the point and rectangle, too).

Any ideas?

A: 

You have to think in terms of rectangles :)

  1. Test: Is mouse within control?
  2. If not: How far away from any single edge?

Then you have to know which controls you are interested in, the form is, for example, a control..

Onkelborg
A: 

For starters, create method that will calculate distance from the rectangle edge to some arbitrary point. Signature for this method should be:

double DistanceFrom(Rect r, Point p);

Then, for the simplest try, iterate all controls, calculate distance, remeber the minimum distance and control that provided it.

For rectangle distance, check this out.

EDIT:

In fact, you can maintain a sorted list of controls so you can always have first one that is closer on top, and maintain that list as the mouse moves - it may prove to be more efficient in the terms of speed. Interesting issue though :)

Daniel Mošmondor
+3  A: 

You need to find the following:
- Distance to the closest corner
- Distance to the closest edge
- (optionally) distance to the center

Basically, you want the smaller of these three values. Take the min of that for two controls to determine which is closer.

Begin when you load the form by iterating all the controls on the form and creating a collection of the class below.

To find the closest control to a point, iterate the collection (see code at bottom). Keep track of the control with the minimum distance you've found so far. You can test for ContainsPoint() if you want... if you find a control where the point falls within the control bounds, you've got your control (so long as you don't have overlapping controls). Else, when you get to the end of the collection, the control you found with the shortest distance to the center/edge is your control.

public class HitControl {

    public Control ThisControl;

    private Rectangle ControlBounds;
    private Point Center;

    public HitControl (Control FormControl) {
        ControlBounds = FormControl.Bounds;
        Center = new Point(ControlBounds.X + (ControlBounds.Width/2), ControlBounds.Y + (ControlBounds.Height/2));
    }

    //  Calculate the minimum distance from the left, right, and center
    public double DistanceFrom(Point TestPoint) {

        //  Note:  You don't need to consider control center points unless
        //  you plan to allow for controls placed over other controls... 
        //  Then you need to test the distance to the centers, as well, 
        //  and pick the shortest distance of to-edge, to-side, to-corner

        bool withinWidth = TestPoint.X > ControlBounds.X && TestPoint.X < ControlBounds.X + ControlBounds.Width;
        bool withinHeight = TestPoint.Y > ControlBounds.Y && TestPoint.Y < ControlBounds.Y + ControlBounds.Height;

        int EdgeLeftXDistance = Math.Abs(ControlBounds.X - TestPoint.X);
        int EdgeRightXDistance = Math.Abs(ControlBounds.X + ControlBounds.Width - TestPoint.X);

        int EdgeTopYDistance = Math.Abs(ControlBounds.Y - TestPoint.Y);
        int EdgeBottomYDistance = Math.Abs(ControlBounds.Y + ControlBounds.Height - TestPoint.Y);

        int EdgeXDistance = Math.Min(EdgeLeftXDistance, EdgeRightXDistance);
        int EdgeYDistance = Math.Min(EdgeTopYDistance, EdgeBottomYDistance);


        // Some points to consider for rectangle (100, 100, 100, 100):
        //  - (140, 90):  Distance to top edge
        //  - (105, 10):  Distance to top edge
        //  - (50, 50):   Distance to upper left corner
        //  - (250, 50):  Distance to upper right corner
        //  - (10, 105):  Distance to left edge
        //  - (140, 105):  Distance to top edge
        //  - (105, 140):  Distance to left edge
        //  - (290, 105):  Distance to right edge
        //  - (205, 150):  Distance to right edge
        //  ... and so forth


        //  You're within the control
        if (withinWidth && withinHeight) {
            return Math.Min(EdgeXDistance, EdgeYDistance);
        }

        //  You're above or below the control
        if (withinWidth) {
            return EdgeYDistance;
        }

        //  You're to the left or right of the control
        if (withinHeight) {
            return EdgeXDistance;
        }

        //  You're in one of the four outside corners around the control.
        //  Find the distance to the closest corner
        return Math.Sqrt(EdgeXDistance ^ 2 + EdgeYDistance ^ 2);


    }

    public bool ContainsPoint (Point TestPoint) {
        return ControlBounds.Contains(TestPoint);
    }


}



//  Initialize and use this collection
List<HitControl> hitControls = (from Control control in Controls
                                select new HitControl(control)).ToList();

Point testPoint = new Point(175, 619);
double distance;
double shortestDistance = 0;
HitControl closestControl = null;

foreach (HitControl hitControl in hitControls) {

    //  Optional... works so long as you don't have overlapping controls
    //  If you do, comment this block out
    if (hitControl.ContainsPoint(testPoint)) {
        closestControl = hitControl;
        break;
    }

    distance = hitControl.DistanceFrom(testPoint);
    if (shortestDistance == 0 || distance < shortestDistance) {
        shortestDistance = distance;
        closestControl = hitControl;
    }
}

if (closestControl != null) {
    Control foundControl = closestControl.ThisControl;
}
James B
+1  A: 

First check the point is in any rectangle. if not u can find distance between your point and each line segment with the algorithm in this. also you can find the 4 segments of your control, so u have a list (initiated first time) of four segments (determining the control sides) and now you can find the nearest segment, its nearest rectangle.

SaeedAlg
A: 

I agree with Daniel that we need: double DistanceFrom(Rect r, Point p);

But before that, we need: double DistanceFrom(Line r, Point p); and double AngleBetweenPoints(Point p1, Point p2);

QYY