Windows Programmierung: List View
Willemers Informatik-Ecke

Einführende Informationen über die Programmierung von Kontrollelementen werden hier vorausgesetzt.

Allgemein

Das ListView gehört zu den Common Controls. In C-Programmen ist das Einbinden der Datei COMMCTRL.H notwendig und die comctl32.lib muss zum Programm gelinkt werden.

Ein ListView unterscheidet sich von der bisher bekannten Listbox in diversen Details. Während die Listbox typischerweise eine Liste von Textzeilen repräsentiert, ist das ListView eher die Darstellung von Objekten. Beide Elemente können mehrere Spalten darstellen. Das ListView ermöglicht eine Überschrift und die Verwendung dieser als Schaltleiste.

Stile

Typen

Die grundlegenden Stile eines ListView sind die unterschiedlichen Erscheinungsformen des Elements, wie man es aus dem Explorer kennt.

LVS_ICON
Jedes Element erscheint als ein großes Icon. Es kann vom Anwender frei verschoben werden.
LVS_SMALLICON
Jedes Element wird mit einem kleinen Icon dargestellt und einem Textlabel an seiner rechten Seite. Auch diese können frei verschoben werden.
LVS_LIST
Die Listendarstellung unterscheidet sich im Aussehen nicht von SMALLICON, die Elemente sind allerdings nicht verschiebbar und werden spaltenweise angeordnet.
LVS_REPORT
Jedes Element erscheint in einer eigenen Zeile mit mehreren Spalten. Die linke Spalte stellt dabei den Ankerpunkt dar. Hier kann auch ein kleines Icon stehen. Die Spalte ist immer linksbündig. Die weiteren Spalten werden als Subitem angesprochen. Jede Spalte besitzt einen Header, sofern LVS_NOCOLUMNHEADER nicht als Stil angegeben wurde.
Alle Anzeigen mit Icons verwenden eine Image-Liste, die vom Programmierer anzulegen ist.

Darstellung

LVS_ALIGNLEFT Linksbündig (ICON und SMALLICON)
LVS_ALIGNTOP Obenbündig (ICON und SMALLICON)
LVS_AUTOARRANGE Elemente richten sich automatisch selbst aus (ICON und SMALLICON)
LVS_EDITLABELS Die Labels sind direkt editierbar.
LVS_NOCOLUMNHEADER Spaltenüberschriften sind nicht sichtbar (REPORT)
LVS_NOLABELWRAP Das Label bricht bei langen Zeilen nicht um. (ICON)
LVS_NOSCROLL Das Scrollen ist abgeschaltet. Alle Elemente müssen innerhalb der Clientfläche dargestellt werden. (ICON und SMALLICON)
LVS_NOSORTHEADER Die Spaltenüberschriften arbeiten nicht als Schaltflächen. Sinnvoll, wenn Sortierung o. ä. nicht unterstützt wird.
LVS_OWNERDATA Version 4.70. Specifiziert ein virtuelles ListView.
LVS_OWNERDRAWFIXED Die Anwendung zeichnet die Elemente. Sie erhält dazu die WM_DRAWITEM-Nachricht. (REPORT)
LVS_SHAREIMAGELISTS Die Imageliste wird nicht gelöscht, wenn das ListView zerstört wird.
LVS_SHOWSELALWAYS Die Selektion bleibt auch erhalten, wenn das Element den Fokus verliert.
LVS_SINGLESEL Es kann nur ein Element gleichzeitig selektiert werden. Standard ist die Multiselektierbarkeit.
LVS_SORTASCENDING Die Item Indizes werden in aufsteigender Folge anhand ihrer Texte sortiert.
LVS_SORTDESCENDING Die Item Indizes werden in absteigender Folge anhand ihrer Texte sortiert.

LVS_SORTASCENDING und LVS_SORTDESCENDING werden bei LIST und REPORT sofort sichtbar, da sie nach der Reihenfolge ihrer Indizes angezeigt werden. Dagegen ist die Position der Icons bei ICON und SMALLICON vom Index unabhängig.

Nachträgliches Verändern der Stile

Der Stil eines ListView kann nach der Erzeugung geändert werden. Dazu werden die Funktionen GetWindowLong and SetWindowLong verwendet. Über Masken können verschiedene Stile abgegrenzt werden.

Maske Maskierte Stile
LVS_TYPEMASK LVS_ICON, LVS_LIST, LVS_REPORT und LVS_SMALLICON
LVS_ALIGNMASK LVS_ALIGNLEFT und LVS_ALIGNTOP
LVS_TYPESTYLEMASK LVS_ALIGNLEFT und LVS_ALIGNTOP aber auch LVS_NOCOLUMNHEADER und LVS_NOSORTHEADER

Bei der folgenden Sequenz enthält dwView den Stil, in den gewechselt werden soll, beispielsweise LVS_REPORT oder LVS_ICON.

  DWORD dwStyle = GetWindowLong(hwndLV, GWL_STYLE);  // hole aktuellen Stil
  if ((dwStyle & LVS_TYPEMASK) != dwView)            // nur bei Aenderung
    SetWindowLong(hwndLV, GWL_STYLE, (dwStyle & ~LVS_TYPEMASK) | dwView); 	} 

Ansteuerung des ListView-Controls

Erzeugung einer Liste

Ein List View wird mit der Funktion CreateWindow erzeugt. Als Fensterklasse wird die Konstante WC_LISTVIEW verwendet. Dazu muß die Common Control Header Datei eingebunden werden.

#include "commctrl.h"

  InitCommonControls();
  hwndList = CreateWindow(WC_LISTVIEW, "", 
             WS_VISIBLE|WS_BORDER|WS_CHILD | LVS_REPORT | LVS_EDITLABELS, 
             10, 10, 300, 100, 
             hWnd, (HMENU)ID_LIST, hInst, 0); 

Im Dialog wird sie einfach in der Ressource definiert.

Kommt es zu nicht aufgelösten Externals, sollte man prüfen, ob die Library für die Common Controls (comctl32.lib) eingebunden wird.

Spalten des ListViews

Bevor in einem REPORT etwas eingefügt werden kann, müssen die Spalten definiert werden. Beschrieben wird eine Spalte durch die Struktur LVCOLUMN. Die folgende Routine erzeugt eine Spalte.

int CreateColumn(HWND hwndLV, int iCol, char *Text, int iBreite)
{
LVCOLUMN lvc;

	lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
	lvc.fmt = LVCFMT_LEFT;
	lvc.cx = iBreite;
	lvc.pszText = Text;  
	lvc.iSubItem = iCol;
	return ListView_InsertColumn(hwndLV, iCol, &lvc);
}

Die Spalten können per Nachricht an das ListView geädert werden oder durch Aufruf von Makros, die letztlich ein SendMessage ausführen.

Nachricht Makro-Aufruf Funktion
LVM_INSERTCOLUMN ListView_InsertColumn(HWND, int, LVCOLUMN *) Spalte einfügen
LVM_DELETECOLUMN ListView_DeleteColumn(HWND, int) Spalte löschen
LVM_GETCOLUMN ListView_GetColumn(HWND, int, LVCOLUMN *) Eigenschaften der Spalte holen
LVM_SETCOLUMN ListView_SetColumn(HWND, int, LVCOLUMN *) Eigenschaften der Spalte ändern
LVM_GETCOLUMNWIDTH ListView_GetColumnWidth(HWND, int) Ermittle Spaltenbreite
LVM_SETCOLUMNWIDTH ListView_SetColumnWidth(HWND, int, int) Setze Spaltenbreite

Einfügen einer Zeile

Ein Element des ListView wird durch die Struktur LVITEM beschrieben. Jedes Element kann als ICON, SMALLICON, LIST-Element oder als linke Spalte einer REPORT-Zeile dargestellt werden.

int CreateItem(HWND hwndList, char *Text)
{ 
LVITEM lvi = {0};

    lvi.mask = LVIF_TEXT;
    lvi.pszText = Text;
    return ListView_InsertItem(hwndList, &lvi);
} 

Das Feld mask bestimmt, welche Elemente der LVITEM-Struktur wirklich verwendet werden. Da es oft sinnvoll ist, einen Zeiger auf das Speicherobjekt mitzuführen, das die Daten hält, die hinter dem Objekt stehen, bietet sich das lParam-Feld an. Damit dies aber auch verwendet wird, muss LVIF_TEXT|LVIF_PARAM als Maske gesetzt werden.

Die Konstanten von mask und die Felder, die sie aktivieren:

LVIF_IMAGE iImage
LVIF_INDENT iIndent
LVIF_PARAM lParam
LVIF_STATE state
LVIF_TEXT pszText

Die weiteren Spalten eines Reports

Das Element selbst steht in der Reportansicht immer links und ist allein selektierbar. Um weitere Spalten zu füllen, wird ein Text dem Element hinzugefügt.

int Create2ColItem(HWND hwndList, char *Text1, char *Text2)
{ 
LVITEM lvi = {0};
int Ret;

    // Initialize LVITEM members that are common to all items. 
    lvi.mask = LVIF_TEXT;
    lvi.pszText = Text1;
    Ret = ListView_InsertItem(hwndList, &lvi);
    if (Ret>=0) {
        ListView_SetItemText(hwndList, Ret, 1, Text2);
    }
    return Ret;
}

Die Struktur LVITEM

Die Struktur LVITEM beschreibt ein Element der ListView. Die wichtigsten Elemente werden hier kurz beschrieben. Zunächst die Definition:

typedef struct _LVITEM { 
    UINT mask; 
    int iItem; 
    int iSubItem; 
    UINT state; 
    UINT stateMask; 
    LPTSTR pszText; 
    int cchTextMax; 
    int iImage; 
    LPARAM lParam;
#if (_WIN32_IE >= 0x0300)
    int iIndent;
#endif
} LVITEM, FAR *LPLVITEM; 

Mit den Nachrichten LVM_GETITEM und LVM_SETITEM werden die Attribute eines Elements geändert. Sie erhalten als Parameter neben dem HWND des ListViews einen Zeiger auf eine LVITEM-Struktur, die vorher zu besetzen ist.

Die Strukturelemente im Einzelnen:

mask
Spezifiziert, welche Elemente verwendet werden. Eine Oderung der folgenden Flags ist möglich:
LVIF_IMAGE iImage
LVIF_INDENT iIndent
LVIF_PARAM lParam
LVIF_STATE state
LVIF_TEXT pszText
iItem
Index (0-basierend) des Items, auf das sich die Struktur bezieht.
iSubItem
Index (1-basierend) des Subitems, auf das sich die Struktur bezieht. 0, wenn sich die Struktur auf ein Item statt auf ein Subitem bezieht.
pszText
zeigt auf einen nullterminierten String. Ist der Wert LPSTR_TEXTCALLBACK, ist es ein Callback-Item. Wenn sich dieser ändert, muß pszText auf LPSTR_TEXTCALLBACK gesetzt werden und das ListView durch LVM_SETITEM oder LVM_SETITEMTEXT informiert werden.

pszText darf nicht auf LPSTR_TEXTCALLBACK gesetzt werden, wenn das ListView den Stil LVS_SORTASCENDING oder LVS_SORTDESCENDING hat.

cchTextMax
Größe des Puffers, wenn der Text ausgelesen wird.
iImage
Index des Icons dieses Elements aus der Imageliste.
lParam
32-bit Wert, der spezifisch für dieses Element ist.

Aktionen mit Elementen

LVM_INSERTITEM Einfügung eines Elementes
LVM_DELETEITEM Löschen eines Elementes
LVM_DELETEALLITEMS Löschen aller Elemente
LVM_GETITEM Eigenschaften des Elementes lesen
LVM_GETITEMTEXT Text des Elementes lesen
LVM_SETITEM Änderung
LVM_SETITEMTEXT Änderung am Text

Vor dem Einfügen mehrerer Items wird dem ListView eine LVM_SETITEMCOUNT Nachricht gesendet werden, die angibt, wieviele Elemente letztlich enthalten wird. Dies ermöglicht es dem ListView seine Speicherreservierung und -freigabe zu optimieren. Wieviele Elemente ein ListView enthält, ist mit LVM_GETITEMCOUNT zu ermitteln.

Bearbeiten selektierter Elemente

int Pos = -1;
LVITEM Item;

    Pos = ListView_GetNextItem(hwndList, Pos, LVNI_SELECTED);
    while (Pos>=0) {
        Item.iItem = Pos;
        Item.iSubItem = 0;
        ListView_GetItem(hwndList, &Item);
        TuWasMitElement((ElementType*)Item.lParam);
        Pos = ListView_GetNextItem(hwndList, Pos, LVNI_SELECTED);
    }

Ereignisse

Das ListView sendet WM_NOTIFY-Nachrichten an das Elternfenster. Der code kann folgende Werte annehmen:

Nachricht Beschreibung
LVN_BEGINDRAG Start einer Drag-And-Drop Aktion
LVN_BEGINRDRAG Start einer Drag-And-Drop Aktion über rechte Maustaste
LVN_BEGINLABELEDIT Start Editieren eines Labels
LVN_ENDLABELEDIT Ende Editieren eines Labels
LVN_DELETEITEM Meldet, daß das Item gelöscht wird
LVN_DELETEALLITEMS Meldet, daß alle Items gelöscht werden
LVN_COLUMNCLICK Zeigt an, daß der Anwendet in den Header einer Reportdarstellung klickte
LVN_GETDISPINFO Das Kontrollelement erfragt Informationen zur Darstellung vom Elternfenster
LVN_SETDISPINFO Die Information des Elternfensters für das Item muß erneuert werden
LVN_INSERTITEM Zeigt das Einfügen eines Items an
LVN_ITEMCHANGED Zeigt an, daß ein Item geändert wurde
LVN_ITEMCHANGING Zeigt die beabsichtigte Änderung eines Items an
LVN_KEYDOWN Taste wurde gedrückt

Editieren der Labels

Das List View muß mit dem Stil LVS_EDITLABELS erzeugt worden sein. Dann kann bereits ein Label angeklickt werden und Eingaben werden entgegengenommen. Allerdings wird die Eingabe direkt anschließend wieder verworfen. Um Änderungen im Label zu gestatten, braucht man nur die WM_NOTIFY zu fangen und return TRUE zu geben. Um dazwischen auf den eingegebenen Text zuzugreifen, wird ein Zugriff auf den Text des Items durchgeführt. Das Beispiel zeigt die Eingabe in einer Messagebox.

    case WM_NOTIFY: 
        switch (((LPNMHDR) lParam)->code) {
        case LVN_ENDLABELEDIT:
            pItem = (NMLVDISPINFO) lParam;
            MessageBox(hWnd, pItem->item.pszText, "entry", MB_OK);
            return TRUE;

Wurde das Editieren abgebrochen, wird das pszText-Element 0 sein.

Soll das Editieren verhindert werden, wird die Nachricht LVN_BEGINLABELEDIT gefangen und TRUE zurückgegeben. Auch hier kann über lParam in gleicher Weise auf das Item zugegriffen werden und so beispielsweise eine bestimmte Itemgruppe ausgegrenzt werden.

Anklicken des Spaltenkopfs im ListView

    case WM_NOTIFY: 
        switch (((LPNMHDR) lParam)->code) {
        case LVN_COLUMNCLICK:
            SpalteNr = ((LPNMLISTVIEW) lParam)->iSubItem;

Selektionsereignis

Das Ereignis LVN_ITEMACTIVATE wird gesendet, wenn der Anwender ein Item aktiviert. Wie bei den anderen ListView Ereignissen erreicht es die Fensterfunktion im Rahmen einer WM_NOTIFY Nachricht.

    case WM_NOTIFY: 
        switch (((LPNMHDR) lParam)->code) {
        case LVN_ITEMACTIVATE:
            HWND hwndFrom = (HWND)((LPNMHDR) lParam)->hwndFrom;
            MarkedItemIndex = ListView_GetNextItem(hwndFrom,-1,LVNI_SELECTED); 

Mit Hilfe der LVM_GETSELECTEDCOUNT-Nachricht kann ermittelt werden, wieviele Items aktiviert wurden. Die LVM_GETNEXTITEM Nachricht wird mit dem Attribut LVNI_SELECTED gesendet bist alle Items bearbeitet wurden.