|
Sicher haben auch Sie schon einmal einem vorhandenen
Steuerelement zu neuen Fähigkeiten und Eigenschaften verholfen,
indem Sie es auf einem UserControl platziert und so ein neues
Steuerelement geschaffen haben. Sie kennen daher auch den
notwendigen Aufwand und die sich oft ergebenden Probleme. Wenn das
neue Steuerelement genauso flexibel wie das Original-Steuerelement
sein soll, müssen Sie nahezu alle Eigenschaften, Methoden und
Ereignisse über das UserControl an die Außenwelt weiterreichen -
selbst mit Hilfe des Schnittstellen-Assistenten ist das meistens
kein leichtes Unterfangen. Und die Probleme treten vor allem dann
auf, wenn es um Eigenschaften geht, die Visual Basic stur als nur
zur Entwicklungszeit änderbar behandelt.
Oftmals reicht es statt dessen, ein einfaches, zur Laufzeit
selbst nicht sichtbares Zusatzsteuerelement zu schaffen, das die
Funktionalität der gewünschten neuen Features aufnimmt und dem das
Original lediglich als Partner zugewiesen wird (der Partner eines
solchen Zusatzsteuerelements wird oft als "Buddy"
bezeichnet). Die naheliegendste, wenn auch sehr unkomfortable
Möglichkeit besteht darin, diese Zuweisung zur Laufzeit per Code
vorzunehmen. Eleganter ist es da schon, das Form oder den Container
von diesem Steuerelement selbst nach möglichen Partnern durchsuchen
zu lassen und die Fundstücke auf einer Eigenschaftenseite (PropertyPage)
zur Auswahl anzubieten.
Aber wäre es nicht der eleganteste Weg, wenn Sie die Partnerwahl
ganz einfach im bequemen visuellen Design-Stil, sozusagen per
Drag&Drop vornehmen könnten? Ganz einfach, indem Sie das
Zusatzsteuerelement auf dem gewünschten Partner platzieren oder auf
diesen darauf schieben? Es geht und ist gar nicht einmal sonderlich
aufwändig.
Der Trick beruht darauf, dass nach dem Neuplatzieren eines
Steuerelements auf einem Form und nach jedem Verschieben die
Eigenschaften neu in das Eigenschaftenfenster eingelesen werden.
Genauer gesagt bedeutet das, dass alle Property Get-Prozeduren
derjenigen Eigenschaften ausgeführt werden, die im
Eigenschaftenfenster erscheinen. Sie brauchen also lediglich eine
Eigenschaft, deren Property Let-Prozedur zur Entwicklungszeit gar
nichts tut und alles andere ihrem Gegenstück, der Property
Get-Prozedur überlässt.
Dort bestimmen Sie (über die private Hilfsprozedur zFindListBox)
zuerst die Position des Mittelpunkts des UserControls und ermitteln
dann das Steuerelement des gewünschten Typs auf dem Elternformular,
innerhalb dessen Fläche der zuvor bestimmte Mittelpunkt liegt, das
sich im gleichen Container befindet und das nicht Element eines
Control-Arrays ist (für Steuerelemente in Arrays funktioniert
leider die Zuweisung zu einer mit WithEvents deklarierten
Objekt-Variablen nicht).
Das folgende Beispiel zeigt, wie das Grundgerüst für ein
solches Zusatzsteuerelement eine ListBox als Partner findet.
Während der Laufzeit wird diese ListBox dann einer mit WithEvents
als Ereignisempfänger deklarierten Objektvariablen zugewiesen. Ein
ähnliches Verfahren des "Mithörens" von Ereignissen habe
ich für Forms in Lausch-Eingriffe
beschrieben.
Private WithEvents eListBox As ListBox
Private pListBox As String
Public Property Get ListBox() As String
zFindListBox
ListBox = pListBox
End Property
Private Sub zFindListBox()
Dim nControl As Control
Dim nLeft As Single
Dim nTop As Single
If Not Ambient.UserMode Then
pListBox = ""
With Extender
nLeft = .Left + (.Width \ 2)
nTop = .Top + (.Height \ 2)
End With
On Error Resume Next
For Each nControl In UserControl.Parent.Controls
If TypeOf nControl Is ListBox Then
With nControl
If .Container Is Extender.Container Then
Select Case nLeft
Case .Left To .Left + .Width
Select Case nTop
Case .Top To .Top + .Height
If .Index < 0 Then
pListBox = .Name
If Ambient.UserMode Then
zSetListBoxControl
End If
Exit For
End If
End Select
End Select
End If
End With
End If
Next
Extender.ZOrder 0
End If
End Sub
Über die Methode FindListBox können Sie auch zur Laufzeit die
ListBox ausfindig machen, über der sich das UserControl aktuell
befindet:
Public Function FindListBox() As Object
zFindListBox
Set FindListBox = eListBox
End Function
Zur Entwicklungszeit passiert in den Prozeduren zum Setzen der
Eigenschaft wirklich nichts, wie Sie hier sehen:
Public Property Let ListBox(ByVal New_ListBox As String)
If Ambient.UserMode Then
pListBox = New_ListBox
zSetListBoxControl
End If
End Property
Public Property Set ListBox(New_ListBox As Object)
Dim nSuccess As Boolean
If Ambient.UserMode Then
pListBox = ""
If TypeOf New_ListBox Is ListBox Then
With New_ListBox
If .Container Is Extender.Container Then
On Error Resume Next
Set eListBox = New_ListBox
nSuccess = Not (eListBox Is Nothing)
If nSuccess Then
pListBox = .Name
Else
Set eListBox = Nothing
End If
RaiseEvent InitDone(nSuccess)
Else
End If
End With
End If
End If
End Property
Private Sub zSetListBoxControl()
Dim nControl As Control
Dim nSuccess As Boolean
Set eListBox = Nothing
On Error Resume Next
For Each nControl In UserControl.Parent.Controls
With nControl
If .Container Is Extender.Container Then
If .Name = pListBox Then
Set eListBox = nControl
nSuccess = Not (eListBox Is Nothing)
RaiseEvent InitDone(nSuccess)
If nSuccess Then
Exit For
End If
End If
End If
End With
Next
pListBox = ""
End Sub
Zur Laufzeit jedoch kann der Eigenschaft entweder der Name einer
ListBox als String oder eine ListBox als Objekt-Referenz zugewiesen
werden. In der Property Let-Prozedur wird die private Prozedur
zSetListBoxControl aufgerufen. In dieser wird die
Controls-Collection des UserControl.Parent nach einer ListBox anhand
der gleichen Kriterien wie in der Prozedur zFindListBox durchsucht.
Wird eine entsprechende ListBox gefunden, wird sie der
Ereignisempfänger-Variablen eListBox zugewiesen. In der Property
Set-Prozedur wird direct versucht, die übergebene ListBox der
Ereignisempfänger-Variablen eListBox zuzuweisen, wenn sie sich im
gleichen Container befindet. Konnte sie nicht zugewiesen werden,
weil die ListBox aus einem Control-Array stammt, wird eine eventuell
bereits vorhandene Referenz auf eine andere ListBox gelöscht.
Sowohl in zSetListBoxControl als auch in der Property Set-Prozedur
wird das Ereignis InitDone mit der entsprechenden Erfolgsmeldung
ausgelöst.
Den Namen des gefundenen Partner-Steuerelements (hier: der
ListBox) speichern wir auf für Eigenschaften übliche Weise:
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
pListBox = PropBag.ReadProperty("ListBox", "")
' ...
End Sub
Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
PropBag.WriteProperty "ListBox", pListBox, ""
End Sub
Da so ein Zusatzsteuerelement oftmals zur Laufzeit nicht sichtbar
zu sein braucht, steht der normalerweise ideale Platz für eine
Zuweisung des Partner-Steuerelements zur Laufzeit nicht zur
Verfügung - die Ereignisprozedur UserControl_Show. Das Problem
besteht nämlich darin, dass die Reihenfolge, in der Steuerelemente
vom Container (Form) geladen werden, prinzipiell nicht vorhersehbar
ist (auch wenn es anders scheinen mag!). Beim ersten greifbaren
Ereignis, UserControl_ReadProperties, ist noch niocht
gewährleistet, dass das gewünschte Partner-Steuerelement bereits
geladen ist und somit zugewiesen werden könnte. Beim Eintreffen des
Ereignisses UserControl-Show eines sichtbaren Steuerelements wäre
die Sicherheit gegeben - nur wie gesagt, in diesem Beispiel wird das
Ereignis wegen der Unsichtbarkeit des Zusatzsteuerelements gar nicht
ausgelöst. Wir behelfen uns daher mit einem Timer, der nur zur
Laufzeit (Ambient-UserMode = True) im Ereignis ReadProperties
aktiviert wird und nach Abschluss aller Ladevorgänge das erste Mal
ausgelöst wird. In dessen Timer-Ereignis rufen wir nun die
Hilfsprozedur zSetListBox auf, um die zum während der
Entwicklungszeit gefundenen Namen passende ListBox zu finden. Die
vollständige Ereignis-Prozedur UserControl_ReadProperties und die
Timer-Ereignis-Prozedur sehen wie folgt aus:
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
pListBox = PropBag.ReadProperty("ListBox", "")
If Ambient.UserMode Then
Extender.Visible = False
If Len(pListBox) Then
tmrSetListBox.Enabled = True
End If
End If
End Sub
Private Sub tmrSetListBox_Timer()
tmrSetListBox.Enabled = False
zSetListBoxControl
End Sub
Das weitere ist nur noch "Kosmetik" in diesem Beispiel
zur Darstellung einer Art "Fadenkreuz" und zur Fixierung
der Größe während der Entwicklungszeit. Ob wie Sie das bei Ihren
eigenen Zusatzsteuerelementen ausgestalten, bleibt völlig Ihnen
selbst überlassen.
Private Sub UserControl_Paint()
If Not Ambient.UserMode Then
With UserControl
UserControl.Line (.ScaleWidth \ 2, 0)-Step(0, .ScaleHeight), _
vbBlue
UserControl.Line (0, .ScaleHeight \ 2)-Step(.ScaleWidth, 0), _
vbBlue
UserControl.Line (0, 0)-(.ScaleWidth - 1, .ScaleHeight - 1), _
vbInfoText, B
End With
End If
End Sub
Private Sub UserControl_Resize()
Static sInProc As Boolean
If sInProc Then
Exit Sub
Else
sInProc = True
End If
If Not Ambient.UserMode Then
With UserControl
.Size 1.5 * .TextHeight("A") * Screen.TwipsPerPixelX, _
1.5 * .TextHeight("A") * Screen.TwipsPerPixelY
End With
End If
sInProc = False
End Sub
Während der Laufzeit können Sie die gefundene Partner-ListBox
beispielsweise folgendermaßen nutzen:
Private Sub eListBox_MouseUp(Button As Integer, Shift As Integer, _
X As Single, Y As Single)
MsgBox "MouseUp: " & eListBox.Name
End Sub

|
Korrekturen und
Ergänzungen
|
 |
 |
|
|
09.11.1999
|
 |
Erweiterung zum Setzen der Partner-ListBox zur
Laufzeit
|
|