Rubrik: Forms/Controls | VB-Versionen: VB5, VB6 | 01.03.05 |
Dynamisches Hinzufügen von Controls mit Klasse Mit diesem Workshop wollen wir Ihnen zeigen, wie Sie Controls zu Ihrer Anwendung dynamisch hinzufügen und gleichzeitig auch auf die Control-Ereignisse reagieren können, ohne hierfür ein Steuerelementfeld erstellen zu müssen. | ||
Autor: Wolfgang Christ | Bewertung: | Views: 34.684 |
Mit diesem Workshop wollen wir Ihnen zeigen, wie Sie Controls zu Ihrer Anwendung dynamisch hinzufügen und gleichzeitig auch auf die Control-Ereignisse reagieren können, ohne hierfür ein Steuerelementfeld erstellen zu müssen.
Viele von Ihnen werden nun sagen, dass dies ein alter Hut ist und doch sehr einfach funktioniert. Wir möchten Ihnen aber einen Weg zeigen, wie Sie dies vollkommen dynamisch realisieren und auf alle Events reagieren können, wenn Sie wollen.
Voraussetzung hierfür wäre, dass Sie den Workshop Polymorphismus (Schnittstellenimplementierung) bereits durchgearbeitet haben.
- Das Prinzip
- Wir definieren uns unsere Schnittstelle
- Implements IDynamicControl
- Eine Collection
- clsDynamicControl
- Abfangen und Feuern
- Reagieren
- Ein kleines Beispiel
1. Das Prinzip
Das Prinzip zu dieser Methode ist im Grunde genommen relativ einfach. Wir definieren über eine Schnittstelle dem Container der Controls (der Form) weitere Ereignisse bzw. Methoden hinzu, die wir innerhalb der Form implementieren müssen. Diese Ereignisse repräsentieren alle Events der hinzugefügten Controls, auf die wir während der Laufzeit reagieren wollen. Nun fügen wir alle gewünschten neuen Controls während der Laufzeit über eine Klasse hinzu, in welche das Control seine Events feuert. Diese fangen wir ab und "feuern" in die Form.
Die hier vorgestellte Methode zur Realisierung kann um beliebig viele Controls erweitert werden. Um den Rahmen dieses Workshops nicht zu sprengen, werden wir uns auf zwei Control-Typen beschränken (Button und Textbox).
2. Wir definieren uns unsere Schnittstelle
An dieser Stelle müssen wir definieren, auf welche Events der Controls wir reagieren wollen. Wir definieren dies als Methoden innerhalb unserer Schnittstelle, und übergeben unsere oben genannte Klasse. Auf welche Ereignisse wollen wir reagieren ?
Nun für den Button fällt mir eigentlich nur eines ein, das Click-Ereignis.
Für die Textbox fallen mir adhoc das Click-, Lost/GotFocus- und KeyPress-Ereignis ein.
Wir starten ein neues Projekt und fügen ein neues Klassenmodul ein. Dies nennen wir IDynamicControl. Dort fügen wir einfach nur folgenden Code ein:
Option Explicit Public Sub KeyPress(ByVal oEventSource As clsDynamicControl, ByRef KeyAscii As Integer) ' End Sub Public Sub Click(ByVal oEventSource As clsDynamicControl) ' End Sub Public Sub GotFocus(ByVal oEventSource As clsDynamicControl) ' End Sub Public Sub LostFocus(ByVal oEventSource As clsDynamicControl) ' End Sub
Wie bereits gesagt, dies ist nur ein kleines Beispiel zum Erläutern und Verdeutlichen des Prinzips, das sich beliebig erweitern lässt.
3. Implements IDynamicControl
In unserer Form, auf der wir Controls dynamisch hinzufügen wollen, fügen wir im Allgemeinteil folgenden Code hinzu:
Private oDynControls As clsDynamicControl ' mehr dazu später Implements IDynamicControls
Und im Form_Load-Event fügen wir folgende Zeilen ein:
Private Sub Form_Load() Set oDynControls = New clsDynamicControl oDynControls.Form = Me End Sub
Was bedeutet dies nun?
Nun mittels Implements erklären wir VB, dass innerhalb unserer Form alle Methoden der Schnittstelle als Events der Schnittstelle hinzugefügt werden müssen. Im Code Editor ist unter Objekten ein neues vorhanden, IDynamicControl. Nun müssen alle Funktionen mit Code belegt werden, und wenn es nur ein Kommentar ist. Mehr dazu im bereits bezeichneten Workshop Polymorphismus (Schnittstellenimplementierung).
4. Eine Collection
Wie bereits in der Einleitung geschrieben, wollen wir eine vollkommen dynamische Form. Aus diesem Grunde wäre es schön, wenn wir auch nur ein zusätzliches Objekt deklarieren müssen, in welchem alle dynamisch hinzugefügten Controls "behandelt" werden. Da wir gleichzeitig alles kapseln möchten, können wir hier nur eine Klasse verwenden, in der eine Collection alle einzelnen Objekte aufnimmt.
Wir fügen also eine weitere Klasse hinzu, diese nennen wir clsDynamicControls.
Innerhalb dieser Klasse benötigen wir Eigenschaften zur Übergabe der gewünschten Form, des implementierten Interfaces und zum Zugriff auf die hinzugefügten Objekte, ebenso wie Funktionen zum Hinzufügen und Entfernen von Controls. Fügen wir in diese Klasse einfach folgenden Code ein, die jeweiligen Kommentare sollten alles Nötige zum Verstehen aussagen.
Option Explicit Private m_oControls As Collection Private m_oForm As Form Private m_oInterface As IDynamicControl Private m_lID As Long
Private Sub Class_Initialize() Set m_oControls = New Collection End Sub Private Sub Class_Terminate() Set m_oControls = Nothing End Sub
' Eigenschaften ' Stuff Public Property Get Interface() As IDynamicControl Set Interface = m_oInterface End Property Public Property Set Interface(Value As IDynamicControl) Set m_oInterface = Value End Property Public Property Get Form() As Form Set Form = m_oForm End Property Public Property Set Form(Value As Form) Set m_oForm = Value End Property
' Eigenschaften für Objekte Public Property Get GetControl(ByVal sName As String) As clsDynamicControl ' Objekt über Name aus Collection ermitteln und zurückgeben Set GetControl = m_oControls.Item(sName) End Property
' Nicht öffentliche Methoden Private Sub Inc(ByRef lID As Long) lID = lID + 1 End Sub Private Function GetID() As Long Inc m_lID GetID = m_lID End Function
' Öffentliche Methoden Public Function AddControl(ByVal ControlType As eTypeOfControl, _ ByVal sControlName As String) As clsDynamicControl Dim oControl As Control Dim oDynControl As clsDynamicControl ' evtl. Leerzeichen abschneiden sControlName = Trim(sControlName) If Len(sControlName) = 0 Then sControlName = "oControl" & GetID End If ' Objekt vom Typ clsDynamicControl erstellen und einrichten Set oDynControl = New clsDynamicControl With oDynControl .ControlType = ControlType Set .Form = m_oForm Set .Interface = m_oInterface .Name = sControlName Set oControl = .CreateControl End With ' Objekt der Collection hinzufügen m_oControls.Add oDynControl, sControlName Set AddControl = oDynControl Set oControl = Nothing Set oDynControl = Nothing End Function Public Function DeleteControl(ByVal sControlName As String) ' entfernt ein bereits bestehendes Control ' sofern vorhanden ... m_oControls.Remove sControlName End Function
5. clsDynamicControl
Diese Klasse wird unsere dynamischen Controls beinhalten sowie alle Events der Controls abfangen und weiterreichen. Wollen wir weitere Control-Typen hinzufügen müssen wir nur diese Klasse anpassen.
Wir fügen in unser Projekt ein neues Klassenmodul ein und nennen es clsDynamicControl.
Als Erstes definieren wir, welche Controls wir überhaupt dynamisch hinzufügen wollen; in unserem Beispiel Button und Textboxen.
Option Explicit Private WithEvents m_oTextBox As TextBox Private WithEvents m_oButton As CommandButton Public Enum eTypeOfControl eTextBox eButton End Enum
Des Weiteren noch ein paar private Variablen der Klasse:
' Welches Control wurde mit dieser Klasse hinzugefügt Private pControlType As eTypeOfControl ' Wurde das Control bereits erzeugt Private bCreated As Boolean ' Auf welcher Form? Private m_oForm As Form ' Welches Interface Private m_oInterface As IDynamicControl ' Welchen Namen soll das Control erhalten Private m_sName As String
' Eigenschaften Public Property Get Name() As String Name = m_sName End Property Public Property Let Name(Value As String) m_sName = Value End Property Public Property Get Interface() As IDynamicControl Set Interface = m_oInterface End Property Public Property Set Interface(Value As IDynamicControl) Set m_oInterface = Value End Property Public Property Get Form() As Form Set Form = m_oForm End Property Public Property Set Form(Value As Form) Set m_oForm = Value End Property Public Property Let ControlType(ByVal vData As eTypeOfControl) pControlType = vData End Property Public Property Get ControlType() As eTypeOfControl ControlType = pControlType End Property
' Für den direkten Zugriff auf das erzeugte Control Public Property Get ControlObject() As Control Select Case pControlType Case eTypeOfControl.eTextBox Set ControlObject = m_oTextBox Case eTypeOfControl.eButton Set ControlObject = m_oButton End Select End Property
Nun fehlen noch die benötigten privaten Funktionen der Klasse:
' Wurde alles Benötigte übergeben? Private Function AllOK() As Boolean AllOK = False If m_oForm Is Nothing Then Exit Function If m_oInterface Is Nothing Then Exit Function If Len(Trim(m_sName)) < 1 Then Exit Function Select Case pControlType Case eTypeOfControl.eButton Case eTypeOfControl.eTextBox Case Else Exit Function End Select AllOK = True End Function
' Welchen Klassennamen hat das gewünschte Control? Private Function GetControlClassName() Select Case pControlType Case eTypeOfControl.eTextBox GetControlClassName = "VB.TextBox" Case eTypeOfControl.eButton GetControlClassName = "VB.CommandButton" End Select End Function
' Und natürlich die obligatorischen Funktionen Private Sub Class_Initialize() bCreated = False End Sub Private Sub Class_Terminate() DestroyControl End Sub
Nun fehlen natürlich noch die Funktionen zum Anlegen und Zerstören der Controls:
' Control erzeugen Public Function CreateControl() As Control ' Wenn nicht alles korrekt ist, darf das Control nicht erzeugt werden If Not AllOK Then Err.Raise 1001, "CreateControl", "Control konnte nicht erstellt werden" Exit Function End If ' nur ein Control pro Instanz der Klasse If bCreated Then ' Control wurde bereits erstellt, bereits erstelltes ' Control zurückgeben Set CreateControl = Me.ControlObject Exit Function End If bCreated = True Select Case pControlType Case eTypeOfControl.eTextBox Set m_oTextBox = m_oForm.Controls.Add(GetControlClassName, m_sName) Set CreateControl = m_oTextBox Case eTypeOfControl.eButton Set m_oButton = m_oForm.Controls.Add(GetControlClassName, m_sName) Set CreateControl = m_oButton End Select End Function
' Control wieder zerstören, aber nur wenn es auch angelegt wurde Public Sub DestroyControl() If Not bCreated Then m_oForm.Controls.Remove m_sName Set m_oTextBox = Nothing Set m_oButton = Nothing bCreated = False End If End Sub
6. Abfangen und feuern
Nun haben wir alles was wir benötigen, um beliebig viele Controls während der Laufzeit anzulegen und wieder zu entfernen. Aber da dies uns nicht reicht und wir möchten, dass wir auf die Events dieser Controls reagieren können, wenn wir wollen, bedienen wir uns eines kleinen Tricks. Wir haben die Controls mittels des Schlüsselwortes WithEvents deklariert. Dies bedeutet, dass wir alle Events dieses Controls innerhalb der Klasse empfangen können. Um diese weiterreichen zu können, haben wir mittels Implements unserer Form weitere Funktionen im Sinne von haben müssen hinzudefiniert. Da wir diese Funktionen nun in der Form haben definieren müssen, können wir diese jetzt immer weiterreichen und die Klasse selber mit übergeben.
Aus diesem Grunde haben wir unser Interface immer mit übergeben; dies ist nun in m_oInterface verfügbar.
' TextBox Events Private Sub m_oTextBox_Click() Call m_oInterface.Click(Me) End Sub Private Sub m_oTextBox_GotFocus() Call m_oInterface.GotFocus(Me) End Sub Private Sub m_oTextBox_KeyPress(KeyAscii As Integer) Call m_oInterface.KeyPress(Me, KeyAscii) End Sub Private Sub m_oTextBox_LostFocus() Call m_oInterface.LostFocus(Me) End Sub
' Button Events Private Sub m_oButton_Click() Call m_oInterface.Click(Me) End Sub Private Sub m_oButton_GotFocus() Call m_oInterface.GotFocus(Me) End Sub Private Sub m_oButton_KeyPress(KeyAscii As Integer) Call m_oInterface.KeyPress(Me, KeyAscii) End Sub Private Sub m_oButton_LostFocus() Call m_oInterface.LostFocus(Me) End Sub
7. Regaieren
Unsere Form haben wir, wie im 3. Abschnitt beschrieben, mittels Implements um weitere Funktionen erweitert, in diesen können wir nun die Ereignisse empfangen. Das Control selber wird in der übergebenen Klasse "mitgeliefert".
8. Ein kleines Beispiel
Erstellen Sie ein neues Projekt und fügen Sie die drei Klassen wie beschrieben ein. Danach fügen Sie einfach folgenden Code in den Code-Teil von Form1 ein und starten die Anwendung. Die Funktionsweise selber wird im Quellcode erklärt.
Option Explicit ' Interface Implements IDynamicControl ' Dynamische Controls Private oDynamicControls As clsDynamicControls
Private Sub Form_Load() ' Aufbauen der Oberfläche Set oDynamicControls = New clsDynamicControls Dim oDynControl As clsDynamicControl ' Form und Interface übergeben With oDynamicControls Set .Form = Me Set .Interface = Me End With ' Eine Textbox anlegen Set oDynControl = oDynamicControls.AddControl(eTextBox, "DynamischeTextBox1") With oDynControl ' Verschieben .ControlObject.Move 240, 240, 2535, 375 ' Sichtbar machen .ControlObject.Visible = True ' Eigenschaft setzen .ControlObject.Text = .Name End With ' Einen Button anlegen Set oDynControl = oDynamicControls.AddControl(eButton, "DynamischerButton1") With oDynControl ' Verschieben, Sichtbar und eine Eigenschaft .ControlObject.Move 240, 840, 2535, 375 .ControlObject.Visible = True .ControlObject.Caption = "Dynamischer Button" End With End Sub
' Ereignisse Private Sub IDynamicControl_KeyPress(ByVal oEventSource As clsDynamicControl, _ KeyAscii As Integer) ' nur zur Demonstration gedacht, es können auch Parameter ' der Ereignisse übergeben werden Debug.Print oEventSource.Name & "_" & "KeyPress(" & CStr(KeyAscii) & ")" End Sub Private Sub IDynamicControl_LostFocus(ByVal oEventSource As clsDynamicControl) Debug.Print oEventSource.Name & "_LostFocus" If oEventSource.ControlType = eTextBox Then oEventSource.ControlObject.BackColor = vbWhite End If End Sub Private Sub IDynamicControl_Click(ByVal oEventSource As clsDynamicControl) Debug.Print oEventSource.Name & "_Click" If oEventSource.ControlType = eButton Then ' Die Textbox disablen With oDynamicControls.GetControl("DynamischeTextBox1").ControlObject .Enabled = Not .Enabled End With End If End Sub Private Sub IDynamicControl_GotFocus(ByVal oEventSource As clsDynamicControl) Debug.Print oEventSource.Name & "_GotFocus" If oEventSource.ControlType = eTextBox Then oEventSource.ControlObject.BackColor = vbRed End If End Sub
Nun wünschen wir Ihnen viel Spaß mit diesen Möglichkeiten.