12.21 Listenansicht (ListView)
In der linken Fensterhälfte des Windows Explorers wird die Struktur des Dateisystems durch ein TreeView-Steuerelement angezeigt, und in der rechten Hälfte erscheinen die untergeordneten Elemente des ausgewählten Knotens. Hier hat der Anwender die Wahl, wie er sich die Elemente anzeigen lässt. Beispielsweise können die Elemente als Tabelle aufgelistet werden, zum Beispiel mit Spalten für die Dateigröße, das Änderungsdatum usw. Jedes Element kann auch durch ein kleines oder ein großes Symbol dargestellt werden.
Wir können diese Fähigkeiten des Explorers durch das Steuerelement ListView nachbilden, das auch als Listenansicht bezeichnet wird. Die Programmierung ist etwas komplexer, weil bei diesem Steuerelement deutlich mehr Klassen eine Rolle spielen als bei den Steuerelementen, die wir bisher behandelt haben.
12.21.1 Beteiligte Klassen
Fangen wir mit einem Überblick über die wichtigsten Klassen an, die im Zusammenhang mit Listenansichten stehen. Ein ListView-Objekt repräsentiert eine Tabelle und stellt insgesamt sechs Auflistungen bereit:
- Elemente: Eine Tabelle besteht aus Zeilen vom Typ ListViewItem. Jeder Spaltenwert hat den Typ ListViewSubItem und wird nur in der Detailansicht angezeigt. Die Zeilen werden in einer Liste vom Typ ListViewItemCollection gespeichert, und jede Zeile speichert ihre Spalten in einer Liste vom Typ ListViewSubItemCollection.
- Spaltenköpfe: Überschriften vom Typ ColumnHeader werden in einer Liste vom Typ ColumnHeaderCollection gespeichert und nur in der Detailansicht angezeigt.
- Selektion: Die Liste SelectedListViewItemCollection liefert Referenzen der ausgewählten Listenelemente zurück, SelectedIndexCollection deren Indizes.
- Gruppierung: ListViewGroupCollection gruppiert logisch zusammengehörige Zeilen. Jede Gruppe besteht aus einer Überschrift, auf den eine horizontale Linie und die dieser Gruppe zugewiesenen Elemente folgen.
Eine Listenansicht kann durch Bildchen klarer gestaltet werden. Dazu können dem Steuerelement drei ImageList-Steuerelemente mittels folgender Eigenschaften hinzufügt werden:
- LargeImageList: Darstellung Grosse Symbole
- SmallImageList: Darstellung Kleine Symbole
- StateImageList: Zwei Bildchen zur Unterscheidung zwischen ausgewählten und nicht ausgewählten Elementen
12.21.2 Eigenschaften und Ereignisse
Listenelemente und Spaltenbeschreibungen
Über die Eigenschaft Items vom Typ ListViewItemCollection sprechen Sie auf die Elemente (Zeilen) der Listenansicht an, während Columns vom Typ ColumnHeaderCollection auf die Spaltenüberschriften der Detailansicht zugreift. Beide Auflistungen können Sie mit den schon von anderen Auflistungen her bekannten Methoden manipulieren (Add, AddRange, Contains, Insert usw.).
Markieren Sie im Eigenschaftsfenster der Entwicklungsumgebung die Eigenschaften Items bzw. Columns, werden Dialoge zum Hinzufügen von Listenelementen und Spalten geöffnet. Beide sind zwar intuitiv zu bedienen, können aber nur den Zustand der Listenansicht beim Start der Anwendung beschreiben. In den meisten Fällen wird eine Listenansicht jedoch zur Laufzeit dynamisch aufgebaut. Daher nimmt bei einer Listenansicht die Programmierung einen höheren Stellenwert ein als bei den meisten anderen Steuerelementen.
Ansichtsmodi
Es sind zwei ImageList-Objekte notwendig, wenn eine Listenansicht vollständig entwickelt werden soll: Eines enthält die Liste der kleinen Symbole, das andere die Liste der großen. Die Listenansicht muss mit beiden Bildlisten verknüpft werden. Dazu dienen die Eigenschaften SmallImageList und LargeImageList.
Mit der Eigenschaft View legt man fest, wie die Elemente zur Laufzeit dargestellt werden. View ist vom Typ der gleichnamigen Enumeration mit Konstanten, die die Ansichten beschreiben, die wir vom Windows Explorer her kennen:
Konstante | Elemente werden dargestellt als |
Details |
Zeilen mit spezifischen Informationen in Spalten und Spaltenüberschriften für die ganze Tabelle der Listenansicht |
LargeIcon |
große Symbole (oft 32 × 32 oder 48 × 48 Pixel) mit darunterliegender Bezeichnung. Sie haben eine feste Breite mit neuen Zeilen bei Überlauf. |
List |
kleine Symbole (meist 16 × 16 Pixel) mit Beschriftung rechts davon. Sie haben eine feste Höhe mit neuen Spalten bei Überlauf. |
SmallIcon |
kleine Symbole mit Beschriftung rechts davon. Sie haben eine feste Breite mit neuen Zeilen bei Überlauf. |
Tile |
kleine Symbole mit Beschriftung und Zusatzinformationen rechts davon. Sie haben eine feste Breite mit neuen Zeilen bei Überlauf. Diese Konstante setzt XP oder Server 2003 voraus. |
Hinweis |
Ohne Spaltenüberschriften hat die Detailansicht Darstellungsprobleme. |
Elementaktivierung
Die Listenansicht bietet viele Eigenschaften, um die Anzeige und die Funktionalität des Steuerelements zu gestalten. Mit AllowColumnReorder=True kann der Anwender die Reihenfolge der Spalten verändern, HoverSelection=True erlaubt die Auswahl eines Elements ohne Mausklick, und MultiSelect=True gibt an, ob mehrere Listeneinträge gleichzeitig ausgewählt werden dürfen.
Bei einer Listenansicht muss genau zwischen Aktivierung und Auswahl unterschieden werden. Ein Listenelement wird standardmäßig ausgewählt, wenn mit der Maus einmal auf das Element geklickt wird oder die Auswahl mit den Pfeiltasten verändert wird. Aktiviert wird ein Listenelement, wenn durch einen Doppelklick eine Programmaktion gestartet wird. Das ist zumindest der Standard.
Manchmal ist es wünschenswert, vom Standardverhalten abweichend eine andere Aktivierung vorzusehen. Die Eigenschaft Activation vom Typ der der Enumeration ItemActivation bietet dazu die drei in Tabelle 12.24 gezeigten Möglichkeiten.
Konstante | Beschreibung |
Standard |
Die Elemente werden durch einen Doppelklick aktiviert. |
OneClick |
Die Aktivierung erfolgt durch einen Einfachklick. Ein Mauszeiger über einem ausgewählten Element wird als Hand angezeigt, und die Farbe des Elementtextes ändert sich. |
TwoClick |
Verhält sich wie Standard. Ein Mauszeiger über einem ausgewählten Element wird als Hand angezeigt, und die Farbe des Elementtextes ändert sich. |
Wichtige Eigenschaften
Die Tabelle 12.25 gibt einen Überblick über die wichtigsten Eigenschaften einer Listenansicht. Einige werden im weiteren Verlauf des Abschnitts noch erläutert.
Eigenschaft | Beschreibung |
Activation |
Gibt an, wie ein Element der Listenansicht aktiviert werden kann. Mögliche Werte stehen in der Enumeration ItemActivation. |
AllowColumnReorder |
Gibt an, ob der Anwender die Spaltenreihenfolge verändern darf. |
CheckBoxes |
Gibt an, ob neben jedem Element eine Checkbox angezeigt wird. |
Columns |
Referenz auf die ColumnHeaderCollection |
FullRowSelect |
Gibt an, ob in der Detailansicht jeweils ganze Zeilen selektiert werden. Der Standard False nimmt nur das Element selbst in der ersten Spalte. |
GridLines |
Gibt an, ob Linien zwischen den Zeilen und Spalten mit den Elementen und Unterelementen angezeigt werden. Der Standard ist False. |
Groups |
Referenz auf die ListViewGroupCollection |
HeaderStyle |
Gibt an, ob in der Detailansicht (anklickbare) Spaltenköpfe angezeigt werden: None, Clickable (Standard) und Nonclickable. |
HotTracking |
Elemente präsentieren sich als Hyperlinks, wenn die Maus darüber bewegt wird. |
HoverSelection |
Auswahl eines Elements durch Verharren mit der Maus darüber für einige Sekunden. Der Standard ist False. |
Items |
Referenz auf die Auflistung der angezeigten Elemente |
LabelEdit |
Beschriftung der Elemente durch den Benutzer |
LabelWrap |
Gibt an, ob eine zu lange Beschriftung eines großen Symbols umbrochen wird. Der Standard ist True. |
LargeImageList |
Bilderliste, aus der die goßen Symbole über einen Index zugewiesen werden |
MultiSelect |
Gibt an, ob mehrere Listeneinträge gleichzeitig ausgewählt werden können. |
ShowGroups |
Gibt an, ob die Listenelemente in Gruppenform angezeigt werden. |
SmallImageList |
Bilderliste, aus der die kleinen Symbole über einen Index zugewiesen werden |
Sorting |
Art der Sortierung der Elemente |
StateImageList |
Bilderliste, die den von der Anwendung definierten Zuständen im Steuerelement zugeordnet ist |
Ereignisse
Von den vielen Ereignissen einer ListView möchte ich nur drei gesondert erwähnen:
- SelectedIndexChanged wird ausgelöst, wenn der Anwender ein anderes Element in der Listenansicht auswählt.
- ItemActivate wird ausgelöst, wenn der Anwender ein Element aktiviert. Was als Aktivierung interpretiert wird, wird durch die Eigenschaft Activation festgelegt.
- ColumnClick tritt auf, wenn der Benutzer auf einen Spaltenkopf klickt. Dieses Ereignis ist insbesonders dann interessant, wenn eine Spalte sortiert werden soll.
Die beiden zuerst angeführten Ereignisse sind vom Typ EventHandler. Das bedeutet, dass dem Ereignishandler keine weiteren Informationen zur Verfügung gestellt werden. ColumnClick liefert im Parametertyp ColumnClickEventArgs in der Eigenschaft Column den Index der angeklickten Spalte.
12.21.3 Listenelemente (ListViewItem)
Hinzufügen und verwalten
Die in einer Listenansicht enthaltenen Elemente sind vom Typ ListViewItem und werden in der Ansicht Detail in der linken Spalte angezeigt oder in den Ansichten Kleine Symbole bzw. Grosse Symbole als Symbol. Ist die Detailansicht aktiviert, werden in weiteren Spalten die Eigenschaften des Elements angezeigt. Handelt es sich bei den Elementen um Dateien, können das beispielsweise die Dateigröße und der Typ sein.
Ein ListViewItem-Objekt ist immer eine Zeichenfolge. Das spiegelt sich in den Konstruktoren wider, die bis auf den parameterlosen Konstruktor alle eine Zeichenfolge oder ein Zeichenfolge-Array erwarten. Mit der Methode Add der ListViewItemCollection der Listenansicht wird ein Listenelement hinzugefügt:
lvwPerson.Items.Add(New ListViewItem("Abel")) lvwPerson.Items.Add(New ListViewItem("Müller")) lvwPerson.Items.Add(New ListViewItem("Rosnick")) lvwPerson.Items.Add(New ListViewItem("Berenbach")) lvwPerson.Items.Add(New ListViewItem("Keser"))
Hier ist lvwPerson das ListView-Objekt. Sie müssen nicht unbedingt ausdrücklich die Klasse ListViewItem instanziieren. Sie können als Argument auch eine Zeichenfolge übergeben:
listView1.Items.Add("Müller")
Ein Listenelement kann durch spezifische Eigenschaften beschrieben werden. Sie werden auch als Unterelemente bezeichnet und haben den Typ ListViewSubItem. Alle Unterelemente sind ebenfalls nur Zeichenfolgen und werden von der Auflistung ListViewSubItemCollection des Listenelements verwaltet. Die Eigenschaft SubItems eines Elements liefert die Referenz auf die untergeordnete ListViewSubItemCollection. Die folgende Anweisung greift auf die vierte Zusatzinformation des dritten Elements der Listenansicht zu.
lvwPerson.Items(2).SubItems(3)
Bildchen
In den beiden Ansichten Grosse Symbole und Kleine Symbole können Sie jedem Listenelement ein Bildchen zuordnen und so die Darstellung optisch ansprechender und übersichtlicher gestalten. Dazu müssen Sie der Form zwei ImageList-Steuerelemente hinzufügen, die die gewünschten Symbole enthalten.
Außerdem kann ein Listenelement, unabhängig von der gewählten Ansicht, zur Laufzeit auch ein Bildchen anzeigen, das den ausgewählten bzw. nicht ausgewählten Zustand symbolisiert. Um Zustandssymbole anzubieten, sind einige Vorgaben einzuhalten. Zunächst müssen Sie der Form eine weitere Bildliste hinzufügen, in der nur zwei Symbole enthalten sein sollten: Das erste Bild mit dem Index 0 wird vor den nicht ausgewählten Listenelementen angezeigt, das Bild mit dem Index 1 vor den ausgewählten. Anschließend weisen Sie der Eigenschaft State-ImageList des ListView-Objekts die Referenz auf diese Bildliste zu. Im letzten Schritt muss die Eigenschaft CheckBoxes=True gesetzt werden, denn Zustandssymbole ersetzen die Checkboxen.
Wollen Sie sich von der starren Zuordnung der beiden Bildchen zu den beiden Status lösen, können Sie mit der Eigenschaft StateImageIndex eines ListViewItem-Objekts einen Bildindex spezifizieren, zum Beispiel um die Reihenfolge der Zuordnung umzukehren. Zur Laufzeit steht der Index des Bildes in direktem Zusammenhang zur Eigenschaft Checked des Listenelements.
Zur Laufzeit genügt ein Klick auf das Symbol, um ein Listenelement auszuwählen beziehungsweise die Auswahl aufzuheben. Dabei wechselt das Symbol automatisch.
Auswahl der Listenelemente
In einer Listenansicht können Sie durch den Standardwert True der Eigenschaft MultiSelect der Listenansicht entweder ein oder mehrere Elemente auswählen.
Die Eigenschaft Selected eines Listenelements beschreibt den Auswahlzustand und kann nicht nur gelesen, sondern auch gesetzt werden, beispielsweise um bei der ersten Anzeige der Listenansicht bereits eine Vorauswahl zu treffen.
Im Fall einer Mehrfachauswahl können Sie die ausgewählten Elemente durch zwei Auflistungen der ListView ermitteln, ähnlich wie bei einer ListBox. Beide unterscheiden sich nur durch die Art der Verwaltung der Listenelemente.
- SelectedIndexCollection enthält die Indizes der ausgewählten Listenelemente.
- SelectedListViewItemCollection enthält die Referenzen auf die ausgewählten Listenelemente.
Sie greifen auf die Auswahl über die Eigenschaften SelectedIndices und SelectedItems zu. Ein konkretes Element erreichen Sie einfach über den Indexer der Auflistung. Welche der beiden Listen Sie verwenden, ist reine Geschmackssache. Sie bekommen in beiden Fällen die Referenzen auf die ausgewählten Listenelemente zurückgeliefert, um damit die erforderlichen Operationen auszuführen. Die folgende Anweisung liest die Text-Eigenschaft des ersten ausgewählten Elements über den Indexer ein und gibt sie in einem Meldungsfenster aus:
MessageBox.Show(listView1.SelectedItems(0).Text)
12.21.4 Unterelemente in Spalten (ListViewSubItem)
Die Unterelemente eines ListViewItem-Objekts sind vom Typ der inneren Klasse ListViewSubItem. Jedes Unterelement wird durch eine Zeichenfolge beschrieben. Verwaltet werden die Unterelemente in einer Auflistung ListViewSubItemCollection. Bitte beachten Sie, dass das erste Unterelement mit dem Index 0 in der ListViewSubItemCollection immer das Element ist, das die Unterelemente besitzt. Wenn Sie alle Unterelemente abfragen wollen, müssen Sie demnach die Auflistung vom Index 1 an durchlaufen und nicht vom Index 0 an.
Am einfachsten erzeugen Sie ein Unterelement mit dem Konstruktor, dem Sie neben der beschreibenden Zeichenfolge im zweiten Argument die Referenz auf das Listenelement übergeben, dem das neue Unterelement zugeordnet werden soll.
Public Sub New(ByVal owner As ListViewItem, ByVal [text] As String) |
Sie können bei der Erzeugung eines Listenelements auch sofort die Eigenschaften, also Unterelemente festlegen. Benutzen Sie dazu bei der Instanziierung der Klasse ListViewItem einen Konstruktor, der ein Zeichenfolge-Array entgegennimmt. Dann werden alle Array-Elemente automatisch zur Auflistung der Unterelemente hinzugefügt.
Etwas ungewöhnlich ist die Tatsache, dass sich jedes ListViewItem-Objekt nicht nur in die Auflistung ListViewItemCollection der Listenansicht einträgt, sondern gleichzeitig auch das erste Element in seiner eigenen ListViewSubItemCollection ist.
lvwPerson.Items.Add(New ListViewItem(New String() _ {"Abel","Rainer", "30", "Essen"})) lvwPerson.Items.Add(New ListViewItem(New String() _ {"Müller", "Uwe", "25", "Berlin"})) lvwPerson.Items.Add(New ListViewItem(New String() _ {"Rosnick", "Bernd", "44", "Bonn"})) lvwPerson.Items.Add(New ListViewItem(New String() _ {"Berenbach", "Peter", "35", "Köln"})) lvwPerson.Items.Add(New ListViewItem(New String() _ {"Keser", "Wolfgang", "48", "Düren"}))
Eigenschaften der Unterelemente
Unterelemente haben viel weniger Eigenschaften als Elemente. Neben der Eigenschaft Text, die die textuelle Beschreibung des Unterelements enthält, gibt es nur noch die Eigenschaften BackColor, ForeColor und Font, um der Anzeige zur Laufzeit ein anderes oder ansprechenderes Aussehen zu verleihen.
12.21.5 Spaltenüberschriften (ColumnHeader)
Neben der internen Auflistung ListViewItemCollection verfügt die ListView-Klasse über die bereits erwähnte Auflistung ColumnHeaderCollection, in der alle Spaltenköpfe vom Typ ColumnHeader aufgelistet sind (siehe Tabelle 12.26). Diese werden nur in der Detailansicht angezeigt. Die Referenz auf die interne Auflistung liefert die Eigenschaft Columns. Auch für die Spaltendefinitionen stellt Visual Studio einen intuitiv zu bedienenden Assistenten zur Verfügung, der Ihnen das einfache Hinzufügen und Beschreiben der Spalten ermöglicht.
Ein Spaltenkopf wird nur durch wenige Eigenschaften beschrieben: Text legt die Beschriftung der Spalte fest, Width die Breite der Spalte und TextAlign die Ausrichtung der Spaltenüberschrift innerhalb der Spalte. Durch die beiden Eigenschaften ImageIndex oder ImageKey, die sich beide auf die Eigenschaft SmallImageList der ListView beziehen, können Sie auch Symbole in den Spaltenköpfen anzeigen lassen.
Die schreibgeschützte Eigenschaft Index beschreibt die Position der Spalte innerhalb der ColumnHeaderCollection. Der Wert entspricht nicht zwangsläufig der sichtbaren Position des Spaltenkopfes, denn wenn die Eigenschaft AllowColumnReorder der Listenansicht auf True eingestellt ist, kann der Anwender die Anzeigereihenfolge geändert haben.
Eigenschaft | Beschreibung | |
ImageIndex |
Index des in der Spalte angezeigten Bilds |
|
ImageKey |
Schlüssel des in der Spalte angezeigten Bilds |
|
Index |
Position innerhalb der ColumnHeaderCollection |
R |
Text |
Im Spaltenkopf angezeigter Text |
|
TextAlign |
Horizontale Ausrichtung des in ColumnHeader angezeigten Textes |
|
Width |
Breite der Spalte |
12.21.6 Listenelemente Gruppen zuordnen
Sie können alle angezeigten Elemente visuell logischen Gruppen zuordnen. Jede einzelne Gruppe wird durch einen Text beschrieben, der beliebig ausgerichtet werden kann. Gruppen sind vom Typ ListViewGroup (siehe Tabelle 12.27).
Die Gruppen eines ListView-Steuerelements werden, da es sich in der Regel um mehrere handelt, von einer Auflistung verwaltet, die in der Eigenschaft Groups des Steuerelements gespeichert ist. Ein Assistent erleichtert Ihnen auch hier die Arbeit. Jedes Listenelement können Sie nach eigenen Kriterien einer Gruppe zuordnen.
Sie weisen ein Element einer Gruppe zu, indem Sie die Gruppe im ListViewItem-Konstruktor angeben, die ListViewItem.Group-Eigenschaft festlegen oder das Element direkt der Items-Auflistung einer Gruppe hinzufügen. Alle Elemente, die keiner Gruppe zugewiesen sind, werden in der Standardgruppe mit der Bezeichnung Default angezeigt.
Eigenschaft | Beschreibung | |
Header |
Beschriftung der Gruppe |
|
HeaderAlignment |
Ausrichtung der Beschriftung |
|
Items |
Auflistung aller Elemente, die dieser Gruppe zugeordnet sind |
R |
Name |
Name der Gruppe |
Die Personenauflistung von oben kann nach Altersklassen gruppiert werden, zum Beispiel:
For Each item As ListViewItem in lvwPerson.Items setGroup(item) Next
Die benutzerdefinierte Methode setGroup entscheidet über die Gruppenzugehörigkeit, die in Abbildung 12.37 zu sehen ist:
Private Sub setGroup(item As ListViewItem) If Convert.ToInt32(item.SubItems(2).Text) < 31 Then item.Group = lvwPerson.Groups(0) Else If Convert.ToInt32(item.SubItems(2).Text) < 41 Then item.Group = lvwPerson.Groups(1) Else item.Group = lvwPerson.Groups(2) End If End Sub
Abbildung 12.37 Ausgabe von Gruppierungen
Sie können dem Benutzer auch die Entscheidung selbst überlassen, ob er die Gruppierung der Listenelemente angezeigt bekommt oder nicht. Die Anzeige richtet sich nämlich nach der Eigenschaft ShowGroups der Listenansicht. Voreingestellt ist True.
12.21.7 Sortierung der Spalten
ListView sortiert die Listeneinträge, wenn die Eigenschaft Sorting vom Typ der Enumeration SortOrder einen vom Standardwert None abweichenden Wert hat. Ascending sortiert aufsteigend, Descending absteigend.
Im Windows Explorer führt ein Klick auf einen Spaltenkopf dazu, dass alle Zeilen nach dieser Spalte sortiert werden. Das ist auch in einer ListView sehr einfach zu realisieren, wenn Sie die Standardeinstellung HeaderStyle=Clickable beibehalten haben.
Eine vom Standard abweichende Sortierung wird mit der Eigenschaft ListViewItemSorter definiert, die nicht im Eigenschaftsfenster der Entwicklungsumgebung angezeigt wird:
Public Property ListViewItemSorter As IComparer |
Wir müssen der Eigenschaft ein Objekt zuweisen, das die Schnittstelle IComparer implementiert. Uns kommt dabei entgegen, dass alle Einträge in der Listenansicht vom Typ String sind. Daher benötigen wir nicht für jede Spalte eine eigene Vergleichsklasse, sondern kommen, wenn wir es geschickt anstellen, mit einer einzigen aus. In der Vergleichsklasse sollten wir auch noch eine übliche Verhaltensweise berücksichtigen: Wird ein Spaltenkopf ein zweites Mal angeklickt, kehrt sich die Sortierreihenfolge der Listenelemente um.
Ein Klasse, die alle Ansprüche erfüllt, kann wie folgt codiert werden:
Public Class ListViewComparer : Implements Icomparer Private col As Integer Private order As SortOrder Public Sub New(ByVal col As Integer, ByVal order As SortOrder) Me.col = col Me.order = order End Sub Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer _ Implements System.Collections.IComparer.Compare Dim item1, item2 As ListViewItem item1 = CType(If(order = SortOrder.Ascending, x, y), ListViewItem) item2 = CType(If(order = SortOrder.Ascending, y, x), ListViewItem) Return item1.SubItems(col).Text.CompareTo(item2.SubItems(col).Text) End Function End Class
Um die Klasse allgemein zu halten, übergeben wir dem Konstruktor sowohl den Index der angeklickten Spalte als auch die gewünschte Sortierreihenfolge. Diese Informationen werden von der Methode Compare als Grundlage des Vergleichs benötigt. Ihr werden zwei Objekte vom Typ ListViewItem übergeben. Das etwas eigenartige Verhalten eines ListViewItem-Objekts, sich in seine eigene ListViewSubItemCollection einzutragen, ist nun für uns von großem Nutzen. Auch die konstante Indizierung der Spaltenköpfe, die nicht zwangsläufig der sichtbaren Reihenfolge entspricht (wenn AllowColumnReorder=True eingestellt ist), macht die Vergleichsoperation sehr einfach.
Bei genauer Betrachtung hat die Klassendefinition sogar einen allgemeinen Charakter und kann, wenn keine besonderen Vergleichskriterien eine Rolle spielen, praktisch von jeder Listenansicht benutzt werden.
Wir wollen jetzt die Liste der Personen, der wir uns im Laufe des Abschnitts schon einige Male bedient haben, vervollständigen und im ListView auch die Spaltensortierung vorsehen.
'...\WinControls\Container\Listensortierung.vb |
Public Class Listensortierung ... Private Sub Laden(ByVal sender As Object, ByVal e As EventArgs) _ Handles MyBase.Load For Each c As String In _ New String() {"Zuname", "Vorname", "Alter", "Wohnort"} Personen.Columns.Add(c) Next Personen.Groups.Add("Teenies", "30 oder jünger") Personen.Groups.Add("Jung", "31 bis 40") Personen.Groups.Add("Alt", "30 oder älter") Personen.Items.Add(New ListViewItem(New String() _ {"Abel", "Rainer", "30", "Essen"})) Personen.Items.Add(New ListViewItem(New String() _ {"Müller", "Uwe", "25", "Berlin"})) Personen.Items.Add(New ListViewItem(New String() _ {"Rosnick", "Bernd", "44", "Bonn"})) Personen.Items.Add(New ListViewItem(New String() _ {"Berenbach", "Peter", "35", "Köln"})) Personen.Items.Add(New ListViewItem(New String() _ {"Keser", "Wolfgang", "48", "Düren"})) For Each item As ListViewItem In Personen.Items setGroup(item) Next End Sub Private Sub setGroup(ByVal item As ListViewItem) If Convert.ToInt32(item.SubItems(2).Text) < 31 Then item.Group = Personen.Groups(0) ElseIf Convert.ToInt32(item.SubItems(2).Text) < 41 Then item.Group = Personen.Groups(1) Else item.Group = Personen.Groups(2) End If End Sub Private Sub Hinzufügen_Click(sender As Object, e As EventArgs) _ Handles Hinzufügen.Click Personen.Items.Add(New ListViewItem( _ New String() {"Hamster", "Karl", "34", "Bremen"})) setGroup(Personen.Items(Personen.Items.Count – 1)) Hinzufügen.Enabled = False End Sub Private Sub Gruppen_CheckedChanged(sender As Object, e As EventArgs) _ Handles Gruppen.CheckedChanged Personen.ShowGroups = Gruppen.Checked End Sub Dim col As Integer Private Sub Personen_ColumnClick(sender As Object, _ e As ColumnClickEventArgs) Handles Personen.ColumnClick If e.Column = col Then If Personen.Sorting = SortOrder.Ascending Then Personen.Sorting = SortOrder.Descending Else Personen.Sorting = SortOrder.Ascending End If End If col = e.Column Personen.ListViewItemSorter = _ New ListViewComparer(e.Column, Personen.Sorting) End Sub End Class
Um die Sortierreihenfolge zu ändern, klickt der Anwender auf einen Spaltenkopf. Dabei wird das Ereignis ColumnClick ausgelöst, das in der Eigenschaft Column des Args-Parameters den Index des angeklickten Spaltenkopfs bereitstellt. Die Sortierreihenfolge kehrt sich bei jedem erneuten Anklicken derselben Spalte um. Die Vergleichsklasse wird mit der korrekten Sortierung instanziiert und der Eigenschaft ListViewItemSorter zugewiesen.
Eine zusätzliche Schaltfläche fügt noch eine Person hinzu, um den Einfluss auf die Sortierreihenfolge zu testen. Das neue Listenelement wird an der richtigen Position eingefügt. Erfreulicherweise wird auch in der Gruppierungsansicht innerhalb einer Gruppe richtig sortiert.
Wenn Sie die Listenansicht mit vielen Elementen ergänzen, indem Sie die Add-Methode aufrufen, wird das Steuerelement jedes Mal neu gezeichnet. Möglicherweise kann das zum Flackern der Anzeige führen. Sie können das vermeiden, wenn Sie vor dem Hinzufügen der neuen Listenelemente mit der Methode BeginUpdate das wiederholte Neuzeichnen unterdrücken. Nach dem Hinzufügen des letzten Listenelements müssen Sie das ListView-Objekt mit EndUpdate jedoch neu aufbauen.
Hinweis |
Die Gruppendarstellung war bei mir nach einer Neusortierung ohne Gruppen fehlerhaft. |
12.21.8 Listenelemente ändern
Standardmäßig werden die Listenelemente nur angezeigt und können nicht verändert werden. Setzen Sie LabelEdit=True für die Listenansicht, darf der Anwender den Text des Listenelements editieren. Zur Laufzeit muss das zu ändernde Listenelement zunächst mit einem Klick markiert werden. Beim nächsten Klick befindet sich der Eingabecursor am Ende des Textes. Eine Möglichkeit, auch den Inhalt der Unterelemente zu verändern, gibt es nicht.
Eine Änderung hat nicht zur Folge, dass auch die zugrunde liegenden Daten geändert werden, wenn eine ListView beispielsweise die Listenelemente aus einer Datenbank bezogen hat. Im Kontext mit dem Zurückschreiben von Änderungen bieten sich zwei Ereignisse an: BeforeLabelEdit und AfterLabelEdit. Das erste Ereignis wird nach dem zweiten Klick ausgelöst, das zweite beim Verlassen der Eingabebox.
Beide Ereignisse stellen dem Ereignishandler im zweiten Parameter ein Objekt vom Typ LabelEditEventArgs zur Verfügung. Mittels der Eigenschaft CancelEdit kann die Änderung des Elements verworfen werden, Item ruft den Index des zur Änderung anstehenden ListViewItem-Objekts ab, und Label beinhaltet die Zeichenfolge, die der Text-Eigenschaft des Listenelements zugewiesen werden soll.
Die Daten schreiben Sie am besten im AfterLabelEdit-Ereignishandler in die Originaldatenquelle zurück. Vorher sollten Sie den Text in der Eigenschaft Label auf Richtigheit überprüfen. Brechen Sie die Operation mit CancelEdit=True ab, wird der ursprüngliche Inhalt automatisch wiederhergestellt.
12.21.9 Dateiexplorer-Beispiel
Vielleicht haben Sie den Eindruck gewonnen, dass das ListView-Steuerelement kompliziert zu programmieren ist. Es umfasst eine große Anzahl von Klassen und Eigenschaften. Wie komplex eine Listenansicht letztendlich wird, hängt natürlich von den darzustellenden Datenstrukturen und den Anforderungen an die Qualität des Layouts ab. Zum Beispiel sind zusätzliche Bildchen etwas mehr Aufwand. Noch schwieriger ist es, wenn der Benutzer das Aussehen zur Laufzeit ändern können soll.
Ich möchte Ihnen nun das Beispiel Explorer vorstellen, das den Windows Explorer teils nachprogrammiert. Das Beispielprogramm soll und kann kein vollwertiger Ersatz des Microsoft Explorers sein, aber dennoch die wichtigsten Aspekte berücksichtigen. Das Beispiel kann für Sie die Basis eines ganz individuellen Explorers sein. Abbildung 12.38 zeigt das Aussehen der hier vorgestellten Implementation.
Abbildung 12.38 Die Ausgabe des Beispiels »Explorer«
Fangen wir mit der Initialisierung an, die ich im Load-Ereignis des Formulars untergebracht habe. In der ersten Schleife wird die Baumstruktur für den linken Teil aufgebaut. Als Wurzeln werden die von GetLogicalDrives() gelieferten Laufwerke genommen. Damit beim Öffnen eines Knotens die darunterliegenden Verzeichnisse mit Unterverzeichnissen ein »+«-Zeichen zur Expansion zeigen, wird für jeden Knoten der Ereignishandler der Expansion aufgerufen, der dynamisch Knoten hinzufügt. Das Laufwerksbildchen ist in der ImageList und hat den Index null. Um nicht ohne Selektion zu starten, wird das erste Laufwerk selektiert.
Als Nächstes werden die Überschriften der Detailansicht festgelegt. In der zweiten Schleife werden schließlich die Menüpunkte zur Ansichtswahl der Listendarstellung im rechten Fensterteil hinzugefügt und wird die erste Ansichtsart gewählt. Durch den Menüaufbau in der Schleife kann keine Darstellungsart vergessen werden.
'...\WinControls\Container\Explorer.vb |
Public Class Explorer Private Sub Laden(sender As Object, e As EventArgs) Handles Me.Load For Each lw As String In Environment.GetLogicalDrives() Dim node As New TreeNode(lw) node.Tag = lw node.ImageIndex = –1 'nur eine Rekursion in Struktur_BeforeExpand() Dim pos As Integer = Struktur.Nodes.Add(node) Struktur_BeforeExpand(node, New TreeViewCancelEventArgs( _ node, False, TreeViewAction.Unknown)) node.ImageIndex = 0 node.SelectedImageIndex = 0 Next Struktur.SelectedNode = Struktur.Nodes(0) Inhalt.Columns.Add("Name") Inhalt.Columns.Add("Größe") Inhalt.Columns.Add("Zeit") Inhalt.Columns.Add("Attribute") For Each v As View In [Enum].GetValues(GetType(View)) Dim item As New ToolStripMenuItem(v.ToString()) AddHandler item.Click, AddressOf Wechseln item.Tag = v Ansicht.DropDownItems.Add(item) Next Wechseln(Ansicht.DropDownItems(0), Nothing) End Sub ... End Class
Damit nicht alle Datenträger vor dem Öffnen des Formulars analysiert werden müssen, fügt der Ereignishandler von BeforeExpand nur die nächsten beiden Knotenebenen dem Baum hinzu. Die erste enthält die Kindknoten für die Darstellung, die zweite (rekursiver Aufruf von Struktur_BeforeExpand()) sorgt für die korrekte Anzeige von »+«-Zeichen. Der temporär negative Wert von ImageIndex stoppt eine weitere Rekursion. Jeder Verzeichnis-Knoten speichert in seiner Eigenschaft Tag den kompletten Pfad, um die Darstellung von dem Datenmodell zu entkoppeln. Verzeichnisse ohne Leserechte erzeugen eine Ausnahme vom Typ UnauthorizedAccessException. In diesem Fall wird ein anderes Bildchen zur Darstellung gewählt. Andere Ausnahmen werden nur berichtet. Ein Grund können unter Windows ungültige Zeichen in einem Verzeichnisnamen sein, die auf einem Rechner im Netzwerk mit anderem Betriebssystem gültig sind.
'...\WinControls\Container\Explorer.vb |
Public Class Explorer ... Private Sub Struktur_BeforeExpand(sender As Object, _ e As TreeViewCancelEventArgs) Handles Struktur.BeforeExpand Dim di As New IO.DirectoryInfo(e.Node.Tag) Try e.Node.Nodes.Clear() For Each dir As IO.DirectoryInfo In di.GetDirectories() Dim node As TreeNode = e.Node.Nodes.Add(dir.Name) node.Tag = dir.FullName node.ImageIndex = –1 If e.Node.ImageIndex >= 0 Then Struktur_BeforeExpand(node, New TreeViewCancelEventArgs( _ node, False, TreeViewAction.Unknown)) End If If node.ImageIndex < 1 Then node.ImageIndex = 1 Next Catch ex As UnauthorizedAccessException e.Node.ImageIndex = 3 e.Node.SelectedImageIndex = 3 Catch ex As Exception MessageBox.Show("Ausnahme für Pfad " & di.FullName & ": " & ex.Message) e.Cancel = True End Try End Sub ... End Class
Damit kein Speicher verschwendet wird, entsorgt ein geschlossener Knoten Urenkel, die ausschließlich zur korrekten Anzeige der »+«-Zeichen benötigt werden.
'...\WinControls\Container\Explorer.vb |
Public Class Explorer
...
Private Sub Struktur_AfterCollapse(sender As Object, _
e As TreeViewEventArgs) Handles Struktur.AfterCollapse
For Each kind As TreeNode In e.Node.Nodes
For Each enkel As TreeNode In kind.Nodes
enkel.Nodes.Clear()
Next
Next
End Sub
...
End Class
Als Nächstes wird für die Baumansicht der Ereignishandler von AfterSelect implementiert, der ausgelöst wird, wenn ein Verzeichnis-Knoten im Baum selektiert wird. Der Handler baut die Listenansicht im rechten Teil des Formulars neu auf. Wenn für das Verzeichnis keine Leserechte bestehen, wird die Methode nach dem Löschen des Inhalts der Listenansicht verlassen. Sonst werden die Elemente der Listenansicht mit allen Spalten aus den Unterverzeichnissen und danach den Dateien aufgebaut. Um das Beispiel einfach zu halten, habe ich auf Mehrdeutigkeiten der Dateiattribute nicht geachtet (mehrere Attribute fangen mit demselben Buchstaben an). Die Eigenschaften LargeImageList und SmallImageList der Listenansicht sollten korrespondierende Bilder enthalten, da derselbe Index für beide benutzt wird (hier: Nummer eins für Ordner und Nummer zwei für Dateien). Durch die Anweisung Struktur.SelectedNode = Struktur.Nodes(0) während der Initialisierung startet die Anwendung mit einer befüllten Listenansicht.
'...\WinControls\Container\Explorer.vb |
Public Class Explorer ... Private Sub Struktur_AfterSelect(sender As Object, e As TreeViewEventArgs) _ Handles Struktur.AfterSelect Inhalt.Items.Clear() If Struktur.SelectedNode.ImageIndex = 3 Then Return Dim item As ListViewItem Dim dirInfo As New IO.DirectoryInfo(Struktur.SelectedNode.FullPath) Try For Each dir As IO.DirectoryInfo In dirInfo.GetDirectories() item = Inhalt.Items.Add(dir.Name, 1) item.SubItems.Add("") item.SubItems.Add(dir.LastWriteTime.ToString()) item.Tag = dir.FullName Next For Each file As IO.FileInfo In dirInfo.GetFiles() item = Inhalt.Items.Add(file.Name, 2) item.SubItems.Add(file.Length.ToString()) item.SubItems.Add(file.LastWriteTime.ToString()) Dim attr As String = "" For Each a As IO.FileAttributes In _ [Enum].GetValues(GetType(IO.FileAttributes)) If (file.Attributes And a) <> 0 Then _ attr += a.ToString().Substring(0, 1) Next item.SubItems.Add(attr) Next Catch ex As Exception MessageBox.Show("Ausnahme in Liste: " & ex.Message) End Try End Sub ... End Class
Damit die Listenansicht sich wie gewohnt verhält, muss noch das Ereignis ItemActivate behandelt werden, das bei einem Doppelklick oder durch Druck auf die -Taste ausgelöst wird. Eine äußere Schleife untersucht alle selektierten Elemente (Mehrfachauswahl ist erlaubt). Ein aktiviertes Verzeichnis durchsucht den Elternknoten nach sich selbst und expandiert den Knoten in der Baumansicht, was wiederum die Listenansicht mit dem Verzeichnisinhalt füllt. Ist das aktivierte Element eine Datei, wird sie mit Process.Start() aus dem Namensraum System.Diagnostics gestartet bzw. wird die Datei in der unter Windows registrierten zugehörigen Applikation geöffnet.
'...\WinControls\Container\Explorer.vb |
Public Class Explorer ... Private Sub Inhalt_ItemActivate(sender As Object, e As EventArgs) _ Handles Inhalt.ItemActivate For Each item As ListViewItem In Inhalt.SelectedItems If item.Tag IsNot Nothing Then ' nur Verzeichnisse bekommen ein Tag in Struktur_AfterSelect() For Each node As TreeNode In Struktur.SelectedNode.Nodes If item.Tag.Equals(node.Tag) Then Struktur.SelectedNode = node Struktur.SelectedNode.Expand() Struktur.Refresh() Exit For End If Next Else Dim path As String = _ IO.Path.Combine(Struktur.SelectedNode.Tag, item.Text) Try Process.Start(path) Catch ex As Exception MessageBox.Show("Fehler beim Start von " & path & ": " & ex.Message) End Try End If Next End Sub ... End Class
Im Menü Ansicht kann die Darstellung der Listenansicht beliebig in Kleine Symbole, Grosse Symbole, Liste und Details verändert werden. Der Ereignishandler Wechseln() der korrespondierenden Menüpunkte markiert nur die gewählte Ansicht im Menü und setzt diese in der Listenansicht.
'...\WinControls\Container\Explorer.vb |
Public Class Explorer ... Private Sub Wechseln(ByVal sender As Object, ByVal e As EventArgs) For Each a As ToolStripMenuItem In Ansicht.DropDownItems a.Checked = a Is sender If a.Checked Then Inhalt.View = CType(a.Tag, View) Next End Sub ... End Class
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.