Office Forum
www.Office-Loesung.de
Access :: Excel :: Outlook :: PowerPoint :: Word :: Office :: Wieder Online ---> provisorisches Office Forum <-
Events - Objekte entkoppeln, passive Ausführung von Code
zurück: INI File erstellen, ändern und lesen weiter: Textbox wie Combobox zum Suchen von Datensätzen verwenden Unbeantwortete Beiträge anzeigen
Neues Thema eröffnen   Neue Antwort erstellen     Status: Tutorial Facebook-Likes Diese Seite Freunden empfehlen
Zu Browser-Favoriten hinzufügen
Autor Nachricht
Bitsqueezer
Office-VBA-Programmierer


Verfasst am:
15. Apr 2012, 20:04
Rufname:

Events - Objekte entkoppeln, passive Ausführung von Code - Events - Objekte entkoppeln, passive Ausführung von Code

Nach oben
       Version: (keine Angabe möglich)

Hallo zusammen,

in dem Artikel "Split Form"/Formularsynchronisation mit Events hatte ich ja schon mal ausführlich eine Methode dargestellt, wie man Events benutzen kann, um Formulare zu synchronisieren bzw. wie man ein globales Event-Objekt dazu einsetzen kann, um sich als Empfänger von Nachrichten "dranzuhängen".

Events können aber noch viel mehr, werden aber in der Praxis komischerweise kaum eingesetzt. Dabei verwenden wir alle Events andauernd, der beliebteste dürfte wohl "Form_Load" sein. Man nimmt es als Einsteiger irgendwie als eine "Funktion von Formularen" hin, daß man eben dieses Sub in den Code setzt, wenn man etwas ausführen möchte, was "Beim Laden" des Formulars passieren soll.

Also was ist ein Event eigentlich? Wie schon in dem o.g. Artikel beschrieben, ist ein Event eine Möglichkeit, einen Code in einem u.U. unbekannten Modul auszuführen oder auch, um eine Schnittstelle anzubieten, damit während der Ausführung eines ganz anderen Codes ein VBA-Programmierer etwas "zwischendurch" erledigen kann. Das ist z.B. beim Form_Load Event der Fall: Er existiert nur in einem Formular-Objekt, und er wird vom internen Access-Formular-Code nach Ablauf einer bestimmten Reihenfolge ausgelöst. Access bekommt die Order, ein Formular zu öffnen, also wird der interne Code gestartet, Formular X zu öffnen. Als erstes wird also das Formular X instantiiert, aber noch nicht angezeigt, auch keine Daten geladen.
Nun startet der interne Access-Code den Event "Form_Open" (mit etwas Ähnlichem wie dem VBA-Befehl RaiseEvent). Nun verzweigt die Ausführung zu VBA, dort wird gesucht, ob es eine Form_Open Sub gibt, wenn ja, wird sie ausgeführt, wenn dort "Cancel=True" gesetzt wird, wird das Formular-Objekt auch gleich wieder zerstört.
Dann macht Access wieder weiter, initialisiert andere Dinge, bis der "Form_Load"-Event ausgelöst wird - gleiches Spiel: Das Formular-Modul wird nun eine Sub "Form_Load" ausführen, sofern vorhanden - usw. mit Form_Current, und,und...

Als klassischer prozedural gewohnter Programmierer fragt man sich - warum nicht einfach ein "GoSub Form_Load" oder "Call Form_Load", warum ein Event?

Der Grund ist, daß man Events dazu benutzen kann, etwas auszuführen, von dem man nicht weiß, ob es existiert oder nicht.

Das Formular X KÖNNTE eine Sub "Form_Load" haben - muß es aber nicht. Na gut, könnte man sagen, dann lasse ich Access das Modul nach der Sub durchsuchen und führe sie aus, wenn sie gefunden wurde. Ja, das wäre natürlich möglich, da Access selbst ja theoretisch auf den kompilierten oder unkompilierten Sourcecode zugreifen könnte. Aber mit Events geht es viel eleganter. Denn das Modul, das den Event auslöst, muß nicht wissen, ob das Ziel existiert, es wird also nicht AKTIV aufgerufen, das Modul sagt nur "bitte JETZT reagieren" und interessiert sich nicht mehr dafür, OB jemand reagiert oder nicht. Das Objekt, das den Event "abonniert" hat, bekommt dieses Signal mit und weiß nun, daß es den passenden Event-Code jetzt ausführen soll, was es dann auch tut. Das Modul, das den Event auslöst, startet also PASSIV eine theoretisch unendliche Zahl an Event-Subs.

Bei Formularen kann man den Zweck nicht ganz so gut nachvollziehen, da jedes Formular ein eigenes Klassenmodul ist und etwa ein "Form_Open" nur spezifisch für dieses eine Formular ausgelöst wird, wenn es geöffnet wird. Daher kommt es dann auch, daß man Formular-Events (oder Berichte etc.) oft nur als "Formular-Funktionalität" wahrnimmt.

Events können aber nach dem Gesagten eine ganze Menge mehr. Die Formularsynchronisation im Artikel oben ist schon ein Beispiel, wie man viele Objekte, die nichts voneinander "wissen", miteinander koppeln kann, ohne sie "fest" zu verbinden. Man sieht immer wieder, wie aus einem Formular irgendetwas spezifisches aus einem anderen aufgerufen wird, dabei werden die Formulare dann über den Code fest miteinander verzahnt, so daß man stark aufpassen muß, wenn man an dem einen etwas ändert und die Änderung in dem anderen vergißt.
Die Formularsynchronisation zeigt, daß es ganz einfach über ein drittes Objekt, den "Radiosender", auch ganz anders geht: Man löst in einem Formular nur in diesem einen Objekt einen Event aus, der das Signal dann automatisch an alle anderen Objekte verteilt, ohne diese zu kennen - weil nicht das Radio jeden Zuhörer anspricht, sondern jeder Zuhörer sich beim Radio als Zuhörer anmeldet (also sozusagen durch das Einschalten des Radios/Wahl des Senders). Das Radio löst also passiv das Zuhören bei tausenden Zuhörern aus, ohne zu wissen, wieviele es sind oder ob sie überhaupt zuhören.

Daß man Events zum Beispiel auch für "Multitasking in VBA" einsetzen kann, habe ich z.B. mal hier demonstriert:
ADO-Recordset asynchron laden

Dabei wird das Recordset-Objekt, das über (oft unbekannte) Events verfügt, dazu benutzt, einen lang dauernden Prozess zu starten, ohne daß VBA dabei blockiert wird. Der Prozess läuft parallel zu VBA und immer dann, wenn dort etwas Interessantes passiert (z.B. ein neuer Datensatz geladen oder alle Datensätze fertig geladen), dann löst das Recordset-Objekt einen Event aus, ohne zu wissen, ob sich jemand dafür interessiert. Hat man aber einen Event-Code für die Recordset-Events in VBA eingefügt, wird VBA benachrichtigt, sobald im Recordset etwas den Event ausgelöst hat und was immer gerade VBA in einem anderen Codeteil macht, wird unterbrochen, der Eventcode wird ausgeführt und danach macht VBA dort weiter, wo es vorher war. Multitasking (eigentlich Multithreading) ist also auch mit VBA möglich.

Und das ist eigentlich nichts Neues: Der Form_Timer-Event macht genau das gleiche. Man kann damit zu regelmäßigen Zeiten den laufenden VBA-Code unterbrechen und etwas ausführen lassen, danach wird der übrige VBA-Code an der gleichen Stelle weiter ausgeführt. (Wenn man ein solches Formular mit einem Timer-Code offen hat und gerade nebenbei im VBA-Editor schreibt, dann wird man die Auswirkung auch hier bemerken, mit jedem Aufruf des Timer-Codes wird die aktuell bearbeitete Zeile compiliert, als wenn man die Zeile mit dem Cursor verlassen hätte. Bei kurz eingestellten Intervallen schafft man es dann nicht einmal, ein Leerzeichen am Ende einer Zeile zu tippen...Smile )

Aber das ist nicht nur da der Fall: Auch andere Ereignisse wie Form_Current können durch VBA-Code ausgelöst werden (etwa MoveNext), so daß der Code hier unterbrochen wird, in Form_Current weitergeht, dann wieder zum früheren Code zurückgesprungen wird.

Events sind also nicht nur ein simples "GoSub/Call", sondern können, wie man das in der PC-Hardware sagt "IRQs" auslösen, "Interrupt Requests", "Unterbrechungsanforderung". Man kocht gerade etwas am Herd, dann klingelt jemand an der Tür, während man an der Tür ist, klingelt das Telefon. Das sind IRQs. Und wenn man den Typen am Telefon abgewimmelt hat, kann man auch den Vertreter aus der Tür treten und endlich zum Kochen zurückkehren. Coderücksprung also.

Da man sich als "Zuhörer" bei dem Objekt "anmeldet", über dessen Events man informiert werden möchte, ist es auch möglich, Events "weiterzuleiten". Das kann man mit dem Zusatz "WithEvents" erreichen.

Ein Beispiel dafür hatte ich hier mal geschrieben: ButtonArray
Hier wurde ein Haufen Buttons erstellt, die ja als wichtigsten Event den Click-Event verwenden. Statt für jeden der vielen Buttons einen eigenen Click-Event-Handler zu schreiben (die Button_Click Sub für jeden Button), wurde ein Klassenmodul erstellt, bei dem der Button als Objektvariable mit dem Zusatz "WithEvents" definiert wurde. Das Formular hat dann alle Buttons in einer Schleife zusammengetragen und sie dieser Objektvariablen zugewiesen, das Objekt, das aus diesem Klassenmodul (für jeden Button eins) enstand, dann in einer Collection gesammelt, damit sie nicht verlorengehen, solange das Formular geöffnet ist. Wenn nun der Button seinen Click-Event in die Welt hinausposaunt, wird eben nicht nur im Formularcode einfach nach der betreffenden Sub gesucht, sondern alles ausgeführt, was sich an diesen Click-Event "drangehängt" hat, das Button-Objekt selbst "weiß" nichts von dem Extra-Klassenmodul und daß die Sub für den Click-Event hier ausgeführt wird.

Daß man bei den Access-Objekten noch z.B. die "OnClick"-Eigenschaft auf "[Event Procedure]" einstellen muß, ist nur eine Schnittstelle zwischen VBA und dem internen Code von Access, für Events selbst ist das eigentlich nicht notwendig. Access führt die Eventcodes in VBA aber ohne diese Einstellung nicht aus, das gilt aber nur für die Access-eigenen Objekte wie Buttons, Formulare usw.
Daß das für Events prinzipiell nicht notwendig ist, sieht man, wenn man eigene Events in eigenen Klassenmodulen erstellt.

Zum Beispiel so:
Man erstellt zunächst das Klassenmodul (geht nicht mit Standardmodulen), das den Event definiert. Um mal ein ganz einfaches Codebeispiel zu machen, soll der Event die Farbe an eine Zielprozedur weitergeben, was diese damit macht, welche Farbe das ist oder ob es ein Control oder die Farbe eines Formulars oder ein Farbname in einem Textfeld werden wird, soll den Event nicht interessieren. Nur Event und Farbnummer (als RGB-Wert, also ein Long-Wert). Also ein neues Klassenmodul anlegen, in "clsEvtChangeColor" umbenennen und folgenden Code einfügen:
Code:
Public Event ChangeColor(lngRGB As Long)

Public Sub RaiseChangeColor(lngRGB As Long)
    RaiseEvent ChangeColor(lngRGB)
End Sub
Das ist tatsächlich schon alles, was man braucht, um einen eigenen Event zu schreiben!

Ein Event kann nur in dem Objekt ausgelöst ("RaiseEvent") werden, in dem es definiert wird, also muß man sich eine Wrapper-Sub schreiben, damit man den Event auch von außen "anstoßen" kann. Das macht hier die Sub "RaiseChangeColor". Die bekommt den RGB-Wert als Parameter von außen mit übergeben und gibt ihn an den Event weiter. Der Event "funkt" nun diese Zahl "in den Äther", wodurch überall, wo dieses Objekt verwendet wird, die dortige "ChangeColor"-Eventsub aufgerufen wird - sofern vorhanden. Das Objekt objChangeColor, das nun in einem Standardmodul mit einem Einzeiler aus der Klasse "clsEvtChangeColor" erstellt wird, wird, wie in der Formularsynchronisation oben, wieder von allen anderen Modulen wiederverwendet.

Der Einzeiler in dem neuen Standardmodul "modObjects":
Code:
Public objChangeColor As New clsEvtChangeColor

Mit "New" wird in einem Standardmodul das Objekt nicht nur deklariert, sondern gleich auch instantiiert. Was man bei Objektdeklarationen normalerweise nicht machen soll, weil diese Variante verhindert, daß man die Objektvariable wieder auf "Nothing" setzen kann. Das ist hier aber auch unerwünscht, das Objekt soll so lange "leben", wie die Anwendung besteht. Zweiter Vorteil ist, daß das Objekt seinen Wert nicht verliert, wenn man mal einen nicht abgefangenen Fehler produziert hat.

Zum Ausprobieren braucht es nun zwei Formulare, um zu demonstrieren, daß beide nun vollständig voneinander entkoppelt sind und nichts voneinander "wissen". Ein Formular soll den Event aufgrund der Auswahl einer Kombobox auslösen und das andere Formular soll auf den Event reagieren.

Damit man auch sieht, daß das auch mit beliebig vielen (und im Gegensatz zu der Demo theoretisch auch ganz verschiedenen) Formularen funktioniert, werden von dem Formular, das auf die Events reagieren soll, 5 Instanzen erstellt:
Code:
Private Sub cmdOpenForms_Click()
    Dim i As Long
    Dim frm As Form_frmChangeColorTest1

    Set prv_colForms = New Collection
    For i = 1 To 5
        Set frm = New Form_frmChangeColorTest1
        frm.Visible = True
        prv_colForms.Add frm, "C" & i
    Next i
    DoCmd.RunCommand acCmdTileVertically
End Sub
Der Button "Formulare öffnen" namens "cmdOpenForms" soll in seinem Click-Event also nur die 5 Formulare öffnen, dazu speichert er die erstellten Formularinstanzen in einer modulglobalen Collection im Formularmodul:
Code:
Private prv_colForms As Collection
Der "DoCmd"-Befehl verteilt die erstellten Formulare auf dem Bildschirm, da sie sonst alle übereinander stünden.

Eine Kombobox namens "cmbFarbe" bekommt nun die Farbwerte und Farbnamen in zwei Spalten und einen "AfterUpdate"-Event, der den neuen "ChangeColor"-Event auslösen soll:
Code:
Private Sub cmbFarbe_AfterUpdate()
    objChangeColor.RaiseChangeColor Me.cmbFarbe
End Sub
Damit hat dieses Formular seinen Job schon erledigt. Der Benutzer kann eine Farbe auswählen, und irgendwo ganz anders wird darauf nun reagiert. Damit das auch noch optisch interessanter wird, fügen wir nun noch einen Change-Event der Kombobox hinzu, so daß beim Durchwandern der Auswahl der Kombobox mit dem Cursor ebenfalls der Event ausgelöst wird:
Code:
Private Sub cmbFarbe_Change()
    objChangeColor.RaiseChangeColor Me.cmbFarbe.Column(0, Me.cmbFarbe.ListIndex)
End Sub
Das Objekt "objChangeColor" steht ja durch den Zusatz "New" im Standardmodul immer zur Verfügung, muß also hier nicht getestet werden, ob es existiert.

Nun zum Code in den Formularen, die auf den "ChangeColor"-Event reagieren sollen:

Als erstes braucht man hier nun eine neue Objektvariable mit dem Zusatz "WithEvents", die einfach nur auf die Referenz des bestehenden Objektes "objChangeColor" gesetzt wird, das geht am besten in Form_Load:
Code:
Private WithEvents prv_objChangeColor As clsEvtChangeColor

Private Sub Form_Load()
    Set prv_objChangeColor = objChangeColor
End Sub
Jetzt ist unser "Zuhörer" wieder dem "Radio" namens "objChangeColor" zugeordnet und durch das "WithEvents" kann der Zuhörer nun reagieren, wenn er eine Nachricht bekommt. Dazu muß natürlich eine Event-Sub her - das funktioniert genauso wie bei Formularen und anderen Objekten. Da man hier kein sichtbares Objekt hat, kann man das nur im VBA-Editor zuweisen. Links oben findet man in der Liste nun "prv_objChangeColor", wenn man das auswählt, wird bereits automatisch die Sub
Code:
Private Sub prv_objChangeColor_ChangeColor(lngRGB As Long)
eingefügt, da das Objekt "objChangeColor" ja nur diesen einen Event hat. Wären dort mehr eingebaut, könnte man diese, wie gewohnt, in der Liste rechts oben auswählen.

Das Formular soll nun mit der übergebenen Farbe in "lngRGB" irgendetwas anfangen und setzt nun die Hintergrundfarbe für eine Textbox namens "txtTest1":
Code:
Private Sub prv_objChangeColor_ChangeColor(lngRGB As Long)
    Me.txtTest1.BackColor = lngRGB
End Sub
Fertig!
Mit so wenig Code kann man eine schon optisch beeindruckende Demo erstellen, wenn man nun das erste Formular öffnet und "Formulare öffnen" klickt, sieht man die 5 Formularinstanzen mit der Textbox und kann nun in dem Steuerformular in der Kombobox die Farbe ändern, die sich nun auf alle Formulare auswirkt.

Dabei sind nun alle Objekte vollständig entkoppelt: Das Steuerformular weiß nichts von der Existenz der anderen 5 Formulare (außer daß sie hier der Einfachheit halber hierin geöffnet wurden, was ja theoretisch auch ein drittes Formular hätte machen können oder der User selbst im Navigationsfenster), die 5 Formulare wissen nichts von der Existenz eines Steuerformulars und das Eventobjekt "objChangeColor" weiß ebenfalls nichts von dem Steuerformular und den anderen 5.

Jedes ist ein Element der Gesamtfunktionalität, aber alle sind vollständig unabhängig voneinander. Das Bindeglied zwischen allen ist das Eventobjekt "objChangeColor", aber nur soweit, daß das Steuerformular diesem die Aufgabe gibt, bei allen Anderen etwas auszulösen und daß die Zielformulare eine Referenz auf dieses Objekt setzen, um dessen Events mitzubekommen und daraufhin eine eigene Reaktion darauf durchzuführen.

Wenn man jetzt einfach mal eine Kopie des Zielformulars herstellt, daß das Steuerformular 5mal öffnet, dann braucht es nur eine kleine Änderung, um zu sehen, daß der Event universell einsetzbar ist. Die einzige Zeile, die geändert wird, ist die Zeile, in der oben "BackColor = lngRGB" gesetzt wurde:
Code:
Private Sub prv_objChangeColor_ChangeColor(lngRGB As Long)
    Me.txtTest1.Value = lngRGB
End Sub
Wenn man jetzt wieder Steuerformular und die 5 Formulare offen hat und nun manuell das neue Formular zusätzlich öffnet, sieht man bei Änderung der Kombobox im Steuerformular, daß nun statt der Hintergrundfarbe der Farbwert in die Textbox geschrieben wird. Da der Event nicht "weiß", wo er alles verwendet wird, spielt es keine Rolle, ob man irgendwelche von den geöffneten Formularen nun schließt - der Event wirkt sich nur auf die Objekte aus, die vorhanden sind.

Daß der Event auch vom Steuerformular unabhängig ist, kann man im Direktfenster von VBA ausprobieren: Einfach mal dort folgende Zeile eingeben:
Code:
objChangeColor.RaiseChangeColor 65535
Wenn die Formulare noch geöffnet waren, sind nun alle Textfelder gelb und im neuen Formular steht "65535".

Wozu kann man das in der Praxis einsetzen? Zum Beispiel der ColorChange-Event könnte dazu verwendet werden, um alle Formulare in der Anwendung auf eine einheitliche Kopffarbe einzustellen, wie der User es gerne hätte. Das Steuerformular wäre dann ein Konfigurationsformular, in dem der User seine gewünschte Farbe definiert.

Ein anderes Beispiel hatte ich schon mal hier vorgestellt: Sprache der Anwendung dynamisch wechseln
Dabei wird, genau wie bei ChangeColor, ein Sprachenwechsel-Event ausgelöst, der den Zielformularen signalisiert, daß sie alle Elemente auf die übergebene Sprache umstellen sollen. Wie sie das machen und was genau sie ändern, bleibt jedem Zielobjekt selbst überlassen. Wie im ChangeColor-Beispiel, bei dem einmal die Farbe und einmal der Farbwert verwendet wird. Genauso könnte bei ChangeColor ein Code stehen, der das Formular mit Requery aktualisiert oder die Sprache wechselt - es wird nur ein Event ausgelöst und reagiert, aber WIE reagiert wird, ist Sache des Programmierers, und theoretisch muß es nicht das Geringste mit dem Zweck des Events zu tun haben. Beispielsweise könnte ein unsichtbares Hintergrundformular auf den ChangeColor-Event reagieren und den geänderten Wert in die Userkonfiguration speichern, so daß der User nicht mal "Save" klicken müßte. Es muß also nicht zwangsläufig eine Farbe gewechselt werden, nur weil der Event "ChangeColor" genannt wurde.

Umgekehrt kann das Steuerformular im Beispiel natürlich auch selbst auf den Event reagieren, wenn es genauso mit einer "WithEvents"-Objektreferenz versehen wird und eine "ChangeColor"-Sub erhält. So könnte der Event, wie im Beispiel mit dem Direktfenster, auch ganz woanders ausgelöst werden und dazu führen, daß nun wieder in der Combobox der korrekte Farbname angezeigt wird. Aber Vorsicht: Hier können sich Events auch "in die Quere" kommen: Denn auch eine Änderung der Einstellung der Kombobox per VBA kann einen Change- oder AfterUpdate-Event auslösen, und dieser löst nun aus unserer Definition wieder den ChangeColor-Event aus, der dann wieder dem Steuerformular sagt, daß der Wert der Kombobox geändert werden muß, die dann wieder...
Um solche Endlosschleifen zu verhindern, kann man der Wrapper-Funktion oben aus der Klasse "clsEvtChangeColor" ein einfaches Flag mitgeben, daß prüft, ob der erste ausgelöste Event schon beendet wurde, ganz einfach so:
Code:
Public Sub RaiseChangeColor(lngRGB As Long)
    Static bolEventIsRunning As Boolean

    If Not bolEventIsRunning Then
        bolEventIsRunning = True
        RaiseEvent ChangeColor(lngRGB)
        bolEventIsRunning = False
    End If
End Sub
Durch "Static" behält die Variable ihren Wert auch nach dem Verlassen der Sub, so daß beim nächsten Aufruf der Wrapper-Sub erst geprüft wird, ob die durch den Event ausgelösten Subs in den Zielformularen schon fertig sind mit ihrer Ausführung oder nicht. Denn durch "RaiseEvent" wird nicht nur einfach das Eventsignal geschickt, es wartet auch, bis alle Eventfunktionen überall ausgeführt wurden und kommt dann hierhin zurück. Dadurch kann man danach die Variable wieder auf "False" setzen und nun erst kann der Event von außen erneut gestartet werden.

Ein Event kann aber auch noch mehr. Als Parameter ist nicht nur eine einfache Variable möglich, man kann beliebige Parameter definieren, wie bei einer Sub oder Function. Damit kann man also auch komplette Objekte übergeben - einfach deshalb, weil ein Objekt nie mit "ByVal" übergeben werden kann, sondern immer nur mit "ByRef" (da "ByVal" bedeutet würde, daß das komplette Objekt mit all seinen Eigenschaften kopiert werden müßte). Man reicht also nur einen Zeiger auf das eigentliche Objekt weiter, damit ist man aber in der Lage, ohne lange Parameterlisten beliebig komplexe Objekte und auch noch beliebigen Typs über den "Äther" zu senden.

Als Beispiel soll hier mal ein allgemeines Objekt (also Typ "Object") versendet werden können, so daß jeder Eventstarter jedes Objekt senden kann und jedes Eventziel jedes Objekt empfangen kann und dann entscheidet, was es damit macht.

Die Lösung ist ganz simpel:

In ein neues Klassenmodul namens "clsEvtForwardObject" schreiben wir wieder die bekannte Eventstruktur:
Code:
Public Event ForwardObject(obj As Object)

Public Sub RaiseEventForwardObject(obj As Object)
    Static bolEventIsRunning As Boolean
    If Not bolEventIsRunning Then
        bolEventIsRunning = True
        RaiseEvent ForwardObject(obj)
        bolEventIsRunning = False
    End If
End Sub
Und schon können wir beliebige Objekte durch die Welt schicken. Das Objekt kann ein Recordset sein, ein Formular, eine eigene Klasse, was auch immer.

Als erstes Beispiel soll ein einfaches Datenobjekt verschickt werden können, damit das Zielobjekt dieses auswerten und irgendetwas damit machen kann. Also wieder eine neue Klasse namens "clsDataPerson", die nur aus folgenden Zeilen besteht (neben den üblichen Options natürlich):
Code:
Public Name As String
Public Vorname As String
Public Ort As String
Daraus kann man jetzt ein Objekt instantiieren, das aus drei Variablen besteht. Das habe ich in der Demo mal im Startformular erledigt:
Code:
Private Sub cmdForwardObjectDemo2_Click()
    Dim objPerson As clsDATAPerson
    Dim varNames() As Variant
    Dim varCities() As Variant
    Dim varFirstNames() As Variant
   
    Set objPerson = New clsDATAPerson
    varNames = Array("Meier", "Müller", "Schulz", "Schmidt", "Schneider", "Schubert", "")
    varCities = Array("Köln", "München", "Hamburg", "Hannover", "Berlin", "Düsseldorf", "")
    varFirstNames = Array("Sabine", "Thomas", "Ulrike", "Bernhard", "Miriam", "Peter", "")
    With objPerson
        .Name = varNames(Int(Rnd() * UBound(varNames)))
        .Ort = varCities(Int(Rnd() * UBound(varNames)))
        .Vorname = varFirstNames(Int(Rnd() * UBound(varNames)))
    End With
    objForwardObject.RaiseEventForwardObject objPerson
    Set objPerson = Nothing
End Sub
Die Arrays und Random-Funktionen sind nur dazu da, um bei jedem Aufruf ein anderes Objekt aus zufälligen Werten zu füllen. Am Ende wird wieder der Event aufgerufen, der nun das Objekt "objPerson" an den Event in "clsEvtForwardObject" übergibt. Bewußt habe ich hier nicht das Formular "frmObjectTest1" geöffnet, um zu demonstrieren, daß das Start-Formular nicht davon abhängig ist, ob dieses geöffnet ist oder nicht, der Event wird ausgelöst, das Objekt gesendet, aber um einen Zuhörer kümmert sich das Start-Formular nicht.
Wenn man nun also manuell das Formular "frmObjectTest1" öffnet, sieht man drei leere Textfelder. Klickt man nun wiederum erneut auf den Button "Demo 2: Objektübergabe Daten", kann man sehen, daß das Objekt von diesem Formular empfangen, ausgelesen und die Inhalte auf die einzelnen Textfelder verteilt wird.
Dazu mußte das Zielformular nur wieder, wie schon gehabt, eine WithEvents-Referenz auf ein globales Objekt in einem Standardmodul setzen, das dort genau wie oben bei clsEvtChangeColor definiert wurde:
Code:
Public objForwardObject As New clsEvtForwardObject
Im Formularcode dann:
Code:
Private WithEvents prv_objForwardObject As clsEvtForwardObject

Private Sub Form_Load()
    Set prv_objForwardObject = objForwardObject
End Sub

Private Sub Form_Unload(Cancel As Integer)
    Set prv_objForwardObject = Nothing
End Sub

Private Sub prv_objForwardObject_ForwardObject(obj As Object)
    If TypeOf obj Is clsDATAPerson Then
        Dim objPerson As clsDATAPerson
        Set objPerson = obj
        With objPerson
            Me.txtName = .Name
            Me.txtVorname = .Vorname
            Me.txtOrt = .Ort
        End With
        Set objPerson = Nothing
    End If
End Sub
Der obere Teil ist wie gehabt. Auch die Event-Sub ist ähnlich wie die ColorChange-Event-Sub. Als Parameter ist es hier "obj" für ein Objekt unbekannten Typs ("Object" ist für Objekte das, was "Variant" für normale Variablen ist). Da nun der Typ von "obj" unbekannt ist, kann man es nicht einfach risikolos verwenden, denn das Objekt müßte ja nicht zwangsläufig vom Typ "clsDataPerson" sein, es könnte ja auch z.B. ein geöffnetes Recordset sein. Also muß man hier mit "TypeOf" testen, ob das empfangene Objekt wirklich vom Typ "clsDataPerson" ist, wenn ja, kann man ein neues Objekt mit clsDataPerson deklarieren ("objPerson") und mit Set das generische Object in ein typisiertes clsDataPerson zurückverwandeln.
Das ist aber nur dazu da, um den folgenden Code compilersicher zu machen und IntelliSense zu unterstützen - notwendig ist es nicht. Es würde ebensogut statt "Me.txtName = objPerson.Name" funktionieren, wenn man schreiben würde: "Me.txtName = obj.Name", nur würde dann kein IntelliSense aufklappen und die drei Variablen anzeigen - und wenn man sich verschreibt, sagt einem der Compiler nicht, daß es etwa "obj.Nahme" nicht gibt.

Am Ende kann man objPerson wieder auf Nothing setzen, da es nur ein Duplikat der Referenz zu obj war. Dagegen darf man hier nicht "Set obj = Nothing" setzen, denn da es sich um einen Event handelt, könnte das gleiche Objekt auch noch zu anderen Zielobjekten "weiterwandern" und die würden dann auf ein "Nothing" stoßen. Strenggenommen müßte man hier also das empfangene "obj" noch auf Nothing testen, um zu verhindern, daß fehlerhafter Code in anderen Event-Subs das Objekt "obj" zwischenzeitlich gelöscht haben.

Event-Subs werden nicht in einer bestimmten Reihenfolge aufgerufen, das muß man bei der Programmierung berücksichtigen. Die Reihenfolge, welche Event-Sub nun als nächstes aufgerufen wird, bestimmt VBA selbst und ist nicht beeinflußbar.

Um zu zeigen, daß der gleiche Event auch anders genutzt werden kann, habe ich in der angehängten Demo noch ein Klassenmodul namens "clsColor" eingefügt, das einen RGB-Long-Wert in die einzelnen Byte-Werte für Rot, Grün und Blau aufteilen kann oder umgekehrt aus den drei Byte-Werten einen Long-RGB-Wert macht.

Das wird nun im Start-Formular für einen zweiten Test eingesetzt, um im gleichen Formular, an das die Daten aus dem letzten Beispiel geschickt wurde, die Farbe der Textboxen nach Zufall zu verändern:
Code:
Private Sub cmdForwardObjectDemo3_Click()
    Dim objColor As clsColor

    Set objColor = New clsColor
    With objColor
        .RGB = RGB(Int(Rnd() * 256), Int(Rnd() * 256), Int(Rnd() * 256))
    End With
    objForwardObject.RaiseEventForwardObject objColor
    Set objColor = Nothing
End Sub
Hier wird also diesmal ein Objekt aus "clsColor" erstellt, mit zufälligen RGB-Werten gefüllt und dann einfach wieder mit dem Event-Aufruf "in die Welt" hinausgeschickt.

Da das Zielformular schon eine Event-Sub dafür hat, muß man darin nur noch zwischen den Objekttypen unterscheiden, also:
Code:
Private Sub prv_objForwardObject_ForwardObject(obj As Object)
    If TypeOf obj Is clsDATAPerson Then
        Dim objPerson As clsDATAPerson
        Set objPerson = obj
        With objPerson
            Me.txtName = .Name
            Me.txtVorname = .Vorname
            Me.txtOrt = .Ort
        End With
        Set objPerson = Nothing
    ElseIf TypeOf obj Is clsColor Then
        Dim objColor As clsColor
        Set objColor = obj
        With objColor
            Me.txtName.BackColor = .RGB
            Me.txtOrt.BackColor = RGB(.Red, .Green, .Blue) ' andere Methode für das gleiche
            Me.txtVorname.BackColor = .RGB
        End With
        Set objColor = Nothing
    End If
End Sub
Jetzt kann das Zielformular schon zwei komplett verschiedene Objekte empfangen und verarbeiten, ohne überhaupt zu wissen, wo sie herkommen.

Da man in ein Objekt nicht direkt einfache Variablen hineinpacken kann, kann man die gleiche Methode natürlich auch (mit einem neuen Event) mit Variant-Variablen verwenden, also in der Klasse clsEvtForwardObject mit folgenden Zeilen:
Code:
Public Event ForwardValue(var As Variant)

Public Sub RaiseEventForwardValue(var As Variant)
    Static bolEventIsRunning As Boolean

    If Not bolEventIsRunning Then
        bolEventIsRunning = True
        RaiseEvent ForwardValue(var)
        bolEventIsRunning = False
    End If
End Sub
Wie bereits in der Formularsynchronisation oben erläutert, kann man ein Zielobjekt natürlich auch indirekt gezielt ansteuern - der Event wird ja an alle geschickt, die zuhören, wenn aber nur bei bestimmten Zuhörern eine Verarbeitung erfolgen soll, kann man einfach weitere Parameter den Eventdefinitionen hinzufügen, die dann mitteilen, wer darauf reagieren soll - dabei natürlich nicht den Formularnamen verwenden, dann könnte man die Sub ja auch direkt aufrufen und hätte wieder eine Verkettung. Stattdessen allgemeinere Unterscheidungsmerkmale, etwa eine Enumeration in clsEvtForwardObject:
Code:
Public Enum EnmForwardObjectType
    enmFwdObjType_DisplayData
    enmFwdObjType_DeleteData
    enmFwdObjType_UpdateData
End Enum
So hat man nun drei verschiedene Optionen, welche Art von Formularen auf das gesendete Objekt reagieren sollen. Der Event und die Hilfs-Sub sowie der Aufruf des Events in der auslösenden Sub müssen dann natürlich noch angepaßt werden:
Code:
Public Event ForwardObject(obj As Object, ForwardObjectType As EnmForwardObjectType)

Public Sub RaiseEventForwardObject(obj As Object, ForwardObjectType As EnmForwardObjectType)
    Static bolEventIsRunning As Boolean

    If Not bolEventIsRunning Then
        bolEventIsRunning = True
        RaiseEvent ForwardObject(obj, ForwardObjectType)
        bolEventIsRunning = False
    End If
End Sub
Und im Startformular dann:
Code:
    objForwardObject.RaiseEventForwardObject objPerson, enmFwdObjType_DisplayData
Jetzt wird nicht mehr nur das Objekt gesendet, sondern auch noch signalisiert, daß das Zielobjekt die Daten nur anzeigen soll. Das muß den Parameter natürlich dazu entsprechend auswerten - ist dazu aber nicht gezwungen. Was das Zielobjekt also mit dem übergebenen Objekt macht, bleibt immer diesem überlassen, der Event gibt nur den Hinweis, aber keine Überprüfung, was wirklich passiert (wie in Form_Load: Was der Programmierer in diese Sub alles reinpackt, überprüft der Load-Event nicht).

Das Übergeben von Daten an ein unbekanntes Zielobjekt ist so ja schon mal möglich. Wie kann man es nun aber noch erreichen, daß auch das leidige "DoCmd.OpenForm" entfällt und somit das Startformular den Namen des Zielformulars nicht mehr kennen muß?

Das kann man zum Beispiel erreichen, in dem man die Hilfsfunktion in clsEvtForwardObject, die bislang einfach nur den Event gestartet hat, mit einer Sub ausrüstet, die ebenfalls den Typ des gesendeten Objektes überprüft und wenn es sich um das Objekt "clsDataPerson" handelt, soll diese nun das passende Formular im ReadOnly-Mode öffnen - oder je nach Typ von "ForwardObjectType" das gleiche oder ein anderes Formular im Edit-Mode:
Code:
Private Const cPersonFormDisplay As String = "frmObjectTest1"
Private Const cPersonFormUpdate  As String = "frmObjectTest1"

Private Sub CheckObject(obj As Object, ForwardObjectType As EnmForwardObjectType)
    If TypeOf obj Is clsDATAPerson Then
        Select Case ForwardObjectType
          Case enmFwdObjType_UpdateData
            If Not CurrentProject.AllForms(cPersonFormUpdate).IsLoaded Then
                DoCmd.OpenForm cPersonFormUpdate, acNormal, , , acFormEdit
            End If
          Case enmFwdObjType_DisplayData
            If Not CurrentProject.AllForms(cPersonFormDisplay).IsLoaded Then
                DoCmd.OpenForm cPersonFormDisplay, acNormal, , , acFormReadOnly
            End If
        End Select
    End If
End Sub
Diese kann man nun in der Hilfs-Sub aufrufen:
Code:
Public Sub RaiseEventForwardObject(obj As Object, ForwardObjectType As EnmForwardObjectType)
    Static bolEventIsRunning As Boolean

    If Not bolEventIsRunning Then
        bolEventIsRunning = True
        CheckObject obj, ForwardObjectType
        RaiseEvent ForwardObject(obj, ForwardObjectType)
        bolEventIsRunning = False
    End If
End Sub
Ohne etwas im Startformular ändern zu müssen, öffnet nun ein Klick auf den Button "Demo 2: Objektübergabe Daten" erst das passende Formular und zeigt dann die Daten an - alles mit einem einzigen Event.

Der große Vorteil hierbei ist, daß nun die Formulare wirklich komplett entkoppelt sind. Das Startformular muß den Namen des Zielformulars nicht kennen, das Zielformular muß nicht wissen, von wo es aufgerufen wurde, trotzdem könnten parallel weitere Formulare geöffnet sein, die die gleichen Daten auch noch empfangen und etwas damit machen. Ebenso kann das Zielformular selbst auch wieder einen solchen Event aufrufen und ein Objekt zurückschicken, wenn das Startformular die Daten dann weiterverarbeiten soll. "Daten als Nachrichten", könnte man sagen.

Das einzige Objekt, das über alle Formularnamen und Datenobjekte zu Formularen bescheid wissen muß, ist das Objekt aus "clsEvtForwardObject", sinnvollerweise hier als Konstanten definiert, damit auch die Sub "CheckObject" nicht genau den Namen kennen muß. Zur Anpassung muß man also jetzt einfach nur noch einen Satz Konstanten definieren mit den Namen der Formulare, sowie entsprechende Checks dem "CheckObject" hinzufügen. Alles nur noch gesammelt an einer Stelle, man muß nicht den ganzen Code durchforsten, wenn man den Namen eines Formulars ändern möchte. Und da die Objekte perfekt entkoppelt sind, kann man sie auch voneinander trennen und in anderen Anwendungen wiederverwenden, ohne sich Gedanken um Formularnamen machen zu müssen.

Die Methoden hier sind natürlich nur als Anregung gedacht und können noch bei weitem verbessert werden, aber der Code sollte ja auch nicht zu kompliziert werden. Mit weiteren Event-Parametern kann man genau wie in der gezeigten Enumeration die Events noch feiner steuern und den Code durch sprechende Namen in den Enums sehr viel lesbarer machen. Keine "DoCmd.OpenForm"-Parameterwüsten mehr, nur einen Event hinausschicken, der seine Aufgabe dann unabhängig erfüllt - und dabei nicht nur ein Formular öffnen kann, sondern gleich auch noch x weitere mit den gleichen Daten versorgen kann, ohne Mehraufwand.

Viel Spaß bei eigenen Experimenten mit Events.

Gruß

Christian



EventDemo.zip
 Beschreibung:
Kleine Demonstration der Möglichkeiten von selbstgeschriebenen Events. Format: A2003

Download
 Dateiname:  EventDemo.zip
 Dateigröße:  37.31 KB
 Heruntergeladen:  83 mal

Botti
Gast


Verfasst am:
17. Apr 2012, 23:55
Rufname:


AW: Events - Objekte entkoppeln, passive Ausführung von Code - AW: Events - Objekte entkoppeln, passive Ausführung von Code

Nach oben
       Version: (keine Angabe möglich)

Hallo Christian,

kannst du folgendes bestätigen:

Wenn man in MS-Access 2010 ein Event "abfeuert", dann wartet die Haupt-Methode, die das "raiseevent" erzeugt hat, so lange, bis alle "Verbraucher" des Events ihre mit dem Event verbundenen Aktionen abgeschlossen haben.

Damit erreicht man zwar eine Parallelisierung der Verbraucherprozesse, allerdings geht die Hauptmethode nicht weiter.

Grüße
Jörg
Bitsqueezer
Office-VBA-Programmierer


Verfasst am:
18. Apr 2012, 07:38
Rufname:

AW: Events - Objekte entkoppeln, passive Ausführung von Code - AW: Events - Objekte entkoppeln, passive Ausführung von Code

Nach oben
       Version: (keine Angabe möglich)

Hallo Jörg,

ja, das ist so, und nicht nur in A2010. Das Problem ist, daß VBA nicht multithreading-fähig ist, also nicht per übergeordneter Zeitscheibensteuerung Prozesse parallel laufen können. Die einzige Ausnahme, die einen solchen Effekt simulieren kann, ist der Timer-Event, da der Event selbst nicht in VBA geschrieben wurde, sondern Bestandteil des Formular-Objektes ist, das vermutlich in C# oder C++ geschrieben wurde (das wiederum multithreading-fähig ist). Somit kann der Timer-Event aufgrund der Tatsache, daß er nach einer festgelegten Zeit ausgelöst wird und dann garantiert den betreffenden VBA-Code ausführt, tatsächlich einen Zeitscheibeneffekt erzeugen, wenn man es geschickt einsetzt.
Hierbei muß man aber auch wieder berücksichtigen, daß der Timer-Event auch seinen eigenen Timer-Event-Code garantiert nach Ablauf der eingestellten Zeit unterbricht, was zu unerwünschten Nebeneffekten bei zu klein eingestelltem Timer führen kann.

Man kann also sagen: Alle Events, die von Haus aus mit Access geliefert werden, können theoretisch zu jeder Zeit gefeuert werden, da sie in einer multithreading-fähigen Programmiersprache geschrieben wurden. Die selbstgeschriebenen Events von VBA können das nicht, da sie nur auf Anforderung durch VBA mit RaiseEvent "künstlich" gestartet werden und dann auf die Ausführung aller Eventkonsumenten warten müssen (weswegen ja auch der oben gezeigte Trick mit der booleschen Variable funktioniert, der feststellt, ob ein Event noch läuft oder nicht).

Trotzdem kann man nicht einfach sicher sein, daß nicht auch ein selbstgeschriebener Event unterbrochen wird, da ja eben auch die Access-eigenen Events auftreten können, die diese wieder unterbrechen können (wie den Timer-Event). Und da der Timer theoretisch auch die selbstgeschriebenen Events aufrufen kann, muß man solche Sicherheitsvorkehrungen treffen, wenn man das vermeiden möchte.

Auf der anderen Seite kann man aber auch genau so einen selbstgeschriebenen Event zeitgesteuert starten, man muß nur beachten, daß der Timer eben immer nach der abgelaufenen Zeit erneut startet, also muß der Eventcode und seine Konsumenten schnell genug "durch" sein, oder man muß mit Tricks wie der booleschen Variable testen, ob der Event noch läuft und dann entscheiden, ob er erneut gestartet werden darf oder nicht. Somit kann man mit dem Timer VBA auch zu einem Multithreading bewegen, wenn auch nicht "so richtig".

Bestes Beispiel für echte Parallelisierung ist die oben beschriebene asynchrone Recordset-Verarbeitung - da das Recordset wieder ein Access-Objekt ist, kann es "im Hintergrund" arbeiten und somit VBA ermöglichen, tatsächlich parallel weiterzulaufen.

Gruß

Christian
Botti
Gast


Verfasst am:
18. Apr 2012, 14:38
Rufname:

AW: Events - Objekte entkoppeln, passive Ausführung von Code - AW: Events - Objekte entkoppeln, passive Ausführung von Code

Nach oben
       Version: (keine Angabe möglich)

Vielen Dank Christian,

das gibt wenigstens Gewissheit, dass nicht doch ein Hintertürchen offen ist.

By the way zum Thema Parallelisierung mit Recordsets:
Mit DAO-Recordsets scheint's nicht zu gehen, da diese keine Events bereitstellen.

Beste Grüße
Jörg
Bitsqueezer
Office-VBA-Programmierer


Verfasst am:
18. Apr 2012, 16:07
Rufname:


AW: Events - Objekte entkoppeln, passive Ausführung von Code - AW: Events - Objekte entkoppeln, passive Ausführung von Code

Nach oben
       Version: (keine Angabe möglich)

Hallo Jörg,

ja, bis auf die erwähnten Möglichkeiten.

In DAO gibt es offenbar keine Events (für VBA), obwohl damit auch asynchrone Verarbeitung möglich ist, allerdings wohl nicht in Access.
Eine Beschreibung dieses Features findet sich z.B. hier: Introducing VB5's ODBC Direct

Gruß

Christian
Neues Thema eröffnen   Neue Antwort erstellen Alle Zeiten sind
GMT + 1 Stunde

Diese Seite Freunden empfehlen

Seite 1 von 1
Gehe zu:  
Du kannst Beiträge in dieses Forum schreiben.
Du kannst auf Beiträge in diesem Forum antworten.
Du kannst deine Beiträge in diesem Forum nicht bearbeiten.
Du kannst deine Beiträge in diesem Forum nicht löschen.
Du kannst an Umfragen in diesem Forum nicht mitmachen.
Du kannst Dateien in diesem Forum nicht posten
Du kannst Dateien in diesem Forum herunterladen

Verwandte Themen
Forum / Themen   Antworten   Autor   Aufrufe   Letzter Beitrag 
Keine neuen Beiträge Access Tabellen & Abfragen: Alle Access Objekte anzeigen 2 investmentclub 96 28. Jan 2014, 00:26
investmentclub Alle Access Objekte anzeigen
Keine neuen Beiträge Access Tabellen & Abfragen: SQL Code verschachteln 5 littlejohn 421 01. Okt 2012, 09:40
KlausMz SQL Code verschachteln
Keine neuen Beiträge Access Tabellen & Abfragen: OLE Objekte werden auf bestimmtem PC nicht angezeigt. 1 jojolino 911 10. Jun 2010, 09:26
Marina_Arida OLE Objekte werden auf bestimmtem PC nicht angezeigt.
Keine neuen Beiträge Access Tabellen & Abfragen: Warum ändert Access den sql code? 12 abraxa 704 19. Jul 2009, 20:55
abraxa Warum ändert Access den sql code?
Keine neuen Beiträge Access Tabellen & Abfragen: Darstellungsart "Kontrollkästchen" per Code setzen 3 fragerer 285 13. März 2009, 16:32
Gast Darstellungsart "Kontrollkästchen" per Code setzen
Keine neuen Beiträge Access Tabellen & Abfragen: Button VBA Code zum Verknüpfen aller ODBC -Tabellen 3 adamth 3953 08. Sep 2008, 13:10
rita2008 Button VBA Code zum Verknüpfen aller ODBC -Tabellen
Keine neuen Beiträge Access Tabellen & Abfragen: Wie lassen sich JA/NEIN Objekte filtern? 4 Gast 610 25. Mai 2008, 15:00
rita2008 Wie lassen sich JA/NEIN Objekte filtern?
Keine neuen Beiträge Access Tabellen & Abfragen: schnelle komplexe Abfrage per Code? 4 SaschaR 586 25. März 2008, 15:57
SaschaR schnelle komplexe Abfrage per Code?
Keine neuen Beiträge Access Tabellen & Abfragen: Excel zellen in Access Tabelle kopieren mit einem VBA code 10 SzP/TEF13 1846 09. Nov 2007, 16:55
Willi Wipp Excel zellen in Access Tabelle kopieren mit einem VBA code
Dieses Thema ist gesperrt, du kannst keine Beiträge editieren oder beantworten. Access Tabellen & Abfragen: Hyperlinks funktionieren nicht nach Ausführung Anfügeabfrage 1 dshigit 1018 15. Apr 2007, 10:20
steffen0815 Hyperlinks funktionieren nicht nach Ausführung Anfügeabfrage
Keine neuen Beiträge Access Tabellen & Abfragen: DateDiff in VBA Code berechnet liefert Nonsens 8 Utali 1324 15. Jan 2007, 17:08
Gast DateDiff in VBA Code berechnet liefert Nonsens
Keine neuen Beiträge Access Tabellen & Abfragen: Access Höhe eines Rechteck per VBA Code regulieren 2 Gast1 1835 30. Dez 2006, 16:13
Gast Access Höhe eines Rechteck per VBA Code regulieren
 

----> Diese Seite Freunden empfehlen <------ Impressum - Besuchen Sie auch: Microsoft Word Serienbriefe