• Keine Ergebnisse gefunden

Arbeiten mit Listboxen

Im Dokument Win32API-Tutorials für Delphi (Seite 32-38)

1. Fenster und Controls

2.1. Arbeiten mit Listboxen

2.1.1. Die Listbox erzeugen

Dieses Tutorial beschäftigt sich mit Listboxen und dem Hinzufügen und Löschen von Einträgen. Auch hier nimmt einem die VCL eine Menge Arbeit ab, aber so schwer ist es auch mit dem API nicht.

An dieser Stelle ganz kurz etwas zur Funktionsweise des Beispielprogramms, da dieses Tutorial auf Teile davon noch näher eingehen wird. Nach dem Start befinden sich schon fünf Einträge in der Liste. Wählt man einen aus, erscheint der Eintrag im Label rechts oben. Es lassen sich neue Einträge über ein Eingabefeld hinzufügen, und es lassen sich markierte Einträge aus der Liste löschen.

Außerdem lassen sich alle Einträge via Button auswählen, und die ausgewählten Einträge können in ein Eingabefeld kopiert werden.

Widmen wir uns also zuerst dem Erzeugen der Listbox. Wie gehabt wird auch sie wie alle anderen Fensterelemente erzeugt. Es gibt ein paar spezielle Stilattribute, von denen einige im folgenden Codeauszug benutzt wurden:

hwndListBox := CreateWindowEx(WS_EX_CLIENTEDGE, 'LISTBOX', nil, WS_CHILD or WS_VISIBLE or LBS_STANDARD or LBS_EXTENDEDSEL, 10, 10, 200, 230, hWnd, IDC_LB, hInstance, nil);

Wert Bedeutung

LBS_EXTENDEDSEL erlaubt die Auswahl mehrerer Einträge (STRG und Shift werden unterstützt) LBS_MULTICOLUMN erstellt eine Listbox mit mehreren Spalten, die horizontal gescrollt werden können

LBS_NOTIFY das übergeordnete Fenster erhält eine Benachrichtigung wenn auf einen Eintrag geklickt wird LBS_SORT die Einträge werden alfabetisch sortiert

Weitere Informationen zu Stilattributen finden Sie im MSDN oder in der Hilfe.

2.1.2. Der Listbox einen Eintrag hinzufügen

Zum Hinzufügen von Einträgen bietet die Listbox die Nachricht "LB_ADDSTRING". Der erste Parameter wird nicht genutzt (ist also Null), und der zweite enthält einen Zeiger auf den Textpuffer.

buffer := 'Peter';

SendMessage(hwndListbox, LB_ADDSTRING, 0, Integer(@buffer));

Im Beispielprogramm finden Sie eine Funktion, die - via Buttonklick - neue Einträge hinzufügt. Das Prinzip entspricht dabei dem eben gezeigten Muster.

2.1.3. Einen Eintrag aus der Listbox entfernen

Bevor wir einen Eintrag löschen, sollten wir zunächst herausfinden, ob überhaupt Einträge vorhanden, und ob davon min.

einer markiert ist. Wir wollen das Programm ja möglichst logisch gestalten und den Anwender nicht verwirren. Wir ermitteln also zunächst mit der Nachricht "LB_GETCOUNT" die Anzahl der Einträge:

i := SendMessage(hwndListbox, LB_GETCOUNT, 0, 0);

Der Rückgabewert ist entweder die Anzahl der Einträge oder der Wert LB_ERR, der besagt, dass ein Fehler aufgetreten ist. Wenn dieser Fehlercode oder der Wert Null zurückgeliefert werden, sollten wir die Funktion an der Stelle beenden.

Im günstigen Fall können wir uns aber nun den Index des selektierten Eintrages mit der Nachricht "LB_GETCURSEL"

holen. Auch hier wird im Fehlerfall LB_ERR zurückgegeben. Ansonsten ist das Ergebnis unser gesuchter Index, und wir können den Eintrag mit "LB_DELETSTRING" löschen:

i := SendMessage(hwndListbox, LB_GETCURSEL, 0, 0);

SendMessage(hwndListbox, LB_DELETESTRING, i, 0);

2.1.4. Mehrere markierte Einträge auslesen

Die Listbox in unserem Beispielprogramm gestattet die Auswahl mehrerer Einträge. Auch hier wäre eine Fehlerkorrektur empfehlenswert: zuerst sollte man feststellen, wie viele Einträge markiert sind. Dann sucht man den Index des jeweiligen Eintrages und den Text selbst heraus. Das Beispielprogramm enthält eine entsprechende Prozedur, die durch einen Button aufgerufen wird.

Zuerst finden wir mit Hilfe der Nachricht "LB_GETSELCOUNT" die Anzahl der markierten Einträge heraus:

CountItems := SendMessage(hwndListbox, LB_GETSELCOUNT, 0, 0);

Auf die Weise müssen wir nicht mühsam alle Einträge auf ihren Status prüfen. Wir nutzen stattdessen die Nachricht

"LB_GETSELITEMS" und übergeben den eben ermittelten Wert als ersten Parameter. Der zweite ist ein Integer-Array, dass die Indexwerte der markierten Einträge aufnehmen soll. Dieses Array muss groß genug sein. Im Beispielprogramm ist er auf 100 Einträge begrenzt.

SendMessage(hwndListbox, LB_GETSELITEMS, CountItems, Integer(@SelItems));

Der Rest ist eine for-Schleife, in der wir die Indexes durchlaufen, den zugehörigen Text ermitteln und in das Eingabefeld eintragen lassen. Zum Auslesen eines Eintrages nutzen wir die Nachricht "LB_GETTEXT". Diese Nachricht benötigt zwei Parameter. Der erste ist der Index, dessen Text wir ermitteln wollen, und der zweite ist ein Zeiger auf den Puffer, der den Text aufnehmen soll. Das Prinzip ähnelt dabei dem Auslesen eines Editfeldes.

for i := 0 to CountItems-1 do begin

SendMessage(hwndListbox, LB_GETTEXT, SelItems[i], Integer(@buffer));

lstrcat(buffer1, buffer);

lstrcat(buffer1, '; ');

SetWindowText(hwndEditSelItems, buffer1);

end;

Eine elegantere Methode wäre ein dynamisches Array, das sich nach der Anzahl der markierten Einträge richtet und damit auf keine feste Größe (wie 100, in dem Fall) beschränkt ist. Die Umsetzung ist auch recht einfach. Das Array wird wie folgt deklariert:

var SelItems : array of integer;

Wenn wir nun die Anzahl der markierten Einträge ermittelt haben, dann setzen wir die Länge dieses Arrays entsprechend:

SetLength(SelItems,CountItems);

Der einzige Unterschied zwischen dynamischem und festem Array ist nun der, dass das dynamische Array noch dereferenziert werden muss, damit auch auf das erste Element (mit dem Index Null) zugegriffen wird. Es gibt dafür zwei Möglichkeiten:

1. Sie geben den Index mit an:

SendMessage(hwndListbox, LB_GETSELITEMS, CountItems, LPARAM(@SelItems [0]));

2. Sie benutzen diese Anweisung zum Dereferenzieren:

SendMessage(hwndListbox, LB_GETSELITEMS, CountItems, LPARAM(pointer (@SelItems)^));

Würden wir das nicht tun, wäre eine Zugriffsverletzung die Folge. Und am Ende müssen wir den belegten Speicher des Arrays wieder freigeben:

SetLength(SelItems,0);

2.1.5. Die Auswahl programmgesteuert treffen

Soll ein Eintrag programmgesteuert markiert - bzw. seine Markierung aufgehoben - werden, dann benutzt man dazu die Nachricht "LB_SETSEL". Als zweiter Parameter wird dabei der Index des gewünschten Eintrags angegeben, während der erste Parameter den gewünschten Status (true, false) enthält. Zu beachten ist auch hier wieder, dass der Bool-Wert in einen Integer konvertiert werden muss. Als Beispiel soll gezeigt werden, wie man den dritten Eintrag einer Listbox markieren kann:

SendMessage(hwndListbox, LB_SETSEL, WPARAM(true), 2);

Die Prozedur "SelAll" des Beispielprogramms demonstriert das Markieren aller Einträge der Listbox. Anzumerken ist aber, dass "LB_SETSEL" nur für eine Listbox gilt, in der man mehrere Einträge gleichzeitig auswählen kann.

Für die typischen Listboxen, die nur die Auswahl eines einzigen Items erlauben, muss die Nachricht "LB_SETCURSEL"

benutzt werden. Hierbei geben Sie den gewünschten Index im wParam an, während der lParam nicht genutzt wird und Null bleibt:

SendMessage(hwndSimpleLB, LB_SETCURSEL, 2, 0);

Mit dem wParam-Wert -1 können Sie hier die Auswahl aufheben.

2.1.6. Auf die Änderung der Auswahl einer Listbox reagieren

Hier benutzen wir wieder die Nachricht "WM_COMMAND". Im höherwertigen Wort von wParam befindet sich die Listbox-Nachricht "LBN_SELCHANGE", die ausgelöst wird wenn die Auswahl des Listenfeldes ändert. (Andere Benachrichtigungscodes finden Sie in der Hilfe und im MSDN.) Im niederwertigen Wort von wParam befindet sich der Bezeichner des betreffenden Listenfeldes.

WM_COMMAND:

case hiword(wParam) of LBN_SELCHANGE:

case LoWord(wParam) of IDC_LB:

begin end;

end;

end;

Zu Demonstrationszwecken benutze ich hier einmal "LB_GETSEL" um den Status eines Eintrages abzufragen. Die Alternative wäre wieder "".

Items := SendMessage(hwndListbox, LB_GETCOUNT, 0, 0);

for i := 0 to Items do

if SendMessage(hwndListbox, LB_GETSEL, i, 0) > 0 then begin

Inc(SelItems);

wvsprintf(buffer1, 'davon markiert: %d', PChar(@SelItems));

lstrcpy(buffer, buffer1);

end;

2.1.7. Drag-Listboxen 2.1.7.1. Vorbemerkung

Als "DragListbox" bezeichnet man eine Listbox, deren Einträge mit der Maus verschoben werden können (to drag). Weil dabei Funktionen der so genannten Common Controls benutzt werden, ist es erforderlich, die Unit "CommCtrl.pas"

einzubinden und den Befehl "InitCommonControls" (bzw. "InitCommonControlsEx") im Programm aufzurufen.

Ich habe die Demo als Konsolenanwendung definiert, {$APPTYPE CONSOLE}, um das so zusätzlich erzeugte Konsolenfenster zur Ausgabe von Text mittels "writeln" nutzen zu können. Genauer gesagt: ich gebe in diesem Konsolenfenster die beim Verschieben eines Eintrages mit der Maus erzeugten Nachrichten aus, um den Vorgang des Verschiebens zu verdeutlichen. Diese Zeilen habe ich mit dem Kommentar "DEBUG" gekennzeichnet.

2.1.7.2. Eine Listbox mutiert ...

Eine DragListbox ist nur eine etwas modifizierte normale Listbox. Letztendlich bringen wir ihr nur bei, Einträge per Drag&Drop verschieben zu können. Diese Modifizierung wird ganz einfach durch einen Aufruf der API-Funktion

"MakeDragList" erreicht. Übergeben wird einfach das Handle der zu modifizierenden Listbox:

MakeDragList(GetDlgItem(HDlg,IDC_LISTBOX));

Der bool-Rückgabewert kann zur Kontrolle dienen. Sollte die Listbox nicht modifiziert werden können, sollten Sie das Programm beenden, bzw. zumindest alle Funktionen, die irgendwie von der Modifizierung abhängig sind, deaktivieren.

MakeDragList-Definition

BOOL MakeDragList(

HWND hLB );

Zusätzlich muss man noch eine Nachricht bei Windows registrieren, die von Windows generiert wird, wenn es zu einer Drag&Drop-Operation in der Listbox kommt:

DL_MESSAGE := RegisterWindowMessage(DRAGLISTMSGSTRING);

"DL_MESSAGE" ist unsere Nachricht, und hinter DRAGLISTMSGSTRING verbirgt sich eine vom System vorgegebene Stringkonstante.

Die Funktion "RegisterWindowMessage" erzeugt eine systemweit einzigartige Nachricht. Das bedeutet, ruft ein anderes Fenster die Funktion mit der gleichen Stringkonstante auf, dann wird kein neuer Wert erzeugt, sondern die Funktion liefert den zuvor registrierten numerischen Wert zurück.

Auf diese Weise könnten Sie Kontakt mit anderen Programmen aufnehmen, die solche Nachrichten im System registrieren. Aber das ist hier nicht das Thema ... :o)

RegisterWindowMessage-Definition

UINT RegisterWindowMessage(

LPCTSTR lpString );

2.1.7.3. Nachrichten der DragListbox

Die DragListbox generiert vier Nachrichten, die wir als Reaktion auf das "Ziehen" nutzen können:

Wert Bedeutung

DL_BEGINDRAG wird generiert, wenn der "Zieh"-Vorgang begonnen wird

DL_CANCELDRAG wird beim Abbrechen des Vorgangs erzeugt (ausgelöst durch ESC) DL_DROPPED wird beim Beenden des "Zieh"-Vorgangs ausgelöst

Da es sich aber bei unserer zuvor registrierten Drag-Nachricht (DL_MESSAGE) um eine Variable handelt, können wir die o.g. Benachrichtigungen nicht wie sonst üblich auswerten. Wir müssen das daher im else-Teil von case machen. Dieses Prinzip wird uns beim Suchen- und Ersetzendialog des Standarddialoge-Tutorials noch einmal begegnen

else begin

result := FALSE;

// message handling of the drag listbox comes here

if DL_MESSAGE <> 0 then // DL_MESSAGE cannot be WM_NULL (=0)!

if uMsg = DL_MESSAGE then

Welche Nachricht nun generiert wurde, sagt uns das PDRAGLISTINFO-Record, das uns im lParam übermittelt wird.

Dabei ist für uns die Membervariable uNotification von Interesse, die die o.g. Benachrichtigungen der DragListbox enthält

case PDRAGLISTINFO(lParam)^.uNotification of DL_BEGINDRAG:

{ ... } DL_DRAGGING:

{ ... } DL_CANCELDRAG:

{ ... } DL_DROPPED:

{ ... }

end; // case (PDRAGLISTINFO(lParam)^.uNotification of) end; // else (case uMsg of)

DRAGLISTINFO-Definition

typedef struct {

UINT uNotification;

HWND hWnd;

POINT ptCursor;

} DRAGLISTINFO

2.1.7.4. Die Funktion "DrawInsert"

Die Funktion "DrawInsert" dient dazu, den Vorgang zu visualisieren und dem Benutzer eine Orientierung zu geben, wo er sich gerade befindet. Dazu zeichnet sie einen Pfeil links neben die DragListbox auf das Elternfenster. Das folgende kleine Beispiel ermittelt zunächst das Item der Listbox, auf dem wir uns befinden:

ItemIdxDragging:= LBItemFromPt(GetDlgItem(hDlg, IDC_LISTBOX), PDRAGLISTINFO(lParam)^.ptCursor, TRUE);

Damit können wir den Pfeil anzeigen lassen

DrawInsert(hDlg, PDRAGLISTINFO(lParam)^.hWnd, ItemIdxDragging);

Die ersten beiden Paramter geben jeweils das Handle des Elternfensters und der Listbox an. Der letzte Parameter ist der Index des Items, auf dem wir uns gerade im Moment des "Ziehens" befinden.

Um die Markierung wieder zurückzusetzen bzw. verschwinden zu lassen, wird als letzter Paramter einfach -1 übergeben:

DrawInsert(hDlg, PDRAGLISTINFO(lParam)^.hWnd, -1);

DrawInsert-Definition

void DrawInsert(

HWND handParent, HWND hLB,

int nItem );

2.1.7.5. Was noch wichtig wäre...

Ich möchte Ihr Augenmerk an dieser Stelle speziell auf einen Punkt lenken (der Rest der Programmlogik kann anhand der ausführlich kommentierten Demo nachvollzogen werden)

// return the message result explicitly, otherwise we would not // recieve DL_DRAGGING

SetWindowLong(hDlg, DWL_MSGRESULT, Integer(TRUE));

// message handled Result := True;

Wie der Kommentar schon sagt, ist es wichtig, dass wir dem System mitteilen, dass wir die "DL_BEGINDRAG"-Nachricht behandelt haben. Täten wir dies nicht, würde uns Windows keine "DL_DRAGGING"-Nachrichten schicken, weil es nicht weiß, ob der "Zieh"-Vorgang begonnen hat oder nicht. Und im Zweifelsfall bleibt Windows eben stumm - sicher ist sicher.

Da ich auch hier wieder aus Bequemlichkeit zu einer Dialogressource gegriffen habe, erfolgt die Benachrichtigung durch den Aufruf der Funktion "SetWindowLong" mit dem Index-Parameter DWL_MSGRESULT. Das PSDK bemerkt dazu (frei übersetzt):

PSDK

Wird "SetWindowLong" mit dem Index DWL_MSGRESULT benutzt, um den Rückgabewert einer Dialog-Prozedur zu setzen, sollte danach auch noch zusätzlich true zurückgegeben werden, da sonst der übergebene Rückgabewert wieder überschrieben werden könnte.

Im Dokument Win32API-Tutorials für Delphi (Seite 32-38)