Posts Tagged: ‘event handling’

Event-handling with LotusScript Part II – Inheritance

18. Juni 2014 Posted by Björn Großewinkelmann

So, let's try it in English this time!

First of all, sorry for the delay - I know I've promised to get this post out to you in a weeks time when I wrote the first blog post in this series "Event Handling mit LotusScript Teil I - Persistente Event-Handler" and now it's been FOUR weeks...Sch?chtern  But to make this up to you I will publish the first blog post in English as well - let's say within the next four weeks Winken.

But now, let's get started so I can show you how event-handlers can also profit from inheritance in LotusScript!

Once you have a class that implements custom event-handlers you should know how its instances behave in case the class is part of an inheritance hierarchy. There are a couple of things you have to consider when designing inheritance hierarchies in general and inheritance with event-handlers in particular.

Flow Control

As briefly mentioned in the first part of this series: you can in fact bind multiple event-handlers to the same event. In case of Query-Events the triggering command (Open, Save, Send, Close) will only be executed if none of the bound handlers set ‘Continue’ to False. Since ‘Continue’ is declared as a Variant (which of course is a reference type) it is shared between all methods that handle the event. So if you set ‘Continue’ to True it will be true for all handlers from there on until another handler sets it to false again. Since you can’t be (reasonably) sure in which order your bound event-handlers are executed you really shouldn’t build handlers that depend on each other or use the ‘Continue’ to communicate. Fortunately this is not how inherited event-handlers work.

Inheritance

If your class overwrites an event-handler which it inherited from some base class only the child event-handler will be triggered when the event fires. The event-handler of the base class will be ignored completely. It does not matter where you bind the handler, in the base class or the child. Both times only the child event-handler will be bound.

Let’s take a look at an example (Code 1), the following code defines a controller base class:

 

Public Class UIDocControllerBase

 

           Private p_uidoc As NotesUIDocument

 

           Public Property Get UIDoc() As NotesUIDocument

                      Set UIDoc = p_uidoc

           End Property

 

           Public Sub New( pUIDoc As NotesUIDocument )

                      Set p_uidoc = pUIDoc

           End Sub

     

           Public Sub BindEvents()

                      On Event PostOpen From UIDoc Call PostOpen

                      On Event QueryClose From UIDoc Call QueryClose

                      On Event QuerySave From UIDoc Call QuerySave

           End Sub

     

           Public Sub PostOpen( Source As NotesUIDocument )

                      Print "BASE: Post Open"

           End Sub

     

           Public Sub QueryClose( Source As NotesUIDocument, Continue As Variant )

                      Print "BASE: Query Close"

           End Sub

     

           Public Sub QuerySave( Source As NotesUIDocument, Continue As Variant )

                      Print "BASE: Query Save"

           End Sub

     

           Public Sub Delete()

                      Print "Delete: UIDocControllerBase"

           End Sub

     

End Class

 

The class implements handlers for the PostOpen, QueryClose and QuerySave events. In addition it implements the BindEvents-Method which takes care of the event binding.

The following code (Code 2) defines a class CompanyUIDocController which inherits from the UIDocControllerBase class. It implements its own handlers for the QueryClose and PostOpen events, it does however not overwrite its parent’s QuerySave event-handler nor does it overwrite the BindEvents-Method.

 

Public Class CompanyUIDocController As UIDocControllerBase

     

           Public Sub New( pUIDoc As NotesUIDocument )

           End Sub

 

           Public Sub QueryClose( Source As NotesUIDocument, Continue As Variant )

                      Call UIDocControllerBase..QueryClose( Source, Continue )

                      Print "CompanyUIDocController: QueryClose"

           End Sub

     

           Public Sub PostOpen( Source As NotesUIDocument )

                      Print "CompanyUIDocController: PostOpen"

           End Sub

                       

           Public Sub Delete()

                      Print "Delete: CompanyUIDocController"

           End Sub

           

End Class

 

If you now substitute the class definition from the first blog entry of this series with the two classes of Code 1 and Code 2 and then Open, Save and finally Close the corresponding document the output will be as follows:

Fig. 1: Example output

 

Analysis

First the BindEvents-Method is called and binds the events of the NotesUIDocument to all not overridden event-handlers. That means that the PostOpen and QueryClose events are bound to the CompanyUIDocController class while the QuerySave event is bound to the base class. When we trigger the events those and only those event-handler are being executed. The QueryClose event-handler in the base class is only executed because we are explicitly calling it inside the QueryClose event-handler of CompanyUIDocController by using the “..”-notation in the function call.

     Call UIDocControllerBase..QueryClose( Source, Continue )

A call like that will always refer to the base-instance instead of the “youngest”-instance like

     Call me.QueryClose( Source, Continue )

would. And before you get any ideas both of the above calls are illegal in the event binding definition!


The only two methods which automatically call their equivalents in the base instance(s) are New and Delete. If you wish to cascade any other function calls you have to do it explicitly.
 

In conclusion, if you want to implement some functionality which is the same for all of your derived classes that code should go in the base class event-handler. On the other hand  any functionality that deals with issues specific to your current class should be located within the derived class. An example would be a base class that implements a function which forces the user to write a comment during the QuerySave event. More specific functionality like updating a parent document which is only needed on a specific form would go in the event-hander of the derived class. Just remember that you have to call the base class handler explicitly if you need the base functinality too.

If you have any more questions concerning event handling in LotusScript, please leave us a comment so we can expand this mini-series a little further. 

 

Event Handling mit LotusScript Teil I – Persistente Event-Handler

2. Juni 2014 Posted by Björn Großewinkelmann

In dieser Beitragsreihe möchte ich euch eine Einführung in das Event Handling mit objektorientiertem LotusScript geben. In dem heutigen Artikel geht es um das Erstellen persistenter Event-Handler. In der nächsten Woche sprechen wir dann über Event-Handler in Vererbungshierarchien.

In beinahe allen Projekten, die mit objektorientiertem LotusScript umgesetzt werden, erstellt sich der Entwickler eine eigene Wrapperklasse für NotesUIDocument, oft eine pro Maske. Im Falle von größeren Projekten kommt dann noch zusätzlich Vererbung ins Spiel. Sobald ein Projekt eine gewisse Größe überschritten hat, steht man oft vor dem Problem wie man den Code nicht nur effizient sondern auch wartbar organisiert. Dem Event Handling kommt hierbei eine besondere Bedeutung zuteil, da man schnell versucht ist, die in der Maske eingebetteten Routinen zu benutzen. Dies trägt im besten Fall zwar nur zur allgemeinen Unübersichtlichkeit bei; im schlimmsten Fall führt es aber dazu, dass man Code dupliziert, was am Ende den Wartungsaufwand potenziert.

Unter diesen Gesichtspunkten macht es immer Sinn, sich eigene Event Handler zu schreiben, um sich insbesondere Vererbungshierarchien zu Nutze zu machen.

Im folgenden Beispiel benutze ich einen sehr generischen Wrapper, welcher das Event Handling für UI-Dokumente der Maske „Company“ übernimmt. Auf Besonderheiten wie Error-Traps werde ich der Übersichtlichkeit halber in diesem Beitrag verzichten.

 

Public Class CompanyUIDocController

     

      Private p_uidoc As NotesUIDocument

     

      Public Property Get UIDoc As NotesUIDocument

                         Set UIDoc = p_uidoc

      End Property

     

      Public Sub New( pUIDoc As NotesUIDocument )

                         Set p_uidoc = pUIDoc

      End Sub

     

End Class

 

Die Klasse besteht zunächst einmal nur aus ihrem Konstruktor, welcher als Parameter das NotesUIDocument erwartet und dieses über die Property UIDoc nach außen zugänglich macht. Nun möchten wir diese Klasse zum Handler für die Events machen, die von der NotesUIDocument-Instanz gefeuert werden. Dazu müssen wir zunächst einmal dafür sorgen, dass uns eine persistente Instanz der Klasse zur Verfügung steht. Wir brauchen also eine Instanz, welche beim Erstellen der unterliegenden NotesUIDocument-Instanz, sprich dem Öffnen des Dokumentes, einmal instanziert und erst recyclet wird, nachdem das Dokument geschlossen wurde. Dafür benutzen wir die globalen Definitionen der entsprechenden Maske.

Zunächst wird wie üblich die benutze Skript-Bibliothek eingebunden (siehe Abb. 1). Danach deklarieren wir eine private Variable p_this vom Typ unserer Controller-Klasse (siehe Abb. 2).

 

Abb. 1: Options Abb. 2: Declarations


















 

Diese Variable dient als Backend für eine Property desselben Typs. Die notwendige Objekt-Referenz für p_this  (siehe Abb. 3) wird mit dem Sub InitThis (siehe Abb. 4) erzeugt.

 

 
Abb. 3: This  
 
Abb. 4: InitThis  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

In dem in der Maske eingebetteten QueryOpen-Handler schließlich wird unsere CompanyUIDocController-Klasse, durch Aufruf von InitThis, instanziiert und die Referenz in p_this gespeichert (siehe Abb. 5).

 

Abb. 5: der eingebettete QueryOpen-Event Handler

 

An diesem Punkt angekommen werden wir mit der ersten Einschränkung dieses Ansatzes konfrontiert. Die notwendige Instanz von CompanyUIDocController kann logischerweise nicht vor dem Feuern des QueryOpen-Events erzeugt werden. Dementsprechend kann unser zukünftiger Event-Handler das QueryOpen-Event nicht automatisch verarbeiten sondern der ensprechende Handler muss manuell aufgerufen werden.

Zunächst einmal haben wir an dieser Stelle aber unser erstes Teilziel erreicht. Wir verfügen nun über eine persistente Instanz der Klasse CompanyUIDocController. Diese Instanz wird solange referenziert sein wie die unterliegende NotesUIDocument-Instanz existiert, also das aktuelle Dokument vom Typ „Company“ geöffnet ist. Testen können wir das ganze durch Überschreiben des Destruktors der Klasse CompanyUIDocController.

 

Public Class CompanyUIDocController

     

      Private p_uidoc As NotesUIDocument

     

      Public Property Get UIDoc As NotesUIDocument

                         Set UIDoc = p_uidoc

      End Property

     

      Public Sub New( pUIDoc As NotesUIDocument )

                         Set p_uidoc = pUIDoc

      End Sub

           

      Public Sub Delete()

            Print "Deleting instance of CompanyUIDocController"

      End Sub

     

End Class

 

Delete wird erst beim Schließen des Dokumentes aufgerufen, da erst dann die in p_this gespeicherte Instanz dereferenziert und damit das Objekt invalidiert und vom Garbage Collector recycelt wird.

Nachdem wir also nun diesen ganzen Aufwand betrieben haben, um eine persistente Instanz unserer Controller-Klasse zu erhalten, sollten wir auch etwas damit anfangen. Zunächst einmal in aller Kürze die Grundlagen:

 On Event <Event> From <Instance> Call <Handler>

Mit demOn Event“-Statement wird angezeigt, dass das Event <Event>, welches von dem Objekt <Instance> gefeuert wird, durch die Methode <Handler> verarbeitet werden soll. Die Signatur der Handler-Methode ist dabei fix und durch das zu verarbeitende Event vorgegeben, dementsprechend wird unter <Handler> nur der Methodenname angegeben, keine Parameter.

Nun erweitern wir unsere Controller-Klasse um einen Handler für das QuerySave-Event und eine Methode BindEvents, welche das Binden des Events an den entsprechenden Handler für uns erledigt:

 

Public Class CompanyUIDocController

     

      Private p_uidoc As NotesUIDocument

     

      Public Property Get UIDoc As NotesUIDocument

                          Set UIDoc = p_uidoc

      End Property

     

      Public Sub New( pUIDoc As NotesUIDocument )

                          Set p_uidoc = pUIDoc

      End Sub

 

      Public Sub BindEvents()

                          On Event QuerySave From UIDoc Call QuerySave

      End Sub

     

      Private Sub QuerySave( Source As NotesUIDocument, Continue As Variant )

                          Print "CompanyUIDocController: Query Save"

      End Sub

     

End Class

 

Abschließend ergänzen wir den in der Maske eingebetteten QueryOpen-Event Handler um den Aufruf von BindEvents, damit die Events an unsere persistente Instanz gebunden werden.

 

Abb. 6: der eingebettete QueryOpen-Event Handler mit Aufruf von BindEvents()

 

Fertig!

Von nun an wird das QuerySave-Event bei jedem Öffnen eines Dokumentes in der Maske Firma automatisch immer an eine, für diese NotesUIDocument-Instanz spezifische, This-Instanz gebunden und in dieser verarbeitet. Der gesamte Code zum Event Handling liegt nun in der Skript Bibliothek und die entsprechenden Routinen in der Maske müssen nie wieder angefasst werden. Sollen auch andere Events verarbeitet werden genügt es lediglich die entsprechenden Handler in der CompanyUIDocController-Klasse zu definieren und in der Methode BindEvents zu binden.

In der nächsten Woche erzähle ich euch dann, wie Event Handler von Vererbungshierarchien profitieren können. Man darf also gespannt sein!