|
Leider bietet das ListView-Steuerelement aus den Microsoft Common Controls so gut wie keine Möglichkeiten zum Scrollen und zur Navigation per Code. Zwar können Sie sicherstellen, dass ein ListItem in den sichtbaren Bereich gerollt wird, indem Sie dessen Selected-Eigenschaft auf True setzen. Doch ist die Änderung des Selektionszustandes sicher nicht immer erwünscht. Die Methode EnsureVisible eines ListItems vermeidet zwar diese Änderung, dennoch haben Sie keine genaue Kontrolle über die Position dieses ListItems im sichtbaren Bereich. Die horizontale Rollposition entzieht sich dabei gänzlich Ihrer Kontrolle - die Sichtbarkeit eine bestimmten (List)SubItems können Sie nicht kontrollieren.
Immerhin existiert die API-Nachricht LVM_SCROLL, die Sie über die API-Funktion SendMessage an das ListView senden können. Damit können Sie den Inhalt eines ListViews zum einen pixelweise horizontal rollen. Vertikales Rollen zum anderen hängt vom View-Modus des ListViews ab. Im Report-Modus kann es nur zeilenweise gerollt werden - wobei die Rollweite zwar in Pixels angegeben werden muss, aber automatisch auf ganze Zeilen auf- bzw. abgerundet wird. Für eine einigermaßen zuverlässige Navigation, vor allem für seiten- oder spaltenweises Blättern im Report-Modus ist da noch einiges an Kalkulationen darum herum notwendig.
Das einfachste ist noch das saubere zeilenweise Rollen. Immerhin können Sie die Höhe beispielsweise des ersten ListItems (Index 1 - wenn kein ListItem vorhanden sein sollte, wäre schließlich auch das Rollen sinnlos), also die Zeilenhöhe, dessen Eigenschaft Height entnehmen. Durch die Anzahl der vertikalen Twips pro Pixel geteilt (Screen.TwipsPerPixelY), ergibt sich so ein sauberer Faktor - und damit die erste Prozedur zum zeilenweisen Rollen auf- oder abwärts (negative bzw. positive Werte im Parameter Rows):
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
Private Const LVM_SCROLL = &H1014
Public Sub LvwScrollRows(ListView As ListView, _
Optional ByVal Rows As Long = 1)
Dim nWnd As Long
With ListView
nWnd = .hwnd
With .ListItems
If .Count Then
SendMessage nWnd, LVM_SCROLL, 0, _
Rows * (.Item(1).Height \ Screen.TwipsPerPixelY)
End If
End With
End With
End Sub
Über die Ober- und Untergrenzen brauchen Sie sich glücklicherweise keine Sorgen zu machen - über die Grenzen hinaus lässt sich das ListView auch per Code nicht rollen.
Zur Ansteuerung einer bestimmten Zeile dient die folgende Prozedur. Da das ListView in der Eigenschaft GetFirstVisible das oberste sichtbare ListItem liefert, erhalten Sie aus dessen Index-Eigenschaft den obersten Index. Somit können Sie nun die notwendige Differenz der zu rollenden Zeilen zur gewünschten Zeile ermitteln.
Public Sub LvwScrollToRow(ListView As ListView, _
Optional ByVal Row As Long = 1)
Dim nWnd As Long
Dim nTop As Long
Dim nRows As Long
With ListView
nWnd = .hwnd
nTop = .GetFirstVisible.Index
With .ListItems
If .Count Then
nRows = Row - nTop
SendMessage nWnd, LVM_SCROLL, 0, _
nRows * (.Item(1).Height \ Screen.TwipsPerPixelY)
End If
End With
End With
End Sub
Seitenweises Rollen erfordert ein klein wenig mehr Arbeit. Die Anzahl der in einer Seitenhöhe darstellbaren Zeilen liefert uns das ListView freundlicherweise über die Nachricht LVM_GETCOUNTPERPAGE (hier auch als eigenständige Funktion zur Verfügung gestellt, siehe aber auch: "Sichtbare Zeilen im ListView") - und damit den benötigten Faktor:
Private Const LVM_GETCOUNTPERPAGE = &H1028
Public Function LvwRowsPerPage(ListView As ListView) As Long
LvwRowsPerPage = SendMessage(ListView.hwnd, _
LVM_GETCOUNTPERPAGE, 0&, 0&)
End Function
Public Sub LvwScrollPages(ListView As ListView, _
Optional ByVal Pages As Long = 1)
Dim nRowsPerPage As Long
Dim nWnd As Long
With ListView
nWnd = .hwnd
nRowsPerPage = SendMessage(nWnd, LVM_GETCOUNTPERPAGE, 0&, 0&)
With .ListItems
If .Count Then
SendMessage nWnd, LVM_SCROLL, 0, _
nRowsPerPage * Pages * _
(.Item(1).Height \ Screen.TwipsPerPixelY)
End If
End With
End With
End Sub
Das Ansteuern einer bestimmten Seite erfolgt im Prinzip wieder wie beim Ansteuern einer bestimmten Zeile:
Public Sub LvwScrollToPage(ListView As ListView, _
Optional ByVal Page As Long = 1)
Dim nRowsPerPage As Long
Dim nWnd As Long
Dim nTop As Long
Dim nRows As Long
With ListView
nWnd = .hwnd
nRowsPerPage = SendMessage(nWnd, _
LVM_GETCOUNTPERPAGE, 0&, 0&)
nTop = .GetFirstVisible.Index
With .ListItems
If .Count Then
nRows = nRowsPerPage * Page - nTop + 1
SendMessage nWnd, LVM_SCROLL, 0, _
nRows * (.Item(1).Height \ Screen.TwipsPerPixelY)
End If
End With
End With
End Sub
Das Ansteuern einer bestimmten Spalte ist nur unwesentlich aufwändiger. Zwar liefert Ihnen die API-Funktion GetScrollPos mit der Kennung SB_HORZ die absolute horizontale Rollposition in Pixels. Doch da die Spalten unterschiedlich breit sein können, können wir hier mit einer relativen Rollweite nicht viel anfangen. Der Trick besteht nun darin, zwei Mal zu rollen. Nämlich einmal um die absolute Position ganz nach links, und dann einmal an die Startposition der gewünschten Spalte (dabei wird die Bildschirmaktualisierung mittels der API-Funktion LockWindowUpdate kurzzeitig unterdrückt - siehe auch: "Flackerfreiheit und mehr Rasanz"). Diese Startposition liefert Ihnen die Left-Eigenschaft des entsprechenden ColumnHeader-Elements des ListViews. Da das ListView intern mit einer sehr hohen Auflösung rechnet (HIMETRIC) und die Spaltenpositionen in die gröbere Einheit Twips umgerechnet liefert, und da die Umrechnung in die zum Rollen benötigte Maßeinheit Pixels noch einmal weitere Rundungsfehler mit sich bringt, ist der sich ergebende Pixelwert zum Rollen an eine Spalte außer der äußerst linken Spalte noch ein wenig zu manipulieren, um einigermaßen sauber den Spaltenanfang zu treffen. Einige Versuche haben ergeben, dass im Falle einer zu krassen Abrundung einfach noch ein Pixel hinzugefügt werden kann. Ganz perfekt wird das vor allem bei unregelmäßigen Spaltenbreiten zwar auch nicht, aber besser kann es auch Outlook nicht, wie ich festgestellt habe.
Private Declare Function GetScrollPos Lib "user32" _
(ByVal hwnd As Long, ByVal nBar As Long) As Long
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
Private Declare Function LockWindowUpdate Lib "user32" _
(ByVal hwndLock As Long) As Long
Private Const SB_HORZ = 0
Private Const SB_VERT = 1
Private Const LVM_SCROLL = &H1014
Public Sub LvwScrollToColumn(ListView As ListView, _
ByVal Column As Long)
Dim nWnd As Long
Dim nLeftScroll As Long
Dim c As Long
Dim nLeft As Long
With ListView
nWnd = .hwnd
nLeftScroll = GetScrollPos(nWnd, SB_HORZ)
With .ColumnHeaders
Select Case Column
Case 1
Case 2 To .Count
For c = 1 To Column - 1
nLeft = nLeft + CLng(.Item(c).Width)
Next 'c
End Select
End With
LockWindowUpdate nWnd
SendMessage nWnd, LVM_SCROLL, -nLeftScroll - 1, 0
If nLeft > 0 Then
If nLeft Mod Screen.TwipsPerPixelX = 0 Then
nLeft = nLeft \ Screen.TwipsPerPixelX
Else
nLeft = (nLeft \ Screen.TwipsPerPixelX) + 1
End If
SendMessage nWnd, LVM_SCROLL, nLeft, 0
End If
LockWindowUpdate 0&
End With
End Sub
Aufgrund der Rundungsfehler etwas vertrackter ist das relative spaltenweise horizontale Rollen. Die gleiche Rundungsnachbearbeitung wird auch hier eingesetzt und auch die absolute Rollposition wird hier ebenfalls benötigt. Diesmal müssen wir jedoch von der absoluten Position ausgehend entweder nach links oder rechts, je nach gewünschter Rollrichtung und zu rollender Spaltenzahl, die neue Zielspalte ermitteln. Eine linksseitig "angebrochene" Spalte wird dabei wie eine vollständig sichtbare Spalte gezählt - ein Rollen um eine Spalte setzt dabei die Startposition auf den Anfang oder das Ende der unvollständig sichtbaren Spalte.
Public Sub LvwScrollColumns(ListView As ListView, _
Optional ByVal Columns As Long = 1)
Dim nLeftScroll As Long
Dim c As Long
Dim cc As Long
Dim nMax As Long
Dim nWnd As Long
Dim nLeft As Long
With ListView
nWnd = .hwnd
nLeftScroll = GetScrollPos(nWnd, SB_HORZ)
Select Case Sgn(Columns)
Case 0
Exit Sub
Case 1
With .ColumnHeaders
For c = 1 To .Count
Select Case CLng(.Item(c).Left) _
\ Screen.TwipsPerPixelX
Case Is <= nLeftScroll
Case Is > nLeftScroll
nLeft = CLng(.Item(c).Left)
If Columns > 1 Then
nMax = c + Columns - 2
If nMax > .Count Then
nMax = .Count
End If
For cc = c To nMax
nLeft = nLeft + CLng(.Item(cc).Width)
Next
End If
If nLeft Mod Screen.TwipsPerPixelX <> 0 Then
nLeft = (nLeft \ Screen.TwipsPerPixelX) + 1
Else
nLeft = nLeft \ Screen.TwipsPerPixelX
End If
LockWindowUpdate nWnd
SendMessage nWnd, LVM_SCROLL, _
-nLeftScroll - 1, 0
SendMessage nWnd, LVM_SCROLL, nLeft, 0
LockWindowUpdate 0&
Exit Sub
End Select
Next
End With
Case -1
With .ColumnHeaders
For c = .Count To 1 Step -1
nLeft = CLng(.Item(c).Left)
If nLeft Mod Screen.TwipsPerPixelX = 0 Then
nLeft = nLeft \ Screen.TwipsPerPixelX
Else
nLeft = (nLeft \ Screen.TwipsPerPixelX) + 1
End If
Select Case nLeft
Case Is >= nLeftScroll
Case Is < nLeftScroll
If Columns < -1 Then
nMax = c - (Abs(Columns) - 1)
If nMax <= 1 Then
nLeft = 0
Else
For cc = c - 1 To nMax Step -1
nLeft = nLeft - (.Item(cc).Width _
\ Screen.TwipsPerPixelX)
Next
End If
End If
LockWindowUpdate nWnd
SendMessage nWnd, LVM_SCROLL, _
-nLeftScroll - 1, 0
If nLeft > 0 Then
SendMessage nWnd, LVM_SCROLL, nLeft, 0
End If
LockWindowUpdate 0&
Exit Sub
End Select
Next
End With
End Select
End With
End Sub
|