OO Event/Trigger Handling

TekkamanOmega

New Member
Hi,

I'm looking for a good solution to handling widget triggers and events in classes in 10.1C.

Currently I have a class frmFrame which has a Close button. (I'm trying to mimic MDI kind of behavior without having to use .NET components, just a frame with childframes is sufficient).

Code:
USING presentation.gui.Container.
USING presentation.gui.Controller.
USING presentation.gui.ToolBar.
CLASS presentation.gui.frmFrame INHERITS Container IMPLEMENTS interfaces.iFrame:
  DEFINE PROTECTED VARIABLE hContent     AS HANDLE NO-UNDO.
  DEFINE PROTECTED VARIABLE hTitle       AS HANDLE NO-UNDO.
  DEFINE PROTECTED VARIABLE hButtonMin   AS HANDLE NO-UNDO.
  DEFINE PROTECTED VARIABLE hButtonMax   AS HANDLE NO-UNDO.
  DEFINE PROTECTED VARIABLE hButtonClose AS HANDLE NO-UNDO.
  DEFINE PROTECTED VARIABLE cFrameName AS CHARACTER NO-UNDO.
  CONSTRUCTOR PROTECTED frmFrame():   
    SUPER().
  END CONSTRUCTOR.
  DESTRUCTOR frmFrame():    
    IF VALID-HANDLE(hContent) THEN DO:      
      DELETE OBJECT hButtonClose.
      DELETE OBJECT hTitle.      
      DELETE OBJECT hContent.
    END.       
  END DESTRUCTOR.
  METHOD PUBLIC VOID showFrame():
    IF VALID-HANDLE(hContent) THEN
      ASSIGN hContent:VISIBLE = TRUE.
  END METHOD.
  METHOD PROTECTED VOID loadTitleBar(cTitle AS CHAR):
    DEFINE VARIABLE iButtonX AS INTEGER NO-UNDO.
    ASSIGN hTitle = FactoryInstance:createText(hContent, "  " + cTitle, hContent:WIDTH-PIXELS - 2, 20, 0, 0, 6, 15, 1)
           iButtonX = hContent:WIDTH-PIXELS - 23
           hButtonClose = FactoryInstance:createImageButton(hContent, "graphics\button_close.bmp", 18, 16, iButtonX, 1)
 
    hTitle:MOVE-TO-BOTTOM().
  END METHOD.
  METHOD PUBLIC VOID blurFrame():
    ASSIGN hTitle:BGCOLOR = 7.
  END METHOD.
  METHOD PUBLIC VOID focusFrame():
    ASSIGN hTitle:BGCOLOR = 1.
    hContent:MOVE-TO-TOP().
  END METHOD.
  METHOD PUBLIC ToolBar loadToolBar(hParent AS HANDLE):
    RETURN NEW ToolBar().
  END METHOD.            
END CLASS.

How could I best create a Choose event for the close button and ENTRY/LEAVE events for the whole frame itself (so I can apply the focus and blur methods).

I've tried writing an EventHandler class and also tried adding TRIGGER-phrases to the CREATE widget statment in the GUIFactory.
After this failed, I found a solution on these forums where you can just add ON statments in the class itself... but since I like to work with WIDGET-HANDLES, these HANDLES aren't initialized yet when the ON statment is being processed when instanciating the CLASS.

Can anyone help me with this.

Many thanks in advance,
Tekka.
 
I'm gonna reply to my own thread, I have devised a solution to my problem. It's probably far from perfect, but it does the job which I need it to do. Maybe someone can find this solution usefull for their own projects. :)

I have one superclass Container from which all UI components need to inherit in order to get the event listener procedure. It also has a GUIFactory Singleton instance which the UI components use to produce their widget objects. The triggerEvent method needs to be overridden in all subclasses, in order to maintain a hierarchy of triggerEvent methods.
Code:
USING presentation.api.GUIFactory.
CLASS presentation.api.Container:
  DEFINE PROTECTED VARIABLE hListener AS HANDLE NO-UNDO.
  DEFINE PROTECTED VARIABLE FactoryInstance AS GUIFactory NO-UNDO.
  CONSTRUCTOR Container():
    RUN "presentation\events\EventListener" PERSISTENT SET hListener(THIS-OBJECT).
    ASSIGN FactoryInstance = GUIFactory:getInstance().    
  END CONSTRUCTOR.
  DESTRUCTOR Container():
    IF VALID-HANDLE(hListener) THEN
      DELETE PROCEDURE hListener.
  END DESTRUCTOR.
  METHOD PUBLIC VOID triggerEvent(hSource AS HANDLE, cEvent AS CHAR):
  END METHOD.
END CLASS.

The EventListener procedure listens to the request from the EventPublish procedure(later in this text) and redirects it's event to the appropriate Container.
Code:
DEFINE INPUT PARAMETER oParent AS presentation.api.Container.
PROCEDURE triggerFrameEvent:
    DEFINE INPUT PARAMETER pSource AS HANDLE NO-UNDO.
    IF VALID-OBJECT(oParent) THEN
      oParent:triggerEvent(pSource, LAST-EVENT:FUNCTION).    
END PROCEDURE.
PROCEDURE triggerQueryEvent:
    DEFINE INPUT PARAMETER pSource AS HANDLE NO-UNDO.
    IF VALID-OBJECT(oParent) THEN
      oParent:triggerEvent(pSource, LAST-EVENT:FUNCTION).    
END PROCEDURE.
/* etc ...*/

This is a short example of what the GUIFactory looks like. It produces widget-handles to widget objects which have a trigger to the EventPublish procedure.
Code:
&GLOBAL-DEFINE EventPublish "presentation\events\EventPublish"
USING presentation.api.GUIFactory.
CLASS presentation.api.GUIFactory:
  DEFINE PROTECTED STATIC VARIABLE factoryInstance AS GUIFactory NO-UNDO.
  CONSTRUCTOR PROTECTED GUIFactory():    
  END CONSTRUCTOR.
  DESTRUCTOR GUIFactory():
    DELETE OBJECT factoryInstance.
  END DESTRUCTOR.
  METHOD PUBLIC STATIC GUIFactory getInstance():
    IF NOT VALID-OBJECT(factoryInstance) THEN
      factoryInstance = NEW GUIFactory().
    RETURN CAST (factoryInstance, GUIFactory).
  END METHOD.
 
  METHOD PUBLIC WIDGET-HANDLE createButton(pParent AS HANDLE, pValue AS CHAR, pWidth AS INT, pHeight AS INT, pX AS INT, pY AS INT, pFlat AS LOG, pEvent AS CHAR):
    DEFINE VARIABLE whButton AS WIDGET-HANDLE NO-UNDO.
 
    CREATE BUTTON whButton
    ASSIGN FRAME = pParent
           FLAT-BUTTON = pFlat
           HEIGHT-PIXELS = pHeight
           LABEL = pValue
           SENSITIVE = TRUE
           VISIBLE = TRUE
           WIDTH-PIXELS = pWidth
           X = pX
           Y = pY
      TRIGGERS:
        ON CHOOSE PERSISTENT RUN {&EventPublish}(INPUT whButton, INPUT pEvent).        
      END TRIGGERS.
    RETURN whButton.
  END METHOD.
END CLASS.

The EventPublish.p procedure is nothing more then a simple publish of a certain event(which is determined in every UI component). But we need it to run persistently in the trigger of the widget created in the GUIFactory, otherwise it would be gone when the producing method of the GUIFactory ends.
Code:
DEFINE INPUT PARAMETER hSource AS HANDLE NO-UNDO.
DEFINE INPUT PARAMETER cEvent AS CHAR NO-UNDO.
IF NOT cEvent = "" AND NOT cEvent = ? THEN
  PUBLISH cEvent(INPUT hSource).
RETURN.

Now we have a UIComponent example which uses a button to fire a triggerEvent procedure and catches it in it's own class. This has an advantage that simple UI actions can stay within the same class. Other actions, such as data manipulation or service requesting need to be directed to the Controller class(not in this example) to be handled by the rest of your application.

Code:
USING presentation.api.Panel.
CLASS presentation.gui.pnlQuery INHERITS Container:
  DEFINE PRIVATE VARIABLE whButtonAdd    AS WIDGET-HANDLE NO-UNDO.
  DEFINE PRIVATE VARIABLE whToggleQuery  AS WIDGET-HANDLE NO-UNDO.
  CONSTRUCTOR pnlQuery(pParent AS WIDGET-HANDLE, pWidth AS INT, pHeight AS INT, pX AS INT, pY AS INT):
    DEFINE VARIABLE cEvent AS CHAR INITIAL "triggerQueryEvent" NO-UNDO.
    SUPER(pParent, pWidth, pHeight, pX, pY).
    SUBSCRIBE PROCEDURE hListener TO cEvent ANYWHERE.
 
    ASSIGN whToggleQuery  = FactoryInstance:createToggleBoX(whPanel, whButtonApply:X + 80, 220, cEvent)
           whButtonAdd    = FactoryInstance:createButton(whPanel, "+", 30, 20, whLabelQuery:X + whLabelQuery:WIDTH-PIXELS + 5, 220, FALSE, cEvent)
  END CONSTRUCTOR.
  DESTRUCTOR pnlQuery():
    DELETE OBJECT whButtonAdd.
    DELETE OBJECT whToggleQuery.
  END DESTRUCTOR.
  METHOD PUBLIC VOID toggleQuery():
  /* Do some stuff */
  END METHOD.
  METHOD PUBLIC OVERRIDE VOID triggerEvent(pSource AS WIDGET-HANDLE, pEvent AS CHAR):   
    SUPER:triggerEvent(pSource, pEvent).
    CASE pSource:
      WHEN whToggleQuery    THEN toggleQuery().
      WHEN whButtonAdd      THEN Application:getInstance():oControl:getQueryControl():addQuery().
 
    END CASE.
  END METHOD.
END CLASS.

So clicking button ToggleQuery or ButtonAdd would run the EventPublish procedure which broadcasts an event. This event is picked up by the EventListener procedure and redirected back to the pnlQuery object which handles it in it's triggerEvent method and takes the appropriate actions.

And that's pretty much it. It's fairly easy to comprehend, but also pretty extensive in some ways for what it's meant to do. This sample code was editted to fit into this post, so it can be subject to some errors here and there, but in my application it works fine.
I'm a fairly new Progress user so any suggestions or remarks would be more then welcome to help me improve my skills.

Thanks for reading,
Tekka.
 
Back
Top