ListView-Zeilen in Visual Basic korrekt sortieren

Veröffentlicht: 01. Okt 2002 | Aktualisiert: 22. Jun 2004

Von Mathias Schiffer

Das ListView-Steuerelement der Microsoft Windows Common Controls bietet die Möglichkeit an, die Zeilen nach einer Spalte alphanumerisch sortiert anzuzeigen. Sind die zu sortierenden Spaltenwerte aber Datumswerte oder Zahlen, nutzt die alphanumerische Sortierung nichts. Dieser Artikel zeigt, wie Sie auch nach diesen Spalten richtig sortieren können.

Auf dieser Seite

 Probleme bei Datumswerten und Zahlen
 Individuelle Sortierung im ListView mit LVM_SORTITEMS und CompareFunc
 Von lParam1 und lParam2 zum Eintragsindex
 Vom Eintragsindex zum Text
 Strings, Zahlen und Daten unterscheiden
 Das große Finale: ein Standardmodul als Lösungspaket

Eigentlich scheint alles ganz einfach auszusehen: Um die Zeilen eines ListView-Steuerelements in Report-Ansicht nach einer Spalte zu sortieren, verwende man seine Eigenschaften Sorted, SortKey und SortOrder: Sorted bestimmt, ob die Anzeige überhaupt sortiert erscheinen soll, SortKey bestimmt die Spalte, nach der sortiert werden soll und SortOrder bestimmt die Reihenfolge der Sortierung (auf- oder absteigend):

With ListView1
  ' Drei Spaltenköpfe anlegen:
  .ColumnHeaders.Add Text:="Name"
  .ColumnHeaders.Add Text:="Geburtstag"
  .ColumnHeaders.Add Text:="Fingerskala"
  ' Einige Elemente eintragen:
  With .ListItems.Add(Text:="Carina")
    .SubItems(1) = "01.03.1970"
    .SubItems(2) = "1"
  End With
  With .ListItems.Add(Text:="Birgit")
    .SubItems(1) = "07.06.1968"
    .SubItems(2) = "10"
  End With
  With .ListItems.Add(Text:="Anna")
    .SubItems(1) = "12.05.1972"
    .SubItems(2) = "7"
  End With
  .View = lvwReport ' Report-Ansicht
  .Sorted = True ' Sortierte Anzeige
  .SortKey = 0   ' Sortierung nach erster Spalte
  .SortOrder = lvwAscending ' Aufsteigende Sortierung
End With

Das Ergebnis dieser eingebauten Sortierung: Die angezeigten ListView-Zeilen wurden korrekt nach den Vornamen in der ersten Spalte sortiert.

Probleme bei Datumswerten und Zahlen

Sind Sie mit Ihrem bisherigen Ergebnis zufrieden? Nicht zu früh gefreut, das wird nicht lange so bleiben. Ändern Sie ein kleines Detail: Sortieren Sie nach der zweiten Spalte (den Geburtsdaten), indem Sie den Wert für die SortKey-Eigenschaft von 0 auf 1 verändern. Sind Sie mit dem Ergebnis noch immer zufrieden?

Diesmal sollten Sie sich ärgern dürfen: Die Daten werden nicht in zeitlicher Abfolge der Daten, sondern strikt alphanumerisch zeichenweise von links nach rechts sortiert.

Auch das Setzen der Eigenschaft auf 2 (für Sortierung nach der dritten Spalte, die Zahlen enthält) bringt kein befriedigendes Ergebnis: 7 ist definitiv nicht größer als 10, obwohl das ListView-Control dies durch seine stringbasierte Sortierung behauptet. Das ist sogar durchaus berechtigt: Der ASCII-Wert des Strings 7 ist mit 55 in der Tat größer als derjenige des Strings 1 mit 49, der sowohl für 1 als auch für 10 höchstwertig für die zeichenweise alphanumerische Sortierung ist.

Das Problem ist also schnell erkannt: Das ListView-Steuerelement betrachtet sämtliche Eintragungen als Strings. Nicht für jeden Fall jedoch eignet sich die stringbasierte alphanumerische Sortierung, die das ListView-Steuerelement von Haus aus mitbringt.

Im folgenden soll daher eine Lösung für diese Problemfälle erarbeitet werden, die ganz nebenbei auch gleich die alphanumerische Sortierung mit beherrschen soll, um nicht für die verschiedenen Anforderungen zwischen verschiedenen Lösungen hin- und herwechseln zu müssen.

Für ganz Eilige schon zuvor der Tipp: Am Ende des Artikels finden Sie einen stark erweiterten und großzügig kommentierten Sourcecode als Fazit dieses Lösungswegs.

 

Individuelle Sortierung im ListView mit LVM_SORTITEMS und CompareFunc

Zur individuellen Sortierung eines ListView-Steuerelements steht die Nachricht LVM_SORTITEMS zur Verfügung: Sie wird mithilfe der API-Funktion SendMessage an das Fenster des betroffenen Steuerelements geschickt.

Aus praktischen Gründen wird hier eine Alias-Variante der SendMesage-Funktion verwendet, deren letzter Parameter ByVal als Long-Wert übergeben wird; zur Unterscheidung der üblichen SendMessage-Deklaration ("ByRef lParam As Any") trägt sie den Namen SendMessageLong:

Private Const LVM_FIRST     As Long = &H1000&
Private Const LVM_SORTITEMS As Long = LVM_FIRST + 48&
Private Declare Function SendMessageLong _
  Lib "user32" Alias "SendMessageA" ( _
  ByVal hWnd As Long, _
  ByVal Message As Long, _
  ByVal wParam As Long, _
  ByVal lParam As Long _
  ) As Long

Im vorletzten Parameter wParam der SendMessageLong-Funktion kann dabei ein wahlfreier Wert übergeben werden (im Beispiel die 0). Dem letzten Parameter lParam ist mithilfe des AddressOf-Operators die Adresse einer Funktion in einem Standardmodul anzugeben. Der Name dieser Funktion ist frei wählbar; hier soll sie in Übereinstimmung mit der Dokumentation im "Platform SDK" CompareFunc benannt werden:

SendMessageLong ListView1.hWnd, LVM_SORTITEMS, _
                0, AddressOf CompareFunc

Der Funktion CompareFunc ist das folgende Grundgerüst zueigen:

Function CompareFunc(ByVal lParam1 As Long, _
                     ByVal lParam2 As Long, _
                     ByVal lParamSort As Long _
                     ) As Long
  ' ...
End Function

Durch das Senden der Nachricht LVM_SORTITEMS an das ListView-Steuerelement wird diese Funktion solange für jeweils zwei Zeilen des ListView-Steuerelements angesprochen, bis alle vorhandenen Zeilen gegenseitig abgearbeitet sind. Zweck dieser Funktion ist, in ihrem Rückgabewert anzugeben, welches der beiden übergebenen Elemente als das größere gelten soll.

Die beiden Parameter lParam1 und lParam2 repräsentieren dabei die jeweiligen Zeilen (dazu gleich mehr), während der Parameter lParamSort dem wahlfreien Wert entspricht, der im SendMessageLong-Aufruf als vorletzter Parameter angegeben wurde (im Rahmen dieses Artikels wird dieser Wert nicht weiter berücksichtigt - das vorgefertigte Standardmodul am Ende des Artikels zeigt jedoch auf, wie er sinnvoll genutzt werden kann).

Der Rückgabewert der Funktion ist negativ, 0 oder positiv, wobei diesen Rückgabemöglichkeiten die folgenden Bedeutungen zukommen:

Aufsteigende Sortierung:
Element(lParam1) < Element(lParam2) => Rückgabewert < 0
Element(lParam1) = Element(lParam2) => Rückgabewert = 0
Element(lParam1) > Element(lParam2) => Rückgabewert > 0
Absteigende Sortierung entsprechend:
Element(lParam1) < Element(lParam2) => Rückgabewert > 0
Element(lParam1) = Element(lParam2) => Rückgabewert = 0
Element(lParam1) > Element(lParam2) => Rückgabewert < 0

Zu implementieren ist in CompareFunc also ein Mechanismus, der die Entscheidung trifft, welches der beiden Elemente größer ist (oder ob sie gleich sind). Dafür jedoch bedarf es zunächst natürlich der Kenntnis der Elementinhalte selbst, denn lParam1 und lParam2 sind kryptisch erscheinende Long-Werte, die hierüber keinen direkten Aufschluss geben.

 

Von lParam1 und lParam2 zum Eintragsindex

Aus diesen Long-Werten ist also zunächst der Inhalt der Zelle zu ermitteln, der in der Spalte steht, nach der die zugehörigen Zeilen im ListView-Steuerelement sortiert werden sollen.

Hierfür steht im ersten Schritt die Nachricht LVM_FINDITEM zur Verfügung, die ebenfalls per SendMessageLong an das ListView-Control gesendet wird. Die Nachricht ermöglicht mithilfe der Struktur LV_FINDINFO, ListView-Elemente zu suchen und gibt bei positivem Ergebnis den Eintragsindex des (ersten) gefundenen Elements zurück:

Private Const LVM_FINDITEM As Long = LVM_FIRST + 13&
Private Const LVFI_PARAM   As Long = &H1&
Private Type POINTAPI ' benötigt für LV_FINDINFO
  x As Long
  y As Long
End Type
Private Type LV_FINDINFO
  flags As Long
  psz As String
  lParam As Long
  pt As POINTAPI
  vkDirection As Long
End Type

Für unseren Zweck geben wir im Parameter flags der Struktur die Konstante LVFI_LPARAM an, um darauf hinzuweisen, dass wir einen Eintrag auf Basis des Parameters lParam suchen möchten. An diesen selbst übergeben wir lParam1 für das erste Element und entsprechend in einem späteren Schritt lParam2 für das zweite Element:

Dim udtFindInfo As LV_FINDINFO
Dim lIndex      As Long
With udtFindInfo
  .flags = LVFI_PARAM
  .lParam = lParam1
End With

Hiermit kann nun die Funktion SendMessageLong aufgerufen werden. Ihren dritten Parameter wParam setzen wir auf -1, um anzuzeigen, dass wir in sämtlichen vorhandenen Einträgen nach einem Treffer für den angegebenen lParam-Wert suchen möchten. Die als LV_FINDINFO dimensionierte Variable wird dabei mithilfe der Funktion VarPtr (alternativ zur VarPtr-freien Verwendung des "Originals" von SendMessage) an den letzten Parameter lParam übergeben:

lIndex = SendMessageLong(ListView1.hWnd, LVM_FINDITEM, _
                         -1, VarPtr(udtFindInfo))

lIndex enthält nun den Index des gesuchten Elements, auf dessen Basis wir den gesuchten Text ermitteln können.

 

Vom Eintragsindex zum Text

Um den gesuchten Text zu ermitteln wird eine weitere Nachricht verwendet: LVM_GETITEMTEXT kann auf Basis der Index- und Spaltenangabe den Text einer ListView-Zelle ermitteln. Hierfür bedient die Nachricht sich der Struktur LV_ITEM:

Private Const LVM_GETITEMTEXT As Long = LVM_FIRST + 45&
Private Const LVIF_TEXT       As Long = &H1&
Private Type LV_ITEM
  mask As Long
  iItem As Long
  iSubItem As Long
  State As Long
  stateMask As Long
  pszText As Long
  cchTextMax As Long
  iImage As Long
  lParam As Long
  iIndent As Long
End Type

Für unseren Zweck wird im Parameter mask die Konstante LVIF_TEXT angegeben, so dass wir uns nur noch um die uns interessierenden Parameter der Struktur kümmern müssen: In iSubItem geben wir an, der Text welcher Spalte des ListView-Controls in Report-Ansicht ermittelt werden soll (ihre nullbasierte Nummer entspricht der des zugehörigen SubItems).

pszText stellen wir ein hinreichend groß dimensioniertes Byte-Array zur Verfügung, in das der zu ermittelnde Text kopiert werden soll. Im Beispiel werden großzügige 512 Bytes verwendet, meist wird ein deutlich kleineres Array hinreichend für Sortieraufgaben sein. Schließlich wird in cchTextMax noch angegeben, wie viel Platz in diesem Byte-Array zur Verfügung steht:

Dim udtLVItem As LV_ITEM
Dim baBuffer(512) As Byte
  With udtLVItem
    .mask = LVIF_TEXT
    .iSubItem = 0  ' Text der ersten Spalte ermitteln
    .pszText = VarPtr(baBuffer(0))
    .cchTextMax = UBound(baBuffer) + 1
  End With

Mit diesen Angaben kann die Nachricht LVM_GETITEMTEXT nun verschickt werden: Der gerade ermittelte Index lngIndex wird im Parameter wParam der SendMessageLong-Funktion angegeben, der vierte Parameter erhält die oben belegte Variable vom Typ LV_ITEM. Rückgabewert der Funktion ist die Länge des ermittelten Textes, der mithilfe der VB-Funktionen StrConv und Left$ schließlich in eine String-Variable kopiert wird:

Dim lngLength As Long
Dim LvwText As String
  lngLength = SendMessageLong(ListView1.hWnd, _
                              LVM_GETITEMTEXT, _
                              lngIndex, VarPtr(udtLVItem))
  If lngLength > 0 Then
    LvwText = Left$(StrConv(baBuffer, vbUnicode), lngLength)
  End If

Damit ist aus dem kryptischen Wert lParam1 der CompareFunc-Funktion unter Zugabe der Information, der Text welcher Spalte in der betroffenen Zeile ermittelt werden soll, der Inhalt der gesuchten Zelle gefunden.

Zugegeben, ganz trivial war dieser Weg nicht. Viel schöner wäre es für uns gewesen, würde die Funktion CompareFunc gleich diese Strings übergeben. Den Gefallen tut sie uns aber nicht (was Sie natürlich nicht davon abhalten sollte, mit dieser Information auf Wunsch einfach eine kapselnde Funktion CompareFuncString zu erstellen).

Mithilfe dieser Kenntnis ist es nun einfach, mit VB-Bordmitteln zu bestimmen, welches der beiden von lParam1 und lParam2 repräsentierten Elemente als das größere in Ihrer individuellen Sortierung gelten soll.

 

Strings, Zahlen und Daten unterscheiden

Vielleicht ist Ihnen aufgefallen, dass wir bisher nicht einen Schritt weiter gekommen sind als die vom ListView bereits angebotene Sortiermöglichkeit? Tatsächlich haben wir lediglich zwei Strings, die wir nun miteinander vergleichen können. Haben wir also kaum etwas gewonnen?

Lassen Sie sich nicht täuschen: Der Rest ist nichts mehr als triviales VB-Beiwerk. Wirklich interessant für uns ist schließlich die Möglichkeit, über den von uns bestimmbaren Rückgabewert der Funktion CompareFunc zu bestimmen, welches der beiden angegebenen Elemente als das größere gelten soll.

Ist etwa bekannt, dass die ermittelten Inhalte Zahlen darstellen sollen, bietet die Typkonvertierungsfunktion CDec einen flexiblen Weg an, die ermittelten Strings in Zahlen zu verwandeln und dann als solche zu vergleichen (natürlich können Sie hier bei bekanntem Datentyp der Spalteninhalte auch typsicherer arbeiten, indem Sie die zugehörigen Typumwandlungsfunktionen verwenden):

Function CompareFunc(ByVal lParam1 As Long, _
                     ByVal lParam2 As Long, _
                     ByVal lParamSort As Long _
                     ) As Long
Dim sEntry1 As String
Dim sEntry2 As String
Dim varNumber1 As Variant
Dim varNumber2 As Variant
' (... Ermittlung der Strings aus lParam1/lParam2 ...)
' (Es seien nun sEntry1 und sEntry2 die aus
' lParam1 und lParam2 ermittelten Strings:)
  varNumber1 = CDec(sEntry1)
  varNumber2 = CDec(sEntry2)
  ' Aufsteigende Sortierung:
  If varNumber1 < varNumber2 Then
    CompareFunc = -1
  ElseIf varNumber1 = varNumber1 Then
    CompareFunc = 0
  Else
    CompareFunc = 1
  End If
End Function

Analog lässt sich die Funktion CDate verwenden, wenn bekannt ist, dass es sich bei den Angaben in der Spalte, die für die Zeilensortierung maßgeblich ist, um Datumsangaben handelt. Diese kleine Fingerübung überlasse ich nun jedoch Ihnen.

Vorsicht: Unterliegen Sie nicht dem Trugschluss, eine Spalte mit Datumswerten mithilfe der Funktion IsDate sicher erkennen zu können: Aufgrund des VB-internen Formats der Speicherung des Datentyps Date als wie ein Double aufgebauter Wert kann auch eine Zahl mitunter als Datumsangabe interpretiert werden! Merke: IsDate berichtet nicht, ob ein Datumswert vorliegt, sondern lediglich, ob das Argument der Funktion als Datumswert interpretiert werden kann!

 

Das große Finale: ein Standardmodul als Lösungspaket

Abschließend und wie eingangs versprochen finden Sie hier den Code für ein Standardmodul, das Ihnen sämtliche Arbeit für die individuelle Auf- oder Abwärtssortierung abnimmt. In diesem Modul wurden zusätzlich einige im Artikel nicht erklärte API-Tricks angewendet (vor allem im Zusammenhang mit dem wahlfreien Wert, der beim Versand der Nachricht LVM_SORTITEMS an die Funktion CompareFunc übergeben werden kann), um die Handhabung für Sie so einfach wie möglich zu gestalten:

Sie rufen aus Ihrem Projekt einfach die öffentliche Funktion SortListView auf (Dokumentation siehe Sourcecode), und schon werden Ihre ListView-Zeilen nicht nur für Strings, sondern auch für Zahlen und Datumswerte korrekt sortiert - eine Erweiterung für andere Spezialfälle ist problemlos möglich!

Tipp: Markieren Sie den gesamten Code und fügen Sie ihn zunächst in Write.exe ein. Kopieren Sie den Code von dort aus in Ihren Visual Basic Editor, um den Verlust von Zeilenumbrüchen zu vermeiden.

' --------------------------------------------------------
' ------------ STANDARDMODUL ListViewSort.bas ------------
' --------------------------------------------------------
' Copyright (c) 2002 by Mathias Schiffer, AixSoft Software
' --------------------------------------------------------
' 
' Freie Auf- und Abwärtssortierung einer ListView-Spalte
' für Zahlen, Daten und Strings. Aufruf zur Sortierung:
'
' SortListView hWndListView, SortKey, [SortType], [SortOrder]
'
' Parameter:
' ----------
' hWndListView: Fensterhandle des ListView-Steuerelements
' SortKey:      Spalte (nullbasiert), die sortiert werden
'               soll (= Spaltennummer - 1).
' SortType:     stString, um Strings zu sortieren (Standardwert)
'               stDate, um Datumsangaben zu sortieren
'               stNumeric, um Zahlen zu sortieren
' SortOrder:    lvwAscending für aufsteigende Sortierung (Std.)
'               lvwDescending für absteigende Sortierung
' --------------------------------------------------------
Option Explicit
' --- Notwendige Konstante
Private Const LVM_FIRST       As Long = &H1000&
Private Const LVM_SORTITEMS   As Long = LVM_FIRST + 48&
Private Const LVM_FINDITEM    As Long = LVM_FIRST + 13&
Private Const LVM_GETITEMTEXT As Long = LVM_FIRST + 45&
Private Const LVFI_PARAM      As Long = &H1&
Private Const LVIF_TEXT       As Long = &H1&
' --- Notwendige Strukturen
' ListSortOrderConstants ist bereits in den Common
' Controls identisch definiert, wird hier jedoch
' erneut definiert, damit das Modul auch unabhängig
' von der Referenzierung der Common Controls in einem
' Projekt keinen Laufzeitfehler auslöst.
Public Enum ListSortOrderConstants
  lvwAscending = 0&   ' Aufsteigende Sortierung
  lvwDescending = 1&  ' Absteigende Sortierung
End Enum
Public Enum SortTypes ' Art der Spalteninhalte
  stString = 1&   ' Stringsortierung
  stDate = 2&     ' Datensortierung
  stNumeric = 3&  ' Zahlensortierung
End Enum
' Die Struktur LVWSORT enthält Informationen über das
' zu sortierende ListView-Steuerelement, die Spalte,
' nach der sortiert werden soll, sowie die gewünschte
' Sortierrichtung.
Private Type LVWSORT
  hWndListView As Long  ' Fensterhandle des ListView-Controls
  SortKey As Long       ' Spalte, die sortiert werden soll
  SortType As SortTypes ' Typ der zu sortierenden Daten
  SortOrder As ListSortOrderConstants ' Sortierrichtung
End Type
Private Type POINTAPI ' benötigt für LV_FINDINFO
  x As Long
  y As Long
End Type
Private Type LV_FINDINFO ' benötigt für LVM_FINDITEM
  flags As Long
  psz As String
  lParam As Long
  pt As POINTAPI
  vkDirection As Long
End Type
Private Type LV_ITEM ' benötigt für LVM_GETITEMTEXT
  mask As Long
  iItem As Long
  iSubItem As Long
  State As Long
  stateMask As Long
  pszText As Long
  cchTextMax As Long
  iImage As Long
  lParam As Long
  iIndent As Long
End Type
' --- Notwendige API-Funktionen
Private Declare Function SendMessageLong _
  Lib "user32" Alias "SendMessageA" ( _
  ByVal hWnd As Long, _
  ByVal Message As Long, _
  ByVal wParam As Long, _
  ByVal lParam As Long _
  ) As Long
Private Declare Sub CopyMemory _
  Lib "kernel32" Alias "RtlMoveMemory" ( _
  ByRef Destination As Any, _
  ByRef Source As Any, _
  ByVal Length As Long)
' --- Öffentliche Funktionen
Public Sub SortListView(ByVal hWndListView As Long, _
                        ByVal SortKey As Long, _
                        Optional ByVal SortType _
                          As SortTypes = stString, _
                        Optional ByVal SortOrder _
                          As ListSortOrderConstants _
                          = lvwAscending)
' -----------------------------------------------------
' Öffentlich aufzurufende Prozedur SortListView, die
' für die individuelle Sortierung einer ListView-Spalte
' sorgt.
' -----------------------------------------------------
' hWndListView: Fensterhandle des ListView-Steuerelements
' SortKey:      Spalte (nullbasiert), die sortiert werden
'               soll (= Spaltennummer - 1).
' SortType:     stString, um Strings zu sortieren (Standardwert)
'               stDate, um Datumsangaben zu sortieren
'               stNumeric, um Zahlen zu sortieren
' SortOrder:    lvwAscending für aufsteigende Sortierung (Std.)
'               lvwDescending für absteigende Sortierung
' -----------------------------------------------------
Dim udtLVWSORT As LVWSORT
  ' Übergebene Informationen in einer LVWSORT-
  ' Struktur zusammenfassen:
  With udtLVWSORT
    .hWndListView = hWndListView
    .SortKey = SortKey
    .SortOrder = SortOrder
    .SortType = SortType
  End With
  ' Eigene Sortierfunktionalität in der Funktion
  ' CompareFunc verwenden: Die Informationen der
  ' LVWSORT-Struktur wird mithilfe eines Zeigers
  ' auf die Variable udtLVWSORT beigegeben:
  SendMessageLong hWndListView, _
                  LVM_SORTITEMS, _
                  VarPtr(udtLVWSORT), _
                  AddressOf CompareFunc
End Sub
' --- Private Funktionen
Private Function CompareFunc(ByVal lParam1 As Long, _
                             ByVal lParam2 As Long, _
                             ByVal lParamSort As Long _
                             ) As Long
' -----------------------------------------------------
' Vergleichsfunktion CompareFunc
' -----------------------------------------------------
' Verglichen werden jeweils zwei Elemente der zu
' sortierenden Spalte des ListView-Steuerelements,
' die über lParam1 und lParam2 angegeben werden.
' Hierbei wird über den Rückgabewert der Funktion
' bestimmt, welches der beiden Elemente als größer
' gelten soll (hier für Aufwärtssortierung):
' * Element 1 < Element 2: Rückgabewert < 0
' * Element 1 = Element 2: Rückgabewert = 0
' * Element 1 > Element 2: Rückgabewert > 0
' -----------------------------------------------------
Dim ListViewSort As LVWSORT
Dim sEntry1 As String
Dim sEntry2 As String
Dim vCompare1 As Variant
Dim vCompare2 As Variant
  ' In lParamSort von SortListView als Long-Pointer
  ' übergebene LVWSORT-Struktur abholen, um auf deren
  ' Werte zugreifen zu können:
  CopyMemory ListViewSort, _
             ByVal lParamSort, _
             Len(ListViewSort)
  ' Die Werte der zu vergleichenden Elemente werden
  ' mithilfe der privaten Funktion LvwGetText aus
  ' den Angaben lParam1 und lParam2 ermittelt:
  sEntry1 = LvwGetText(ListViewSort, lParam1)
  sEntry2 = LvwGetText(ListViewSort, lParam2)
  ' Sind die Elemente gleich, kann die Funktion
  ' sofort mit dem aktuellen Rückgabewert 0
  ' verlassen werden:
  If sEntry1 = sEntry2 Then
    Exit Function
  End If
  ' Für die Sortierung wird unterschieden zwischen
  ' Daten, Zahlen und allgemeinen Strings. Hierfür
  ' steht jeweils eine separate, private Vergleichs-
  ' funktion zur Verfügung.
  Select Case ListViewSort.SortType
    Case stDate    ' Spalteninhalte sind Datumswerte
      CompareFunc = CompareDates(sEntry1, _
                    sEntry2, ListViewSort.SortOrder)
    Case stNumeric ' Spalteninhalte sind Zahlen
      CompareFunc = CompareNumbers(sEntry1, _
                    sEntry2, ListViewSort.SortOrder)
    Case stString  ' Spalteninhalte sind Strings
      CompareFunc = CompareStrings(sEntry1, _
                    sEntry2, ListViewSort.SortOrder)
  End Select
End Function
Private Function LvwGetText(ByRef ListViewSort As LVWSORT, _
                            ByVal lParam As Long _
                            ) As String
' -----------------------------------------------------
' Ermittelt aus dem Fensterhandle des ListView-
' Steuerelements, der in ListViewSort.SortKey
' angegebenen (nullbasierten) Spalte im ListView
' und der an CompareFunc übergebenen Werte lParam1/2
' die davon repräsentierten Zelleninhalte.
' -----------------------------------------------------
Dim udtFindInfo As LV_FINDINFO
Dim udtLVItem As LV_ITEM
Dim lngIndex As Long
Dim baBuffer(512) As Byte
Dim lngLength As Long
  ' Den Index der Zelle aus lParam ermitteln:
  With udtFindInfo
    .flags = LVFI_PARAM
    .lParam = lParam
  End With
  lngIndex = SendMessageLong(ListViewSort.hWndListView, _
                             LVM_FINDITEM, -1, _
                             VarPtr(udtFindInfo))
  ' Auf Basis des gefundenen Index den Text der Zelle
  ' in ein Byte-Array übertragen:
  With udtLVItem
    .mask = LVIF_TEXT
    .iSubItem = ListViewSort.SortKey
    .pszText = VarPtr(baBuffer(0))
    .cchTextMax = UBound(baBuffer) + 1
  End With
  lngLength = SendMessageLong(ListViewSort.hWndListView, _
                              LVM_GETITEMTEXT, lngIndex, _
                              VarPtr(udtLVItem))
  ' Byte-Array in passender Länge als String-
  ' Rückgabewert kopieren:
  If lngLength > 0 Then
    LvwGetText = Left$(StrConv(baBuffer, vbUnicode), lngLength)
  End If
End Function
Private Function CompareDates( _
  ByVal dtEntry1 As Date, _
  ByVal dtEntry2 As Date, _
  ByVal SortOrder As ListSortOrderConstants _
  ) As Long
' -----------------------------------------------------
' Gibt zurück, ob das erste der beiden unterschiedlichen
' Elemente nach Maßgabe des Parameters SortOrder größer
' (1 bei aufsteigender Sortierung) oder kleiner (-1 bei
' aufsteigender Sortierung) als das zweite Element ist.
' Gleiche Elemente wurden bereits in CompareFunc ausge-
' schlossen; für sie wäre sonst 0 zurückzugeben.
' -----------------------------------------------------
  ' Rückgabewert je nach erwünschter Sortierung:
  If SortOrder = lvwAscending Then
    ' Aufsteigende Sortierung zweier unterschiedlicher Daten
    If dtEntry1 < dtEntry2 Then
      CompareDates = -1
    Else
      CompareDates = 1
    End If
  Else ' Absteigende Sortierung
    If dtEntry1 > dtEntry2 Then
      CompareDates = -1
    Else
      CompareDates = 1
    End If
  End If
End Function
Private Function CompareNumbers( _
  ByRef sEntry1 As String, _
  ByRef sEntry2 As String, _
  ByVal SortOrder As ListSortOrderConstants _
  ) As Long
' -----------------------------------------------------
' Gibt zurück, ob das erste der beiden unterschiedlichen
' Elemente nach Maßgabe des Parameters SortOrder größer
' (1 bei aufsteigender Sortierung) oder kleiner (-1 bei
' aufsteigender Sortierung) als das zweite Element ist.
' Gleiche Elemente wurden bereits in CompareFunc ausge-
' schlossen; für sie wäre sonst 0 zurückzugeben.
' -----------------------------------------------------
  ' Rückgabewert je nach erwünschter Sortierung:
  If SortOrder = lvwAscending Then
    ' Aufsteigende Sortierung zweier unterschiedlicher Zahlen
    If CDec(sEntry1) < CDec(sEntry2) Then
      CompareNumbers = -1
    Else
      CompareNumbers = 1
    End If
  Else ' Absteigende Sortierung
    If CDec(sEntry1) > CDec(sEntry2) Then
      CompareNumbers = -1
    Else
      CompareNumbers = 1
    End If
  End If
End Function
Private Function CompareStrings( _
  ByRef sEntry1 As String, _
  ByRef sEntry2 As String, _
  ByVal SortOrder As ListSortOrderConstants _
  ) As Long
' -----------------------------------------------------
' Gibt zurück, ob das erste der beiden unterschiedlichen
' Elemente nach Maßgabe des Parameters SortOrder größer
' (1 bei aufsteigender Sortierung) oder kleiner (-1 bei
' aufsteigender Sortierung) als das zweite Element ist.
' Gleiche Elemente wurden bereits in CompareFunc ausge-
' schlossen; für sie wäre sonst 0 zurückzugeben.
' -----------------------------------------------------
  ' Rückgabewert je nach erwünschter Sortierung:
  If SortOrder = lvwAscending Then
    ' Aufsteigende Sortierung zweier unterschiedlicher Strings
    If sEntry1 < sEntry2 Then
      CompareStrings = -1
    Else
      CompareStrings = 1
    End If
  Else ' Absteigende Sortierung
    If sEntry1 > sEntry2 Then
      CompareStrings = -1
    Else
      CompareStrings = 1
    End If
  End If
End Function
' --------------------------------------------------------