• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 3556
  • Last Modified:

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
0
l7leung
Asked:
l7leung
  • 5
  • 4
  • 3
1 Solution
 
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
Windows Server 2016: All you need to know

Learn about Hyper-V features that increase functionality and usability of Microsoft Windows Server 2016. Also, throughout this eBook, you’ll find some basic PowerShell examples that will help you leverage the scripts in your environments!

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

Featured Post

Vote for the Most Valuable Expert

It’s time to recognize experts that go above and beyond with helpful solutions and engagement on site. Choose from the top experts in the Hall of Fame or on the right rail of your favorite topic page. Look for the blue “Nominate” button on their profile to vote.

  • 5
  • 4
  • 3
Tackle projects and never again get stuck behind a technical roadblock.
Join Now