Link to home
Start Free TrialLog in
Avatar of DigitalDan3
DigitalDan3

asked on

Auto collapsing the combox's dropdownlist on mouse leave.

I have added a combobox to a form on the mouse leave event  I display messagebox stating "Mouse Left".  However, if I expand the list even if empty and move the mouse off the combox the MouseLeave event does not fire until i collapse the dropdown list.  

What I would like to do is collapse the combobox dropdown list when the Combobox or the dropdown portion no longer has the mouse over it.
Avatar of Sancler
Sancler

What you have here is a manifestation of a "problem" that is fairly common with VB.NET controls.  This is that, although it LOOKS like a single control, and you might therefore expect it to act like a single control so far as the firing of events is concerned, it is in fact a combination of controls and many of the "normal" events of the subordinate controls are either not fired or are consumed before they get to our own code.

I have deliberately put quotes round the word "problem" above.  This is because I am far from personally sure that - given how a combobox normally works - it is a "problem" in this case.  When its dropdown portion is showing it means that a selection is IN THE PROCESS of being made.  If/when a selection IS made, then the dropdown portion will close anyway.  If the user wants to simply to "accept" any selection that is currently highlighted and s/he therefore moves to another control (e.g by clicking on it or tabbing to it) again the dropdown portion will close anyway.  I am not sure that, as a user, I would be particularly happy if - if I happened to move the mouse off the dropdown while deciding what selection to make - the dropdown were to disappear.

I am not saying that what you want to do definitely cannot be done: I haven't given that a great deal of thought.  I think it would be messy to code, but it may well be possible.  But my question at the moment is: are you sure it is something you really want to do?  And, if so, why?

Roger
Avatar of DigitalDan3

ASKER

Here is what I am trying to avoid.  I have a combobox that shows the current forms I have open in a panel control with the active window chosen. If the user drops down the list but decides not to change the selection and then clicks on a toostrip button the dropdown collapses but the button's click event doesnt fire until you press it again.
That's sounds to me like yet another manifestation of the same problem - this time, at the toolstrip end.  If that makes sense to you, then I think it would probably be better/easier to tackle it at that end.  Would that fill the bill?  I can't say, off the top of my head, how to tackle it at that end.  But if it would meet your needs, I'll give it some thought (or someone else may already have some code: I do seem to remember a post on here that touched on that issue).

By the way, although I picked up your last post pretty quickly, I may not be about for a few hours now.  So if you do post, and there's then a relatively long silence, it's not because I'm ignoring you.

Roger
I just played with regular buttons and it has the same issue of having to double click.
Yes, now I've tried it, I agree we get the same effect.  Here's some code that will overcome it.  I suggest you try it first in its demo form.  One form, two buttons near the top and one combo - called cbo for demo purposes - lower down.  This code

Public Class Form1

    Private Sub cbo_DropDownClosed(ByVal sender As Object, ByVal e As System.EventArgs) Handles cbo.DropDownClosed
        Dim pt As System.Drawing.Point
        pt = New Point(Windows.Forms.Cursor.Position.X, Windows.Forms.Cursor.Position.Y)
        Dim ctl As Control = FindControlAtLocation(pt)
        If Not ctl Is Nothing Then
            ctl.Focus()
            If ctl.Equals(Button1) Then
                Button1PseudoClick()
            End If
            If ctl.Equals(Button2) Then
                Button2PseudoClick()
            End If
        End If
    End Sub

    Private Function FindControlAtLocation(ByVal pt As Point) As Control
        Dim result As Control = Nothing
        Dim testpt As New Point(Me.PointToClient(pt))
        For Each ctl As Control In Me.Controls
            If testpt.Y > ctl.Top Then
                If testpt.Y < (ctl.Top + ctl.Height) Then
                    If testpt.X > ctl.Left Then
                        If testpt.X < (ctl.Left + ctl.Width) Then
                            Return ctl
                        End If
                    End If
                End If
            End If
        Next
        Return result
    End Function

    Private Sub Button1PseudoClick()
        Debug.WriteLine("Button 1 Click")
    End Sub

    Private Sub Button2PseudoClick()
        Debug.WriteLine("Button 2 Click")
    End Sub

    Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
        Button1PseudoClick()
    End Sub

    Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        'put some items in combo
        cbo.Items.AddRange(New Object() {"a", "b", "c", "d"})
    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        Button2PseudoClick()
    End Sub
End Class

It works OK within the scope of the demo but (a) you'll need to consider whether it (or code based on the same approach) would work in your real-life scenario and (b) it may be possible to streamline it somewhat.

The idea is that we hook into the combo's DropDownClosed event because that is one which - unlike mouse and focus events - does seem to be visible to the application at large and, as you say, the dropdown DOES close.  Then we find out where the mouse cursor is, and pass that info to a sub which cycles through the controls to find out if that location is on one of them.  Focus and other properties/events are no good for this purpose because they're still stuck, at that point, in the combo.

If it is in one of the controls we fire the appropriate Click code for that.  To make that a bit easier, I've moved the code into PseudoClick subs, which can be called directly either from this "special" code or from the "normal" Click code.  There are other ways of doing that, but I reckon this is cleaner and clearer.

Finally, you'll see I've used Debug.WriteLine, rather than a MessageBox, to demonstrate that the code is being hit.  That is deliberate.  In fact, I think MessageBox would be OK as the code is now arranged.  But the problem with using that (as your original post did) in this sort of "confused focus" situation is that, as a MessageBox itself takes focus, it can just confuse the focus even further.

Roger
ASKER CERTIFIED SOLUTION
Avatar of Sancler
Sancler

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
A neat solution I came across is:

Declare at top of class:

    Private Declare Sub mouse_event Lib "user32" (ByVal dwFlags As Long, ByVal dx As Long, ByVal dy As Long, ByVal cButtons As Long, ByVal dwExtraInfo As Long)
    Private Const MOUSEEVENTF_LEFTDOWN = &H2 ' left button down
    Private Const MOUSEEVENTF_LEFTUP = &H4 ' left button up

Then in the cmbBox_DropDownClosed use:

    Private Sub cmbBox_DropDownClosed(ByVal sender As Object, ByVal e As System.EventArgs) Handles cmbBox.DropDownClosed

        Dim pt As System.Drawing.Point = New Point(Windows.Forms.Cursor.Position.X, Windows.Forms.Cursor.Position.Y)
        If Not cmbBox.Bounds.Contains(pt.X, pt.Y) Then
            System.Windows.Forms.Cursor.Position = New Point(pt.X, pt.Y)
            mouse_event(MOUSEEVENTF_LEFTDOWN Or MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)
        End If

    End Sub
or to put it more simply:

    Private Sub cmbBox_DropDownClosed(ByVal sender As Object, ByVal e As System.EventArgs) Handles cmbBox.DropDownClosed

        If Not cmbBox.Bounds.Contains(Windows.Forms.Cursor.Position.X, Windows.Forms.Cursor.Position.Y) Then
            mouse_event(MOUSEEVENTF_LEFTDOWN Or MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)
        End If

    End Sub