Ebenfalls ausschließlich Windows XP vorbehalten ist die neue Gruppenansicht der List-View. Das bedeutet, Sie können Einträge zu logischen Gruppen zusammenfassen, die auch räumlich voneinander getrennt sind. Im Vergleich zur normalen Sortierung halte ich das für eine recht nützliche Erweiterung:

Die Gruppenansicht kann in den Modi Icons, Small-Icons, Details und "Tiles" benutzt werden. Nur in der normalen Listenansicht (LVS_LIST) wird sie vom System deaktiviert. Die Manifestdatei ist ebenfalls erforderlich!
Dass Sie sich um die Gruppen selbst kümmern müssen, mag am Anfang wie ein Nachteil erscheinen. Auf der anderen Seite haben Sie dadurch den Vorteil, dass Sie die Bezeichnungen der Gruppen auf Ihre Anwendung zuschneiden können. Das Beispielprogramm etwa imitiert den Explorer von Windows XP:
Im Normalfall sehen Sie zuerst eine alphabetische Sortierung der Dateinamen. Der Screenshot rechts zeigt Ihnen aber auch noch eine andere Möglichkeit: Wenn Sie die Items nach ihren Typen sortieren lassen, dann werden die Gruppen anhand dieser Typennamen gebildet, und die Dateien werden neu zugeordnet. In der alphabetischen Sortierung würden Sie daher etwa die "make.bat" unter M und die "resbuild.bat" unter R finden. In der Typensortierung stecken beide in der gleichen Gruppe.
Und zu guter Letzt gibt es da auch noch die Sortierung nach der Dateigröße, die ebenfalls Auswirkungen auf die Gruppennamen hat.
... sollten wir dafür sorgen, dass schon vorhandene Gruppen entfernt werden. Das hat damit zu tun, dass jede Gruppe eine eigene ID besitzt und von der List-View gespeichert wird. Um nicht unnötig Ressourcen zu vergeuden, entfernen wir deshalb alle Gruppen sobald sich die Auswahl der Sortierung ändert! Die Neubildung der Gruppen ist nicht erforderlich, wenn Sie die selbe Spalte neu sortieren lassen. Erst wenn Sie z.B. von den Dateinamen zu den Dateitypen wechseln, sollten Sie die Gruppen neu erstellen.
Zum Entfernen der Gruppen nutzen wir die Nachricht "LVM_REMOVEALLGROUPS" oder das Makro "ListView_RemoveAllGroups":
ListView_RemoveAllGroups(hLV);
Als Beispiel für unsere erste Gruppe möchte ich die alphabetische Sortierung demonstrieren. Hierbei werden lediglich die Anfangsbuchstaben der Items der List-View benutzt. Wir benötigen also zuerst eine for-Schleife, mit der wir die einzelnen Items ansteuern können. Und weil der entsprechenden Prozedur, "RebuildGroups", der Index der Spalte übermittelt wird, gestaltet sich das Auslesen des Namens recht einfach:
ListView_GetItemText(hLV,i,iSubItem,@buf[1],MAX_PATH);
Nun kürzen wir den String auf ein Zeichen und wandeln dieses in den entsprechenden Großbuchstaben um:
SetLength(buf,1); buf[1] := UPCASE(buf[1]);
Ohne zu weit vorgreifen zu wollen: Wir arbeiten diesmal mit Unicode-Zeichen; das heißt, alle String-Referenzen müssen in widestring- bzw. pwidechar-Typen konvertiert werden. Ich habe zu dem Zweck einen entsprechenden Textpuffer deklariert
var
wbuf : array[0..MAX_PATH]of widechar;
der nun den geplanten Gruppennamen aufnehmen soll. Dabei ist die Frage: Liegt das übrig gebliebene Zeichen unseres Strings im Bereich von A bis Z? Wenn ja, benutzen wir es als Gruppennamen. Wenn nein, dann definieren wir die Gruppe "Andere":
if(buf[1] in['A'..'Z']) then lstrcpyW(wbuf,pwidechar(widestring(buf))) else lstrcpyW(wbuf,'Andere');
Nun ist die Frage: Gibt es diese Gruppe vielleicht schon? Wenn ja, dann müssen wir sie nicht noch einmal erzeugen, sondern dann ist wichtig, dass wir ihre ID ermitteln, damit wir das Item entsprechend zuordnen können. Und damit machen wir Bekanntschaft mit dem neuen TLVGroup-Record, das wir erst einmal zum Auslesen des Gruppennamens benutzen wollen. Da wir die Gruppen nur mit ihrer ID eindeutig bestimmen können, benötigen wir auch noch eine Index-Variable, "gi". Diese Variable wird vor dem Auslesen der Gruppen auf Null gesetzt und bildet mit der minimalen Gruppen-ID (MIN_GROUP_ID = 2000) die jeweilige ID.
Unser Record wird dann initialisiert:
ZeroMemory(@group,sizeof(TLVGroup)); group.cbSize := sizeof(TLVGroup);
Da wir am Namen interessiert sind, setzen wir die Maske wie folgt:
group.mask := LVGF_HEADER;
Mit Hilfe der Nachricht "LVM_GETGROUPINFO" bzw. der Funktion "ListView_GetGroupInfo" können wir dann die gewünschten Informationen auslesen. Hierbei ist auch das Funktionsergebnis von Interesse. Da wir uns in einer Endlosschleife (while-Typ, s. Beispielprogramm) befinden, sollten wir diese abbrechen können! Die Abbruchbedingung wäre in diesem Fall der Wert -1, der von der Nachricht und der Funktion zurückgeliefert wird, wenn auf die gewünschte Gruppe nicht zugegriffen werden konnte:
if(ListView_GetGroupInfo(hLV,MIN_GROUP_ID+gi,group) = -1) then break;
War das Auslesen erfolgreich, enthält die Membervariable pszHeader nun den Namen der Gruppe, den wir mit unserem geplanten Namen vergleichen können. Stimmen beide überein, dann verlassen wir die Schleife, weil wir die ID ja nun haben.
if(lstrcmpiW(wbuf,group.pszHeader) = 0) then
begin
fFound := true;
break;
end;
Nehmen wir nun einmal an, die Gruppe existiert noch nicht. In dem Fall legen wir sie an, wozu wir wieder das TLVGroup-Record benutzen:
ZeroMemory(@group,sizeof(TLVGroup));
group.cbSize := sizeof(TLVGroup); group.mask := LVGF_HEADER or LVGF_GROUPID;
Sie sehen hier als zusätzliches Flag LVGF_GROUPID. Damit wird die Membervariable iGroupId des Records wichtig und muss von uns mit einer ID gefüllt werden. Die ID ergibt sich, wie schon angesprochen, aus einem minimalen Wert und dem ermittelten oder (falls es die Gruppe noch nicht gibt) dem Originalwert der Variable "gi":
group.iGroupId := MIN_GROUP_ID + gi;
Danach weisen wir unseren Gruppennamen und dessen Länge zu
group.pszHeader := wbuf; group.cchHeader := lstrlenW(wbuf);
und fügen die Gruppe mit Hilfe der Nachricht "LVM_INSERTGROUP" oder dem Makro "ListView_InsertGroup" ein:
ListView_InsertGroup(hLV,-1,group);
Der Wert -1 bedeutet hierbei, dass die Gruppe an das Ende der internen Liste gesetzt werden soll.
Auf diese Weise werden nun alle benötigten Gruppen erzeugt. Wie Sie in Ihrem Programm verfahren, das hängt ganz von der Art Ihrer Anwendung ab. Das Beispielprogramm verwendet (wie schon gesagt) auch die Typennamen zur Gruppierung. Lassen Sie die Dateien nach ihrer Größe sortieren, dann verwendet es sechs verschiedene Schwellenwerte, um Gruppen von "Gleich Null" bis "Sehr groß" bilden zu können. Diese Arbeit nimmt Ihnen das System leider nicht ab, dafür können Sie die Gruppierung flexibel an Ihr Programm anpassen.
Sie können den Titel der Gruppe auch zentriert oder rechtsbündig ausgeben. Dazu ergänzen Sie in der mask-Variablen das Flag LVFG_ALIGN
group.mask := { ... } or LVGF_ALIGN;
und dann stehen Ihnen die Konstanten LVGA_HEADER_CENTER und LVGA_HEADER_RIGHT zur Angabe in der uAlign-Membervariablen zur Verfügung. (LVGA_HEADER_LEFT natürlich auch, aber die linksbündige Auswahl ist ohnehin die Voreinstellung.)
group.uAlign := LVGA_HEADER_CENTER;
Bleibt nur noch eins zu tun: das jeweilige Item muss der Gruppe zugeordnet werden. Hierfür benötigen wir allerdings das erweiterte TLVItem-Record, das ich bei der "Tile"-Ansicht bereits angesprochen habe.
Lassen Sie mich das erklären: Ich hatte ursprünglich gedacht, dass die Nachricht "LVM_MOVEITEMTOGROUP" (respektive "ListView_MoveItemToGroup") benutzt wird, um ein Item einer Gruppe zuzuordnen. Aber entweder dient diese Nachricht einem anderen Zweck, oder sie funktioniert schlichtweg nicht. (@Microsoft: ?) Keiner meiner Versuche war von Erfolg gekrönt.
Nun bietet aber das erweiterte TLVItem-Record eine Möglichkeit über die Membervariable iGroupId.
Falls Ihre Delphi-Version diese Variable nicht kennt, benutzen Sie bitte den Typ TLVItem60 aus der Unit "CommCtrl_Fragment.pas. Leeren Sie Ihre Variable und weisen Sie als Maske nur das neue Flag LVIF_GROUPID zu:
ZeroMemory(@lvi,sizeof(lvi)); lvi.mask := LVIF_GROUPID;
Dann übergeben Sie den Index des Items und die ID der Gruppe, in die das Item aufgenommen werden soll:
lvi.iItem := i; lvi.iGroupId := MIN_GROUP_ID + gi;
Und mit Hilfe von "LVM_SETITEM" bzw. "ListView_SetItem" (sofern Ihre Delphi-Version aktuell genug ist und das erweiterte Record benutzt) weisen Sie dem Item die Gruppen-ID zu:
SendMessage(hLV,LVM_SETITEM,0,LPARAM(@lvi));
Die API stellt uns hierfür die die Nachricht "LVM_ENABLEGROUPVIEW" zur Verfügung. Sie erwartet als wParam den gewünschten Status: true (Ansicht aktivieren) oder false (Ansicht deaktivieren)
SendMessage(hLV,LVM_ENABLEGROUPVIEW,WPARAM(true),0);
Alternativ dazu gibt es die Funktion "ListView_EnableGroupView":
ListView_EnableGroupView(hLV,true);