ListView aus Tabellen oder Abfragen füllen, Teil 2

Im ersten Teil dieser Beitragsreihe haben wir ein ListView-Steuerelement mit den Daten aus einer Mitarbeitertabelle gefüllt, Icons hinzugefügt und eine Sortierfunktion implementiert. Im vorliegenden Beitrag bauen wir dieses Beispiel weiter aus: Wir zeigen, wie man dem ListView-Steuerelement neue Einträge hinzufügen und bestehende Einträge bearbeiten oder löschen kann. Außerdem erklären wir, wie man die Elemente auch nach numerischen Daten oder nach Datumsfeldern korrekt sortiert. Schließlich fügen wir noch ein Kontextmenü hinzu, mit dem man den Status der Mitarbeiter anpassen kann.

Rückblick auf Teil 1

Im ersten Teil dieser Beitragsreihe (www.access-im-unternehmen.de/1574) haben wir zunächst eine Abfrage namens qryMitarbeiter erstellt, die alle benötigten Lookup-Tabellen – tblAnreden, tblAbteilungen und tblStatus – einbindet. Anschließend haben wir das ListView-Steuerelement in der Ereignisprozedur Form_Load eingerichtet und mit Spaltenüberschriften versehen.

Die Prozedur ListViewFuellen liest die Datensätze per Recordset aus und fügt sie als ListItem– und ListSubItem-Objekte ein. Außerdem haben wir per Windows-API-Aufruf die Spaltenbreiten automatisch an Inhalt und Überschrift angepasst, für jeden Mitarbeiter ein statusabhängiges Icon aus der Tabelle MSysResources eingeblendet und eine einfache Sortierfunktion per Klick auf den Spaltenkopf ergänzt. Bild 1 zeigt den Stand am Ende von Teil 1.

Aktueller Stand des ListView-Steuerelements

Bild 1: Aktueller Stand des ListView-Steuerelements

Sortierung nach Zahlen- und Datumsfeldern

Im ersten Teil haben wir bereits eine Sortierfunktion eingebaut, die über einen Klick auf den jeweiligen Spaltenkopf ausgelöst wird. Dabei zeigte sich jedoch ein Problem: Zahlen und Datumsangaben werden lexikografisch sortiert, also wie Zeichenketten.

Das führt dazu, dass beispielsweise der Wert 10 vor dem Wert 2 erscheint und Datumsangaben nicht chronologisch, sondern nach ihrem Textwert geordnet werden.

Um das zu beheben, ersetzen wir die eingebaute Sortierfunktion durch eigene Prozeduren, die Zahlen- und Datumswerte korrekt vergleichen.

Als Basis dienen die API-Konstanten und die SendMessage-Deklaration aus Listing 1, die wir im allgemeinen Modul mdlListViewTabellen2 ablegen.

Private Const LVM_FIRST As Long = &H1000
Private Const LVM_SORTITEMSEX As Long = LVM_FIRST + 81
#If VBA7 Then
    Private Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As LongPtr, _
        ByVal wMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr
#Else
    Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, _
        ByVal wParam As Long, ByVal lParam As Long) As Long
#End If

Listing 1: Benötigte API-Deklarationen

Für die eigentliche Sortierung erweitern wir die Ereignisprozedur ctlListView_ColumnClick aus Teil 1.

Nach dem Setzen von SortKey und SortOrder rufen wir eine eigene Prozedur ListViewSortieren auf, die den Vergleichstyp je Spalte festlegt (siehe Listing 2).

Private Sub ctlListView_ColumnClick(ByVal ColumnHeader As Object)
    Static intAktuelleSortierspalte As Integer
    Static bolAufsteigend As Boolean
    Dim objListView As MSComctlLib.ListView
    Set objListView = Me.ctlListView.Object
    objListView.SortKey = ColumnHeader.Index - 1
    If intAktuelleSortierspalte = ColumnHeader.Index - 1 Then
        bolAufsteigend = Not bolAufsteigend
        objListView.SortOrder = Abs(bolAufsteigend)
    Else
        objListView.SortOrder = lvwAscending
    End If
    intAktuelleSortierspalte = ColumnHeader.Index - 1
    Call ListViewSortieren(objListView, ColumnHeader.Index - 1, bolAufsteigend)
End Sub

Listing 2: Angepasste ColumnClick-Prozedur

Die Prozedur ListViewSortieren im Modul mdlListViewTabellen2 verzweigt per Select Case je nach Spaltenindex in die passende Sortierprozedur: Index 1 (ID) landet in ListViewSortNumerisch, Index 7 (Geburtstag) in ListViewSortDatum, alle anderen Spalten nutzen weiterhin die eingebaute Textsortierung über objLV.Sorted = True (siehe Listing 3).

Public Sub ListViewSortieren(objLV As MSComctlLib.ListView, intSpalte As Integer, bolAufsteigend As Boolean)
    Select Case intSpalte
        Case 1 'ID: numerisch
            Call ListViewSortNumerisch(objLV, intSpalte, bolAufsteigend)
        Case 7 'Geburtstag: Datum
            Call ListViewSortDatum(objLV, intSpalte, bolAufsteigend)
        Case Else 'alle anderen: Text
            objLV.Sorted = True
    End Select
End Sub

Listing 3: Verzweigung nach Spaltentyp

Beide Sortierprozeduren arbeiten nach dem gleichen Prinzip: Sie vergleichen im Bubblesort-Durchlauf die Werte zweier benachbarter Zeilen direkt und tauschen bei Bedarf sofort deren Inhalte. Da die Index-Eigenschaft eines ListItem-Objekts schreibgeschützt ist, können wir die Reihenfolge der Einträge nicht durch Umsetzen von Indizes verändern. Stattdessen lassen wir die ListItem-Objekte an ihrer Position und schreiben nur die angezeigten Inhalte um – das Ergebnis ist dasselbe.

Routine zum Vertauschen von ListView-Einträgen

Den Austausch übernimmt die private Hilfsprozedur ListViewZeilenTauschen. Sie erhält das ListView-Objekt sowie die Indizes der beiden zu tauschenden Zeilen. Zunächst speichert sie den Text der ersten Spalte von Zeile A in einer Hilfsvariablen, kopiert den Wert aus Zeile B nach A und schreibt den gespeicherten Wert nach B – der klassische Drei-Wege-Tausch. Anschließend wiederholt sie diesen Vorgang für jedes ListSubItem der Zeile. Da im ListView-Steuerelement auch das Icon je Zeile gespeichert ist, tauscht die Prozedur zuletzt noch den SmallIcon-Index, damit das Icon beim Umsortieren mitwandert und weiterhin zum jeweiligen Datensatz passt (siehe Listing 4).

Private Sub ListViewZeilenTauschen(objLV As MSComctlLib.ListView, lngA As Long, lngB As Long)
    Dim strTemp As String
    Dim i As Long
    strTemp = objLV.ListItems(lngA).Text
    objLV.ListItems(lngA).Text = objLV.ListItems(lngB).Text
    objLV.ListItems(lngB).Text = strTemp
    For i = 1 To objLV.ListItems(lngA).ListSubItems.Count
        strTemp = objLV.ListItems(lngA).ListSubItems(i).Text
        objLV.ListItems(lngA).ListSubItems(i).Text = objLV.ListItems(lngB).ListSubItems(i).Text
        objLV.ListItems(lngB).ListSubItems(i).Text = strTemp
    Next i
    'SmallIcon-Index ebenfalls tauschen
    Dim lngIconA As Long
    lngIconA = objLV.ListItems(lngA).SmallIcon
    objLV.ListItems(lngA).SmallIcon = objLV.ListItems(lngB).SmallIcon
    objLV.ListItems(lngB).SmallIcon = lngIconA
End Sub

Listing 4: Hilfsprozedur zum Tauschen zweier ListView-Zeilen

Sortieren mit Bubblesort

Die Prozedur ListViewSortNumerisch implementiert den Bubblesort mit zwei verschachtelten Schleifen. Die äußere Schleife über i läuft von 1 bis lngAnzahl – 1 und steuert die Durchläufe. Die innere Schleife über j läuft von 1 bis lngAnzahl – i – nach jedem Durchlauf steht der größte noch unsortierte Wert am Ende, weshalb der Vergleichsbereich mit jedem Durchlauf um eine Position kürzer wird. Im Schleifenrumpf lesen wir die Werte der beiden betrachteten Zeilen aus: Bei Spaltenindex 0 steht der Wert in ListItems(j).Text, bei allen anderen Spalten in ListItems(j).ListSubItems(intSpalte).Text. Beide Werte wandeln wir per CLng in ganzzahlige Werte um und vergleichen sie.

Ist die Reihenfolge falsch – bei aufsteigender Sortierung also lngA > lngB, bei absteigender lngA < lngB – rufen wir ListViewZeilenTauschen auf (siehe Listing 5).

Public Sub ListViewSortNumerisch(objLV As MSComctlLib.ListView, intSpalte As Integer, bolAufsteigend As Boolean)
    Dim i As Long, j As Long
    Dim lngAnzahl As Long
    lngAnzahl = objLV.ListItems.Count
    'Bubblesort: Zeileninhalt direkt tauschen
    For i = 1 To lngAnzahl - 1
        For j = 1 To lngAnzahl - i
            Dim lngA As Long, lngB As Long
            If intSpalte = 0 Then
                lngA = CLng(objLV.ListItems(j).Text)
                lngB = CLng(objLV.ListItems(j + 1).Text)
            Else
                lngA = CLng(objLV.ListItems(j).ListSubItems(intSpalte).Text)
                lngB = CLng(objLV.ListItems(j + 1).ListSubItems(intSpalte).Text)
            End If
            If bolAufsteigend Then
                If lngA > lngB Then Call ListViewZeilenTauschen(objLV, j, j + 1)
            Else
                If lngA < lngB Then Call ListViewZeilenTauschen(objLV, j, j + 1)
            End If
        Next j
    Next i
End Sub

Listing 5: Numerische Sortierung

Die Prozedur ListViewSortDatum ist identisch aufgebaut – der einzige Unterschied ist die Typumwandlung per CDbl(CDate(…)), da Access Datumsangaben intern als Gleitkommazahl speichert (siehe Klassenmodul Form_frmMitarbeiterImListView).

Nach diesen Anpassungen liefert ein Klick auf den Spaltenkopf ID eine korrekte numerische Sortierung, und ein Klick auf Geburtstag eine chronologische.

In Bild 2 sehen wir, dass die Sortierung nach dem Geburtstag nun funktioniert.

Korrekte Sortierung nach dem Feld Geburtstag

Bild 2: Korrekte Sortierung nach dem Feld Geburtstag

Bearbeitungs-Dialog per Doppelklick öffnen

Damit der Benutzer einen Mitarbeiterdatensatz direkt aus dem ListView-Steuerelement heraus bearbeiten kann, öffnen wir per Doppelklick auf einen Eintrag ein Bearbeitungsformular. Das Formular frmMitarbeiterDetail ist an die Tabelle tblMitarbeiter gebunden und enthält alle Felder des Datensatzes (siehe Bild 3).

Das Formular zum Bearbeiten der Mitarbeiterdetails

Bild 3: Das Formular zum Bearbeiten der Mitarbeiterdetails

Die Eigenschaft Automatisch zentrieren stellen wir aus Ja, Bildlaufleisten, Navigationsschaltflächen und Datensatzmarkierer auf Nein und Zyklus auf Aktueller Datensatz ein.

Wir öffnen es mit DoCmd.OpenForm und übergeben die MitarbeiterID des angeklickten Eintrags als Filterargument.

Für das Doppelklick-Ereignis des ListView-Steuerelements hinterlegen wir die Ereignisprozedur aus Listing 6.


Nur für Abonnenten

Ab hier wird’s wirklich spannend – der Rest ist exklusiv für Abonnenten.

Mit dem Abo von Access im Unternehmen bekommst du den kompletten Artikel – inklusive vollständigem Code, Beispieldatenbank und Schritt-für-Schritt-Erklärung.

So sparst du dir stundenlanges Herumprobieren, vermeidest teure Fehler in deiner Access-Anwendung und kannst Lösungen direkt in deinem Unternehmen einsetzen, statt nur darüber zu lesen.

Teste Access im Unternehmen jetzt 4 Wochen lang kostenlos: Voller Zugriff auf alle Artikel, Downloads und Beispieldatenbanken. Kein Risiko – wenn es für dich nicht passt, kündigst du einfach innerhalb der ersten vier Wochen.

Bereits Abonnent? Hier einloggen


Kostenlos & unverbindlich

Oder hast Du eine konkrete Frage zu Deiner eigenen Access-Anwendung?

Vielleicht stellt Deine Anwendung Dich vor eine Herausforderung, zu der Du bisher keine Lösung findest. Schlechte Performance, kein ausreichender Zugriffsschutz, Du bist unsicher über Dein Datenmodell oder Dein Code liefert unerklärliche Fehler?

In unserem kostenlosen Access-Audit schaut sich André Minhorst persönlich gemeinsam mit Dir Deine Lösung per Zoom an – und zeigt Dir, wo Datenmodell, VBA-Code, Ergonomie und Sicherheit Optimierungspotenzial bieten.

Jetzt kostenloses Access-Audit anfordern →

Schreibe einen Kommentar