Recent

Author Topic: [SOLVED] How to assign default event handler?  (Read 9922 times)

egsuh

  • Hero Member
  • *****
  • Posts: 1273
[SOLVED] How to assign default event handler?
« on: March 07, 2021, 01:35:20 am »
Hi,
Is this possible? I'd like to define default methods to events in a specific project.  Very simple example would be:

    TreeView1.OnClick := TreeView1Click(Sender: TObject);

Not in the object inspector, but in program codes.
To extend this, I'm thinking putting this in type helper.

Code: Pascal  [Select][+][-]
  1. unit raqFrame;
  2. type
  3.    HaqTreeView = class helper for TTreeView
  4.    public
  5.         function NewName: string;
  6.         procedure Click;  // <== Can I assign this function to OnClick event?
  7.    end;

I want I don't have to define any more,  with just adding the unit containing helper type definition in the uses clause.

Code: Pascal  [Select][+][-]
  1. unit fmain;
  2. interface
  3. uses
  4.   ...., raqFrame;  // <== Previous helper type is defined in the raqFrame unit.
  5.  
  6. type
  7.   TForm1 = class(TForm)
  8.     TreeView1: TTreeView;   // <== Here's TTreeView that access the helper type.
  9.   end;

Of course, I can write :

Code: Pascal  [Select][+][-]
  1. procedure TForm1.TreeView1Click(Sender: TObject);
  2. begin
  3.     TreeView1.Click;
  4. end;
« Last Edit: March 10, 2021, 03:42:11 am by egsuh »

egsuh

  • Hero Member
  • *****
  • Posts: 1273
Re: How to assign default event handler?
« Reply #1 on: March 07, 2021, 02:26:40 pm »
Quote
I think what you are looking for Is to sub class some known classes and override existing events. ?

No. Not exactly. Currently, I have to define "OnClick" event handler for any control, for example TEdit's onclick event.  Normally we do this in the object inspector, but we can assign this programmatically, like:

     Edit1.Onclick := nil;   
         or
     Edit1.OnClick := Edit1OnClick;

What I'm looking for is assigning this automatically, within constructor, etc. I will be able to do this if I define a descendant class, like following example:

Code: Pascal  [Select][+][-]
  1. interface
  2.  
  3. TMyEdit = Class(TEDIT)
  4.      Procedure DoClick(Sender:TObject);
  5.      constructor Create(Owner: TObject); override;
  6. End;
  7.  
  8. implementation
  9.  
  10. constructor TMyEdit.Create(Owner: TObject);
  11. begin
  12.       inherited Create(Owner);
  13.       OnClick:= DoClick;
  14. end;

But in this case, I think I have to register TMyEdit on the component pallette. I'm looking for ways to do this without registering new component --- only using helpers. 


Zoran

  • Hero Member
  • *****
  • Posts: 1829
    • http://wiki.lazarus.freepascal.org/User:Zoran
Re: How to assign default event handler?
« Reply #2 on: March 07, 2021, 02:32:47 pm »

Any method existing in a class can be overridden ..


Only virtual methods can be overridden.

egsuh

  • Hero Member
  • *****
  • Posts: 1273
Re: How to assign default event handler?
« Reply #3 on: March 08, 2021, 02:31:59 am »
I forgot that I cannot override creators.. they are not defined as virtual.
Virtual creators are only needed when I use classref types. I cannot think of other cases that require virtual creators.

Zoran

  • Hero Member
  • *****
  • Posts: 1829
    • http://wiki.lazarus.freepascal.org/User:Zoran
Re: How to assign default event handler?
« Reply #4 on: March 08, 2021, 05:47:29 am »
I forgot that I cannot override creators.. they are not defined as virtual.
Virtual creators are only needed when I use classref types. I cannot think of other cases that require virtual creators.

But in your example, it is virtual and you can override it. TEdit is derived from TComponent.

lucamar

  • Hero Member
  • *****
  • Posts: 4219
Re: How to assign default event handler?
« Reply #5 on: March 08, 2021, 10:34:56 am »
I forgot that I cannot override creators.. they are not defined as virtual.
Virtual creators are only needed when I use classref types. I cannot think of other cases that require virtual creators.

You'll discover that most constructors (and destructors) are in fact virtual precisely for this reason, so that one can derive a new subclass and initialize new fields or modify the default ones on the base class. That's why most implementations of derived classes start (or end, in destructors) with inherited :)
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus/FPC 2.0.8/3.0.4 & 2.0.12/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

Sieben

  • Sr. Member
  • ****
  • Posts: 310
Re: How to assign default event handler?
« Reply #6 on: March 08, 2021, 11:13:38 am »

Any method existing in a class can be overridden ..


Only virtual methods can be overridden.

A static method can be 'sort of' overridden, however. You cannot use keyword override but you can create an new method of the same name and you can even call inherited from within this new method. Drawback is that you need to explicitely call the instance by it's new class name (otherwise it would call the method of the ancestor class called), but in case of creators this the case anyway. TObject.Create is not virtual, btw.
Lazarus 2.2.0, FPC 3.2.2, .deb install on Ubuntu Xenial 32 / Gtk2 / Unity7

lucamar

  • Hero Member
  • *****
  • Posts: 4219
Re: How to assign default event handler?
« Reply #7 on: March 08, 2021, 11:43:02 am »
A static method can be 'sort of' overridden, however. You cannot use keyword override but you can create an new method of the same name [...]

What you're doing then is reintroducing the method, and the new one (if it's not an overload) will hide the previous one from then on. This can also be done with virtual methods, though then you have to use an explicit reintroduce modifier. Note that calling inherited from such a method can produce strange effects and might not work as you think in different versions of the compiler.


TObject.Create is not virtual, btw.

Probably because it does nothing other than call NewInstance in hidden, compiler-internal code, which is done for all classes. Being the common ancestor for all classes, TObject is somewhat "special" in many ways.
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus/FPC 2.0.8/3.0.4 & 2.0.12/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

howardpc

  • Hero Member
  • *****
  • Posts: 4144
Re: How to assign default event handler?
« Reply #8 on: March 08, 2021, 02:39:57 pm »
A static method can be 'sort of' overridden, however. You cannot use keyword override but you can create an new method of the same name ...
I think it is extremely confusing to use the term "overridden" in this situation, since in well-designed object-oriented code "override" has a clear and specific meaning, namely that this unique method name will be implemented differently in this descendant from how the identically-named method is implemented in its ancestor(s), and that the overridden method may, or may not, include inherited code.
Even the term "reintroduce" (which derives from Delphi) is somewhat misleading, since although it could be said that you are reintroducing the method name, what is more to the point is that you are redefining the method, albeit confusingly reusing a name that means something different in the ancestral hierarchy.

I cannot conceive of a situation in which a programmer has no option but to redefine methods in a well-designed hierarchy in this way, particularly in an English-based language such as Object Pascal where the mother tongue has such a superfluity of synonyms for virtually every term in common use.

To deliberately reuse a method name to mean two things at the same time in the same code seems to me pointlessly confusing, and a mark of poor design.
Programmers are restricted by the syntax and grammar of the language they choose. Intelligent choice of method (and other) identifiers  can make the code virtually self-explanatory, and sloppy naming can obfuscate it and give rise to all kinds of debugging difficulties.
« Last Edit: March 08, 2021, 02:42:18 pm by howardpc »

lucamar

  • Hero Member
  • *****
  • Posts: 4219
Re: How to assign default event handler?
« Reply #9 on: March 08, 2021, 02:50:45 pm »
I cannot conceive of a situation in which a programmer has no option but to redefine methods in a well-designed hierarchy in this way, [...]

Very few, granted, but some times one has no other option than to completely redefine a method, if for nothing else than to have a descendant with a completely different behaviour. One can't always avoid having to inject a subclass that way and not always is the class hierarchy under your full control.

And once or twice I have had to use reintroduce to work around a Delphi inconsistency. Ok, maybe once or twice in 30+ years is not much, but there it is :D
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus/FPC 2.0.8/3.0.4 & 2.0.12/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

Zoran

  • Hero Member
  • *****
  • Posts: 1829
    • http://wiki.lazarus.freepascal.org/User:Zoran
Re: How to assign default event handler?
« Reply #10 on: March 09, 2021, 10:39:20 am »
TObject.Create is not virtual, btw.

In egsuh's example, the constructor he tries to override is TComponent.Create(Owner: TObject). And it is virtual.

Sieben

  • Sr. Member
  • ****
  • Posts: 310
Re: How to assign default event handler?
« Reply #11 on: March 09, 2021, 01:37:16 pm »
Absolutely correct, as is your statement that only virtual methods can be overridden, of course. I just wanted to mention that you can take similar - but not equal - action with static methods as well if you are aware of the drawbacks. There seems to be a bit of confusion on the use of inherited with static methods but that should be discussed in another thread then.
Lazarus 2.2.0, FPC 3.2.2, .deb install on Ubuntu Xenial 32 / Gtk2 / Unity7

Sieben

  • Sr. Member
  • ****
  • Posts: 310
Re: How to assign default event handler?
« Reply #12 on: March 09, 2021, 04:53:05 pm »
Back to OP's question (and sorry for sort of abusing your thread). If you want to assign methods to component events you might want to use code like this:

Code: Pascal  [Select][+][-]
  1. // on a TForm call like GrabEvents(Self, 'OnMouseEnter', TMethod(@ComponentsMouseEnter))
  2.  
  3. procedure GrabEvents(AContainer: TComponent; AnEventName: string; AnEvent: TMethod);
  4. var i: Integer;
  5. begin
  6.   with AContainer do
  7.     for i:=0 to pred(ComponentCount) do
  8.       if Assigned(GetPropInfo(Components[i],AnEventName)) then
  9.         SetMethodProp(Components[i],AnEventName,AnEvent);
  10. end;

This assigns a single event to all components on your form which for example expose OnMouseEnter. It can be easily modified to assign an event to only TEdits, for example:

Code: Pascal  [Select][+][-]
  1. // within eg TForm.OnCreate call like GrabEvents(Self, TEdit, 'OnClick', TMethod(@EditsClick))
  2.  
  3. procedure GrabEvents(AContainer: TComponent; AClass: TClass; AnEventName: string; AnEvent: TMethod);
  4. var i: Integer;
  5. begin
  6.   with AContainer do
  7.     for i:=0 to pred(ComponentCount) do
  8.       if Components[i] is AClass
  9.       and Assigned(GetPropInfo(Components[i],AnEventName)) then
  10.         SetMethodProp(Components[i],AnEventName,AnEvent);
  11. end;

That way you only need to call this procedure once in eg TForm.OnCreate to have all your TEdits connected to a single OnClick event. If you want only certain TEdits to be connected you can use the Tag property for example.

Edit: forgot to mention that you need to add TypInfo to your uses clause.
« Last Edit: March 09, 2021, 05:03:53 pm by Sieben »
Lazarus 2.2.0, FPC 3.2.2, .deb install on Ubuntu Xenial 32 / Gtk2 / Unity7

eny

  • Hero Member
  • *****
  • Posts: 1634
Re: How to assign default event handler?
« Reply #13 on: March 09, 2021, 07:05:49 pm »
What I'm looking for is assigning this automatically, within constructor, etc.
If you want to customize the behavior of such a standard component, then you would typically create a Decorator (https://en.wikipedia.org/wiki/Decorator_pattern).
For example:
Code: Pascal  [Select][+][-]
  1. unit uEditDecorator;
  2. {$mode objfpc}{$H+}
  3. interface
  4.  
  5. uses
  6.   Classes, SysUtils, StdCtrls;
  7.  
  8. type
  9.   { TEditDecorator }
  10.   TEditDecorator = class(TComponent) // TComponent so it's auto-freed on form destruction
  11.   private
  12.     FEdit: TEdit;
  13.   public
  14.     constructor Create(pOwner: TComponent; pAssociated: TEdit); reintroduce;
  15.     destructor Destroy; override;
  16.     procedure OnEditClick(Sender: TObject);
  17.   end;
  18.  
  19. implementation
  20.  
  21. constructor TEditDecorator.Create(pOwner: TComponent; pAssociated: TEdit);
  22. begin
  23.   inherited Create(pOwner);
  24.   FEdit := pAssociated;
  25.   if assigned(FEdit) then
  26.     FEdit.OnClick := @OnEditClick;
  27. end;
  28.  
  29. destructor TEditDecorator.Destroy;
  30. begin
  31.   if assigned(FEdit) then
  32.     FEdit.OnClick := nil;
  33.   inherited Destroy;
  34. end;
  35.  
  36. procedure TEditDecorator.OnEditClick(Sender: TObject);
  37. begin
  38.   FEdit.Text := 'Clicked...';
  39. end;
  40.  
  41. end.

And then use it as:
Code: Pascal  [Select][+][-]
  1. procedure TfrmMain.FormCreate(Sender: TObject);
  2. begin
  3.   TEditDecorator.Create(self, Edit1);
  4. end;
  5.  
All posts based on: Win10 (Win64); Lazarus 2.0.10 'stable' (x64) unless specified otherwise...

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 1312
    • Lebeau Software
Re: How to assign default event handler?
« Reply #14 on: March 09, 2021, 07:23:40 pm »
What I'm looking for is assigning this automatically, within constructor, etc. I will be able to do this if I define a descendant class, like following example:

Code: Pascal  [Select][+][-]
  1. interface
  2.  
  3. TMyEdit = Class(TEDIT)
  4.      Procedure DoClick(Sender:TObject);
  5.      constructor Create(Owner: TObject); override;
  6. End;
  7.  
  8. implementation
  9.  
  10. constructor TMyEdit.Create(Owner: TObject);
  11. begin
  12.       inherited Create(Owner);
  13.       OnClick:= DoClick;
  14. end;

DoClick() is a virtual method that fires the OnClick event.  The whole point of having such a virtual method is so that you don't have to assign an event handler at all, just override the virtual method instead, eg:

Code: Pascal  [Select][+][-]
  1. interface
  2.  
  3. type
  4.   TMyEdit = class(TEdit)
  5.     procedure DoClick; override;
  6.   end;
  7.  
  8. implementation
  9.  
  10. procedure TMyEdit.DoClick;
  11. begin
  12.   // do your work here as needed...
  13.   // if desired, call inherited to fire the OnClick event...
  14. end;

Most components have virtual event firers for exactly this reason.

But in this case, I think I have to register TMyEdit on the component pallette. I'm looking for ways to do this without registering new component --- only using helpers.

Then you can define your class to be an interposer instead.  An interposer is a class that derives from another class in a different unit, and has the same name as the class it is deriving from, eg:

Code: Pascal  [Select][+][-]
  1. interface
  2.  
  3. type
  4.   TEdit = class(StdCtrls.TEdit)
  5.     procedure DoClick; override;
  6.   end;
  7.  
  8. implementation
  9.  
  10. procedure TEdit.DoClick;
  11. begin
  12.   // do your work here as needed...
  13.   // if desired, call inherited to fire the OnClick event...
  14. end;

This way, if you place your unit in another unit's uses clause after the StdCtrls unit, then the compiler will use your TEdit class automatically, instead of using the standard TEdit class, eg:


Code: Pascal  [Select][+][-]
  1. uses
  2.   ..., StdCtrls, ..., MyUnit;
  3.  
  4. type
  5.   TSomeForm = class(TForm)
  6.     Edit1: TEdit; // <=-- will use MyUnit.TEdit, not StdCtrls.TEdit!
  7.     ...
  8.   end;
« Last Edit: March 09, 2021, 07:29:29 pm by Remy Lebeau »
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

 

TinyPortal © 2005-2018