Eine Drop-Down-Auswahlliste wie bei einer ComboBox - aber ohne die ComboBox selbst? Dafür gäbe es einige praktische Einsatzmöglichkeiten, angefangen von einem CommandButton oder einer CheckBox im Button-Stil (Style = grafisch) bis hin zu Auswahllisten unter einem ListView-Item oder einem TreeView-Knoten. Wie sie die beiden letzteren Ideen realisieren, überlasse ich Ihnen. Hier in diesem Artikel geht es nur darum, wie Sie die Auswahlliste zur Anzeige bringen.
Der eigentliche Trick ist simpel - Sie brauchen nur dafür zu sorgen, dass die ComboBox selbst nicht erscheint und klappen die Liste selbst aus. Wie Sie das Aus- und Einklappen per Code bewerkstelligen, sehen Sie in "Klappe auf - Klappe zu".
Das Verbergen der ComboBox könnten Sie nun etwa mit einem passgenau darüber gelegten Frame oder einer PictureBox erledigen. Doch dann hätten Sie noch ein wenig Arbeit damit. Denn sie hätten den Fokus während und nach dem Ausklappen der Auswahlliste richtig zu steuern. Sie müssten dafür sorgen, dass die ComboBox selbst auch nicht unvorhergesehen den Fokus erhalten kann. Und Sie müssten gegebenenfalls prüfen, wann die Auswahlliste wieder geschlossen wird, um beispielsweise die oben erwähnte CheckBox wieder in den Ruhezustand zurück versetzen zu können.
Da bietet es sich an, ein UserControl mit einer darauf platzierten ComboBox zu verwenden. Die Breite der Auswahlliste bestimmen Sie über die Breite des UserControls, an dessen innere Breite sich die ComboBox automatisch anpasst. Und indem Sie die Höhe des UserControls zur Laufzeit auf 0 setzen, verschwindet die ComboBox wie beabsichtigt. Nun schieben Sie die ComboBox noch gerade über die Oberkante des UserControls "ins Nichts" hinaus, damit die Oberkante der Auswahlliste mit der Oberkante des UserControls übereinstimmt. So können Sie zur Entwicklungszeit das UserControl gezielt positionieren. Von alledem wird die Auswahlliste jedoch nicht betroffen - sie erscheint wie gewohnt und wie erhofft an der gewünschten Stelle. Allerdings sollten Sie dabei berücksichtigen, dass die Auswahlliste nicht immer nach unten aufklappt. Denn wenn sich die nunmehr "virtuelle" ComboBox zu nahe an der Unterkante des Bildschirms befindet, klappt die Auswahlliste nach oben auf - oberhalb der virtuellen ComboBox. Sie erhalten dadurch entweder einen unschönen und eher unbeabsichtigten Versatz. Setzen Sie jedoch beispielsweise die Höhe einer zugehörigen CheckBox zur Entwicklungszeit auf die die Höhe des UserControls, erscheint eine nach oben hin aufklappende Auswahlliste dann auch wunschgemäß an der Oberkante dieser CheckBox.
Um das UserControl einfach zu halten und nicht alle Eigenschaften, Methoden und Ereignisse der darauf platzierten ComboBox durchreichen zu müssen, wird die ComboBox als Eigenschaft "Combo" über eine Nur-Lesen-Eigenschaft nach draußen gereicht. So haben Sie den gewohnten Zugriff auf die ComboBox. Und Sie können jederzeit eine Ereignisempfänger-Variable darauf setzen und so auch die Ereignisse der ComboBox wie gewohnt auswerten. Sie können dieses UserControl daher auch nur privat innerhalb eines Projektes verwenden, da eine Referenz auf ein VB-eigenes Steuerelement nicht über eine Eigenschaft öffentlich nach außen gegeben werden kann (und darf)!
An eigenen, zusätzlichen Eigenschaften bietet das UserControl lediglich die Eigenschaft IsDropped (siehe ebenfalls "Klappe auf - Klappe zu"), die Sie darüber informiert, ob die Auswahlliste aktuell ausgeklappt ist, und die Eigenschaft Enabled, die das Ausklappen der Auswahlliste sperrt.
Das Ausklappen und gegebenenfalls manuelle Einklappen der Auswahlliste erfolgt über die Methode DropDown. In deren Parameter ShowHide geben Sie True an, um die Auswahlliste auszuklappen, und False, um sie einzuklappen. Wird sie ausgeklappt, wird zunächst mittels der API-Funktion GetFocus festgestellt, welches andere Steuerelement aktuell noch den Fokus inne hat, um ihn nach dem Schließen der Auswahlliste wieder dorthin zurück setzen zu können. Die interne, auf False voreingestellte Enabled-Eigenschaft des UserControls wird nun auf True gesetzt, damit die ComboBox den Fokus erhalten kann. Und eben die Zuweisung des Fokus an die ComboBox ist hier dann die letzte Aktion.
Ein Timer, der nach dem Ausklappen aktiviert wird, prüft in kurzen Abständen, ob die Auswahlliste noch geöffnet ist. Wird festgestellt, dass sie geschlossen ist, deaktiviert sich der Timer und setzt den Fokus über die API-Funktion SetFocusAPI wieder auf das beim Ausklappen gemerkte Fenster-Handle. Dann wird die interne Enabled-Eigenschaft des UserControls wieder auf False zurück gesetzt - und damit wird auch die ComboBox wieder gesperrt. Das Schließen der Auswahlliste wird nun noch über das Ereignis Closed an Ihre Anwendung gemeldet.
Die ausgeklappte Auswahlliste verhält sich vollkommen wie gewohnt. Die Navigation per Tastatur, das Verhalten beim Darüberfahren mit dem Mauszeiger, das Scrollen, und auch das Schließen per Mausklick auf ein Element in der Liste oder irgendwo anders hin oder per Escape- oder Enter-Taste sind identisch.
Private Declare Function GetFocus Lib "user32" () 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 Declare Function SetFocusAPI Lib "user32" _
Alias "SetFocus" (ByVal hwnd As Long) As Long
Private mStartFocus As Long
Public Event Closed()
Private pEnabled As Boolean
Public Property Get Combo() As Object
Set Combo = cbo
End Property
Public Property Get Enabled() As Boolean
Enabled = pEnabled
End Property
Public Property Let Enabled(New_Enabled As Boolean)
pEnabled = New_Enabled
If pEnabled = False Then
Me.DropDown False
End If
End Property
Public Property Get IsDropped() As Boolean
Const CB_GETDROPPEDSTATE = &H157
IsDropped = CBool(SendMessage(cbo.hwnd, CB_GETDROPPEDSTATE, 0, 0))
End Property
Public Sub DropDown(Optional ByVal ShowHide As Boolean = True)
Const CB_SHOWDROPDOWN = &H14F
If ShowHide Then
If Not pEnabled Then
Exit Sub
End If
mStartFocus = GetFocus()
End If
SendMessage cbo.hwnd, CB_SHOWDROPDOWN, ShowHide, 0
If ShowHide Then
UserControl.Enabled = True
cbo.SetFocus
tmr.Enabled = True
End If
End Sub
Private Sub tmr_Timer()
If Not Me.IsDropped Then
SetFocusAPI mStartFocus
tmr.Enabled = False
UserControl.Enabled = False
RaiseEvent Closed
End If
End Sub
Private Sub UserControl_Initialize()
pEnabled = True
End Sub
Private Sub UserControl_InitProperties()
If Ambient.UserMode Then
With UserControl
.BackStyle = 0
.Enabled = False
End With
Else
With cbo
.AddItem Ambient.DisplayName
.ListIndex = 0
.ForeColor = vbHighlightText
.BackColor = vbHighlight
End With
End If
End Sub
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
UserControl_InitProperties
pEnabled = PropBag.ReadProperty("Enabled", True)
End Sub
Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
PropBag.WriteProperty "Enabled", pEnabled, True
End Sub
Private Sub UserControl_Resize()
Static sInProc As Boolean
If sInProc Then
Exit Sub
Else
sInProc = True
End If
If Ambient.UserMode Then
UserControl.Height = 0
With cbo
.Move 0, -.Height, UserControl.ScaleWidth
End With
Else
With UserControl
.Height = cbo.Height
cbo.Move 0, 0, .ScaleWidth
End With
End If
sInProc = False
End Sub
|