Seite 1 von 1

Event-Handling (wie in DotNET)

Verfasst: 15.04.2008 18:02
von Leonhard
Ich habe hier mal versucht, EventHandling in PureBasic im Styl von DotNET zu verwirklichen. Das Beispiel hat evtl. in PureBasic nicht richtig den Sinn getroffen, aber man kann verstehen, was der Sinn von EventHandling ist. Wenn man noch spaß hat, kann man sich eine Window-Lib basteln (sollte nicht allzu schwer sein, wenn man sich in der API auskennt).

Code: Alles auswählen

;-{ Definition von Macros, die verwendet werden

Macro false : 0 : EndMacro
Macro true : 1 : EndMacro
Macro nil : 0 : EndMacro
Macro null : 0 : EndMacro

Macro Is(expression)
  ( false Or (expression) )
EndMacro

;}

;-{ EventArg (für den *e - Parameter)

;/ Diese Struktur dient dazu, wenn Default-Argumente für Events
;/ definiert werden sollen. Bis jetzt war dies nicht nötig.
;/ (bitte bei Veröffendlichten Projekten mit dieser Lib nicht diese
;/  Struktur verändern !!! - dies führt zu Komplikationen)
Structure EventArgAtom
EndStructure

;# <description>
;#   Gibt die Struktur des Event-Argument zurück.
;# </description>
;# <param name="EventArgName" type="identifier">
;#   Der Name des Event-Argument.
;# </param>
;# <return type="identifier<structure>">
;#   Die Struktur des Event-Argument.
;# </return>
Macro eArg(EventArgName)
  EventArg_#EventArgName
EndMacro

;# <description>
;#   Definiert ein neues Event-Argument.
;# </description>
;# <param name="EventArgName" type="identifier">
;#   Der Name des neuen Event-Attributes.
;# </param>
;# <param name="m_Extends" type="identifier">
;#   Name des EventArgument, von dem die Argumente "geerbt" werden.
;# </param>
Macro EventArg(EventArgName, m_Extends = EventArgAtom)
  CompilerIf Defined(eArg(EventArgName), #PB_Structure) = 0
  Structure eArg(EventArgName) Extends m_Extends
EndMacro
Macro EndEventArg
  EndStructure
  CompilerEndIf
EndMacro

;}

;-{ Event

;/ die Event-Call-Funktion
Prototype Event_Callback(*sender, *e)

;/ Ein Element aus der Liste der Call-Funktionen
Structure EventItem
;// Listen-Verknüpfung
  *Prev.EventItem ;/ das vorherige Element
  *Next.EventItem ;/ das nächste Element
  
;// Listen-Inhalt
  Call.Event_Callback ;/ die Call-Funktion dieses Elementes
EndStructure

;/ Die Event-Struktur, die pro Event abgespeichert wird
Structure Event
  ;/ für Debug-Zwecke ist es hier, wenn gewollt, möglich, den Namen des Eventes abzulegen
  CompilerIf #PB_Compiler_Debugger
  EventName.s
  CompilerEndIf
  
  *list.EventItem ;/ Die Liste der Call-Funktionen
EndStructure

;# <description>
;#   Erstellt eine neue Event-Liste.
;# </description>
Macro NewEvent()
  AllocateMemory(SizeOf(Event))
EndMacro

Procedure.l NextEventCall(*event.Event) ;/ springt (wenn möglich) zum nächsten Element der Liste
  If *event\list\Next
    *event\list = *event\list\Next
    
    ProcedureReturn true
  EndIf
EndProcedure
Procedure.l PreviousEventCall(*event.Event) ;/ springt (wenn möglich) zum vorherigen Element der Liste
  If *event\list\Prev
    *event\list = *event\list\Prev
    
    ProcedureReturn true
  EndIf
EndProcedure
Procedure.l FirstEventCall(*event.Event) ;/ springt (wenn möglich) zum ersten Element der Liste
  If *event\list
    While PreviousEventCall(*event)
    Wend
    
    ProcedureReturn true
  EndIf
EndProcedure
Procedure.l LastEventCall(*event.Event) ;/ springt (wenn möglich) zum letzten Element der Liste
  If *event\list
    While NextEventCall(*event)
    Wend
    
    ProcedureReturn true
  EndIf
EndProcedure
Procedure AddEventCall(*event.Event, *call.Event_Callback) ;/ fügt eine neue Call-Funktion zur Event-Liste hinzu
  If *event
    ;/ erstelle neues Listen-Element
    Protected *newItem.EventItem = AllocateMemory(SizeOf(EventItem))
    *newItem\Call = *call
    
    ;/ verknüpfe Listen-Element
    If *event\list <> null
      If *event\list\Next
        *newItem\Next = *event\list\Next
        *event\list\Next\Prev = *newItem
      EndIf
      
      *newItem\Prev = *event\list
      *event\list\Next = *newItem
    EndIf
    
    ;/ setzte das neue Listen-Elemet als aktuelles
    *event\list = *newItem
  EndIf
EndProcedure
Procedure DeleteEventCall(*event.Event) ;/ entfernt die akturelle Call-Funktion der Event-Liste
  If *event And *event\list
    Protected *tmpItem.EventItem = *event\list
    
    If *event\list\Next
      If *event\list\Prev
        ;/ verfollständige Verknüpfung
        *event\list\Next\Prev = *event\list\Prev
        *event\list\Prev\Next = *event\list\Next
        
        ;/ setzte neues aktuelles Element
        *event\list = *event\list\Prev
      Else
        ;/ verfollständige Verknüpfung
        *event\list\Next\Prev = null
        
        ;/ setzte neues aktuelles Element
        *event\list = *event\list\Next
      EndIf
    ElseIf *event\list\Prev
      ;/ verfollständige Verknüpfung
      *event\list\Prev\Next = null
      
      ;/ setzte neues aktuelles Element
      *event\list = *event\list\Prev
    Else
      ;/ setzte neues aktuelles Element
      *event\list = null
    EndIf
    
    ;/ gebe das aktuelle Element frei
    FreeMemory(*tmpItem)
  EndIf
EndProcedure

;# <description>
;#   Dient zum Durchlauf der Event-Aufrufe in der Event-Call-Liste.
;# </description>
Macro ForEach_Event(m_Event)
  If m_Event And m_Event\list
    FirstEventCall(m_Event)
    While (m_Event\list)
EndMacro
Macro Next_Event(m_Event)
      NextEventCall(m_Event)
    Wend
  EndIf
EndMacro

;# <description>
;#   Gibt alle angegebenen Event-Aufrufe für dieses Event frei.
;# </description>
;# <param attr="(pointer)" name="event" type="Event">
;#   Der Pointer des Events.
;# </param>
Procedure ClearEventCallList(*event.Event)
  If *event And *event\list
    ;/ springe zum ersten Element der Liste
    FirstEventCall(*event)
    
    ;/ durchlaufe die Liste und lösche die Call-Aufrufe
    While (Not *event\list = null)
      NextEventCall(*event)
      
      FreeMemory(*event\list\Prev)
    Wend
  EndIf
EndProcedure

;# <description>
;#   Gibt das angegebene Event wieder frei.
;# </description>
;# <param attr="(pointer)" name="event" type="Event">
;#   Der Pointer des Events.
;# </param>
Macro FreeEvent(m_Event)
  ClearEventCallList(m_Event)
  FreeMemory(m_Event)
EndMacro

;}

;# <description>
;#   Mit dieser Funktion ruft man einen Event auf (alle Call-Proceduren, die in die Liste eingeschrieben wurden).
;# </description>
;# <param attr="(pointer)" name="event" type="Event">
;#   Der Pointer des Events.
;# </param>
;# <param attr="(sender)" name="sender" type="void">
;#   Das Objekt, dem die Event-Liste angehört.
;# </param>
;# <param attr="(pointer)" name="e" type="EventArgAtom">
;#   Der Event-Argumen-Parameter.
;# </param>
Procedure CallEvent(*event.Event, *sender, *e.EventArgAtom)
  If *event And *event\list
    ;/ Springe zum ersten Element der Liste
    While *event\list\Prev <> null
      *event\list = *event\list\Prev
    Wend
    
    ;/ durchlaufe die Liste des Eventes und rufe jeden Funktion auf
    While *event\list
      ;/ rufe den Event auf
      *event\list\Call(*sender, *e)
      
      ;/ nächstes Element der Liste
      If *event\list\Next = null : Break : EndIf
      *event\list = *event\list\Next
    Wend
  EndIf
EndProcedure

;-/ Default-Typen
;# <description>
;#   Dies ist das Default-Event. Es wird dann verwendet, wenn keine Event-Attribute angegeben werden.
;# </description>
EventArg(Empty)
EndEventArg
Beispiel:

Code: Alles auswählen

;{ Application

Enumeration ;#Application_Close_Reason_*
  #Application_Close_Reason_Unknow = 0;/ unbekannter Schließe-Grund
  #Application_Close_Reason_CloseWindow
  ;[...]
EndEnumeration
EventArg(Application_Close)
  Reason.b ;/ der Grund, warum das Fenster beschlossen werden soll
  BreakClose.b ;/ ob das Schließen des Programms abgebrochen werden soll.
EndEventArg
EventArg(Application_Closing)
  Reason.b ;/ der Grund, warum das Fenster beschlossen werden soll
EndEventArg

Structure Application
  *Close.Event ;*e.eArg(Application_Close) ;/ ob die Application geschlossen werden soll
  *Closing.Event ;*e.eArg(Application_Closing) ;/ die Application wird geschlossen
EndStructure

;/ der lSize - Parameter ist für die "Vererbung" gedacht
Procedure NewApplication(lSize.l = SizeOf(Application))
  If lSize < SizeOf(Application) : lSize = SizeOf(Application) : EndIf
  Protected *this.Application = AllocateMemory(lSize)
  With *this
    \Close = NewEvent()
    \Closing = NewEvent()
  EndWith
  ProcedureReturn *this
EndProcedure

Procedure FreeApplication(*this.Application)
  If *this
    With *this
      FreeEvent(\Close)
      FreeEvent(\Closing)
    EndWith
    
    FreeMemory(*this)
  EndIf
EndProcedure
;}

Procedure WaitWindow(*Application.Application)
  Protected lEvent.l
  Protected *EventArg_Close.eArg(Application_Close)
  Protected EventArg_Closing.eArg(Application_Closing)
  
  Repeat
    lEvent = WaitWindowEvent()
    
    Select lEvent
    Case #PB_Event_CloseWindow ;{
      ;/ definiere die Event-Argument-Struktur für Event-Aufruf vor
      *EventArg_Close = AllocateMemory(SizeOf(eArg(Application_Close)))
      *EventArg_Close\Reason = #Application_Close_Reason_CloseWindow
      *EventArg_Close\BreakClose = false
      
      ;/ rufe ab, ob das Programm beendet werden soll
      CallEvent(*Application\Close, *Application, *EventArg_Close)
      
      ;/ rufe die "Rückgabe" der Events zurück
      If *EventArg_Close\BreakClose = false
        EventArg_Closing\Reason = *EventArg_Close\Reason
        
        FreeMemory(*EventArg_Close)
        
        Break
      EndIf
      
      FreeMemory(*EventArg_Close)
    ;}
    EndSelect
  ForEver
  
  CallEvent(*Application\Closing, *Application, @EventArg_Closing)
EndProcedure

;[...] - noch mehr Libs z.B. eine Window-Lib

Enumeration ;#Window_*
  #Window_Main
EndEnumeration

Procedure Application_Close(*sender.Application, *e.eArg(Application_Close))
  If *e\Reason = #Application_Close_Reason_CloseWindow
    If MessageRequester("Programm beenden", "Soll das Programm wirklich beendet werden?", #PB_MessageRequester_YesNoCancel) <> #PB_MessageRequester_Yes
      *e\BreakClose = true
    EndIf
  EndIf
EndProcedure
Procedure Application_Closing(*sender.Application, *e.eArg(Application_Closing))
  CloseWindow(#Window_Main)
EndProcedure

;/ definiere Programm-Struktur
Define *MyApplication.Application = NewApplication()
AddEventCall(*MyApplication\Close, @Application_Close())
AddEventCall(*MyApplication\Closing, @Application_Closing())

;/ erstelle Fenster
OpenWindow(#Window_Main, #PB_Ignore, #PB_Ignore, 150, 100, "<title>", #PB_Window_SystemMenu)

;/ warte, bis Windows geschlossen wird (alle Aktionen sollten per Event-Handling geregelt werden)
WaitWindow(*MyApplication)

End

Verfasst: 15.04.2008 20:08
von Hroudtwolf
Servus,
Das schaut ja schonmal Klasse aus.
Echt cool. Werde mich damit mal näher beschäftigen.

Fast sowas hab ich vor ein paar Tagen experimentellerweise auch gemacht.
http://www.purebasic-lounge.com/viewtopic.php?t=5256

MfG

Wolf

Verfasst: 16.04.2008 18:56
von Leonhard
Du arbeitest wohl viel mit Interfaces, hm?
Der Code ist schon interesant. Ich persöhnlich stoße Interfaces ab, da man damit nicht auf die Variablen der Klasse zugreifen kann.

Aber ist ja egal. Der Code von mir ist ganz nah an DotNET gebunden bzw. Ist dies der kleiche aufbau (soweit ich ihn durchleuchten konnte).

Wenn du willst, kannst du dir mal die Form anschauen, wie ich die "Virtuellen" Klassen in PB schreibe (anhand von 'Application').

Verfasst: 16.04.2008 19:38
von NicTheQuick
Damit bei 'LastEventCall()' und 'FirstEventCall()' nicht mit der While-Schleife
durchgerattert wird, würde es sich doch eher anbieten in der 'Event'-Struktur
noch ein '*first.EventItem' und '*last.EventItem' einzubauen, damit diese
direkt angesprungen werden können. So mache ich es zumindest immer,
wenn ich mal wieder eine LinkedList brauche.

Verfasst: 18.04.2008 14:56
von Leonhard
Das mach ich auch immer. Doch frag ich mich, ob dies wirklich nötig ist. Man verwendet 8 Byte (bei 64-Bit-Programm 16), die eigendlich kaum verwendet werden. Ich meine nur, das eigendlich, wenn man diese Lib in OOP-Klassen verwendet, dann verwendet man eigendlich, wenn es hoch kommt, 5 Funktionen. Meistens ist es so, das bestimmte Events einfach nicht verwendet werden, und da lohnt es sich IMHO nicht. Z.B. gibt es bein Windows rund 1023 Window-Events. Wenn man jetzt ein WindowCallback mit Events bestücken würde, währen da eine mänge Ressourcen (im Vorfeld 8184 Byte, bei 64-Bit das doppelte) belegt.

Oder bist du da anderer Meinung?

Es währe doch ziemlich nerfig, immer, bevor man einen Event-Call hinzufügt, überprüfen zu müssen, ob der Event schon erstellt wurde.

Verfasst: 19.04.2008 22:06
von mk-soft
Leonhard hat geschrieben:Ich persöhnlich stoße Interfaces ab, da man damit nicht auf die Variablen der Klasse zugreifen kann.
Das ist ja der sinn von den Klassen (Kapselung der Daten)

Hab ihr mal den OOP-PreCompiler von mir geteset. braucht dann die Interfaces nicht mehr selber anlegen.

FF :wink:

Verfasst: 21.04.2008 11:37
von inc.
Leonhard hat geschrieben:Der Code ist schon interesant. Ich persöhnlich stoße Interfaces ab, da man damit nicht auf die Variablen der Klasse zugreifen kann.
Zumal dann alles nicht mehr COM kompatibel ist. Was nützt es mir eine Klasse in PB zu erstellen, die ich sodann nicht exportieren und in einer anderen Umgebung aufrufen kann.

Verfasst: 21.04.2008 14:16
von Leonhard
Bis jetzt habe ich noch nicht mit COM-Verbindungen auseinandergesetzt bzw. hatte eine Veringung über eine Schnittstelle noch nicht nötig. Natürlich bin ich daran interesiert, aber möchte ich nicht die einfache Daten-Handlung wie bis jetzt verlieren.

Verfasst: 21.04.2008 14:40
von mk-soft
inc. hat geschrieben:Zumal dann alles nicht mehr COM kompatibel ist. Was nützt es mir eine Klasse in PB zu erstellen, die ich sodann nicht exportieren und in einer anderen Umgebung aufrufen kann.
PB Interfaces sind COM kompatibel. Bin zwar noch nicht weiter gekommen, aber der Precompiler soll noch so erweitert werden COM DLLs erzeugen kann.

Verfasst: 21.04.2008 16:31
von inc.
mk-soft hat geschrieben:PB Interfaces sind COM kompatibel. Bin zwar noch nicht weiter gekommen, aber der Precompiler soll noch so erweitert werden COM DLLs erzeugen kann.
Das ist mir klar, aber Leonhard "stösst ja Interfaces" ab, und wenn er's sodann eben anders macht gibts Probleme.

Da das oben aber recht kompliziert ausschaut (und ich rede jetzt auch nur von den "Beispiel"), werden wohl fast alle PB Nutzer an der gewohnten PB-nativen Event Vorgehensweise festhalten. Keine Kritik, sondern nur eine Vermutung.