[fpc-pascal] Interface delegates and the implements property specifier

Adriaan van Os fpc at microbizz.nl
Fri Dec 27 21:39:13 CET 2019


Ryan Joseph via fpc-pascal wrote:

> Adriaan, what was your idea you had in mind when you brought this up?

Well, to give you an idea, here is an example (somewhat simplified for clarity). I am currently 
porting MacApp to 64-bit. MacApp currently has

	TView = class( TEventHandler)

However, I have a TQDGraphPort class with QuickDraw emulation. For the QuickDraw emulation to work 
with current application code, TQDGraphPort methods (like TQDGraphPort.MoveTo, TQDGraphPort.LineTo, 
etcetera) have to be methods (or be in the namespace) of TView. So, I would really need

	TView = class( TQDGraphPort, TEventHandler)

The TEventHandler methods have to be methods (or be in the namespace) of TView, because existing 
application code overrides the methods of TEventHandler in objects inheriting from TView. This 
mechanism needs to continue working.

Other classes also inherit from TEventHandler: TDocument, TPrintHandler and TApplication.

I solved this now by defining four event-handling interfaces: IIdleHandler, IMenuHandler, 
IKeyHandler, IMouseHandler, all inheriting from IEventHandler, Something like:

     IEventHandler                           =
       interface
           [ 'IEventHandler']

            function GetNextEventObj         : TObject;
       end;

     IKeyHandler                             =
       interface
         ( IEventHandler)
           [ 'IKeyHandler' ]

            function DoKeyCommand
               (     theChar                 : char;
                     theKeyCode              : Int16;
                 var theEventInfo            : EventInfo): TCommand;

            function DoCommandKey
               (     theChar                 : char;
                 var theEventInfo            : EventInfo): TCommand;
       end;

etcetera. The event-handling classes can now import the interfaces:

     TApplication = class( TRefCountObj, IKeyHandler, iMenuHandler, IIdleHandler)
     TDocument = class( TDBFile, IKeyHandler, iMenuHandler, IIdleHandler)
     TView = class( TMacAppGraphPort, IKeyHandler, IMenuHandler, IIdleHandler, IMouseHandler)

etcetera. The disadvantage of this approach is that for example a default DoKeyCommand must be 
written three times, for TApplication, TDocument and TView, where the code for 
TDocument.DoKeyCommand and TView.DoKeyCommand is identical.

Identical code is clumsy. Therefore my wish that a helper object could somehow be put in the 
namespace of the importing object, including the ability to override the imported methods. I hope 
that answers you question.

I admit however that with some clever interface programming, the duplicated code can be reduced in 
the above case to a single line. As follows:

  function GetObjEventHandler
         (     theObj                  : TObject): IEventHandler;
       var
         theEventHandler               : IEventHandler;
     begin
       if ( theObj <> nil) and theObj.GetInterface
        ( IEventHandler, theEventHandler)
         then GetObjEventHandler       := theEventHandler
         else GetObjEventHandler       := nil
     end;

  function GetObjKeyHandler
         (     theObj                  : TObject): IKeyHandler;
       var
         theKeyHandler                 : IKeyHandler;
     begin
       if ( theObj <> nil) and theObj.GetInterface
        ( IKeyHandler, theKeyHandler)
         then GetObjKeyHandler         := theKeyHandler
         else GetObjKeyHandler         := nil
     end;

  function NextKeyCommand
         (     theObj                  : TObject;
               theChar                 : char;
               theKeyCode              : Int16;
           var theEventInfo            : EventInfo): TCommand;
       var
         theEventHandler               : IEventHandler;
         theKeyHandler                 : IKeyHandler;
     begin
       theKeyHandler                   := nil;
       while ( theObj <> nil) and ( theKeyHandler = nil) do
       begin
         theEventHandler               := GetObjEventHandler
           ( theObj);
         if theEventHandler = nil
           then theObj                 := nil
           else theObj                 := theEventHandler.GetNextEventObj;
         theKeyHandler                 := GetObjKeyHandler
           ( theObj)
       end;
       if theKeyHandler <> nil
         then NextKeyCommand           := theKeyHandler.DoKeyCommand
            ( theChar, theKeyCode, theEventInfo)
         else NextKeyCommand           := nil
     end;

  function TDocument.DoKeyCommand
         (     theChar                 : char;
               theKeyCode              : Int16;
           var theEventInfo            : EventInfo): TCommand;
     begin
       DoKeyCommand                    := NextKeyCommand
         ( self, theChar, theKeyCode, theEventInfo)
     end;

  function TView.DoKeyCommand
         (     theChar                 : char;
               theKeyCode              : Int16;
           var theEventInfo            : EventInfo): TCommand;
     begin
       DoKeyCommand                    := NextKeyCommand
         ( self, theChar, theKeyCode, theEventInfo)
     end;

Note that TDocument.DoKeyCommand and TView.DoKeyCommand are identical, but not larger than one 
function call.

Regards,

Adriaan van Os



More information about the fpc-pascal mailing list