6

I am trying to handle a mouseclick event on a particular form that should fire if the mouse cursor falls between a set of coordinates - lets say a square.

I understand that if I had an empty form I could simply tie in to the mousemove event and off I go. But in reality there may be up to 10 different overlapping controls and in my test app the mousemove event only fires if the cursor is on the actual form itself and not if its over a child control.

Does anyone know how to handle this event when there are an unknown number of child controls at design time?

Is there an easy one-liner I can use?

BWA
  • 5,672
  • 7
  • 34
  • 45
Grant
  • 11,138
  • 32
  • 94
  • 140

5 Answers5

8

try this:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        AddMouseMoveHandler(this);
    }

    private void AddMouseMoveHandler(Control c)
    {
        c.MouseMove += MouseMoveHandler;
        if(c.Controls.Count>0)
        {
            foreach (Control ct in c.Controls)
                AddMouseMoveHandler(ct);
        }
    }

    private void MouseMoveHandler(object sender, MouseEventArgs e)
    {
        lblXY.Text = string.Format("X: {0}, Y:{1}", e.X, e.Y);
    }
}
TheVillageIdiot
  • 40,053
  • 20
  • 133
  • 188
  • The OP says the controls he is using do not implement MouseMove, unless I am misunderstanding him. – Robert Harvey Nov 12 '09 at 03:41
  • Yes I read it only after submitting answer. This leaves him with the option of installing global mouse hooks. – TheVillageIdiot Nov 12 '09 at 04:08
  • 3
    Glad you didn't delete your post though the OP was wanting something else. Helped me out in my situation. – Chuck Savage Aug 31 '11 at 18:19
  • @TheVillageIdiot Thanks helped a lot. Can change event to get relative position like so Point pt = e.Location; Control c = sender as Control; if (sender != this) { // Loop from this child to (this) do { //Adjust the point for each parent container pt.Offset(c.Left, c.Top); c = c.Parent; } while (c != this); } MessageBox.Show(string.Format("X: {0}, Y:{1}", pt.X, pt.Y)); – clamchoda Jul 31 '18 at 03:54
7

I know this post is quite old, but it seems to me that the simplest method would be for the form to implement IMessageFilter. In the constructor (or in OnHandleCreated) you call

Application.AddMessageFilter(this);

and then you can catch the messages of all windows in your implementation of IMessageFilter.PreFilterMessage.

You'd likely need to use P/Invoke for the WIN32 IsChild method

[DllImport("user32.dll")]
public static extern bool IsChild(IntPtr hWndParent, IntPtr hWnd);

along with the form's Handle property to ensure that you're handling the right messages.

stritch000
  • 355
  • 4
  • 10
0

I know I'm a bit late to the punch, but I was having troubles with this earlier today when using a Panel as a title bar. I had a label to display some text, a picturebox and a few buttons all nested within the Panel, but I needed to trap the MouseMove event regardless.

What I decided to do was implement a recursive method handler to do this, as I only had 1 level of nested controls, this may not scale overly well when you start approaching ridiculous levels of nesting.

Here's how I did it:

    protected virtual void NestedControl_Mousemove(object sender, MouseEventArgs e)
    {
        Control current = sender as Control;
        //you will need to edit this to identify the true parent of your top-level control. As I was writing a custom UserControl, "this" was my title-bar's parent.
        if (current.Parent != this) 
        {
            // Reconstruct the args to get a correct X/Y value.
            // you can ignore this if you never need to get e.X/e.Y accurately.
            MouseEventArgs newArgs = new MouseEventArgs
            (
                e.Button, 
                e.Clicks, 
                e.X + current.Location.X, 
                e.Y + current.Location.Y, 
                e.Delta
            );
            NestedControl_Mousemove(current.Parent, newArgs);
        }
        else
        {
            // My "true" MouseMove handler, called at last.
            TitlebarMouseMove(current, e);
        }
    }

    //helper method to basically just ensure all the child controls subscribe to the NestedControl_MouseMove event.
    protected virtual void AddNestedMouseHandler(Control root, MouseEventHandler nestedHandler)
    {
        root.MouseMove += new MouseEventHandler(nestedHandler);
        if (root.Controls.Count > 0)
            foreach (Control c in root.Controls)
                AddNestedMouseHandler(c, nestedHandler);
    }

And then to set it up is relatively simple:

Define your "true" handler:

    protected virtual void TitlebarMouseMove(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            this.Text = string.Format("({0}, {1})", e.X, e.Y);
        }
    }

And then set up the controls event subscribers:

//pnlDisplay is my title bar panel.
AddNestedMouseHandler(pnlDisplay, NestedControl_Mousemove);

Relatively simple to use, and I can vouch for the fact it works :)

Jason Larke
  • 5,289
  • 25
  • 28
0

Why don't you just use the controls' mouseover event handlers?

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
0

imho there is a bit of a binary situation here : and there is no "one-liner." the only solution I can see is to get your controls that don't implement events into a .NET container that does.

When any control gets a click, the normal expected behavior is that it will become the Active Control of the Form (which can always be accessed by this.ActivceControl).

But, particulary if the control you clicked captures the mouse, something has got to raise an event since .NET does not implement event "bubbling" (as WPF does).

The usual way to deal with extending behavior of any object that is sealed or whatever is to write an extension method, and I have found writing extensions for Control quite easy, but I don't know if that will help you in this case. Unfortunately I am out of my home country right now, and do not have Visual Studio to play around with.

One strategy you can use to determine if a given Point on a Form falls within the bounds of any Control is to enumerate the areas (Bounds) of all controls on the Form via 'forall of the Forms Control.Collection (this.Controls). But, if you have overlapping Controls, you then have the issue of more than one control possibly containing a given point.

best, Bill

BillW
  • 3,415
  • 4
  • 27
  • 46
  • 1
    TheVillageIdiot got there first about Global Hooks, and you might wish to accept his answer rather than mine. I also was going to suggest you look into Global Mouse Hooks like : George Mamaladze's "classic" article : http://www.codeproject.com/KB/cs/globalhook.aspx I can verify that his GlobalHook code will compile, run fine in VS 2010 beta 2 compiled against FrameWork <= 3.5 (breaks against FrameWork 4.0). You'll find other articles on GlobalHooks on CodeProject also. I'm curious what kind of control you are using that does not expose a mousedown or click event. best, – BillW Nov 13 '09 at 07:20