Die ZOrder-Methode der Steuerelemente hat einen entscheidenden Nachteil: Sie können damit ein Steuerelement nur an die Spitze (Control.ZOrder [0]) oder ans Ende (Control.ZOrder 1) der ZOrder-Reihenfolge eines Containers verfrachten. Ein Steuerelement an eine beliebige Position innerhalb der Reihung zu verschieben, ist mit Visual Basic-Bordmitteln eigentlich nicht möglich. Sie können damit höchstens eine Holzhammer-Methode anwenden, indem Sie die Steuerelemente eines Containers der Reihe nach an die Spitze setzen. Dasjenige Steuerelement, das letztlich als unterstes erscheinen soll, wird zuerst an die Spitze gesetzt, danach das vorletzte Steuerelement, dann das vorvorletzte - und so weiter, bis schließlich das Steuerelement, das am Ende an der Spitze stehen soll, tatsächlich an die Spitze gesetzt wird. Der folgenden Prozedur übergeben Sie eine Collection, in die Sie zuvor die neu zu reihenden Steuerelemente eingefügt haben:
Public Sub ZOrderControlsFromTop(Controls As Collection)
Dim i As Integer
For i = Controls.Count To 1 Step -1
Controls(i).ZOrder 0
Next 'i
End Sub
Sie brauchen natürlich nicht alle Steuerelemente eines Containers in die zu übergebende Collection einfügen. Dann werden eben nur die darin enthaltenen Steuerelemente entsprechend sortiert ans obere Ende der Reihung gesetzt.
Umgekehrt können Sie auch eine Gruppe von Steuerelemente ans untere Ende der Reihung verschieben:
Public Sub ZOrderControlsFromBottom(Controls As Collection)
Dim nControl As Control
For Each nControl In Controls
nControl.ZOrder 1
Next 'i
End Sub
Um jedoch ein einzelnes Steuerelement oder eine Auswahl von Steuerelementen gezielt an beliebiger Stelle innerhalb der Reihung eines Containers anzuordnen, müssen Sie nach wie vor sämtliche Steuerelemente des Containers in der Collection in der gewünschten Reihenfolge übergeben.
Einen Ausweg aus dieser Umständlichkeit bietet die API-Funktion SetWindowPos. Dies kann dazu verwendet werden, ein bestimmtes Steuerelement (Fenster) hinter einem anderen zu positionieren, das im zweiten Parameter dieser Funktion angegeben wird (Dies ist allerdings nur bei Steuerelementen möglich, die ihr Fenster-Handle über eine hWnd-Eigenschaft zur Verfügung stellen). Die übrigen Parameter, ausgenommen den letzten, können sie ignorieren (den Wert 0 übergeben). Lediglich im letzten Parameter übergeben Sie eine Kombination von Flags, die der für eine ganze Reihe von Zwecken einsetzbaren Funktion mitteilen, dass sie sich hier nur um die ZOrder-Reihung der Fenster kümmern soll.
Private Declare Function SetWindowPos Lib "user32" _
(ByVal hWnd As Long, ByVal Order As Long, ByVal X As Long, _
ByVal Y As Long, ByVal cX As Long, ByVal cY As Long, _
ByVal Flags As Long) As Long
Private Const SWP_NOMOVE = &H2
Private Const SWP_NOSIZE = &H1
Private Const SWP_NOACTIVATE = &H10
Private Const kFlags = SWP_NOMOVE Or SWP_NOSIZE Or SWP_NOACTIVATE
Public Sub ZOrderControls(Controls As Collection)
Dim i As Integer
Dim nWnd As Long
Dim nLastWnd As Long
On Error Resume Next
nLastWnd = Controls(1).hWnd
For i = 2 To Controls.Count
nWnd = Controls(i).hWnd
If Err.Number Then
Err.Clear
Else
SetWindowPos nWnd, nLastWnd, 0, 0, 0, 0, kFlags
nLastWnd = nWnd
End If
Next 'i
End Sub
Dieser Prozedur ZOrderControls können Sie beliebige Steuerelemente eines Containers übergeben. Die neue Reihung beginnt tatsächlich erst mit dem ersten in der Collection übergebenen Steuerelement.
Möchten Sie nur ein einzelnes Steuerelement um eine Position nach oben oder unten in der Reihung verschieben, können Sie es sich etwas einfacher machen. Anhand des Fenster-Handles dieses Steuerelements können Sie mittels der API-Funktion GetWindow und den beiden Flags GW_HWNDNEXT bzw. GW_HWNDPREV das aktuell nachfolgende bzw. vorhergehende Fenster ermitteln. Mit der bereits beschriebenen Funktion SetWindowPos vertauschen Sie nun einfach nur die Reihenfolge der beiden Fenster:
Private Declare Function GetWindow Lib "user32" _
(ByVal hWnd As Long, ByVal wCmd As Long) As Long
Private Const GW_HWNDNEXT = 2
Private Const GW_HWNDPREV = 3
Public Function ZOrderDown(Control As Control) As Boolean
Dim nWnd As Long
Dim nControlWnd As Long
nControlWnd = Control.hWnd
nWnd = GetWindow(nControlWnd, GW_HWNDNEXT)
If nWnd Then
SetWindowPos nControlWnd, nWnd, 0, 0, 0, 0, kFlags
ZOrderDown = True
End If
End Function
Public Function ZOrderUp(Control As Control) As Boolean
Dim nWnd As Long
Dim nControlWnd As Long
nControlWnd = Control.hWnd
nWnd = GetWindow(nControlWnd, GW_HWNDPREV)
If nWnd Then
SetWindowPos nWnd, nControlWnd, 0, 0, 0, 0, kFlags
ZOrderUp = True
End If
End Function
Auf ähnliche Weise können Sie ein gegebenes Steuerelement auch vor oder hinter ein anderes, in der Reihung an beliebiger Stelle befindliches Steuerelement verschieben. Neben dem zu verschiebenden Steuerelement übergeben Sie wahlweise das Steuerelement, hinter dem es einsortiert werden soll (AfterControl) oder das Steuerelement vor dem es einsortiert werden soll (BeforeControl):
Public Sub SetZOrder(Control As Control, _
Optional AfterControl As Control, _
Optional BeforeControl As Control)
If AfterControl Is Nothing Then
If BeforeControl Is Nothing Then
Err.Raise 5
Else
SetWindowPos BeforeControl.hWnd, Control.hWnd, _
0, 0, 0, 0, kFlags
End If
Else
SetWindowPos Control.hWnd, AfterControl.hWnd, _
0, 0, 0, 0, kFlags
End If
End Sub
Nach der Anwendung der API-Funktion GetWindow ahnen Sie vielleicht schon, dass sich damit ein anderes Manko der ZOrder-Geschichte in VB ausbügeln lässt: das Ermitteln der aktuellen ZOrder-Position eines Steuerelements oder das Ermitteln der gesamten Reihenfolge innerhalb eines Containers. Die folgende Funktion ControlsZOrder liefert die Steuerelemente eines Containers in der ZOrder-Reihung sortiert in einem Array ab:
Private Const GW_CHILD = 5
Public Function ControlsZOrder(Container As Object) As Variant
Dim nControlWnds As Collection
Dim nWnd As Long
Dim nControl As Control
Dim nControls As Object
Dim nOrderedControls() As Control
Dim nCount As Integer
With Container
nWnd = GetWindow(.hWnd, GW_CHILD)
If nWnd Then
Set nControlWnds = New Collection
With nControlWnds
nCount = 1
.Add nCount, CStr(nWnd)
Do
nWnd = GetWindow(nWnd, GW_HWNDNEXT)
If nWnd = 0 Then
Exit Do
Else
nCount = nCount + 1
.Add nCount, CStr(nWnd)
End If
Loop
End With
End If
Select Case True
Case TypeOf Container Is Form
Set nControls = .Controls
Case TypeOf Container Is Control
Set nControls = .Parent.Controls
End Select
End With
ReDim nOrderedControls(1 To nControlWnds.Count)
On Error Resume Next
For Each nControl In nControls
nWnd = nControl.hWnd
If Err.Number Then
Err.Clear
Else
Set nOrderedControls(nControlWnds(CStr(nWnd))) = nControl
End If
Next
ControlsZOrder = nOrderedControls
End Function
Hier wird zunächst mit GetWindow und dem Flag GW_CHILD das erste Kind-Fenster des gegebenen Containers ermittelt. Davon ausgehend wird für jedes weitere Fenster das jeweils nachfolgende Fenster innerhalb des Containers ermittelt (GW_HWNDNEXT), bis keines mehr gefunden wird (GetWindow hat den Wert 0 zurückgegeben). Für jedes gefundene Fenster-Handle wird die Position (nCount) in eine Collection eingetragen, wobei das Handle in einen String konvertiert als Schlüssel verwendet wird. Nun wird die Controls-Collection des Forms (falls dieses als Container übergeben worden ist) bzw. des Forms, in dem sich der Container befindet, durchlaufen. Anhand der Fenster-Handles (wieder in einen String konvertiert) der Steuerelemente in der Controls-Collection wird der Positionswert aus der zuvor zusammengestellten Sammlung gezogen und das betreffende Steuerelement an dieser Position in ein Array eingetragen. Steuerelemente, die nicht über eine hWnd-Eigenschaft verfügen oder die sich in einem anderen (untergeordneten) Container befinden, werden übersprungen.
Falls Ihnen statt des Arrays eine Collection mit den Steuerelementen in der aktuellen ZOrder-Reihenfolge lieber ist, setzt die folgende Funktion das Array in einen Collection um:
Public Function ControlsZOrderColl(Container As Object) _
As Collection
Dim nControlsArr() As Control
Dim nControls As Collection
Dim i As Integer
nControlsArr = ControlsZOrder(Container)
Set nControls = New Collection
With nControls
For i = 1 To UBound(nControlsArr)
.Add nControlsArr(i)
Next 'i
End With
Set ControlsZOrderColl = nControls
End Function
Da Sie ein UserControl-Objekt nicht so ohne weiteres als Container an diese beiden Funktion übergeben können, um auf dessen Controls-Collection zugreifen zu können, muss es mittels eines kleinen Tricks zunächst in ein übergebbares Objekt konvertiert werden (Funktion CUcl, siehe auch: "UserControl unter Kontrolle"khwcucl.htm). Die beiden Funktionen müssen dazu auch noch ein wenig abgewandelt werden: Der Container-Parameter wird gleich als UserControl deklariert, und die Ermittlung der Controls-Collection erübrigt sich.
Public Function UCControlsZOrder(Container As UserControl) _
As Variant
Dim nControlWnds As Collection
Dim nWnd As Long
Dim nControl As Control
Dim nOrderedControls() As Control
Dim nCount As Integer
With Container
nWnd = GetWindow(.hWnd, GW_CHILD)
If nWnd Then
Set nControlWnds = New Collection
With nControlWnds
nCount = 1
.Add nCount, CStr(nWnd)
Do
nWnd = GetWindow(nWnd, GW_HWNDNEXT)
If nWnd = 0 Then
Exit Do
Else
nCount = nCount + 1
.Add nCount, CStr(nWnd)
End If
Loop
End With
End If
ReDim nOrderedControls(1 To nControlWnds.Count)
For Each nControl In .Controls
Set nOrderedControls(nControlWnds(CStr(nControl.hWnd))) _
= nControl
Next
End With
UCControlsZOrder = nOrderedControls
End Function
Public Function UCControlsZOrderColl(Container As UserControl) _
As Collection
Dim nControlsArr() As Control
Dim nControls As Collection
Dim i As Integer
nControlsArr = UCControlsZOrder(Container)
Set nControls = New Collection
With nControls
For i = 1 To UBound(nControlsArr)
.Add nControlsArr(i)
Next 'i
End With
Set UCControlsZOrderColl = nControls
End Function
Public Function CUcl(ByVal iUCL As Variant) As UserControl
Dim nUCL As UserControl
CopyMemory nUCL, ObjPtr(iUCL), 4
Set CUcl = nUCL
CopyMemory nUCL, 0&, 4
End Function
Das gleiche Grundprinzip, den jeweiligen Nachfolger zu einem Fenster zu ermitteln, können sie auch verwenden, um die Position eines einzelnen Steuerelements zu ermitteln. Anders als zuvor wird vom Fenster-Handle dieses Steuerelements nicht das erste Kind-Fenster, sondern das erste Fenster-Handle auf der gleichen Ebene (im gleichen Container) ermittelt (GW_HWNDFIRST). Da uns hier nun die übrigen Steuerelemente nicht interessieren, zählen wir lediglich mit - und zwar nur solange, bis das gegebene Steuerelement ausfindig gemacht worden ist. Der Zählerstand ist demnach die Position des Steuerelements in der ZOrder-Reihung:
Private Const GW_HWNDFIRST = 0
Public Function GetZOrder(Control As Control) As Integer
Dim nWnd As Long
Dim nCtlWnd As Long
Dim nCount As Integer
nCtlWnd = Control.hWnd
nWnd = GetWindow(nCtlWnd, GW_HWNDFIRST)
If nWnd Then
nCount = 1
Do
nWnd = GetWindow(nWnd, GW_HWNDNEXT)
If nWnd = 0 Then
Exit Do
Else
nCount = nCount + 1
End If
If nWnd = nCtlWnd Then
GetZOrder = nCount
Exit Function
End If
Loop
End If
End Function
Nun folgen noch einige Funktionen, die Ihnen mittels GetWindow das erste oder letzte Steuerelement eines Containers oder den Nachfolger oder Vorgänger zu einem Steuerelement liefern, ohne die ZOrder-Reihung zu beeinflussen. Zur Ermittlung des zu einem Fenster-Handle gehörenden Steuerelements dient die Funktion zControlFromHandle, die einfach die Controls-Collection durchsucht, bis das Steuerelement zum gegebenen Handle gefunden wird.
Private Function zControlFromHandle(Controls As Object, _
ByVal hWnd As Long) As Control
Dim nControl As Control
Dim nWnd As Long
On Error Resume Next
For Each nControl In Controls
nWnd = nControl.hWnd
If Err.Number Then
Err.Clear
Else
If nWnd = hWnd Then
Set zControlFromHandle = nControl
Exit Function
End If
End If
Next
End Function
Public Function GetFirstChild(Container As Object) As Control
Dim nWnd As Long
Dim nControls As Object
With Container
nWnd = GetWindow(.hWnd, GW_CHILD)
If nWnd Then
On Error Resume Next
Set nControls = .Controls
If Err.Number Then
Set nControls = .Parent.Controls
End If
Set GetFirstChild = zControlFromHandle(nControls, nWnd)
End If
End With
End Function
Private Const GW_HWNDLAST = 1
Public Function GetLastChild(Container As Object) As Control
Dim nWnd As Long
Dim nControls As Object
With Container
nWnd = GetWindow(.hWnd, GW_CHILD)
If nWnd Then
nWnd = GetWindow(nWnd, GW_HWNDLAST)
If nWnd Then
On Error Resume Next
Set nControls = .Controls
If Err.Number Then
Set nControls = .Parent.Controls
End If
Set GetLastChild = zControlFromHandle(nControls, nWnd)
End If
End If
End With
End Function
Public Function GetFirstControl(Control As Control) As Control
Dim nWnd As Long
With Control
nWnd = GetWindow(.hWnd, GW_HWNDFIRST)
If nWnd Then
Set GetFirstControl = _
zControlFromHandle(.Parent.Controls, nWnd)
End If
End With
End Function
Public Function GetLastControl(Control As Control) As Control
Dim nWnd As Long
With Control
nWnd = GetWindow(.hWnd, GW_HWNDLAST)
If nWnd Then
Set GetLastControl = _
zControlFromHandle(.Parent.Controls, nWnd)
End If
End With
End Function
Public Function GetNextControl(Control As Control) As Control
Dim nWnd As Long
With Control
nWnd = GetWindow(.hWnd, GW_HWNDNEXT)
If nWnd Then
Set GetNextControl = _
zControlFromHandle(.Parent.Controls, nWnd)
End If
End With
End Function
Public Function GetPrevControl(Control As Control) As Control
Dim nWnd As Long
With Control
nWnd = GetWindow(.hWnd, GW_HWNDPREV)
If nWnd Then
Set GetPrevControl = _
zControlFromHandle(.Parent.Controls, nWnd)
End If
End With
End Function
|