Capture mouse click before control

Hi,

I am developing a Windows Form application with C# .NET 2.0.

In my form, I have many controls and panels on it.  I need to capture a mouse click within the form before any of the controls or panels do.

What I'm trying to do is to mimic the dropdown menu in Microsoft Word 2007.  In Word 2007, if you click on the top left menu button, a dropdown menu will appear.  If you click anywhere outside of that menu, the menu will disappear/close.

I did it right now by having a menu panel that is "floating" on top of other controls.  So if the user clicks anywhere outside of that menu panel, that menu panel show "close" (by setting the Visible to false).  But I can't figure out how to capture a mouse click outside of that panel unless I put mouse event listener on every controls outside of that panel, which is something I'm trying to avoid.

Any help is appreciated.  Thanks!


Regards,
-Louis
l7leungAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

p_davisCommented:
how about when the panel loses focus it loses visibility
0
l7leungAuthor Commented:
Unlike a form, the menu panel don't have a focus property or event handler.

Also, the menu panel has a lot of controls in it too.  So I can't rely on the MouseEnter / MouseLeave / MouseHover events of the menu panel, because the mouse might be leaving the menu panel but entering into the controls that are inside of the panel  (instead of going outside of the panel).
0
kdwoodCommented:
Could you do something like this:

1. Store the coordinates of your menu panel when it opens.
2. Using the MouseDown event on the Form itself, check to see if the mouse coordinates are outside of the menu panel.  If so, close it.

Regards,

Keith
0
Cloud Class® Course: Microsoft Windows 7 Basic

This introductory course to Windows 7 environment will teach you about working with the Windows operating system. You will learn about basic functions including start menu; the desktop; managing files, folders, and libraries.

l7leungAuthor Commented:
Tried that too, with both overriding the OnMouseDown method on the form and attaching MouseDown event listener.  

The form doesn't fire any MouseDown or Click event if the click happens on some controls that are inside a form.  So if the form has, say, a Label control on it, and the user click on the Label, the event doesn't fire at the form level, but it will fire at the Label control level instead.

And the downside is, I've got a lot of these controls inside of the form.
0
p_davisCommented:
are you using visual studio? are you not wanting to add the events to the controls because it would be tedious or because it is too much code for the hooks?

an easy way to add them would be to highlight all controls all the form and then assign the same mouse event method to them all. that way you only have to code one event but there would be a lot of +='s in the designer.
0
kdwoodCommented:
Gotcha,

Let me test a bit and see if I can come up with something.
0
l7leungAuthor Commented:
Yup, using Visual Studio.

A lot of the controls are dynamically added, with multiple layers.  For example, the form can have a base panel, within it, 2 sub-panels, within those sub-panels, there might be sub-sub-panel or labels or textfields .... you get the idea.  :)

So if I need to attach event listeners to all levels and all controls within those levels, I'll need to do some crazy recursion stuff.  

Another thought I had is to extend the controls classes to fire any mouse click event to the parent form.  But given the variety of controls we use (eg. labels, picturebox, textbox, datagridview, etc etc.), I will need to extend nearly most of the .NET built-in controls just to have this one function implemented, and also do a global search&replace with the extended classes, risking something that might break.

I'm trying to look for some clean and simple way to do this, and hoping that it exists.  

0
p_davisCommented:
have you thought about using a modified form instead of the panel - -that might solve a lot of your problems
0
kdwoodCommented:
Ok . . . I pieced together something for you to try.  Thanks to the brilliance of Idle_Mind and this thread:

http://www.experts-exchange.com/Developer/Programming/Languages/.NET/Visual_Basic.NET/Q_23797396.html#22670308

I have to apologize, but I don't code in C#, so I can only give you the VB.NET version.

1. Copy the following code snippet your form.  I got this code from the thread above and just added a public variable to store the control that the mouse is currently over.

Hope this helps,

Keith


Public Class Form2
 
    Public myCtrlOver As String
    Private WithEvents mf As New MyFilter
 
    Private Sub Form2_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Application.AddMessageFilter(mf)
    End Sub
 
    Private Sub mf_MouseMove() Handles mf.MouseMove
        If Form.ActiveForm Is Me Then
            Dim clientPT As Point
            Dim formPT As Point = Me.PointToClient(Cursor.Position)
            Dim ctlName As String
            Dim ctl As Control = FindControl(Me)
            If Not IsNothing(ctl) Then
                ctlName = ctl.Name
                clientPT = ctl.PointToClient(Cursor.Position)
            Else
                ctlName = Me.Name
                clientPT = Me.PointToClient(Cursor.Position)
            End If
            '            Debug.Print(ctlName & ": " & clientPT.ToString & "    Form: " & formPT.ToString)
 
            myCtrlOver = ctlName
 
            Me.Label1.Text = ctlName
            Me.Label1.Refresh()
        Else
            ' ...WM_MOUSEMOVE is targeting a form other than this one...
        End If
    End Sub
 
    Private Function FindControl(ByVal cont As Control) As Control
        Dim ctl As Control = cont.GetChildAtPoint(cont.PointToClient(Cursor.Position))
        If Not IsNothing(ctl) Then
            If ctl.HasChildren Then
                Dim subCtl As Control = FindControl(ctl)
                If Not IsNothing(subCtl) Then
                    Return subCtl
                Else
                    Return ctl
                End If
            Else
                Return ctl
            End If
        Else
            Return Nothing
        End If
    End Function
 
    Private Class MyFilter
        Implements IMessageFilter
 
        Public Event MouseMove()
        Private Const WM_MOUSEMOVE As Integer = &H200
 
        Public Function PreFilterMessage(ByRef m As System.Windows.Forms.Message) As Boolean Implements System.Windows.Forms.IMessageFilter.PreFilterMessage
            Select Case m.Msg
                Case WM_MOUSEMOVE
                    RaiseEvent MouseMove()
 
            End Select
        End Function
 
    End Class
 
    Private Sub Form2_MouseDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseDown
 
        If myCtrlOver <> "YourMenuPanelName" Then
 
            ' Put your code here to close the menu
 
        End If
 
    End Sub
End Class

Open in new window

0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
kdwoodCommented:
You also do not need these two lines, as they were just there for my testing:

   Me.Label1.Text = ctlName
   Me.Label1.Refresh()
0
l7leungAuthor Commented:
Yeah.  The UI designers have some specific requirements have prevent that.  They want the button that opens up the menu panel to be partially lay on top of that menu panel.  Like this:

----------
|            |
| button |
|            |------------------
----------                       |
      |       menu              |  
      |                              |
      ------------------------

Also, I'll need to implement a lot of form-specific behaviours, like whenever the root form position change, I'll need to reposition that modified form.  In addition, I'll need to re-program or re-factor the stuff inside the menu and move them to a new modified form, because currently all of the menu logic resides in the root form.

0
l7leungAuthor Commented:
Perfect!

Thanks kdwood, it works perfectly.  Thank you p_davis also for pitching in!  :)

Here is my modified C# version:
        private MyFilter mf = new MyFilter();
 
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
 
            InitWindow();
 
            mf.LMouseDown += new MyFilter.LMouseDownEventHandler(mf_LMouseDown);
            Application.AddMessageFilter(mf);
 
        }
 
 
        private class MyFilter : IMessageFilter
        {
 
            public event LMouseDownEventHandler LMouseDown;
            public delegate void LMouseDownEventHandler();
            private const int WM_LBUTTONDOWN = 0x201;
 
            public bool PreFilterMessage(ref System.Windows.Forms.Message m)
            {
                switch (m.Msg)
                {
                    case WM_LBUTTONDOWN:
                        if (LMouseDown != null)
                        {
                            LMouseDown();
                        }
 
                        break;
 
                }
                return false;
            }
        }
 
        void mf_LMouseDown()
        {
            if (object.ReferenceEquals(Form.ActiveForm, this))
            {
                Point formPT = this.PointToClient(Cursor.Position);
                string ctlName = null;
                Control ctl = FindControl(this);
                if (ctl != null)
                {
                    ctlName = ctl.Name;
                    if (CheckRecursiveControlContains(ctl, this.ctrlMenuBasePanel))
                        Console.WriteLine("Click INside menu : " + ctlName);
                    else
                        Console.WriteLine("Click OUTside menu : " + ctlName);
                }
            }
        }
 
        private bool CheckRecursiveControlContains(Control toFind, Control toCheck)
        {
            if (toCheck == toFind)
                return true;
 
            foreach (Control c in toCheck.Controls)
            {
                if (CheckRecursiveControlContains(toFind, c))
                    return true;
            }
            return false;
        }
 
        private Control FindControl(Control cont)
        {
            Control ctl = cont.GetChildAtPoint(cont.PointToClient(Cursor.Position));
            if ((ctl != null))
            {
                if (ctl.HasChildren)
                {
                    Control subCtl = FindControl(ctl);
                    if (subCtl != null)
                        return subCtl;
                    else
                        return ctl;
                }
                else
                    return ctl;
            }
            else
                return null;
        }

Open in new window

0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
.NET Programming

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.