Programmierung

Ereignisverarbeitung mit dem Entwurfsmuster Observer

In diesem Artikel möchte ich Ihnen eine Methode vorstellen, mit der Sie Oberflächenereignisse innerhalb von CONZEPT 16-Applikationen mit dem Entwurfsmuster (engl. design pattern) "Observer" (Beobachter, Listener) verarbeiten können.

Damit haben Sie die Möglichkeit mehrere Ereignisfunktionen für dasselbe Ereignis aufrufen zu können und so Applikationskomponenten, die zur gleichen Zeit die gleichen Oberflächenobjekte verwenden, unabhängig voneinander zu implementieren.

Beispielsweise können Sie das Ereignis EvtLstSelect eines DataList-Objektes in zwei unabhängigen Komponenten "A" und "B" verwenden. Komponente A könnte sich um die Aktualisierung von Steuerobjekten, wie Werkzeugleiste und Schaltflächen kümmern und Komponente B um die Anzeige zusätzlicher Informationen.

Über die nativen Funktionen WinEvtProcNameSet() und WinEvtProcNameGet() ist es bereits möglich zur Laufzeit der Applikation Ereignisfunktionen zu "registrieren" und zu ermitteln. Dabei kann allerdings pro Objekt und Ereignis immer nur eine Funktion angegeben werden.

In der am Ende des Artikels als Download bereit gestellten Prozedur befindet sich eine Implementierung des Observer/Listener-Entwurfsmusters für die CONZEPT 16-Ereignisverarbeitung bei Oberflächenobjekten.

Dabei wird beim Auslösen eines Ereignisses durch ein Oberflächenobjekt nicht nur eine Ereignisfunktion aufgerufen, sondern alle die zu diesem Zeitpunkt für dieses Ereignis in diesem Objekt registriert sind.

Die Prozedur stellt unter anderem zwei wesentliche Funktionen zur Verfügung:

  • Evt.Add(
      aWinObject : handle;    // Oberflächenobjekt
      aEvtID     : int;       // Ereignis-ID
      aCall      : alpha(61); // Ereignisfunktion
    ) : logic                 // Erfolg
  • Evt.Remove(
      aWinObject : handle;    // Oberflächenobjekt
      aEvtID     : int;       // Ereignis-ID
      aCall      : alpha(61); // Ereignisfunktion
    ) : logic                 // Erfolg

Mit der Funktion Evt.Add() kann eine Ereignisfunktion zu einem Objekt und einem Ereignis hinzugefügt und mit Evt.Remove() wieder entfernt werden.

Die beiden Funktionen haben die gleiche Signatur, also die gleichen Argument- und Rückgabetypen, wie die Funktion WinEvtProcNameSet(). Im Gegensatz zu dieser Funktion, werden die Ereignisfunktionen bei Evt.Add() und Evt.Remove() dem Oberflächenobjekt nicht direkt als Ereignisfunktion zugewiesen, sondern in einer Liste im Speicher vorgehalten und zu gegebenem Zeitpunkt aufgerufen.

Die Aufrufreihenfolge der registrierten Ereignisfunktionen hängt dabei von der Reihenfolge ihrer Registrierung ab.
Im folgenden Beispiel sind zwei unabhängige Komponenten "A" und "B" realisiert:

// +++++++++++++++++++++++++++++++++++++++++++++++++++++
// + Prozedur "Component"                              +
// +++++++++++++++++++++++++++++++++++++++++++++++++++++

@A+
@C+
@I:SysEvtInc // Ereignisverarbeitung einbinden

// +++++++++++++++++++++++++++++++++++++++++++++++++++++
// + Komponente A initialisieren                       +
// +++++++++++++++++++++++++++++++++++++++++++++++++++++

sub ComponentAInit
(
  aWinDataList : handle; // DataList
)
{
  // Ereignis hinzufügen
  aWinDataList->Evt.Add(_WinEvtLstSelect,
    'Component:EvtLstSelectA');

  // weitere Initialisierungen
  // ...
}

// +++++++++++++++++++++++++++++++++++++++++++++++++++++
// + Komponente initialisieren                         +
// +++++++++++++++++++++++++++++++++++++++++++++++++++++

sub ComponentBInit
(
  aWinDataList : handle; // DataList
)
{
  // Ereignis hinzufügen
  aWinDataList->Evt.Add(_WinEvtLstSelect,
    'Component:EvtLstSelectB');

  // weitere Initialisierungen
  // ...
}

// +++++++++++++++++++++++++++++++++++++++++++++++++++++
// + Komponente A terminieren                          +
// +++++++++++++++++++++++++++++++++++++++++++++++++++++

sub ComponentATerm
(
  aWinDataList : handle; // DataList
)
{
  // Ereignis entfernen
  aWinDataList->Evt.Remove(_WinEvtLstSelect,
    'Component:EvtLstSelectA');

  // weitere Terminierungen
  // ...
}

// +++++++++++++++++++++++++++++++++++++++++++++++++++++
// + Komponente B terminieren                          +
// +++++++++++++++++++++++++++++++++++++++++++++++++++++

sub ComponentBTerm
(
  aWinDataList : handle; // DataList
)
{
  // Ereignis entfernen
  aWinDataList->Evt.Remove(_WinEvtLstSelect,
    'Component:EvtLstSelectB');

  // weitere Terminierungen
  // ...
}

// +++++++++++++++++++++++++++++++++++++++++++++++++++++
// + Ereignis Komponente A                             +
// +++++++++++++++++++++++++++++++++++++++++++++++++++++

sub EvtLstSelectA
(
  aEvt : event; // Ereignis
  aID  : int;   // Zeile
)
: logic; // ohne Bedeutung
{
  WinDialogBox(0, 'Komponente A',
    'Zeile ' + CnvAI(aID) + ' selektiert.',
    _WinIcoInformation, _WinDialogOK, 1);

  // Verarbeitung
  // ...

  return(true);
}

// +++++++++++++++++++++++++++++++++++++++++++++++++++++
// + Ereignis Komponente B                             +
// +++++++++++++++++++++++++++++++++++++++++++++++++++++

sub EvtLstSelectB
(
  aEvt : event; // Ereignis
  aID  : int;   // Zeile
)
: logic; // ohne Bedeutung
{
  WinDialogBox(0, 'Komponente B',
    'Zeile ' + CnvAI(aID) + ' selektiert.',
    _WinIcoInformation, _WinDialogOK, 1);

  // Verarbeitung
  // ...

  return(true);
}

Beide Komponenten verwenden ein DataList-Objekt und beide registrieren sich für das Ereignis EvtLstSelect.

Innerhalb der Applikation könnten die beiden Komponenten zum Beispiel wie folgt verwendet werden:

main

  local
  {
    tWinFrame : handle;
  }

{
  Evt.Init();

  tWinFrame # WinOpen('ComponentFrame', _WinOpenDialog);

  with ComponentFrame
  {
    $:dlData->WinLstDatLineAdd('1');
    $:dlData->WinLstDatLineAdd('2');
    $:dlData->WinLstDatLineAdd('3');
    $:dlData->WinLstDatLineAdd('4');

    // Komponenten initialisieren
    ComponentAInit($:dlData);
    ComponentBInit($:dlData);

    tWinFrame->WinDialogRun(_WinDialogCenterScreen);

    // Komponenten terminieren
    ComponentBTerm($:dlData);
    ComponentATerm($:dlData);
  }

  tWinFrame->WinClose();

  Evt.Term();
}

Durch die Reihenfolge der Initialisierung der Komponenten ergibt sich auch die Reihenfolge der Aufrufe der Ereignisfunktionen: Wird eine Zeile in dem DataList-Objekt selektiert, wird zuerst die Funtion EvtLstSelectA und anschließend die Funktion EvtLstSelectB aufgerufen.

Ich konnte mit diesem Verfahren bereits in mehreren Anwendungen die Erweiterbarkeit und Wartbarkeit von unabhängigen Applikationskomponenten gewährleisten beziehnungsweise verbessern. Und vielleicht haben Sie auch die ein oder andere Idee, für die diese Technik eine mögliche Lösung darstellt.

Download

CONZEPT 16-Modul "SysEvt" SysEvt-1.3.zip (6.60 KB)
Sie müssen angemeldet sein, um die Datei herunterladen zu können.

2 Kommentare

2 Kommentare “Ereignisverarbeitung mit dem Entwurfsmuster Observer”

Kommentar abgeben