ABOUT Visual Basic Programmieren Programmierung Download Downloads Tips & Tricks Tipps & Tricks Know-How Praxis VB VBA Visual Basic for Applications VBS VBScript Scripting Windows ActiveX COM OLE API ComputerPC Microsoft Office Microsoft Office 97 Office 2000 Access Word Winword Excel Outlook Addins ASP Active Server Pages COMAddIns ActiveX-Controls OCX UserControl UserDocument Komponenten DLL EXE
Diese Seite wurde zuletzt aktualisiert am 19.06.2000

Diese Seite wurde zuletzt aktualisiert am 19.06.2000
Aktuell im ABOUT Visual Basic-MagazinGrundlagenwissen und TechnologienKnow How, Tipps und Tricks rund um Visual BasicActiveX-Komponenten, Controls, Klassen und mehr...AddIns für die Visual Basic-IDE und die VBA-IDEVBA-Programmierung in MS-Office und anderen AnwendungenScripting-Praxis für den Windows Scripting Host und das Scripting-ControlTools, Komponenten und Dienstleistungen des MarktesRessourcen für Programmierer (Bücher, Job-Börse)Dies&Das...

Themen und Stichwörter im ABOUT Visual Basic-Magazin
Code, Beispiele, Komponenten, Tools im Überblick, Shareware, Freeware
Ihre Service-Seite, Termine, Job-Börse
Melden Sie sich an, um in den vollen Genuss des ABOUT Visual Basic-Magazins zu kommen!
Informationen zum ABOUT Visual Basic-Magazin, Kontakt und Impressum

Zurück...

Coole ListBox-Elemente

Zurück...


Anzeige

(-hg) mailto:hg_listhighlightitem@aboutvb.de

Coole Hover-Effekte auch bei einer ListBox oder einer FileListBox? Das ist schließlich ein ganz netter Effekt, wenn die Markierung in einer ListBox genau so wie in der Aufklappliste einer ComboBox dem Mauszeiger folgt.

Auf den ersten Blick scheint es dafür eine einfach Lösung zu geben. Sie bräuchten nur das Element der ListBox ermitteln, das sich gerade unter dem Mauszeiger befindet, und den ListIndex darauf setzen.

Private Type PointInt
  X As Integer
  Y As Integer
End Type

Private Type TypeLong
  l As Long
End Type

Private Declare Function SendMessage Lib "user32" _
 Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, _
 ByVal wParam As Long, lParam As Any) As Long

Private Const LB_ITEMFROMPOINT = &H1A9
Private Const LB_SETCURSEL = &H186

Private Sub List1_MouseMove(Button As Integer, Shift As Integer, _
 X As Single, Y As Single)

  Dim nPointInt As PointInt
  Dim nLong As TypeLong
  Dim nIndex As Long
  
  With List1
    With nPointInt
      .X = X \ Screen.TwipsPerPixelX
      .Y = Y \ Screen.TwipsPerPixelY
    End With
    LSet nLong = nPointInt
    nIndex = SendMessage(.hwnd, LB_ITEMFROMPOINT, 0, ByVal nLong.l)
    Select Case nIndex
      Case 0 To &H10000
        .ListIndex = nIndex
    End Select
  End With
End Sub

Das funktioniert offensichtlich prächtig, aber... so dürfen Sie den Mauszeiger nur auf einer horizontalen Linie nach rechts oder links aus der ListBox herausbewegen, wenn die Auswahl nach einem Click in die ListBox erhalten bleiben soll... (einmal ganz abgesehen von dem auch nicht so schönen Effekt, dass bei jeder Änderung des ListIndex das Click-Ereignis der ListBox ausgelöst wird).

Wir müssen also die Aufgabenstellung ein wenig modifizieren, damit die mit dem Mauszeiger mitwandernde Auswahlmarkierung nicht bloß zum völlig nutzlosen optischen Gag verkommt. Die Aufgabenstellung lautet also treffender:

Die Markierung soll sich mit dem Mauszeiger bewegen. Jedoch wenn der Mauszeiger die ListBox verlässt, soll die Markierung wieder zum zuletzt per Klick oder Code gewählten Element zurückspringen. Wurde ein Element angeklickt und damit der ListIndex geändert, während sich der Mauszeiger nach wie vor innerhalb der ListBox befindet, soll diese neue Auswahl als diejenige gelten, auf die die Markierung gegebenenfalls zurückspringt, wenn der Mauszeiger schließlich die ListBox verlässt.

Damit der Wandereffekt auch richtig Spaß macht, erweitern wir die Aufgabenstellung noch ein wenig. Befindet sich der Mauszeiger unmittelbar unter der Oberkante oder über der Unterkante der ListBox, soll der Inhalt der ListBox schrittweise automatisch gerollt werden, wenn in der jeweiligen Richtung noch Elemente vorhanden sind.

Ich will Sie aber bezüglich der Lösung auch nicht noch länger im Dunklen tappen lassen und verrate Ihnen daher, dass sie auf einem sogenannten Hover-Effekt ("MouseOver/MouseLeave") beruht. Effekte dieser Art kennen Sie ja längst von heutigen Toolbars, etwa im Microsoft Internet Explorer, im Micosoft Office und in vielen anderen Anwendungen. Wie Sie einen solchen Effekt sauber schreiben können, habe ich bereits ausführlich in Immer schön cool bleiben"Immer schön cool bleiben" gezeigt. Sie brauchen zum einen die dort vorgestellte Lösung nur noch um die Darstellung des Hover-Effekts erweitern (also für das Mitwandern der Auswahl sorgen) - in etwa anhand des oben bereits gezeigten Lösungsansatzes, und dafür sorgen, dass der beim Eintreten des Mauszeigers in die ListBox gültige ListIndex beim Verlassen wieder hergestellt wird. Zum anderen wäre noch der Code notwendig, der die Mauszeigerposition auf die Nähe zur Ober- oder Unterkante hin prüft und die ListBox gegebenenfalls rollt.

Zunächst eine kurze Erläuterung, auch als Nachtrag zum obenstehenden Code, wie die beiden Maus-Koordinaten X und Y in den Long-Wert gepackt werden, den die MSDN Library - API SendMessageSendMessage-Funktion im letzten Parameter erwartet. Das Prinzip ist einfach:

Ein Long-Wert wird von vier Bytes im Speicher repräsentiert. Die beiden Koordinaten werden (von Windows) als Integer-Werte in Pixels betrachtet und ein Integer-Wert wird von Bytes im Speicher repräsentiert. Zur Kombination des geforderten Long-Werts aus vier Bytes entsteht werden die beiden Byte-Paare aneinandergereiht, zuerst die beiden Bytes der X-Koordinate, dann die beiden Bytes der Y-Koordinate. Damit Sie sich nicht in Speicher-Arithmetik verlieren müssen, können Sie die Kombination über zwei benutzerdefinierte Variablen vornehmen. Die eine (PointInt) enthält zwei Integer-Elemente, die andere (TypeLong) lediglich ein Long-Element. Füllen Sie nun die Elemente einer Variablen vom Typ von PointInt mit den in Pixels umgerechneten Koordinaten und weisen Sie diese Variable nun einer Variablen vom Type TypeLong zu, geschieht im Hintergrund nichts weiter, als dass Byte für Byte von der benutzerdefinierten Variablen des einen Typs in die andere kopiert werden - die Kombination ist damit vollzogen.

Private Type PointInt
  X As Integer
  Y As Integer
End Type

Private Type TypeLong
  l As Long
End Type

'...
  Dim nPointInt As PointInt
  Dim nLong As TypeLong
'...
  With nPointInt
    .X = X \ Screen.TwipsPerPixelX
    .Y = Y \ Screen.TwipsPerPixelY
  End With
  LSet nLong = nPointInt
'...

Der SendMessage-Funktion übergeben Sie nun das Long-Element der Variablen des Typs TypeLong:

nIndex = SendMessage(.hwnd, LB_ITEMFROMPOINT, 0, ByVal nLong.l)

Hier nun zunächst die im folgenden benötigten weiteren Deklarationen:

Private Type POINTAPI
  X As Long
  Y As Long
End Type

Private Type RECT
  Left As Long
  Top As Long
  Right As Long
  Bottom As Long
End Type

Private Declare Function GetCapture Lib "user32" () As Long
Private Declare Function GetClientRect Lib "user32" _
 (ByVal hwnd As Long, lpRect As RECT) As Long
Private Declare Function GetCursorPos Lib "user32" _
 (lpPoint As POINTAPI) As Long
Private Declare Function ReleaseCapture Lib "user32" () As Long
Private Declare Function SendMessage Lib "user32" _
 Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, _
 ByVal wParam As Long, lParam As Any) As Long
Private Declare Function SetCapture Lib "user32" _
 (ByVal hwnd As Long) As Long
Private Declare Function WindowFromPoint Lib "user32" _
 (ByVal xPoint As Long, ByVal yPoint As Long) As Long
Private Declare Function SetCursorPos Lib "user32" _
 (ByVal X As Long, ByVal Y As Long) As Long

Private Const LB_ITEMFROMPOINT = &H1A9
Private Const LB_SETCURSEL = &H186

Private mLastY As Long
Private mListBoxWnd As Long
Private mStartIndex As Long

Diese Deklarationen liegen in einem Standard-Modul, wie auch die beiden Prozeduren, die die gestellte Aufgabe erfüllen sollen. In der privaten Variable mLastY wird jeweils die letzte Y-Position des Mauszeigers zwischengespeichert. In der Variablen mListBoxWnd merken wir uns weiterhin das Fenster-Handle der betroffenen ListBox. Wir könnten zwar statt dessen auch eine Referenz auf die ListBox speichern. Aber da eventuell vergessene Referenzen nur Probleme bereiten und wir tatsächlich mit dem Fenster-Handle auskommen werden, verzichten wir darauf. Schließlich wird noch in der Variablen mStartIndex der ListIndex festgehalten, auf den die Auswahl beim Verlassen der ListBox zurückgesetzt werden soll. Wann und wie diese Variablen gesetzt werden, erfahren Sie gleich.

Betrachten wir die erste Prozedur einmal näher. Sie rufen sie im MouseMove-Ereignis einer ListBox auf und übergeben ihr die Referenz auf diese ListBox, den Button-Parameter und die X- und Y-Korrdinaten des MouseMove-Ereignisses. Optional können Sie noch die Höhe (in Pixels) der sensitiven Bereiche an der Ober- und Unterkante der ListBox festlegen, in denen gegebenenfalls das Rollen der ListBox ausgelöst werden soll.

Public Sub ListHighlightItem(ListBox As ListBox, _
 ByVal Button As Integer, ByVal X As Single, ByVal Y As Single, _
 Optional ByVal ScrollCheckHeight As Long = 2)

  Dim nPointAPI As POINTAPI
  Dim nHWnd As Long
  Dim nPointInt As PointInt
  Dim nLong As TypeLong
  Dim nIndex As Long
  Dim nRect As RECT
  Dim nScroll As Boolean

Da wir das Mitwandern des Mauszeiger nur bei losgelassener Maustaste bearbeiten wollen (bei gedrückter Maustaste erledigt das Windows nämlich bereits von alleine), verlassen wir die Prozedur gleich wieder, wenn in Button ein Wert übergeben wird. Die beiden Variablen mListBoxWnd und mStartIndex werden sicherheitshalber auf bedeutungslose Standardwerte zurückgesetzt.

  If Button Then
    mListBoxWnd = 0
    mStartIndex = -1
    mLastY = 0
    Exit Sub
  End If

  With ListBox
    If .hwnd <> mListBoxWnd Then
      If mListBoxWnd Then

Wird in mListBoxWnd ein anderes Fenster-Handle angetroffen als das der gerade übergebenen ListBox, setzen wir den ListIndex der vorgefundenen ListBox auf den in mStartIndex noch abgelegten Wert. Wir senden dazu mittels SendMessage die Nachricht MSDN Library - API LB_SETCURSELLB_SETCURSEL an diese ListBox. Zum einen haben wir ja keine Referenz speichern wollen, sondern nur deren Fenster-Handle zur Verfügung, zum anderen hat der Aufruf den Vorteil, dass dabei nicht unerwünschterweise das Click-Ereignis dieser ListBox aufgerufen wird.

        SendMessage mListBoxWnd, LB_SETCURSEL, mStartIndex, 0
      End If

Dann legen wir das Fenster-Handle und den ListIndex der aktuell übergebenen ListBox ab.

      mListBoxWnd = .hwnd
      mStartIndex = .ListIndex
    End If

Nun folgt der Kern des zur Überwachung von Mauseintritt und -austritt des Mauszeigers benötigte Code. Auf diesen gehe ich hier nicht weiter ein. Er ist in dem oben bereits erwähnten Artikel Immer schön cool bleiben"Immer schön cool bleiben" zur Genüge beschrieben.

    GetCursorPos nPointAPI
    nHWnd = WindowFromPoint(nPointAPI.X, nPointAPI.Y)
    If nHWnd = mListBoxWnd Then
      If GetCapture() <> nHWnd Then
        SetCapture nHWnd
      End If

An dieser Stelle wissen wir, dass sich der Mauszeiger innerhalb der ListBox befindet. Daher ermitteln wir nun den Index des sich unter dem Mauszeiger befindlichen Elements. Hierzu verwenden wir tatsächlich den bereits beschriebenen Ansatz.

      With nPointInt
        .X = X \ Screen.TwipsPerPixelX
        .Y = Y \ Screen.TwipsPerPixelY
      End With
      LSet nLong = nPointInt
      nIndex = SendMessage(mListBoxWnd, LB_ITEMFROMPOINT, 0, _
       ByVal nLong.l)

Da wir den ListBox-Inhalt gegebenenfalls rollen lassen wollen, prüfen wir nun, ob sich der Mauszeiger innerhalb der sensitiven Bereiche an der Ober- bzw. Unterkante der ListBox befindet. Die API-Funktion MSDN Library - API GetClientRectGetClientRect liefert uns dazu in einer benutzerdefinierten Variablen des Typs RECT die exakte Größe der reinen Innenfläche der ListBox (innerhalb des 3-D-Rahmens und ohne eventuell vorhandenem Rollbalken).

      GetClientRect mListBoxWnd, nRect
      Select Case nPointInt.Y

Wir prüfen zunächst den Bereich unterhalb der Oberkante. Befindet sich der Mauszeiger dort, schließen wir aus, dass der Mauszeiger wieder von der Oberkante in Abwärtsrichtung weg bewegt worden ist. Den Vergleichswert halten wir in der modulglobalen Variable mLastY weiter unten fest.

        Case 0 To ScrollCheckHeight
          If nPointAPI.Y <= mLastY Then

Ist der eben ermittelte Index größer als 0 (er ist nicht das oberste Element), vermindern wir ihn um 1 und merken uns, dass somit gerollt werden wird.

            If nIndex > 0 Then
              nIndex = nIndex - 1
              nScroll = True
            End If
          End If

Nach dem gleichen Prinzip prüfen wir den Bereich oberhalb der Unterkante. Hier kommt es nun darauf an, dass der Mauszeiger nicht aufwärts von der Unterkante weg bewegt worden ist, und dass der ermittelte Index kleiner als der höchstmögliche Index (ListCount - 1) ist.

        Case nRect.Bottom - ScrollCheckHeight To nRect.Bottom
          If nPointAPI.Y >= mLastY Then
            If nIndex < .ListCount - 1 Then
              nIndex = nIndex + 1
              nScroll = True
            End If
          End If
      End Select

Nun merken wir uns die Y-Position in mLastY und setzen die Auswahl auf den ermittelten Index des Elements unter dem Mauszeiger. Auch hier verwenden wir wieder die Nachricht LB_SETCURSEL, um das Auslösen des Click-Ereignisses zu vermeiden.

      mLastY = nPointAPI.Y
      SendMessage mListBoxWnd, LB_SETCURSEL, nIndex, 0

Nun folgt ein kleiner Trick, der das automatische Rollen besorgt, solange der Mauszeiger in den sensitiven Bereichen steht oder sich den Kanten noch weiter nähert. Der Trick besteht darin, künstlich eine Mausbewegung hervorzurufen, damit das MouseMove-Ereignis der ListBox erneut aufgerufen wird. Nicht ganz erklärbar ist der Grund für die erneute Ermittlung der Mauszeiger-Position (bezogen auf den Bildschirm). Nehmen wir es einfach als gegeben hin, dass mit dem eben erfolgten Setzen des ListIndex auf ein zuvor noch nicht sichtbares Element die ListBox intern bezüglich der ihr bis dahin bekannten Mauszeiger-Position etwas in Verwirrung zu geraten scheint.

      If nScroll Then
        GetCursorPos nPointAPI

Die künstliche Bewegung des Mauszeigers erreichen wir dadurch, dass wir ihn um ein Pixel in der Waagerechten verschieben. Damit er nicht versehentlich außerhalb der ListBox landet, prüfen wir seine nähe zur rechten und linken Kante und verschieben ihn gegebenenfalls zum ListBox-Inneren.

        With nPointAPI
          If .X < nRect.Right - 2 Then
            SetCursorPos .X + 1, .Y
          Else
            SetCursorPos .X - 1, .Y
          End If

Da ja aber keine tatsächliche Bewegung daraus resultieren soll, setzen wir die Position des Mauszeigers sogleich wieder zurück.

          SetCursorPos .X, .Y
        End With
      End If
    Else

Hier folgt nun wieder Code aus dem bereits erwähnten Artikel. Die API-Funktion MSDN Library - API ReleaseCaptureReleaseCapture muss ausgeführt werden, da der Mauszeiger die ListBox verlassen hat.

      ReleaseCapture

Und an dieser Stelle verwirklichen wir nun unser eigentliches Vorhaben: Wir setzen den ListIndex auf den Eingangs gemerkten bzw. zwischenzeitlich aktualisierten Wert der Merkvariablen mStartIndex zurück.

      SendMessage mListBoxWnd, LB_SETCURSEL, mStartIndex, 0

Konsequenterweise setzen wir hier schließlich noch die Merkvariablen wieder auf bedeutungslose Standard-Werte.

      mListBoxWnd = 0
      mStartIndex = -1
      mLastY = 0
    End If
  End With
End Sub

Den Hauptteil der Arbeit hätten wir nun erledigt. Nun bleibt nur noch die zweite angekündigte Prozedur übrig. Sie sorgt dafür, dass in der Merkvariablen mStartIndex der aktuelle ListIndex abgelegt wird, falls sich das eigentlich ausgewählte Element in der ListBox durch einen Klick oder durch Code zwischenzeitlich geändert hat, während sich der Mauszeiger innerhalb der ListBox befindet. Sinnvollerweise erfolgt die Aktualisierung nur dann, wenn das Fenster-Handle der hier übergebenen ListBox mit dem in mListBoxWnd abgelegten übereinstimmt.

Public Sub ListBoxSetNewIndex(ListBox As ListBox)
  With ListBox
    If .hwnd = mListBoxWnd Then
      mStartIndex = .ListIndex
    End If
  End With
End Sub

Das herunterladbare Modul modListHightlightItem.bas enthält zusätzlich noch die Varianten der beiden Prozeduren zur Verwendung mit einer FileListBox.


Modul modListHighlightItem (modListHighlightItem.bas - ca. 7,6 KB)


Artikel
Zum Download-Bereich dieses Artikel
Mail an den Autor dieses Artikels

KnowHow
Zur KnowHow-Übersicht

KnowHow-Themen
Themen - Allgemeines
Themen - Entwicklungsumgebung (VB-IDE)
Themen - Forms
Themen - Steuerelemente (Controls)
Themen - Grafik
Themen - Dateien
Themen - UserControls
Themen - Einsteiger-Tipps
Themen - Wussten Sie...?

Übersicht nach Titeln in alphabetischer Reihenfolge
Übersicht nach Erscheinungsdatum

Schnellsuche



Zum Seitenanfang

Copyright © 1999 - 2017 Harald M. Genauck, ip-pro gmbh  /  Impressum

Zum Seitenanfang

Zurück...

Zurück...

Download Internet Explorer