|
Wenn Sie in den Mausereignissen (MouseDown, MouseMove und MouseUp) oder in den (OLE)Drag-Ereignissen eins TreeView-Steuerelements wissen möchten ob sich ein Knoten unter dem Mauszeiger befindet, und welcher dies ist, können Sie dies über die Methode HitTest des TreeViews ermitteln. Leider ist der Informationsgehalt dieses Tests recht beschränkt und wenig differenziert. Sie erhalten nur als "Ja" eine Referenz auf einen Knoten, wenn sich der Mauszeiger über dessen Symbol, seinem Text oder, falls vorhanden, über seiner CheckBox befindet. Oder Sie erhalten als "Nein" ein schlichtes Nothing.
Das TreeView-Steuerelement selbst hat hingegen eigentlich viel mehr mitzuteilen. Es kann durchaus zwischen Symbol, Text und CheckBox differenzieren. Es kann sogar sagen, ob sich der Mauszeiger rechts oder links von einem Knoten befindet. Und bei letzterem steht sogar die Information zur Verfügung, ob der Mauszeiger über der Einrückung oder über der Schaltfläche ("+" bzw. "-") des Knotens steht.
Erst wenn keine dieser Informationen zutrifft, wird ein Fehlschlag gemeldet. Doch selbst dieser wird noch weiter differenziert. Zum einen wird festgestellt, ob sich der Mauszeiger im Client-Bereich unterhalb des untersten Knotens befindet. Zum anderen wird aber auch mitgeteilt, in welchem "Quadranten" sich der Mauszeiger befindet, sollte die Test-Koordinaten außerhalb des Clientbereichs liegen.
Alle diese Informationen stellt ein TreeView auf die Nachricht TVM_HITTEST zur Verfügung, die ihm mittels der API-Funktion SendMessage zugestellt wird. Im Parameter lParam wird dazu die benutzerdefinierte Variable TVHITTESTINFO übergeben. In deren Unterelement des benutzerdefinierten Typs POINTAPI übergeben Sie die Koordinaten (in Pixels umgerechnet). Nach dem Aufruf von SendMessage enthält das Element Flags die Positionsinformationen, und das Element hItem liefert gegebenenfalls das Handle zu einem getroffenen Knoten.
Leider können wir in Visual Basic nichts mit diesem Knoten-Handle anfangen. Das TreeView-Steuerelement stellt macht uns mit seinen Knoten auf eine andere Weise bekannt, nämlich über einen Schlüssel. Wir könnten zwar über das Handle den Text des betreffenden Knotens ermitteln, was uns aber herzlich wenig nützt, wenn ein Knoten-Text nicht im gesamten Baum oder zumindest im gleichen Ast eindeutig ist. An den Schlüssel eines Knotens kommen wir auf API-Wegen über das Knoten-Handle jedoch auf keinen Fall heran.
Doch wenigstens in dem Fall, dass sich der Mauszeiger über dem Symbol, über dem Text oder über der CheckBox eines Knotens befindet, können wir zusätzlich noch wie gewohnt die TreeView-Methode HitTest aufrufen und somit eine Referenz auf den Knoten erhalten. Für die übrigen drei Fälle, dass sich der Mauszeiger auf der Höhe eines Knotens, aber nicht genau darüber befinden sollte, können wir eine kleine Umweglösung gehen.
Da wir ja bereits wissen, dass sich ein Knoten irgendwo dort befinden muss, und wir ebenfalls wissen, ob sich die Koordinaten rechts vom Knoten oder links von ihm (über der Einrückung oder der Schaltfläche) befinden, brauchen wir mit der HitTest-Methode lediglich alle X-Koordinaten zum jeweils gegenüberliegenden Rand hin bei gleich bleibender Y-Koordinate zu testen, bis ein Knoten angetroffen wird (Dieser kleine Trick versagt nur noch dann, wenn der Knoten horizontal aus dem Client-Bereich hinausgerollt sein sollte). Für den Fall, dass wir uns links des Knotens befinden, besorgen wir uns noch über die API-Funktion GetClientRect die Abmessungen der Innenfläche des TreeViews, um einen eventuellen vertikalen Rollbalken als auch die Rahmenstärke auszuschließen (in TreeView.Width wären diese nämlich eingeschlossen) und ermitteln gegen den rechten Rand des Rechtecks hin. Befinden wir uns rechts vom Knoten, ermitteln einfach gegen 0 hin.
Diese ganzen Ermittlungen funktionieren im Prinzip einwandfrei bei sowohl gedrückten als auch nicht gedrückten Mausknöpfen, wie auch beim Aufruf mit per Code oder anderweitig beliebig erzeugten Koordinaten. Doch wenn ein Mausknopf über dem Symbol oder über dem Text eines Knotens niedergedrückt worden ist, bringt die interne Capture-Verwaltung des TreeViews die Ermittlungen ein wenig aus dem Tritt - es wird nicht mehr angezeigt, wenn sich der Mauszeiger aus dem TreeView hinausbewegt. Abhilfe können Sie schaffen, indem Sie die Capture-Verwaltung des TreeViews überlisten und selbst die Mauszeigerverfolgung mit einem Aufruf der API-Funktion SetCapture dem TreeView wieder zuweisen, wenn ein Mausknopf niedergedrückt ist - und natürlich beim Verlassen des TreeViews wieder entziehen.
Die ganzen Mühen der verschiedenen Aufrufe und Prüfungen erledigt nun die folgende Funktion TvwHitTestEx für Sie. Sie übergeben ihr das betreffende TreeView-Steuerelement, die X- und die Y-Koordinate. Im optionalen Parameter Part können Sie eine Variable übergeben, die Ihnen die genauen Positionsinformationen zurückgibt. Der Rückgabewert der Funktion selbst enthält wie gewohnt einen Verweis auf einen gefundenen Knoten bzw. bei Misserfolg den Wert Nothing.
Beim Aufruf aus einem Mausereignis heraus sollten Sie im nächsten optionalen Parameter Button den Zustand der Mausknöpfe übergeben und den folgenden Parameter Capture auf True setzen, um die Behandlung des oben geschilderten Capture-Problems einzuschalten.
Da das Absuchen der Koordinatenzeile, falls die Koordinaten auf der Höhe eines Knotens, aber nicht direkt darüber, liegen sollten, etwas Zeit kostet, können Sie diese Suche abschalten. Setzen Sie den letzten optionalen Parameter TestRow auf False, wenn Sie darauf verzichten können oder wollen.
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 Type TVHITTESTINFO
pt As POINTAPI
Flags As Long
hItem As Long
End Type
Private Const TVHT_NOWHERE = &H1
Private Const TVHT_ONITEMICON = &H2
Private Const TVHT_ONITEMLABEL = &H4
Private Const TVHT_ONITEMINDENT = &H8
Private Const TVHT_ONITEMBUTTON = &H10
Private Const TVHT_ONITEMRIGHT = &H20
Private Const TVHT_ONITEMSTATEICON = &H40
Private Const TVHT_ABOVE = &H100
Private Const TVHT_BELOW = &H200
Private Const TVHT_TORIGHT = &H400
Private Const TVHT_TOLEFT = &H800
Private Declare Function GetClientRect Lib "user32" _
(ByVal hwnd As Long, lpRect As RECT) 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
Public Enum thtPartConstants
thtClientNowhere = TVHT_NOWHERE
thtItemIcon = TVHT_ONITEMICON
thtItemLabel = TVHT_ONITEMLABEL
thtItemIndent = TVHT_ONITEMINDENT
thtItemButton = TVHT_ONITEMBUTTON
thtItemRight = TVHT_ONITEMRIGHT
thtItemCheck = TVHT_ONITEMSTATEICON
thtClientAbove = TVHT_ABOVE
thtClientBelow = TVHT_BELOW
thtClientRight = TVHT_TORIGHT
thtClientLeft = TVHT_TOLEFT
thtClientLeftAbove = TVHT_TOLEFT Or TVHT_ABOVE
thtClientLeftBelow = TVHT_TOLEFT Or TVHT_BELOW
thtClientRightAbove = TVHT_TORIGHT Or TVHT_ABOVE
thtClientRightBelow = TVHT_TORIGHT Or TVHT_BELOW
End Enum
Public Function TvwHitTestEx(TreeView As TreeView, _
ByVal x As Single, ByVal y As Single, _
Optional Part As thtPartConstants, _
Optional Button As Integer, _
Optional ByVal Capture As Boolean, _
Optional ByVal TestRow As Single = 1) As Node
Dim nHitTestInfo As TVHITTESTINFO
Dim nHitNode As Node
Dim nHit As Boolean
Dim nX As Single
Dim nRect As RECT
Const TVM_HITTEST = &H1111
With TreeView
If Capture And CBool(Button) Then
SetCapture .hwnd
End If
Set nHitNode = .HitTest(x, y)
End With
With nHitTestInfo
With .pt
.x = x \ Screen.TwipsPerPixelX
.y = y \ Screen.TwipsPerPixelX
End With
nHit = _
CBool(SendMessage(TreeView.hwnd, TVM_HITTEST, 0, nHitTestInfo))
Part = .Flags
End With
If nHit Then
If nHitNode Is Nothing Then
If TestRow Then
Select Case Part
Case thtItemButton, thtItemIndent
With TreeView
GetClientRect .hwnd, nRect
For nX = x To nRect.Right * Screen.TwipsPerPixelX _
Step Abs(TestRow) * Screen.TwipsPerPixelX
Set nHitNode = .HitTest(nX, y)
If Not (nHitNode Is Nothing) Then
Set TvwHitTestEx = nHitNode
Exit For
End If
Next 'nX
End With
Case thtItemRight
With TreeView
For nX = x To 0 _
Step Abs(TestRow) * -Screen.TwipsPerPixelX
Set nHitNode = .HitTest(nX, y)
If Not (nHitNode Is Nothing) Then
Set TvwHitTestEx = nHitNode
Exit For
End If
Next 'nX
End With
End Select
End If
Else
Set TvwHitTestEx = nHitNode
If (Part And TVHT_ONITEMSTATEICON) = _
TVHT_ONITEMSTATEICON Then
Part = thtItemCheck
ElseIf (Part And TVHT_ONITEMICON) = TVHT_ONITEMICON Then
Part = thtItemIcon
ElseIf (Part And TVHT_ONITEMLABEL) = TVHT_ONITEMLABEL Then
Part = thtItemLabel
End If
End If
End If
If Capture Then
If Part >= TVHT_ABOVE Then
If Button = 0 Then
ReleaseCapture
End If
End If
End If
End Function
|