Die "Tile"-Ansicht der List-View unter Windows XP


Wenn wir über die verschiedenen Anzeigemodi der List-View sprechen, dann dürfen wir die neue "Tile"-Ansicht von Windows XP nicht außer Acht lassen. Dabei ist, ähnlich wie in der Icon-Ansicht, das Symbol des jeweiligen Eintrags zu sehen. Neben diesem Symbol, und das ist der Unterschied, sehen Sie den Namen des Eintrags und eine oder mehrere zusätzliche Zeilen mit weiterem Text.
Für diese neue Ansicht ist auch wieder eine Manifestdatei erforderlich, die dem Programm entweder beiliegen kann, bzw. die in den Ressourcen integriert ist.

Wenn Ihre Delphi-Version die benötigten Records und Funktionen, auf die wir noch eingehen werden, nicht kennt, dann können Sie die beiliegende "CommCtrl_Fragment.pas" benutzen. Diese enthält alle notwendigen Informationen.

Wo beginnen wir nun?
Am besten überlegen wir uns vorher, welche Informationen wir anzeigen wollen. Dass die Textzeilen im "Tile"-Modus mit einigen, ausgewählten Spalten (aus dem Report-Modus) identisch sind, möchte ich anhand eines Screenshots verdeutlichen:



Von Interesse sind hierbei eigentlich nur die Spalten #2 und #3 (im Bild 1 und 2; wg. der null-basierenden Zählweise). Der Inhalt der ersten Spalte, identisch mit dem Hauptnamen des Items, wird nämlich in jedem Fall und in jedem Darstellungsmodus angezeigt, so dass wir ihn auch ignorieren können.


Schritt #1 - Grundlegende Einstellungen für die List-View

Wenn wir den "Tile"-Modus verwenden wollen, müssen wir zunächst einmal ein paar generelle Einstellungen treffen. Andernfalls werden standardmäßig nur zwei Zeilen (also der Hauptname und eine zusätzliche Zeile) angezeigt.
Das hierfür benötigte Record ist vom Typ TLVTileViewInfo und muss mit seiner Größe initialisiert werden

tileinfo.cbSize  := sizeof(TLVTileViewInfo);

Da wir die Anzahl der Zeilen ändern wollen, müssen wir die dwMask-Membervariablen entsprechend setzen:

tileinfo.dwMask  := LVTVIM_COLUMNS;

Als Flag erscheint mir der Wert LVTVIF_AUTOSIZE ideal. Damit passen sich die einzelnen Tiles den Abmessungen des Fensters an, besitzen aber eine Mindestbreite

tileinfo.dwFlags := LVTVIF_AUTOSIZE;

In der cLines-Membervariablen geben wir nun die Anzahl der zusätzlichen Zeilen für den "Tile"-Modus an. Der Hauptname des Items ist immer sichtbar, zählt also nicht mit:

tileinfo.cLines  := 2;

Das so gefüllte Record reichen wir nun mit Hilfe der Nachricht "LVM_SETTILEVIEWINFO" oder der Funktion "ListView_SetTileViewInfo" an die List-View weiter:

ListView_SetTileViewInfo(hLV,tileinfo);

Feste Größen

Sie können die Größe einer solchen "Kachel" auch selbst definieren. Dazu ergänzen Sie die Maske um das Flag LVTVIM_TILESIZE, lassen die dwFlags-Membervariable weg und geben in tileSize die gewünschte Größe an; beispielsweise:

tileinfo.dwMask      := LVTVIM_COLUMNS or LVTVIM_TILESIZE;
tileinfo.cLines      := 2;
tileinfo.sizeTile.cx := 300;
tileinfo.sizeTile.cy := 70;


Schritt #2 - Die einzelnen Items berücksichtigen

Würden wir die "Tile"-Ansicht jetzt testen, würden wir nur das Symbol und den Itemnamen rechts daneben sehen. Das liegt daran, dass die List-View bisher noch nicht weiß, wie es mit den einzelnen Items zu verfahren hat, wenn der neue Modus gewählt wird.
Uns stehen nun zwei Wege zur Verfügung, dies zu ändern; sprich: der List-View mitzuteilen, welche Informationen uns im "Tile"-Modus interessieren. Beide Wege erfordern eine Erweiterung der Routine, mit der wir die List-View gefüllt haben.

Das erweiterte TLVItem-Record

Für diesen Weg ist zuvor aber noch eine Ergänzung des TLVItem-Records gemäß den Angaben im PSDK erforderlich. Sie müssen einfach nur die drei rot gekennzeichneten Membervariablen in der Deklaration des Typs (in der Unit "CommCtrl.pas") hinzufügen:

type
  tagLVITEMA = packed record
    mask: UINT;
    iItem: Integer;
    iSubItem: Integer;
    state: UINT;
    stateMask: UINT;
    pszText: PAnsiChar;
    cchTextMax: Integer;
    iImage: Integer;
    lParam: LPARAM;
    iIndent: Integer;
    iGroupId: integer;
    cColumns: uint;
    puColumns: puint;
  end;

(Bei der Unicode-Version, "tagLVITEMW", ist ebenso zu verfahren.) Wenn Sie dies getan haben, dann schauen Sie sich bitte noch einmal an, wie Ihr erstes Item in die List-View eingefügt wird. Damit auch die Einstellungen für "Tile"-Modus berücksichtigt werden, erweitern wir die Maske um das neue Flag LVIF_COLUMNS (deklariert in der "CommCtrl_Fragment.pas"):

lvi.mask      := LVIF_TEXT or LVIF_IMAGE or LVIF_COLUMNS;

Dann benutzen wir eine der neuen Membervariablen, cColumns und geben die Anzahl der zusätzlichen Zeilen an, die (zusätzlich zum Hauptnamen des Items) im "Tile"-Modus angezeigt werden sollen:

lvi.cColumns  := 2;

Hier bietet sich übrigens die erste Eingriffsmöglichkeit. Sie können bestimmte Einträge anders darstellen lassen, indem Sie einen anderen Wert eintragen. Nehmen wir als Beispiel an, jede Datei, deren Indexwert eine ungerade Zahl ist, soll nur eine zusätzliche Zeile anzeigen, dann ließe sich das so realisieren:

if(odd(Loop)) then lvi.cColumns := 1
  else lvi.cColumns := 2;

Ebenfalls sehr wichtig für die Darstellung ist die Membervariable puColumns. Sie ist ein Zeiger auf ein Integer-Aray, das die Indexwerte der Spalten enthalten muss, die wir im "Tile"-Modus sehen wollen!
Wenn Sie sich das Bild am Anfang ansehen, dann sind das die Spalten 1 und 2 (null-basierende Indexzählweise), wobei der Dateityp der letzten Spalte noch vor der Dateigröße angezeigt werden soll. Unser Array wird daher wie folgt deklariert:

var
  colArray : array[0..1]of integer = (2,1);

und an die Membervariable übergeben:

lvi.puColumns := @colArray[0];

Das ist auch schon das ganze Geheimnis. Hätten wir noch mehr Spalten, ließe sich das Array entsprechend vergrößern. Da das Item dann wie gewohnt mit der Nachricht "LVM_INSERTITEM" bzw. der Funktion "ListView_InsertItem" in die List-View aufgenommen wird, müssen wir weiter nichts tun. Schalten wir jetzt in den neuen Ansichtsmodus um, dann sehen wir ein Ergebnis wie auf dem Screenshot am Anfang.

Übrigens: das Array selbst bietet die zweite Eingriffsmöglichkeit in die Darstellung. Wenn Sie sich an die Spielerei mit den ungeraden Indexwerten erinnern: etwas Vergleichbares ließe sich auch hier machen; etwa, dass alle Dateien mit ungeradem Index zuerst die Größe und dann den Dateityp anzeigen, usw. usw.
Experimentieren Sie einfach ein wenig!


Die zweite Variante

Der eben gezeigte Weg hat einen Nachteil: Sie benötigen den Quellcode der Unit "CommCtrl.pas" zum Erweitern des TLVItem-Records. Natürlich ließe sich dieser neue Typ auch in einer externen Datei deklarieren (als TLVItem60 finden Sie ihn übrigens in der "CommCtrl_Fragment.pas"), dann könnten Sie allerdings nicht mehr auf die Makro-Funktionen, wie etwa "ListView_InsertItem" usw., zugreifen, da diese nach wie vor die Originalstruktur von TLVItem benutzen.

Aus dem Grund bin ich im Beispielprogramm einen anderen Weg gegangen: Nachdem ein Item eingefügt wurde, benutzen wir ein weiteres neues Record (Typ: TLVTileInfo). Dieses Record wird ebenfalls initialisiert:

tile.cbSize    := sizeof(TLVTileInfo);

Dann geben wir den Index des eben eingefügten Items an. Dieser Index entspricht im Beispielprogramm der Variablen "Loop" aus der Prozedur "GetFiles":

tile.iItem     := Loop;

Die zwei nächsten Parameter und ihre Bedeutung sind bereits vom erweiterten TLVItem-Record bekannt. Wir geben also auch hier an, wie viele zusätzliche Zeilen wir im "Tile"-Modus sehen wollen

tile.cColumns  := 2;

und wir setzen die Referenz auf unser Array mit den Indexwerten der anzuzeigenden Spalten:

tile.puColumns := @colArray[0];

Nun müssen wir diese Einstellungen noch an die List-View übergeben, damit sie auch Beachtung finden:

ListView_SetTileInfo(hLV,tile);

Und das war´s.