Events mit beliebig vielen EventHandlern

Übersicht BlitzMax, BlitzMax NG Codearchiv & Module

Neue Antwort erstellen

Jolinah

Betreff: Events mit beliebig vielen EventHandlern

BeitragMi, März 12, 2014 21:36
Antworten mit Zitat
Benutzer-Profile anzeigen
Hallo zusammen

Vielleicht gibt es so etwas bereits, aber egal...

Ich habe 3 kleine Types erstellt, mit denen man Events ähnlich wie in .NET verwenden kann. Ein Event hat beliebig viele Event-Handler die alle aufgerufen werden, sobald der Event ausgelöst wird.

Es werden sowohl Funktionen wie auch Methoden unterstützt.

Im wesentlichen erstellt man zuerst einen Event. Diesem können Handler hinzugefügt oder auch wieder entfernt werden. Und irgendwann löst man den Event aus. Alle Handler-Funktionen oder -Methoden werden aufgerufen:
BlitzMax: [AUSKLAPPEN]
Local event:TMaxEvent = New TMaxEvent
event.AddHandler(TMaxEventHandler.FuncPtr(meineFunktion))
event.AddHandler(TMaxEventHandler.MethodPtr(meinObjekt, "meineMethode"))
'Vereinfacht
event.AddHandlerFuncPtr(meineFunktion)
event.AddHandlerMethodPtr(meinObjekt, "meineMethode")
'Event auslösen
event.Raise(quellObjekt, eventArgumente)


Hier der Code:
BlitzMax: [AUSKLAPPEN]
Rem
bbdoc: Event mit beliebig vielen EventHandlern
bbdoc: Wenn der Event ausgelöst wird, werden alle aktuell hinzugefügten Handler aufgerufen
End Rem

Type TMaxEvent
Field _handlers:TList = New TList

Rem
bbdoc: Fügt einen EventHandler hinzu, der aufgerufen wird, sobald der Event ausgelöst wird
End Rem

Method AddHandler(handler:TMaxEventHandler)
If Not _handlers.Contains(handler) Then _handlers.AddLast(handler)
End Method

Rem
bbdoc: Fügt einen EventHandler hinzu, der aufgerufen wird, sobald der Event ausgelöst wird (Funktions-Zeiger)
End Rem

Method AddHandlerFuncPtr:TMaxEventHandler(funcPtr(source:Object, e:TMaxEventArgs))
Local handler:TMaxEventHandler = TMaxEventHandler.FuncPtr(funcPtr)
If handler <> Null Then _handlers.AddLast(handler)
Return handler
End Method

Rem
bbdoc: Fügt einen EventHandler hinzu, der aufgerufen wird, sobald der Event ausgelöst wird (Methoden-Zeiger)
End Rem

Method AddHandlerMethodPtr:TMaxEventHandler(obj:Object, methodName:String)
Local handler:TMaxEventHandler = TMaxEventHandler.MethodPtr(obj, methodName)
If handler <> Null Then _handlers.AddLast(handler)
Return handler
End Method

Rem
bbdoc: Entfernt einen EventHandler. Somit wird er zukünftig von diesem Event nicht mehr aufgerufen
End Rem

Method RemoveHandler(handler:TMaxEventHandler)
_handlers.Remove(handler)
End Method

Rem
bbdoc: Löst den Event aus und ruft somit alle EventHandler auf
End Rem

Method Raise(source:Object, e:TMaxEventArgs)
For Local handler:TMaxEventHandler = EachIn _handlers
handler.Invoke(source, e)
Next
End Method
End Type


Rem
bbdoc: EventHandler der entweder eine Funktion oder eine Methode eines bestimmten Objekts aufrufen kann
End Rem

Type TMaxEventHandler
Field _func(source:Object, e:TMaxEventArgs)
Field _obj:Object
Field _method:TMethod

Rem
bbdoc: Erstellt einen EventHandler der eine Funktion aufruft
End Rem

Function FuncPtr:TMaxEventHandler(func(source:Object, e:TMaxEventArgs))
If func = Null Then Return Null

Local handler:TMaxEventHandler = New TMaxEventHandler
handler._func = func
Return handler
End Function

Rem
bbdoc: Erstellt einen EventHandler der eine Methode eines bestimmten Objekts aufruft
End Rem

Function MethodPtr:TMaxEventHandler(obj:Object, methodName:String)
If obj = Null Or methodName = Null Or methodName = "" Then Return Null

Local t:TTypeId = TTypeId.ForObject(obj)
If t = Null Then Return Null

Local m:TMethod = t.FindMethod(methodName)
If m = Null Then Return Null

Local args:TTypeId[] = m.ArgTypes()
If args.Length <> 2 Then Return Null

Local tObj:TTypeId = TTypeId.ForName("Object")
If args[0].Name() <> "Object" And Not args[0].ExtendsType(tObj) Then Return Null

Local tArgs:TTypeId = TTypeId.ForName("TMaxEventArgs")
If tArgs = Null Then Return Null

If args[1].Name() <> "TMaxEventArgs" And Not args[1].ExtendsType(tArgs) Then Return Null

Local handler:TMaxEventHandler = New TMaxEventHandler
handler._obj = obj
handler._method = m
Return handler
End Function

Rem
bbdoc: Ruft den EventHandler auf
End Rem

Method Invoke(source:Object, e:TMaxEventArgs)
If _func <> Null Then
'Funktionszeiger
_func(source, e)
ElseIf _obj <> Null And _method <> Null Then
'Methoden-Zeiger
Try
_method.Invoke(_obj, [source, Object(e)])
Catch ex:Object
DebugLog("Die Methode " + _method.Name() + " in Type " + _method._selfTypeId.Name() + ..
" konnte vom EventHandler nicht aufgerufen werden, da vermutlich die Argument-Typen des EventHandlers nicht mit den~r~n" + ..
"uebergebenen Parametern bei Raise(...) uebereinstimmen. Wenn eine Methode mit abgeleiteten Argumenttypen verwendet wird" + ..
" muessen bei Raise(...) auch Objekte des~r~nentsprechenden Typs uebergeben werden. Ansonsten sollte die Grundform:" + ..
" MethodenName(source:Object, e:TMaxEventArgs) verwendet werden.")
End Try
End If
End Method
End Type


Rem
bbdoc: Argumente für einen ausgelösten Event (kann, bzw. sollte abgeleitet werden)
End Rem

Type TMaxEventArgs
End Type


Und noch ein etwas ausführlicheres Beispiel mit einem Button:
BlitzMax: [AUSKLAPPEN]
Graphics 800, 600, 0, 0

'Test-Objekt mit einer Methode namens "ButtonClicked"
Local test:TTest = New TTest

'Button erstellen
Local button:TButton = TButton.Create(200, 200, 150, 30, "Klick mich!")

'EventHandler für das Click-Ereignis hinzufügen (einmal als Function und einmal als Objekt + Methode)
button.Click.AddHandlerFuncPtr(button_Click)
button.Click.AddHandlerMethodPtr(test, "ButtonClicked")

'Hauptschleife
Local timer:TTimer = TTimer.Create(60)
Repeat
Cls

button.Update()
button.Draw()

Flip 0
timer.Wait()
Until AppTerminate() Or KeyHit(KEY_ESCAPE)

EndGraphics
End


'-----------------


'Handler-Funktion die ausgeführt wird, wenn man auf den Button klickt
Function button_Click(source:Object, eargs:TMaxEventArgs)
Local e:TClickEventArgs = TClickEventArgs(eargs)
DebugLog("1: Button wurde angeklickt bei " + e.x + ", " + e.y + " mit Button Nr. " + e.button)
End Function

'Test-Type mit der Methode "ButtonClicked"
Type TTest
Method ButtonClicked(source:Object, e:TClickEventArgs)
DebugLog("2: Button wurde angeklickt bei " + e.x + ", " + e.y + " mit Button Nr. " + e.button)
End Method
End Type

'Eigenes EventArgs, um dem Event eigene Daten übergeben zu können
Type TClickEventArgs Extends TMaxEventArgs
Field x:Float, y:Float
Field button:Int
End Type

'Button-Type mit dem Click-Event
Type TButton
Field x:Float, y:Float
Field w:Float, h:Float
Field text:String

Field Click:TMaxEvent = New TMaxEvent

Function Create:TButton(x:Float, y:Float, w:Float, h:Float, text:String)
Local b:TButton = New TButton
b.x = x; b.y = y
b.w = w; b.h = h
b.text = text
Return b
End Function

Method Update()
Local mx:Int = MouseX()
Local my:Int = MouseY()

If MouseHit(1)
If mx > x And mx < x + w And my > y And my < y + h Then
'Eigener Type um Informationen bezüglich des Events mitzugeben
Local e:TClickEventArgs = New TClickEventArgs
e.x = mx
e.y = my
e.button = 1

'Löst den Event aus
Click.Raise(Self, e)
End If
EndIf
End Method

Method Draw()
DrawRect(x, y, w, h)
SetColor(0, 0, 0)
DrawRect(x + 2, y + 2, w - 4, h - 4)
SetColor(255, 255, 255)
DrawText(text, x + (w - TextWidth(text)) * 0.5, y + (h - TextHeight(text)) * 0.5)
End Method
End Type

DivineDominion

BeitragMi, Mai 15, 2019 10:40
Antworten mit Zitat
Benutzer-Profile anzeigen
Hast du deinen Code mit bmax-ng mal ausprobiert? Im Vergleich zu meinem eigenen Code (https://www.blitzforum.de/forum/viewtopic.php?t=41017) bietest du ja auch Pointer auf Funktionen und Methoden an. Das sollte eigentlich schneller gehen als mein String-basierter lookup. Ich hatte damit aber auch so meine Schwierigkeiten, als ich wieder in bmax reinkommen wollte Smile
christian.tietze@gmail.com - https://christiantietze.de
macOS

Jolinah

BeitragMi, Mai 15, 2019 18:55
Antworten mit Zitat
Benutzer-Profile anzeigen
Bisher noch nicht, aber kann ich bei Gelegenheit gerne mal testen Wink

Die Funktionszeiger könnten tatsächlich schneller sein, so lange man nicht auf Methoden angewiesen ist.

Die Methodenzeiger verwenden jedoch auch das FindMethod von Reflection.
Der einzige Unterschied ist, dass FindMethod nicht bei jedem "Fire" aufgerufen wird, sondern nur beim Hinzufügen des Handlers.
Bei "Fire" ist dann die Referenz auf das Objekt sowie das TMethod bereits bekannt und kann daher nur noch aufgerufen werden.
Das kann von Vorteil sein, wenn Events sehr häufig aufgerufen werden (jedes Frame oder sogar mehrmals pro Frame).

Neue Antwort erstellen


Übersicht BlitzMax, BlitzMax NG Codearchiv & Module

Gehe zu:

Powered by phpBB © 2001 - 2006, phpBB Group