Alles über Events

    • VB.NET

    Es gibt 2 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

      Alles über Events

      Mir fällt gelegentlich auf, das Begriffe im Zusammenhang mit Events falsch verwendet werden, vermutlich weil sie nicht richtig bekannt sind. Auch Galileio-Book kann da nicht helfen - die behandeln das Thema nur unzureichend, und wissen selbst nicht recht Bescheid - zB verkaufen sie den codeseitigen Aufruf einer Ereignis-Methode als Ereignis-Kette, und eine Endlos-Rekursion als Endlos-Ereignis-Kette.

      Event-orientiertes versus prozedurales Denken
      Ja, mitte Events ist das eine ganze Denkweise. Gelegentlich finden sich Fragen in Foren, wie:
      "ich habe 5 Items, und möchte jede Sekunde eins davon meiner Listbox zufügen - wie muss ich die For-Schleife machen?"
      Der Fehler liegt hier im prozeduralen Denken: Klar, der Computer arbeitet grundsätzlich prozedural, und eine For-Schleife hat in NullkommaNixMinusEins die 5 Items eingefügt - aber jeweils die Sekunde warten - grad das kannernich.
      Natürlich kann man ihn ausbremsen, mit Thread.Sleep(1000) - aber das versetzt die ganze App ins Koma - da wird dann auch die Anzeige der neuen Items nicht mehr geupdated.
      Nein - man musses ereignisorientiert angehen: Jede Sekunde ein Ereignis - füge ein Item zu, und weitermachen, mit - nichts.
      Das ist nämlich, was ein Computer am meisten macht: Nichts.
      Warten, dass sich ein Ereignis ereignet. Das ist, wie eine Anwendung interaktiv ist, und endlos Interaktions-Möglichkeiten anbietet: Grundsätzlich nichts tun - aber wenn der User Button1 klickst, oder in Textbox1 schreibt, oder, oder, oder... - dann tut sie schnell mal was, und dann wieder nichts.
      Diese beiden Denkweisen sind übrigens nicht verfeindet, sondern Player desselben Teams: Die Grundstruktur ist ereignisorientiert und im Wartezustand. Ereignet sich ein Ereignis, wird dieses schnellstmöglich prozedural abgearbeite, denn der Prozessor will seine Ruhe haben damit die App gleich wieder bereitsteht für User-Interaktionen.

      Begrifflichkeiten
      Bei einem Event gibt es immer einen Sender, der das Event versendet, und keinen, einen oder mehrere Empfänger, die es abonnieren - (oder auch wieder abbestellen).
      Der Sender "verschickt" sein Event, indem er alle Methoden aufruft, die sein Event abonnieren. Diese Methoden heißen Handler-Methoden, EventHandler oder einfach Handler. Mit diesen Methoden behandelt der Empfänger das abonnierte Event.
      Ich stelle das deshalb so deutlich heraus, weil viele Leuts haben keine klare Unterscheidung von Event (ein Aufruf-Mechanismus im Sender) und EventHandler (eine Methode im Empfänger)
      Dieses Muster: "mehrere Empfänger können Nachrichten eines Senders abonnieren oder abbestellen" heißt in der Informatik: Observer-Pattern

      Der erste Kontakt
      Die Begriffs-Verwirrung rührt sicher auch daher, dass man als Anfänger über lange Zeit Events ausschließlich von der Handler-Seite her kennt. Das hier:

      VB.NET-Quellcode

      1. Private Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) _
      2. Handles Button1.Click
      3. End Sub
      ist vmtl. der erste Code, den ein Anfänger schreibt - (tatsächlich schreibters ja nicht selbst, sondern lässtes sich generieren, etwa, indem er im Form-Designer einen Button doppelklickst).

      Erst relativ spät kommt man in Verlegenheit, auch die Sender-Seite implementieren zu müssen. Jo, und wenn man gar nicht weiß, dass das ühaupt geht, kommts u.U. zu ziemlich schlimmen Frickel-Lösungen - ich komme noch dazu.

      Observer-Pattern: Handles-Klausel versus AddHandler / RemoveHandler
      Was uns der Designer hingeneriert, ist überaus praktisch und zuverlässig, aber es verschleiert ein bischen den dahinterstehenden Observer-Pattern: Denn im Hintergrund erstellt die Handles-Klausel eine unsichtbare Property, in deren Setter mit AddHandler/RemoveHandler das angegebene Event registriert/deregistriert wird.
      Bei dynamisch erzeugten Objekten geht das statische "Handles" schon rein syntaktisch nicht.

      Ereigniskette
      Eine Ereigniskette hat man am Hals, wenn der EventHandler Code ausführt, welcher das Ereignis selbst wieder auslöst. So eine Ereigniskette unterhält uns mit allerlei originellem Verhalten: miese Performance, erstaunliche Berechnungen "Nanu - wo issn der Wert her??", Geflacker, Rumhopsen von Controls, und im Extremfall Endlos-Ereigniskette - hängt die App.
      Wie gesagt: Hier kann man das Ereignis einfach abbestellen, solange man den sensiblen Code ausführt.

      Beispiel zu AddHandler / RemoveHandler: Richtextbox, die nur bestimmte Zeichen zulassen soll
      Wann immer ein unerlaubtes Zeichen sich anfindet, soll der Text auf den vorherigen Zustand zurückgesetzt werden, inklusive der Selection. Deshalb muß die Überprüfung im _SelectionChanged-EventHandler stattfinden, und ggfs. müssen Text und Selection restauriert werden (was bei der Selection eine Ereigniskette verursachen würde):

      VB.NET-Quellcode

      1. Public Class frmEreignisDemo
      2. Private _SelStore As New SelStore
      3. Private Sub frmEreignisDemo_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
      4. RichTextBox1.Text = "0"
      5. _SelStore.Store(RichTextBox1)
      6. AddHandler RichTextBox1.SelectionChanged, AddressOf RichTextBox1_SelectionChanged
      7. End Sub
      8. Private Sub RichTextBox1_SelectionChanged(ByVal sender As Object, ByVal e As EventArgs)
      9. Dim allowed = "0123456789"
      10. With RichTextBox1
      11. If .Text.All(Function(c) allowed.Contains(c)) Then
      12. _SelStore.Store(RichTextBox1)
      13. Else
      14. RemoveHandler .SelectionChanged, AddressOf RichTextBox1_SelectionChanged
      15. _SelStore.Restore(RichTextBox1)
      16. AddHandler .SelectionChanged, AddressOf RichTextBox1_SelectionChanged
      17. End If
      18. End With
      19. End Sub
      20. End Class
      21. Public Class SelStore
      22. Private Text As String
      23. Private Start, Length As Integer
      24. Public Sub Store(ByVal tb As RichTextBox)
      25. Text = tb.Text
      26. Start = tb.SelectionStart
      27. Length = tb.SelectionLength
      28. End Sub
      29. Public Sub Restore(ByVal tb As RichTextBox)
      30. tb.Text = Text
      31. tb.Select(Start, Length)
      32. End Sub
      33. End Class
      (die SelStore-Klasse ist klar? Damit kann man die Selection einer Richtextbox speichern und wieder restaurieren)

      Schwierigkeiten mit der Signatur
      Obiges Beispiel sieht schmissig aus, aber wenn man selbst mal versucht, ein Ereignis mit AddHandler zu abonnieren, baut man schnell folgenden Fehler, nachdem man die HandlerMethode hingeschreiben hat, und will abonnieren:

      WtF...?
      Ja, ist schon absolut genau und richtig, wasser sagt - blos: wovon redet er? Was ist eine Signatur, was ein Delegat, und wieso kommter jetzt mit diesem EventHandler-Dingsbums an?
      Also langsam: Wir wollen ja ein Event abonnieren. Das heißt, unsere Handler-Methode soll aufgerufen werden durch den Event-Mechanismus. Aber das Event kann ja nicht jede x-beliebige Methode aufrufen, sondern nur eine, die auch passt.
      Logisch: Eine Methode, die einen Integer erwartet, kann nicht ein Event abonnieren, welches einen String verschickt. (Jaja - für Option Strict Off - Progger ist das nicht logisch - aber die brauchen noch grundlegendere Tutorials ;))
      Also Anzahl, Reihenfolge und Datentyp von Argumenten und Rückgabetyp - das ist die Signatur einer Methode.
      Der Verschick-Mechanismus des Events benutzt einen Delegaten: Das ist eine Variable, deren Datentyp eben eine Signatur ist, und der man Methoden zufügen kann (so verrückt das auch klingt). Mehrere sogar (noch verrückter), und wenn man diesen Delegaten dann aufruft, werden dadurch alle geaddeten Methoden aufgerufen. (In C hat man Function-Pointer für sowas - aber die sind total unsicher, weil können überall hinzeigen, und der Absturz bleibt nur aus, wenn sie tatsächlich auf eine passende Funktions-Addresse zeigen.)
      Jdfs, was uns der Compiler hier haarklein (und unverständlich) erklärt, ist, dass der Datentyp des Delegaten, der das Event verschicken will, nicht zur Signatur der Methode passt, die es abonnieren soll.
      Er führt uns sogar nochmal die Signatur unserer eigenen Handler-Methode vor Augen, und stellt sie der Definition des Event-Delegaten gegenüber - gugge nochma:

      So, dassis jetzt geklärt, und wie mans richtig macht, ist ja weiter oben gezeigt.

      Zur Übung bringe ich nochn paar Beispiele von Signaturen - weil dassis fast ebenso wichtig zu verstehen, wie das Konzept der Datentypen überhaupt:

      VB.NET-Quellcode

      1. Private Sub Button1_Click( _
      2. ByVal sender As Object, ByVal e As EventArgs)
      bereits gesagt: erwartet ein Object und ein EventArgs, kein Rückgabewert - nächstes Beispiel:

      VB.NET-Quellcode

      1. Public Function Multiplicate( _
      2. ByVal arg1 As Double, ByVal arg2 As Double) As Double
      Diese Methode erwartet zwei Doubles und gibt einen zurück

      VB.NET-Quellcode

      1. Private Function FindIndex( _
      2. ByVal strings As List(Of String), ByVal pattern As String) As Integer
      Diese erwartet eine List(Of String) und einen String - gibt einen Integer zurück.

      Abschweifung: die generischen Delegaten Action(Of...) und Func(Of...) für jede Signatur
      Im Framework sind tausende verschiedener Delegaten definiert - eigentlich ganz überflüssigerweise, denn seit Einführung der Generika kann man (fast) jede Signatur als Action oder Func definieren: Der obige EventHandler Button1_Click etwa wäre ebensogut als Action(Of Object, EventArgs) definiert, Multiplicate ist eine Func(Of Double, Double, Double) und FindIndex eine Func(Of List(Of String), String, Integer).
      Ist das Prinzip klar geworden? Bei Action(Of...) definieren die TypParameter die Methoden-Argument-Typen von Subs, bei Func(Of...) dito, nur gilt der letzte TypParameter für den Rückgabewert.
      So, jetzt wisst ihr das auch, und dass man Signaturen am kürzesten als Action oder Func ausdrückt.

      Noch abgeschweifter
      Ihr habt euch bei der Betrachtung dieser Zeile

      VB.NET-Quellcode

      1. If .Text.All(Function(c) allowed.Contains(c)) Then
      sicher gefragt:
      "WtF! wie kann man in einer Code-Zeile alle Zeichen von .Text gegen alle Zeichen des allowed-Strings abgleichen?" - habt ihr nicht gefragt? - egal - erklär ich trotzdem, weil das geht mit Func und Signaturen:
      Also im CodeEditor per Rechtsklick auf "All" - "zu Definition gehen" - zeigt uns die .All()-Signatur im ObjectBrowser:
      .Text.All(predicate As Func(Of Char, Boolean)) - als Argument erwartet wird also ein "Bedingungs"-Delegat ("predicate"), der für jedes Zeichen im Text aufgerufen wird, nämlich, um das Zeichen mit True oder False zu "bewerten" (daher Boolean als Rückgabewert der predicate-Func).
      Nur wenn alle Zeichen des Textes mit True bewertet werden, gibt .All() sinnigerweise auch True zurück. Auf diese Weise drückt .All() aus, ob eine Bedingung für alle Zeichen zutrifft, und zwar ohne die Bedingung zu kennen - die kann übergeben werden - als Parameter!
      Jo, und Function(c) allowed.Contains(c) ist so ein Bedingungs-Delegat-Parameter (notiert als anonyme Methode): Die Bedingung für ein Zeichen c ist True, wenn es im allowed-String enthalten ist.
      Und dieses prüft .All() gegen jedes im Text enthaltene Zeichen ab.
      Naja - für wen das jetzt zu schnell war weckts vlt. Interesse, VB2008 richtig zu lernen, u.U. sogar dieses Buch zu lesen ;)

      Ereignisse selbst schreiben
      Neues Code-Beispiel: Angenommen wir wollen ein UserControl, welches alle KnownColors in einer Listbox auflistet, sodass man auswählen kann, ohne den WinForms-ColorDialog jedesmal öffnen und schließen zu müssen:

      VB.NET-Quellcode

      1. Public Class uclColorSelector
      2. Private _Brushes As New List(Of SolidBrush)
      3. Private Sub uclColorSelector_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
      4. For Each pinf As PropertyInfo In GetType(Brushes).GetProperties().Skip(1)
      5. _Brushes.Add(DirectCast(pinf.GetValue(Nothing, Nothing), SolidBrush))
      6. Next
      7. ListBox1.DataSource = _Brushes
      8. End Sub
      9. Private Sub ListBox1_DrawItem(ByVal sender As Object, ByVal e As DrawItemEventArgs) Handles ListBox1.DrawItem
      10. e.DrawBackground()
      11. Dim brsh = _Brushes(e.Index)
      12. With e.Bounds
      13. e.Graphics.FillRectangle(brsh, .X + 2, .Y + 3, .Width - 5, .Height - 6)
      14. End With
      15. With brsh.Color
      16. Dim col = If(CInt(.R) + .B + .G > 400, Color.Black, Color.White)
      17. TextRenderer.DrawText(e.Graphics, .Name, Font, e.Bounds, col)
      18. End With
      19. End Sub
      20. End Class

      Wie kriegen wirs nun hin, dass zB ein Button auf dem Form1 immer die im uclColorSelector angewählte Farbe hat?

      Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „ErfinderDesRades“ ()

      Wie mans nicht hinkriegen sollte:
      1. Quick & Dirty

        VB.NET-Quellcode

        1. Private Sub ListBox1_SelectedIndexChanged( _
        2. ByVal sender As Object, ByVal e As EventArgs) Handles ListBox1.SelectedIndexChanged
        3. Form1.Button1.BackColor = _Brushes(ListBox1.SelectedIndex).Color
        4. End Sub
        Diesen Leuten muß man vergeben, "denn sie wissen nicht, was sie tun" ;)
        Form1 ist gar kein Objekt, sondern ist eine Klasse, also der Bauplan eines Objektes. Unlogischerweise geht das trotzdem (es geht nur mit Forms), und das ist ziemlich katastrophal, weil Microsoft damit die VB-Progger quasi verblödet, sodass diese alsbald nicht mehr klar zwischen Bauplan und Gebautem unterscheiden können (und die c#-ler können sich über uns lustig machen :cursing: )

      2. gut gemeint, aber eigentlich nur umständlicher ist folgende "Konsequenz" aus 1)
        "Lege in uclColorSelector eine Public Property vom Typ Form1 an, initialisiere die, und dann kann der uclColorSelector deren Button1 bunt machen"
        Tja, das wird zu Fehlern im Designer führen, denn wenn man so einen uclColorSelector aufs Form zieht, dann ist die form1-Variable erstmal uninitialisiert, der uclColorSelector will aber gleich den Button bunt machen, der leider noch gar nicht da ist.
      Nein, beide Varianten sind im Grunde Spaghetti-Code, zu enge Kopplung, und verstoßen gegen das Kapselungs-Prinzip, nach dem es nur dem Form zusteht, seine Buttons zu colorieren - nicht iwelchen draufgezogenen UserControls.

      Der Design-Fehler "zu enge Kopplung" wird zu einem richtigen Hindernis, wenn man uclColorSelector mal in eine andere Solution einbauen möchte - das geht nämlich gar nicht: Weil es ist davon abhängig (gekoppelt), dass es ein Form1 gibt, und zwar eines mit einem Button1 drauf. Architektonischer Mist - siehe auch VeryBasics - Kommunikation der Objekte.

      Korrekte Lösung - Observer-Pattern
      Der ColorSelector soll logisch eine Nachricht verschicken, und wer die erhält, soll selber zusehen, wie er seine Buttons bunt kriegt. Also ein Event muß her - diesmal von der Sender-Seite her implementiert:

      VB.NET-Quellcode

      1. <DefaultEvent("SelectedColorChanged")> _
      2. Public Class uclColorSelector
      3. Private _Brushes As New List(Of SolidBrush)
      4. Public Event SelectedColorChanged As EventHandler
      5. Private Sub ListBox1_SelectedIndexChanged( _
      6. ByVal sender As Object, ByVal e As EventArgs) Handles ListBox1.SelectedIndexChanged
      7. RaiseEvent SelectedColorChanged(Me, EventArgs.Empty)
      8. End Sub
      9. Public ReadOnly Property SelectedColor As Color
      10. Get
      11. Return _Brushes(ListBox1.SelectedIndex).Color
      12. End Get
      13. End Property
      (Das <DefaultEvent()> - Attribut veranlasst übrigens den Form-Designer, beim Doppelklick auf meinen uclColorSelector gleich den korrekten EventHandler in den Code-Editor zu generieren - kenntmanja von Button.Click und Konsorten).
      Is einfach - ne? Einfach Public Event SelectedColorChanged As EventHandler hinschreiben, und schon kann man sein eigenes Event raisen.

      Design-Richtlinie Event-Signatur
      Theoretisch kann man für seine Events absolut beliebige Signaturen festlegen - man macht sich damit aber sogleich des Dilettierens verdächtig, weil im Framework ist die folgende Richtlinie ausnahmslos bei allen(!!) Events umgesetzt, und ich habe bisher nur bei Dilettanten erlebt (außer bei mir ;)), dass jmd. sich unterstanden hätten, von dieser Richtlinie abzuweichen (Ausnahme: EBC - event-driven Architecture - aber das ist ein anderes Tutorial)
      Also: Die Signatur eines Events ist immer: Sub EventName(sender As Object, e As EventArgs).
      Anhand des senders kann in Handlern, die Events mehrerer Objekte behandeln, der jeweilige Sender identifiziert werden. Ich nutze das gerne zB für Menüs:

      VB.NET-Quellcode

      1. Private Sub MenuStrip_MenuClicked(ByVal Sender As Object, ByVal e As EventArgs) _
      2. Handles SaveToolStripMenuItem.Click, ReloadToolStripMenuItem.Click, _
      3. TestToolStripMenuItem.Click
      4. Select Case True
      5. Case Sender Is SaveToolStripMenuItem
      6. 'save
      7. Case Sender Is ReloadToolStripMenuItem
      8. 'load
      9. Case Sender Is TestToolStripMenuItem
      10. 'test
      11. End Select
      12. End Sub

      Eigene EventArgs
      EventArgs ist eine wirklich eigentümliche Klasse, hat überhaupt keine Property! Die Klasse kann aber beerbt und mit Properties und allem Möglichen erweitert werden.
      Häufige Verwendung findet etwa das CancelEventArgs, mit seiner Property e.Cancel, die man auf True setzen kann, um beispielsweise in einem Form_Closing-EventHandler das Closing zu canceln.
      Im Beispiel mittm ColorSelector könnte man es nun praktisch finden, beim Versenden der ColorChanged - Nachricht gleich die neue Color mitzuschicken.
      Dazu muß man aber erstmal so ein EventArgs erfinden, mit dem man eine Color auch herumschicken kann:

      VB.NET-Quellcode

      1. Public Class ColorChangedEventArgs : Inherits EventArgs
      2. Public ReadOnly Color As Color
      3. Public Sub New(ByVal color As Color)
      4. Me.Color = color
      5. End Sub
      6. End Class

      Und dann folglich:

      VB.NET-Quellcode

      1. Public Event ColorChanged As EventHandler(Of ColorChangedEventArgs)
      2. Private Sub ListBox_SelectedIndexChanged( _
      3. ByVal sender As Object, ByVal e As EventArgs) Handles ListBox1.SelectedIndexChanged
      4. RaiseEvent ColorChanged(Me, _
      5. New ColorChangedEventArgs(_Brushes(ListBox1.SelectedIndex).Color))
      6. End Sub

      Dann kommt das im Form so raus:

      VB.NET-Quellcode

      1. Private Sub UclColorSelector1_ColorChanged(ByVal sender As Object, _
      2. ByVal e As uclColorSelector.ColorChangedEventArgs) _
      3. Handles UclColorSelector1.ColorChanged
      4. Me.Button1.BackColor = e.Color
      5. End Sub


      Vererbungslehre
      Ein Event unterliegt strikten Einschränkungen: Es ist Readonly, man kann's nicht als Methoden-Argumente weiterreichen - ja man kann's nicht einmal von außerhalb der Klasse auslösen

      VB.NET-Quellcode

      1. RaiseEvent(ColorSelector1.SelectedColorChanged(Nothing, EventArgs.Empty))
      gehtnich :(

      Nicht einmal ein Erbe von uclColorSelector könnte RaiseEvent in Anschlag bringen.
      Diese Einschränkungen sind durchaus sinnvoll, und verhindern, dass Schindluder getrieben wird. Will man eine Event-bestückte Klasse beerben und der abgeleiteten Klasse gestatten, auch das Event auszulösen, muß man halt codetechnisch was dafür tun - sprich: eine OnEvent-Methode bereitstellen. Die gesamte Choose - vonne EventArgs-Klasse, über die OnEvent-Methode bis zum Auslösen - mag sich dann so darstellen:

      VB.NET-Quellcode

      1. Public Class uclColorSelector
      2. Public Class ColorChangedEventArgs : Inherits EventArgs
      3. Public ReadOnly Color As Color
      4. Public Sub New(ByVal color As Color)
      5. Me.Color = color
      6. End Sub
      7. End Class
      8. Public Event ColorChanged As EventHandler(Of ColorChangedEventArgs)
      9. Protected Sub OnColorChanged(ByVal e As ColorChangedEventArgs)
      10. RaiseEvent ColorChanged(Me, e)
      11. End Sub
      12. Private _Brushes As New List(Of SolidBrush)
      13. Private Sub ListBox_SelectedIndexChanged( _
      14. ByVal sender As Object, ByVal e As EventArgs) Handles ListBox1.SelectedIndexChanged
      15. OnColorChanged(New ColorChangedEventArgs(_Brushes(ListBox1.SelectedIndex).Color))
      16. End Sub

      Jetzt kann auch ein Erbe das Event raushauen:

      VB.NET-Quellcode

      1. Public Class uclColorSelectorEx : Inherits uclColorSelector
      2. Private Sub AnythingHappens()
      3. Me.OnColorChanged(New ColorChangedEventArgs(Color.Blue))
      4. End Sub
      5. End Class
      Beachte auch die Unterscheidung zwischen dem ungenerischen DelegatTypen EventHandler aus der ersten Variante und dem hiesigen, generischen DelegatTypen EventHandler(Of ColorChangedEventArgs)

      BenamungsRichtlinie
      Obiges ist ein voll ausprogrammiertes Beispiel eines Events, und folgt den MS-Richtlinien, sowohl was die systematische Benamung aller beteiligten Elemente betrifft, als auch die Signaturen des Events und der OnEvent-Methode.

      In der Praxis muß man entscheiden, wieviel Aufwand erforderlich ist. Tatsächlich ist erstere Variante - das SelectedColorChanged-Event ohne selbstdefiniertes EventArgs - etwas flexibler (die SelectedColor-Property braucht man ja eh) und es unterstützt so nebenbei Databinding (vorrausgesetzt man hat korrekt systematisch benamt).
      Aber Databinding ist auch ein anderes Tutorial (naja - ich hab trotzdem eine Databinderei im Sample eingebaut)
      Dateien
      • EventDemo.zip

        (18,16 kB, 252 mal heruntergeladen, zuletzt: )

      Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()

      Video for Beginners

      Hier hänge ich mal ein Video an über die 4 Varianten, wie VisualStudio den Programmierer beim Abonnieren eines Events unterstützt:
      1. Default-Event, generiert durch Doppelklick im Form-Designer
      2. ausgewähltes Event aus der Event-Ansicht des Eigenschaften-Fensters
      3. Auswahl von WithEvents-Variablen im Code-Editor
      4. Abonnieren per AddHandler-Schlüsselwort


      (Video: Events abonnieren)

      anbei noch das (komische) Demo-Projekt. Naja - alles notwendige dazu ist ja im Video gesagt.
      Dateien

      Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „ErfinderDesRades“ ()