ListBox im Griff

Das ListBox-Control kann mit den Standard-Anweisungen von Visual Basic nur Listen bis zu 32768 Einträgen verwalten.
Dies ist übrigens kein Fehler bzw. Bug, sondern ein Überbleibsel aus vergangenen Windows 95/98 Tagen.

Das Control selbst ist jedoch in der Lage deutlich mehr Daten zu verwalten. Um diese Möglichkeit auch für VB-Anwender nutzbar zu machen, muß man auf die API-Function SendMessage() zurückgreifen.
 
 
ScreenShot der Demo
Das Demo-Programm, links ein ScreenShot zu sehen, zeigt eine Liste mit 200000 zufällig generierten KFT-Kennzeichen.

Die Liste ist unsortiert, um auch die Suchfunktion zu demonstrieren.

Ist Ihr Kennzeichen auch dabei?


 

Wie funktioniert das?

Das ListBox-Control ist wie viele andere Controls auch, ein Fenster, welches Nachrichten empfangen und verarbeiten kann. Mit der Function SendMessage() können somit Nachrichten an das Control gesendet werden, welches dann entsprechend darauf reagiert.

In der Windows-API sind eine Reihe Nachrichten definiert, die speziell auf Listboxen ausgerichtet sind. Sie beginnen stets mit dem Prefix 'LB_' für ListBox. Einige wichtige dieser ListBox-Nachrichten möchte ich später vorstellen.
 

Zur Demo

Ich habe eine kleine Demo geschrieben, die diese Nachrichten-Methode anwendet, um Informationen an das Control zu senden und vom Control zu empfangen.

In einem Modul Namens modListBox.bas sind die wichtigsten Funktionen für das problemlose Einbinden und Verwenden in eigenen VB-Anwendungen verpackt.

Am Beispiel von KFZ-Kennzeichen, die in der Liste angezeigt werden, wird demonstriert, wie diese Funktionen anzuwenden sind.
Der Code der Demo ist gut kommentiert und stellt über ToolTip-Texte weitere Infos bereit.
 

Kontext-Menü

Die ListBox mit den Kennzeichen beinhaltet sogar ein Context-Menü, über rechte Maustaste zu erreichen. Über dieses Menü können Einträge bearbeitet, hinzugefügt und gelöscht werden. Hier werden die Modul-Funktionen LBAppendItem, LBInsertItem, LBDeleteItem, und LBSetItemText demonstriert. Die Funktion LBGetItemText wurde der Vollständigkeit wegen noch hinzugefügt, diese Abfrage kann jedoch auch mit VB-Board-Mitteln erledigt werden. Im Code ist ein entsprechender Vermerk! Die Abfrage kann auch mit variable=ListBox.List erfolgen. Index darf nicht verwendet werden, sonst funktioniert die Abfrage nicht.
 

AutoSelect

Wird die CheckBox  CheckBox AutoSelect aktiviert die Suche bei Texteingabe aktiviert, wird der eingegebene Text sofort in der Listbox markiert. Hier kommt die Modul-Funktion LBSelectListItem zum tragen.
 

Text-Suche

Texteingabe für Suche in der ListBox Im Textfeld Such-String kan ein zu suchender Text eingegeben werden. Die beiden Lupen suchen zum einen nach Anfangs-Übereinstimmung und zum anderen nach exakter Übereinstimmung. Der ToolTip sagt, welche Lupe welche Suchfunktion hat. Hier kommen die beiden Modul-Funktionen LBFindItem und LBFindExactItem zur Anwendung.
 

Buttons

Über dieses Bedienfeld kann (von links nach rechts gehend) die Liste neu gefüllt oder gelöscht werden. Der Button ganz rechts ruft ein Info-Fenster auf.

Info-Fenster



Hinweis:
Die verbleibenden beiden Modul-Funktionen LBGetItemData und LBSetItemData werden in der Demo nicht demonstriert, wurden jedoch getestet und funktionieren.
 

Die Funktionen des Moduls

Alle Funktionen und Sub's beginnen mit dem Prefix LB für ListBox. Der Syntax wurde gleichbleibend für alle Funktionen, soweit dies möglich ist, beibehalten. Erster Parameter immer das ListBox-Objekt, als zweiter Parameter ein Index (wenn erforderlich). Und zum Schluss noch weitere Parameter, sofern dies die Message an das ListBox-Control erfordert.

Hier also alle definierten Funktionen des Moduls modListBox.bas

' ***********************************************
' *     Benutzer-Funktionen ListBox-Control     *
' ***********************************************
 

' Sucht String in ListBox, gibt Index zurück
' wenn Übereinstimmung gefunden, sonst -1
' Der Eintrag wird Markiert
Public Function LBSelectListItem(Obj As ListBox, StartIndex As Long, FindString As String) As Long
    LBSelectListItem = SendMessage(Obj.hwnd, LB_SELECTSTRING, 0, ByVal FindString)
End Function

' Sucht exakten String in ListBox, gibt Index bei
' Übereinstimmung zurück, sonst -1
' Der Eintrag wird nicht markiert
Public Function LBFindExactItem(Obj As ListBox, StartIndex As Long, FindString As String) As Long
    LBFindExactItem = SendMessage(Obj.hwnd, LB_FINDSTRINGEXACT, StartIndex, ByVal FindString)
End Function

' Sucht (nicht exakten) String in ListBox, gibt Index bei
' Übereinstimmung zurück, sonst -1
' Der Eintrag wird nicht Markiert
Public Function LBFindItem(Obj As ListBox, StartIndex As Long, FindString As String) As Long
    LBFindItem = SendMessage(Obj.hwnd, LB_FINDSTRING, StartIndex, ByVal FindString)
End Function

' ListIndex von ListBox ermitteln
' Gibt den aktuell selektierten Index zurück
Public Function LBGetListIndex(Obj As ListBox) As Long
    LBGetListIndex = SendMessage(Obj.hwnd, LB_GETCURSEL, 0, ByVal 0&)
End Function

' ListIndex von ListBox setzen
' setzt den ListIndex und markiert den Eintrag
' Ein Click wird nicht ausgelöst!
Public Sub LBSetListIndex(Obj As ListBox, NewIndex As Long)
    Call SendMessage(Obj.hwnd, LB_SETCURSEL, NewIndex, ByVal 0&)
End Sub

' Anzahl List-Einträge ermitteln (ListCount)
Public Function LBGetListCount(Obj As ListBox) As Long
    LBGetListCount = SendMessage(Obj.hwnd, LB_GETCOUNT, 0, ByVal 0&)
End Function

' Liest den Text in einer ListBox aus mit dem
' angegebenen Index aus und gibt diesen zurück
Public Function LBGetItemText(Obj As ListBox, Index As Long) As String
    Dim Buffer As String
    Dim TextLen As Long
    TextLen = SendMessage(Obj.hwnd, LB_GETTEXTLEN, Index, ByVal 0&)
    If TextLen > 0 Then
        Buffer = Space(TextLen)
        Call SendMessage(Obj.hwnd, LB_GETTEXT, Index, ByVal Buffer)
    End If
    LBGetItemText = Buffer
End Function

' Löscht den Text aus der ListBox
' Gibt bei Erfolg die Anzahl der verbleibenden
' Einträge in der Liste zurück, bei Fehler -1
Public Function LBDeleteItem(Obj As ListBox, Index As Long) As Long
    LBDeleteItem = SendMessage(Obj.hwnd, LB_DELETESTRING, Index, ByVal 0&)
End Function
 
' Fügt ein Eintrag an die Position 'Index' ein
' Der eingefügte Text wird nicht matkiert.
Public Sub LBInsertItem(Obj As ListBox, Index As Long, Text As String)
    Call SendMessage(Obj.hwnd, LB_INSERTSTRING, Index, ByVal Text)
End Sub

' Hängt ans Ende der Listbox einen Eintrag an.
' Liefert die Position zurück, an der der text eingefügt wurde
' Der hinzugefügte Text wird nicht markiert
Public Function LBAppendItem(Obj As ListBox, Text As String) As Long
    LBAppendItem = SendMessage(Obj.hwnd, LB_ADDSTRING, 0, ByVal Text)
End Function

' Ersetzt den Item-Text an der Position 'Index' mit 'NewText'
' Der Eintrag wird markiert!
Public Sub LBSetItemText(Obj As ListBox, Index As Long, NewText As String)
    ' zunächst Eintrag löschen
    If SendMessage(Obj.hwnd, LB_DELETESTRING, Index, ByVal 0&) > 0 Then
        ' und neuen Eintrag an diese Position wieder einfügen
        Call SendMessage(Obj.hwnd, LB_INSERTSTRING, Index, ByVal NewText)
        ' und noch selectieren
        Call SendMessage(Obj.hwnd, LB_SETCURSEL, Index, ByVal 0)
    End If
End Sub

' Ermittelt den benutzerdefinierten Wert ItemData
' Der Wert wird mit LBSetItemData() zugewiesen
Public Function LBGetItemData(Obj As ListBox, Index As Long) As Long
    LBGetItemData = SendMessage(Obj.hwnd, LB_GETITEMDATA, Index, ByVal 0&)
End Function

' setzt den benutzerdefinierten Wert ItemData
Public Sub LBSetItemData(Obj As ListBox, Index As Long, ItemData As Long)
    Call SendMessage(Obj.hwnd, LB_SETITEMDATA, Index, ByVal ItemData)
End Sub
 

ListBox-Nachrichten

Im folgenden möchte ich kurz auf die wichtigsten ListBox-Messages eingehen.

LB_GETCURSEL

Gibt den Index des makierten Listen-Eintrags zurück. Ist kein Eintrag selektiert, erhält man -1 zurück. DNachricht entspricht in VB, wenn man so will, der Eigenschaft ListBox.ListIndex des Controls
 
LB_SETCURSEL
Diese Nachricht ist das Pendanz zu LB_GETCURSEL und setzt die Markierung auf den gewünschten Listen-Index.  Das entspricht in VB der Eigenschaft ListBox.ListIndex=<indexwert>. Hier kann ein Index zwischen 0 und ind dem höchsten Index obergeben werden. Der höchste Index kann mit der Nachricht LB_GETCOUNT ermittelt werden. Im Gegensatz zu VB kann hier noch der Index-Wert -1 übergeben werden, das hat zur Folge, dass kein Eintrag markiert ist.
 
LB_GETCOUNT
Diese Nachricht ermittelt den höchsten Index. In VB wäre das die Eigenschaft ListBox.ListCount. Ist die Liste Leer oder tritt ein Fehler auf, wird -1 zurückgegeben. LB_GETCOUNT wird zusammen mit LB_SETCURSEL verwendet, um sicherzustellen, dass kein ungültiger Index übergeben wird.
 
LB_GETTEXT
Mit dieser Nachricht wird der Text des selektierten Listen-Eintrags ermittelt. Hierfür wird ein ausreichend grosser Buffer benötigt, in dem der Text abgelegt werden kann. Die Grösse des erforderlichen Buffers kann mit der Nachricht LB_GETTEXTLEN ermittelt werde.
In VB entspricht das der Methode Text=ListBox.List(...).


LB_GETTEXTLEN

Diese Nachricht ermittelt die Länge des markierten Textes in Zeichen (Char). Bevor die Nachricht LB_GETTEXT verwendet wird, kann mit dieser Nachricht die erforderliche Buffer-Grösse ermittelt werden.
 
LB_GETITEMDATA
In Visual Basic kann jeder einzelne Eintrag einen zusätzlichen 32-Bit-Wert erhalten. Die entsprechende Eigenschaft lautet <variable>=ListBox.ItemData(<index>). Mit dieser Nachricht kann der entsprechende Wert (ItemData) abgefragt werden. Mit LB_SETITEMDATA können frei wählbare 32-Bit-Werte zugewiesen werden.


LB_SETITEMDATA

Diese Nachricht weist einem Listen-Eintrag einen vom Benutzer frei wählbaren 32-Bit-Wert zu. Dadurch können zum jeweiligen Listeneintrag zusätzliche Informationen gespeichert werden. Beispielsweise in Index von einer Object-Collection oder ein Windows-Handle oder was auch immer. Dieser gesetzte ItemData-Wert kann mit der Nachricht LB_GETITEMDATA wieder ermittelt werden. In VB wäre dies die Eigenschaft ListBox.ItemDate(<Index>)=32-BitWert.
 
LB_ADDSTRING
Diese Nachricht fügt einen neien Eintrag zur Liste hinzu. Ist die Liste unsortiert, wird der Text am Schluss angehängt, im anderen Fall wird er in die entsprechende Stelle eingefügt. Als Rückgabewert erhält man den Index oder -1 bei Fehler.
Die VB-Methode wäre ListBox.AddItem <Text>. Um einen Text an eine bestimmte Position einzufügen, muss die Nachricht LB_INSERTSTRING verwendet werden.


LB_INSERTSTRING

Diese Nachricht fügt einen Text an der angegebenen Index-Position ein. Im Gegensatz zu LB_ADDSTRING muß hier ein Index zwischen 0 und dem höchst zulässigen (siehe LB_GETCOUNT)  angegeben werden. Als Rückgabe erhält man die Index-Position, an der der Text eingefügt wurde. Tritt ein Fehler auf, erhält man -1 zurück.


LB_DELETESTRING

Diese Nachricht löscht den mit Index angegebenen Eintrag aus der Liste. Wurde der Eintrag erfolgreich gelöscht, erhält man als Rückgabewert den höchsten Index der verbleibenden Einträgen in der ListBox. Dies entspricht dem Wert, der bei LB_GETCOUNT ermittelt werden kann. Die entsprechende VB-Methode wäre ListBox.RemoveItem(<Index>).


Was man In Visual Vasic vergeblich sucht ist eine Möglichkeit, einen Listen-Eintrag zu suchen und gebenenenfalls zu markieren. Hier bietet die API 3 weitere ListBox-Nachrichten an, die für viele VB-Programmieren von Interesse sein dürfte.
 

LB_FINDSTRING

Diese Nachricht sucht in der ListBox den Text, der mit den selben Zeichen beginnt wie der Suchstring und liefert dieses Index zurück. Wird der Suchstring nicht gefunden erhält man -1. Der entsprechende Index wird hierbei nicht selektiert. Dies kann man jedoch nachholen und die Nachricht LB_SETCURSEL an das Control senden. Eine andere Möglichkeit wäre, die Nachricht LB_SELECTSTRING zu verwenden, die dann den entsprechenden Index gleich markiert.


LB_FINDSTRINGEXACT

Mit dieser Nachricht wird die ListBox nach einer exakten Übereinstimmung mit dem Suchstring gesucht. Wird ein Eintrag gefunden, wird der Index zurück geliefert. Ist die Suche erfolglos, erhält man -1 zurück. Der gefundene Text wird nicht selektiert.
LB_SELECTSTRING
Diese Nachricht sucht in der ListBox genau wie LB_FINDSTRING den Text, der mit den seben Zeichen beginnt, wie der Suchstring. Im Gegensatz zu LB_FINDSTRING wird ein gefundener Index gleich markiert.

Hinzufügen möchte ich noch, dass kein Maus-Click ausgelöst wird.