Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

ListBox like Combo dropdown 1

Status
Not open for further replies.

HughLerwill

Programmer
Nov 22, 2004
1,818
GB
When a ComboBox is dropped the dropdown window is displayed 'on top' of the underlying Form (or Container) so it is not clipped if it extends beyond the Form's (Container's) borders.
Can I make a ListBox behave in the same way?
 
So your ListBox is NOT "displayed 'on top' of the underlying Form (or Container)"?
Or it IS "clipped if it extends beyond the Form's (Container's) borders"?

How does it look like in design mode?

Have fun.

---- Andy
 
Gi9ven that a ListBox does not have a dropdown component, it is difficult to see how we can make it behave in the same way ...

Perhaps you can clarify your requirement a little.
 
I'm building an AutoComplete TextBox which uses a ListBox as its 'dropdown window'.
You type stuff in the TextBox and the ListBox drops with previous entries like the one you are typing.
I essentially end up with a dropdown combobox but separating the TextBox and ListBox box Controls/ Events gives more flexibility than that offered in a real ComboBox.
All is fine except if the TextBox is near the bottom of a Form; the ListBox drops but is clipped by the Form's borders. Under the same circumstances a real ComboBox's dropdown window is not clipped, it extends out beyond the Form's borders.
Currently I have the ListBox (Top) displayed at Tb.Top + Tb.Height if room in its container below that point is greater than List.Height, otherwise it's displayed above the TextBox at Tb.Top - List.Height; that works well but it is not a viable solution if the Form is a small/ short one.
 
Well ... it's possible, but involves a lot of faffing about with the API if you try and do it directly with the listbox (essentially by making it a popup window instead of a child window). You can emulate it, though, relatively simply - although there is still a lot of housekeeping to make sure the list portion appears in the right place, and disappears when it is supposed to - some of which work it sounds like you may already have done.

Here's the cheap solution

Create Form1 with 1 textbox, and two command buttons (example uses command buttons to make the 'popup' appear and disappear; in reality you'd want to work this in to your autocomplete event chain)

Create Form2 with no borders, and stick a listbox on it (flat style rather than 3d style).

And add a module for good measure

OKay - form 1 gets:

Code:
[blue]Option Explicit

Private Sub Command1_Click()

    Dim x As Long
    Dim y As Long
    
    x = Form1.Text1.Left
    y = Form1.Text1.Top + Form1.Text1.Height
    Form1.ScaleMode = vbPixels
    TranslateCoords Form1, x, y
    Call SetWindowPos(Form2.hwnd, HWND_TOPMOST, x, y, 100, 100, SWP_SHOWWINDOW Or SWP_NOSIZE)
    
End Sub


Private Sub Command2_Click()
     Unload Form2
End Sub[/blue]


Form2 gets:

Code:
[blue]Option Explicit

Private Sub Form_Load()
   
    ' Remember to set List1.IntegralHeight = False
    List1.Appearance = 0
    With Me
        List1.Move 0, 0, .Width, .Height
    End With

End Sub[/blue]

And finally the module gets

Code:
[blue]Option Explicit

Public Declare Function SetWindowPos Lib "user32" (ByVal hwnd As Long, ByVal hWndInsertAfter As Long, ByVal x As Long, ByVal y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long
Public Declare Function ClientToScreen Lib "user32" (ByVal hwnd As Long, lpPoint As POINTAPI) As Long
Public Type POINTAPI
        x As Long
        y As Long
End Type
Public Const HWND_TOPMOST = -1
Public Const SWP_NOSIZE = &H1
Public Const SWP_NOMOVE = &H2
Public Const SWP_SHOWWINDOW = &H40

Public Sub TranslateCoords(myform As Form, ByRef x As Long, ByRef y As Long)
    Dim myPoint As POINTAPI
    
    myPoint.x = x
    myPoint.y = y
    ClientToScreen myform.hwnd, myPoint
    x = myPoint.x
    y = myPoint.y
End Sub[/blue]

Just a rough example, but shows the basic principle.
 
strongm thank you.
I have your example running, albiet the first time in a session I have to click Command1 twice before Form2/ List1 is shown.
I had considered the extra Form approach (and your version is certainly more concise than mine would have been) but I am in the process of trying to streamline handling of the AcTextBox/ Listbox into a class, after hard coding it into several Forms; I'd like to keep it all wrapped in the class if possible without the need for another Form. My Forms thus far have been tall enough to cope and so it is not a current issue, however, you temp me with ...do it directly with the listbox ...please tell me more when you have a mo.
 
>A Form IS a class ...
>...wrapped in the class if possible...
Wrapped in one class if possible; although you've got me wondering now if the Methods I have in my class and the UI for the List could all be done on a Form2...
 
>please tell me more when you have a mo.

Sorry - been working on a project and without access to a VB6 box for the last couple of days. I'll see if I get a mo this afternoon.
 
>Sorry...
No problem I've been hacking away at the 'Form2' approach which you suggested earlier; so far not as tricky as I suspected but I think it's about to get trickier!
 
Welol, here's a very messy prototype for just using a listbox to illustrate what I meant. It needs a LOT of work to make it work like the combobox dropdown, however. Consider it a proof of concept. Frankly I'd stick with the Form2 idea .

You'll neeeda form3, with two textboxes, a listbox and two command buttons, then the following code:

Code:
[blue]Option Explicit

Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long
Private Declare Function SetParent Lib "user32" (ByVal hWndChild As Long, ByVal hWndNewParent As Long) As Long
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long

Private Const LB_ITEMFROMPOINT = &H1A9

Private Const GW_OWNER = 4
Private Const GWL_HWNDPARENT = (-8)
Private Const GWL_EXSTYLE = (-20)
Private Const WS_EX_APPWINDOW = &H40000
Private Const WS_EX_TOOLWINDOW = &H80&

Private Declare Function GetDesktopWindow Lib "user32" () As Long

Private Sub Command1_Click()
    Dim CurrentStyle As Long
    Dim lStyle As Long
    Dim X As Long
    Dim Y As Long
    
    Dim myControl As Control
    
    Set myControl = List1
    
    
    Form3.ScaleMode = vbPixels
    X = Text1.Left
    Y = Text1.Top + Text1.Height
    myControl.Width = Text1.Width
    TranslateCoords Form3, X, Y

    lStyle = GetWindowLong(myControl.hwnd, GWL_EXSTYLE)
   lStyle = lStyle Or WS_EX_TOOLWINDOW
   lStyle = lStyle And Not (WS_EX_APPWINDOW)
   SetWindowLong myControl.hwnd, GWL_EXSTYLE, lStyle
    SetParent myControl.hwnd, 0& 'GetDesktopWindow()
    Call SetWindowPos(myControl.hwnd, HWND_TOPMOST, X, Y, 1000, 1000, SWP_SHOWWINDOW Or SWP_NOSIZE)

    myControl.Visible = True
    myControl.ZOrder

End Sub

Private Sub Command2_Click()
    SetParent List1.hwnd, Form3.hwnd
    List1.Move Text1.Left, Text1.Top + Text1.Height
End Sub

Private Sub Form_Load()
    List1.AddItem "this"
    List1.AddItem "is"
    List1.AddItem "a"
    List1.AddItem "simple"
    List1.AddItem "example"
    List1.Visible = False
    
End Sub

Private Sub List1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
    Dim lXPoint As Long
    Dim lYPoint As Long
    Dim lIndex As Long

    lXPoint = CLng(X / Screen.TwipsPerPixelX)
    lYPoint = CLng(Y / Screen.TwipsPerPixelY)
    With List1
        ' get selected item from list
        lIndex = SendMessage(.hwnd, LB_ITEMFROMPOINT, 0, ByVal ((lYPoint * 65536) + lXPoint))
        If (lIndex >= 0) And (lIndex <= .ListCount) Then
            Text2.Text = .List(lIndex)
        Else
            Text2.Text = ""
        End If
    End With
End Sub[/blue]
 
strongm thank you very much for that, some interesting stuff in there which I'm sure I will find useful. I'll be into it soon. The Form2 approach is tricky to manage because the two forms fight for focus (that does not happen when the ListBox is on Form1 because 1. the ListBox can be made visible without it gaining Focus and 2. the vertical scroll bar on the ListBox can be used without the ListBox gaining Focus); it also gets quirky if Form1 is moved when Form2 is visible...
Working on it.
Thanks again.
 
>it also gets quirky if Form1 is moved when Form2 is visible...

THis is true of any of the soloutions I've suggested - once you play with the parent (and the combo drop down certainly does) you have to manage all this sort of stuff( the combo doesn't because it is built into the control)
 
>..true of any of the soloutions...
Yes, I noticed that the Combo can be made to misbehave if say the Form or Combo's Left/ Top Properties are changed via a Timer in its DropDown Event; however under more usual circumstances the dropdown is withdrawn automatically when a MouseDown happens anywhere on its Parent Form, thus preventing it being visible when the Form is moved. Detecting a MouseDown anywhere on Form1 is currently beyond me.
 
strongm - Ok I went back to the (pretty well debugged) Class I described in my third post and applied the code in your Form3; all works fine except that I still need a way to make the ListBox disappear when I click anywhere on the Form containing the TextBox (except on the TextBox itself!). That could be handled manually by putting code to make the ListBox invisible in all the Form's containers, but then I'd still need code to detect if the Form was moved...The ListBox is already made invisible when focus leaves the TextBox to any control (other than the ListBox when it is made visible).
Any suggestions?
 
You might want to look at the SetCapture API. It might offer you some options.
 
Thanks strongm - I seem to have been a bit busy to get on with that, I expect I will get back to it soon.
 
>...faffing about with the API...
Well you did warn me!
So I applied SetCapture to the Form when the ListBox was made Visible and I got it all to work except for the vertical scroll bar on the ListBox (if shown). Your List1_MouseDown stopped working of course so had to handle that in Form_MouseDown.
I am begining to develop a serious respect for the code underlying a normal ComboBox! I think I'll have another look at using one of them, meantime the class I have does all I need albiet the Listbox gets clipped by the Forms borders. This has become something of a rainy day project and it has mercifully stopped raining over the past few days, hope to have time to get back to it soon. Thanks for your help.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top