Hi!
Wichtiger Edit: Aufgrund der Diskussion dieses ersten Entwurfs habe ich das Teil neu überarbeitet. Ich finde diese Ur-Version (und die Diskussion dazu) immer noch interessant, und die neuere Version weiter unten hat den Schwerpunkt bisserl anders.
Muß man sich halt überlegen, ob man nur die neuere Version wissen will, oder sich den Thread komplett antun möchte.
[/Wichtiger Edit]
Ich denke mir grade ein Tut zum Thema Events aus, und dachte, vlt. fallen euch noch Verbesserungen ein. Und da dachte ich, man könnte besser erstmal im Vorfeld dazu herum-diskutieren, als dassichs gleich als fixnfertig bei die Tuts einstelle. Also los gehts:
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.
Jut. Was ist also ein Ereignis?
Jedem bekannt: In der Intellisense bekommt man die Ereignisse immer mit sonem kleinen Blitz-Icon angezeigt - sinnigerweise, denn Ereignisse können aus heiterem Himmel auftreten, anders als Methoden, die von irgendwoher aufgerufen werden.
Im Grunde aber sind Ereignisse nix anderes als besondere Variablen, nämlich Delegaten.
Wenn man eine Delegat-Variable mit dem Schlüsselwort "Event" deklariert, wird sie zu einem Event, und das bedeutet: Sie ist fast ReadOnly - man kann ihren Wert nur noch mit genau 3 Anweisungen verändern: AddHandler, RemoveHandler und die Handles-Klausel (eine Art initialisierendes AddHandler). (Und sie bekommt den Blitz-Icon ;)).
Was genau ist ein Delegat?
Delegaten sind Variablen, deren Datentyp nicht Double, Integer oder sonstwas ist, sondern die Signatur einer Methode.
Aha.
Und was bitte ist "Signatur einer Methode"?
So bezeichnet man ihre Aufrufe-Syntax - Beispiel:
Obige Methode erwartet 2 Argumente: Das erste vom Typ Object (kann also alles sein), das zweite vom Typ EventArgs. Die Methode gibt keinen Wert zurück.
nächstes Beispiel:
Diese Methode erwartet zwei Doubles und gibt einen zurück
Diese erwartet eine List(Of String) und einen String - und gibt einen Integer zurück.
Wie kann sowas komplexes - mehrere ArgumentTypen und ggfs. ein Rückgabewert - selbst wieder Datentyp einer Variablen sein?
Jo, is eben so - da gewöhnt man sich dran
Einen Delegaten für obige Function FindIndex() könnte man etwa so deklarieren:
Ists erkennbar, wie Func(Of List(Of String), String, Integer) genau die Signatur von FindIndex wiederspiegelt?
Der letzte TypParameter - Integer - bezeichnet bei Func(Of ...) den Rückgabewert.
Für Subs - Methoden ohne Rückgabewert - muß man halt Action-Delegaten definieren:
Func(Of ...) und Action(Of ...) sind generische Delegat-Typen, mit denen man praktisch jede Signatur spezifizieren kann.
Schön - bleiben 2 Fragen offen: a) was hat man davon? b) was hat das mit Events zu tun?
Zu a): Ein Delegat kann eine Methode aufrufen, wenn deren Signatur passt.
Ein Delegat kann sogar mehrere Methoden (fast) gleichzeitig aufrufen.
Und man kann die Methoden auch wieder entfernen:
Und das ist jetzt wirklich toll, denn hier haben wir ein fabelhaftes Instrument zur Implementation des Observer-Patterns.
Das bedeutet: Ein Objekt versendet Benachrichtigungen, und andere Objekte können diese Nachrichten abonnieren oder auch nicht.
Zum Beispiel ein Button namens Button1 versendet die Click - Nachricht, und mit folgender Methode kann man dieses Ereignis behandeln:
Hoho - da habich euch aber schön an der Nase herum geführt: vonne Ereignisse über Delegaten, Signaturen, Observer-Pattern und wieder zurück zu Ereignissen, wie sie der blutigste Anfänger schon kennt, noch bevor sein "Hallo Welt" - Programm überhaupt das Licht eben dieser erblickt hat ;).
Aber tatsächlich war das gar keine Abschweifung, weil - ich wiederhole: Events sind Delegaten. Sie unterliegen nur zusätzlichen Regeln, nämlich dass sie Readonly sind und dasses statt der wirklich abscheulichen Verknüpfungs-Syntax (das mittm DirectCast(Delegate.Combine(....),...)) super-praktische Schlüsselworte gibt: AddHandler, RemoveHandler, Handles-Klausel (und niemals nich vergesse diese hübsche Blitz-Icone ) .
Und Ereignis-Behandlung ist die einfachste und vollkommenste Implementation des Observer-Patterns, die man sich denken kann - so einfach, dass nur die wenigsten überhaupt wissen, was für einen fabelhaften Pattern sie da grade implementieren
Die Begriffe aber nochmal genau genommen
Also .Click ist das Ereignis, und das ist Member der Button-Klasse. Und Button1_Click() ist kein Event, sondern eine EventHandler-Methode (wenn sie tatsächlich das .Click-Event abonniert).
Beim Button ist .Click fertig eingebaut - da kommt man nicht ran. Aber wir können Handler-Methoden für dieses Event schreiben, oder uns sogar generieren lassen (etwa wenn wir im Form-Designer auf einen Button doppelklicken).
Mal was praktisches: Eine Richtextbox, die nur bestimmte Zeichen zulässt.
Das Problem ist, dasses nicht reicht, Tasten-Ereignisse zu überwachen, denn es kann ja auch per Drag & Drop Text eingeben werden, oder nach Guttenberg (copy & paste ;)).
Also müssen wir das TextChanged behandeln, mit folgender Tücke: Wird beim Auftreten eines unerwünschten Zeichens der Text korrigiert, so löst die Korrektur gleich wieder ein TextChanged-Ereignis aus (das ist nämlich eine EreignisKette - nicht wenn man codeseitig einen EventHandler aufruft).
Eine weitere, noch tückischere Tücke: Das TextChanged ist als Event unzureichend, denn wir wollen auch die Selection wieder herstellen, wie sie war, bevor das böse Zeichen eingegeben wurde. Also nehmen wir das SelectionChanged-Event - dort kann man ebensogut den neuen Text abrufen, und man ist zusätzlich immer über die aktuelle Selection im Bilde (und es gibt ebenso eine Ereigniskette, wenn man innerhalb des SelectionChanged-Events die Selection changed ;)).
Ja, die klugen Dinge über Observer-Pattern, und dass man Events abonnieren kann und wieder abbestellen - das wirkt sich hier sehr segensreich aus - ich glaub, ich muß den Code gar nicht weiter kommentieren:
(die SelStore-Klasse ist klar - ja? Eine Klasse, die die Selection einer Richtextbox speichern und wieder restaurieren kann)
Einzig diese (eigentlich ganz unschuldig und plausibel aussehende) Zeile
wird manchem ein Rätsel bleiben: wie zum Kuckuck kann man in einer Zeile Code alle Zeichen von .Text gegen alle Zeichen des allowed-Strings abgleichen?
Hmm - das wäre ein anderes Tutorial, nämlich zu den Themen Linq, Interfaces, Extensions, Generica, Delegaten (schon wieder!), anonymen Methoden und Type-Inference.
Naja - kurz das mit die Delegaten: .Text.All(predicate As Func(Of Char, Boolean)) erwartet einen "Bedingungs"-Delegaten, den es für jedes Zeichen aufruft, um das Zeichen mit True oder False zu "bewerten" (daher Boolean als Rückgabewert der predicate-Signatur). 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 (gewissermaßen ohne die Bedingung selbst zu kennen - listig, nicht?).
Jo, und Function(c) allowed.Contains(c) ist so ein Bedingungs-Delegat (allerdings auch noch als anonyme Methode notiert): Die Bedingung ist, dass ein Zeichen c im allowed-String enthalten ist, und das prüft .All() ab, gegen jedes im Text enthaltene Zeichen.
Naja - für wen das jetzt zu schnell war machts hoffentlich Appetit darauf, VisualBasic.Net 2008 richtig zu lernen, u.U. auch mal dieses Buch zu lesen
Wichtiger Edit: Aufgrund der Diskussion dieses ersten Entwurfs habe ich das Teil neu überarbeitet. Ich finde diese Ur-Version (und die Diskussion dazu) immer noch interessant, und die neuere Version weiter unten hat den Schwerpunkt bisserl anders.
Muß man sich halt überlegen, ob man nur die neuere Version wissen will, oder sich den Thread komplett antun möchte.
[/Wichtiger Edit]
Ich denke mir grade ein Tut zum Thema Events aus, und dachte, vlt. fallen euch noch Verbesserungen ein. Und da dachte ich, man könnte besser erstmal im Vorfeld dazu herum-diskutieren, als dassichs gleich als fixnfertig bei die Tuts einstelle. Also los gehts:
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.
Jut. Was ist also ein Ereignis?
Jedem bekannt: In der Intellisense bekommt man die Ereignisse immer mit sonem kleinen Blitz-Icon angezeigt - sinnigerweise, denn Ereignisse können aus heiterem Himmel auftreten, anders als Methoden, die von irgendwoher aufgerufen werden.
Im Grunde aber sind Ereignisse nix anderes als besondere Variablen, nämlich Delegaten.
Wenn man eine Delegat-Variable mit dem Schlüsselwort "Event" deklariert, wird sie zu einem Event, und das bedeutet: Sie ist fast ReadOnly - man kann ihren Wert nur noch mit genau 3 Anweisungen verändern: AddHandler, RemoveHandler und die Handles-Klausel (eine Art initialisierendes AddHandler). (Und sie bekommt den Blitz-Icon ;)).
Was genau ist ein Delegat?
Delegaten sind Variablen, deren Datentyp nicht Double, Integer oder sonstwas ist, sondern die Signatur einer Methode.
Aha.
Und was bitte ist "Signatur einer Methode"?
So bezeichnet man ihre Aufrufe-Syntax - Beispiel:
Obige Methode erwartet 2 Argumente: Das erste vom Typ Object (kann also alles sein), das zweite vom Typ EventArgs. Die Methode gibt keinen Wert zurück.
nächstes Beispiel:
Wie kann sowas komplexes - mehrere ArgumentTypen und ggfs. ein Rückgabewert - selbst wieder Datentyp einer Variablen sein?
Jo, is eben so - da gewöhnt man sich dran
Einen Delegaten für obige Function FindIndex() könnte man etwa so deklarieren:
Ists erkennbar, wie Func(Of List(Of String), String, Integer) genau die Signatur von FindIndex wiederspiegelt?
Der letzte TypParameter - Integer - bezeichnet bei Func(Of ...) den Rückgabewert.
Für Subs - Methoden ohne Rückgabewert - muß man halt Action-Delegaten definieren:
Func(Of ...) und Action(Of ...) sind generische Delegat-Typen, mit denen man praktisch jede Signatur spezifizieren kann.
Schön - bleiben 2 Fragen offen: a) was hat man davon? b) was hat das mit Events zu tun?
Zu a): Ein Delegat kann eine Methode aufrufen, wenn deren Signatur passt.
Ein Delegat kann sogar mehrere Methoden (fast) gleichzeitig aufrufen.
Und man kann die Methoden auch wieder entfernen:
Und das ist jetzt wirklich toll, denn hier haben wir ein fabelhaftes Instrument zur Implementation des Observer-Patterns.
Das bedeutet: Ein Objekt versendet Benachrichtigungen, und andere Objekte können diese Nachrichten abonnieren oder auch nicht.
Zum Beispiel ein Button namens Button1 versendet die Click - Nachricht, und mit folgender Methode kann man dieses Ereignis behandeln:
Hoho - da habich euch aber schön an der Nase herum geführt: vonne Ereignisse über Delegaten, Signaturen, Observer-Pattern und wieder zurück zu Ereignissen, wie sie der blutigste Anfänger schon kennt, noch bevor sein "Hallo Welt" - Programm überhaupt das Licht eben dieser erblickt hat ;).
Aber tatsächlich war das gar keine Abschweifung, weil - ich wiederhole: Events sind Delegaten. Sie unterliegen nur zusätzlichen Regeln, nämlich dass sie Readonly sind und dasses statt der wirklich abscheulichen Verknüpfungs-Syntax (das mittm DirectCast(Delegate.Combine(....),...)) super-praktische Schlüsselworte gibt: AddHandler, RemoveHandler, Handles-Klausel (und niemals nich vergesse diese hübsche Blitz-Icone ) .
Und Ereignis-Behandlung ist die einfachste und vollkommenste Implementation des Observer-Patterns, die man sich denken kann - so einfach, dass nur die wenigsten überhaupt wissen, was für einen fabelhaften Pattern sie da grade implementieren
Die Begriffe aber nochmal genau genommen
Also .Click ist das Ereignis, und das ist Member der Button-Klasse. Und Button1_Click() ist kein Event, sondern eine EventHandler-Methode (wenn sie tatsächlich das .Click-Event abonniert).
Beim Button ist .Click fertig eingebaut - da kommt man nicht ran. Aber wir können Handler-Methoden für dieses Event schreiben, oder uns sogar generieren lassen (etwa wenn wir im Form-Designer auf einen Button doppelklicken).
Mal was praktisches: Eine Richtextbox, die nur bestimmte Zeichen zulässt.
Das Problem ist, dasses nicht reicht, Tasten-Ereignisse zu überwachen, denn es kann ja auch per Drag & Drop Text eingeben werden, oder nach Guttenberg (copy & paste ;)).
Also müssen wir das TextChanged behandeln, mit folgender Tücke: Wird beim Auftreten eines unerwünschten Zeichens der Text korrigiert, so löst die Korrektur gleich wieder ein TextChanged-Ereignis aus (das ist nämlich eine EreignisKette - nicht wenn man codeseitig einen EventHandler aufruft).
Eine weitere, noch tückischere Tücke: Das TextChanged ist als Event unzureichend, denn wir wollen auch die Selection wieder herstellen, wie sie war, bevor das böse Zeichen eingegeben wurde. Also nehmen wir das SelectionChanged-Event - dort kann man ebensogut den neuen Text abrufen, und man ist zusätzlich immer über die aktuelle Selection im Bilde (und es gibt ebenso eine Ereigniskette, wenn man innerhalb des SelectionChanged-Events die Selection changed ;)).
Ja, die klugen Dinge über Observer-Pattern, und dass man Events abonnieren kann und wieder abbestellen - das wirkt sich hier sehr segensreich aus - ich glaub, ich muß den Code gar nicht weiter kommentieren:
VB.NET-Quellcode
- Public Class frmEreignisDemo
- Private _SelStore As New SelStore
- Private Sub frmEreignisDemo_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
- RichTextBox1.Text = "0"
- _SelStore.Store(RichTextBox1)
- AddHandler RichTextBox1.SelectionChanged, AddressOf RichTextBox1_SelectionChanged
- End Sub
- Private Sub RichTextBox1_SelectionChanged(ByVal sender As Object, ByVal e As EventArgs)
- Dim allowed = "0123456789"
- With RichTextBox1
- If .Text.All(Function(c) allowed.Contains(c)) Then
- _SelStore.Store(RichTextBox1)
- Else
- RemoveHandler .SelectionChanged, AddressOf RichTextBox1_SelectionChanged
- _SelStore.Restore(RichTextBox1)
- AddHandler .SelectionChanged, AddressOf RichTextBox1_SelectionChanged
- End If
- End With
- End Sub
- End Class
- Public Class SelStore
- Private Text As String
- Private Start, Length As Integer
- Public Sub Store(ByVal tb As RichTextBox)
- Text = tb.Text
- Start = tb.SelectionStart
- Length = tb.SelectionLength
- End Sub
- Public Sub Restore(ByVal tb As RichTextBox)
- tb.Text = Text
- tb.Select(Start, Length)
- End Sub
- End Class
Einzig diese (eigentlich ganz unschuldig und plausibel aussehende) Zeile
wird manchem ein Rätsel bleiben: wie zum Kuckuck kann man in einer Zeile Code alle Zeichen von .Text gegen alle Zeichen des allowed-Strings abgleichen?
Hmm - das wäre ein anderes Tutorial, nämlich zu den Themen Linq, Interfaces, Extensions, Generica, Delegaten (schon wieder!), anonymen Methoden und Type-Inference.
Naja - kurz das mit die Delegaten: .Text.All(predicate As Func(Of Char, Boolean)) erwartet einen "Bedingungs"-Delegaten, den es für jedes Zeichen aufruft, um das Zeichen mit True oder False zu "bewerten" (daher Boolean als Rückgabewert der predicate-Signatur). 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 (gewissermaßen ohne die Bedingung selbst zu kennen - listig, nicht?).
Jo, und Function(c) allowed.Contains(c) ist so ein Bedingungs-Delegat (allerdings auch noch als anonyme Methode notiert): Die Bedingung ist, dass ein Zeichen c im allowed-String enthalten ist, und das prüft .All() ab, gegen jedes im Text enthaltene Zeichen.
Naja - für wen das jetzt zu schnell war machts hoffentlich Appetit darauf, VisualBasic.Net 2008 richtig zu lernen, u.U. auch mal dieses Buch zu lesen
Dieser Beitrag wurde bereits 7 mal editiert, zuletzt von „ErfinderDesRades“ ()