Jump to content
Anders Melander

Component with sub-property event

Recommended Posts

I have a component which contains sub-properties; I.e. a component property which is itself an object that contains its own properties. The object is derived from TPersistent and one of its properties is an event. The problem I'm having is getting this event to appear in the object inspector.

 

I'm pretty sure that I had it working at one point but whatever I've tried now I can't get the event property to appear in the OI.

image.png.94fabe2b40d3cf5ea2f2c940c2ab14a6.png

FWIW, it works without problems in the Lazarus IDE.

 

I've looked through the source of the few ToolsAPI units we have available but I haven't found any clues there. As far as I can tell it should work.

 

So what am I missing?

 

The following source reproduces the problem:

unit FooBar.Reg;

interface

uses
  Classes, DesignIntf;

type
  TTestSub = class(TPersistent)
  private
    FOnEvent: TNotifyEvent;
    FTest: string;
  public
    procedure Assign(Source: TPersistent); override;
  published
    property Test: string read FTest;
    property OnTestEvent: TNotifyEvent read FOnEvent write FOnEvent;
  end;

  TTest = class(TComponent)
  private
    FSub: TTestSub;
    procedure SetSub(const Value: TTestSub);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property Sub: TTestSub read FSub write SetSub;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('FooBar', [TTest]);
end;

{ TTest }

constructor TTest.Create(AOwner: TComponent);
begin
  inherited;
  FSub := TTestSub.Create;
  FSub.FTest := 'Hello world';
end;

destructor TTest.Destroy;
begin
  FSub.Free;
  inherited;
end;

procedure TTest.SetSub(const Value: TTestSub);
begin
  FSub.Assign(Value);
end;

{ TTestSub }

procedure TTestSub.Assign(Source: TPersistent);
begin
  FOnEvent := TTestSub(Source).OnEvent;
end;

end.

 

Share this post


Link to post
56 minutes ago, Anders Melander said:

The problem I'm having is getting this event to appear in the object inspector.

The Object Inspector does not display events for nested sub-properties.  It can only display the events that are directly members of the selected object(s), which in this case is your TTest component, not the TTestSub nested sub-object. All of the events need to be exposed only at the top-level of your TTest component.  If you have sub-property events you want to expose in the OI, you will have to propagate them accordingly, eg:

unit FooBar.Reg;

interface

uses
  Classes, DesignIntf;

type
  TTestSub = class(TPersistent)
  private
    FOnEvent: TNotifyEvent;
    FTest: string;
  public
    procedure Assign(Source: TPersistent); override;
  published
    property Test: string read FTest;
    property OnTestEvent: TNotifyEvent read FOnEvent write FOnEvent;
  end;

  TTest = class(TComponent)
  private
    FSub: TTestSub;
    procedure SetSub(const Value: TTestSub);
    function GetOnTestEvent: TNotifyEvent;
    procedure SetOnTestEvent(AValue: TNotifyEvent);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property Sub: TTestSub read FSub write SetSub;
    property OnTestEvent: TNotifyEvent read GetOnTestEvent write SetOnTestEvent;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('FooBar', [TTest]);
end;

{ TTest }

constructor TTest.Create(AOwner: TComponent);
begin
  inherited;
  FSub := TTestSub.Create;
  FSub.FTest := 'Hello world';
end;

destructor TTest.Destroy;
begin
  FSub.Free;
  inherited;
end;

function TTest.GetOnTestEvent: TNotifyEvent;
begin
  Result := FSub.OnTestEvent;
end;

procedure TTest.SetOnTestEvent(AValue: TNotifyEvent);
begin
  FSub.OnTestEvent := AValue;
end;

procedure TTest.SetSub(const Value: TTestSub);
begin
  FSub.Assign(Value);
end;

{ TTestSub }

procedure TTestSub.Assign(Source: TPersistent);
begin
  FOnEvent := TTestSub(Source).OnTestEvent;
end;

end.
56 minutes ago, Anders Melander said:

I'm pretty sure that I had it working at one point

I could be wrong, but I don't recall this ever being possible.

56 minutes ago, Anders Melander said:

FWIW, it works without problems in the Lazarus IDE.

Apples and Oranges.

56 minutes ago, Anders Melander said:

I've looked through the source of the few ToolsAPI units we have available but I haven't found any clues there. As far as I can tell it should work.

 

So what am I missing?

What you are asking for is simply not supported by Delphi without propagating the events manually, or maybe by writing a custom property/component editor that exposes nested events at the top level.

  • Thanks 1

Share this post


Link to post
4 minutes ago, pyscripter said:

You need to overwrite the TTestSub.GetOwner. 

That was my first instinct too, but it doesn't work.  I tried it.

Share this post


Link to post
4 minutes ago, Remy Lebeau said:

The Object Inspector does not display events for nested sub-properties. 

Most of DevExpress' controls beg to disagree:

image.thumb.png.3bd98dca0451a798353322a5cf9d8815.png

The sub-properties are derived from TPersistent...

 

Share this post


Link to post
11 minutes ago, Anders Melander said:

Ohwaitamoment!

I think DevExpress use a custom property editor to get the events listed. I'll investigate.

It works with Components if you call SetSubComponent or set csSubComponent.  Then Events are displayed.     e.g,

 

image.png.abeab2ba62815cc15f4cf7730727d7bd.png

 

So you can change TTestSub to inherit from TComponent.

Share this post


Link to post
2 minutes ago, pyscripter said:

So you can change TTestSub to inherit from TComponent.

Yes, I know I could just use TComponent but nope; I'm not letting a deficiency of the IDE dictate my class design.

  • Like 1

Share this post


Link to post

Okay, so I got it working using the same method as DevExpress:

image.thumb.png.cfffd84d24744e4f13c90332051b6085.png

 

The code required is pretty crazy. I'll post a link to it once it makes into the Graphics32 repository.

 

Functionality like this is really something that ought to be standard.

  • Like 1

Share this post


Link to post
39 minutes ago, Anders Melander said:

Okay, so I got it working using the same method as DevExpress

Which is what, exactly?

Share this post


Link to post
18 minutes ago, Remy Lebeau said:

Which is what, exactly?

Don't worry. I'll post a link once it's ready for consumption but it's too much to post inline here.

 

In short it involves declaring a placeholder property on the top level class registering a dedicated property editor for this property which in turn redirects to the actual sub-property and then enumerates the delegates/events of that object to create TNestedProperty property editors for each of them.

Share this post


Link to post
17 minutes ago, Anders Melander said:

In short it involves declaring a placeholder property on the top level class registering a dedicated property editor for this property which in turn redirects to the actual sub-property and then enumerates the delegates/events of that object to create TNestedProperty property editors for each of them.

That sounds like a lot of extra work for very little gain.  I don't think it is very good design to expose design-time access to sub-property events.  The sub-properties are part of the main component.  All events should be part of the main component, as well.  But that is just my opinion.

Share this post


Link to post

Decoupling.

In this case the events belongs in the sub-property objects. They do not concern the main component.

 

I understand what you are saying but IMO that view is driven by the fact that we are talking about "components" which is a RAD thing, thus design-time stuff, monolithic design and so on.

 

I'm designing this like I would if there was no design-time to influence things. If I hadn't gotten this to work then the events simply wouldn't have been accessible at design-time.

 

In fact, at the moment the sub-properties are objects that are statically created by the component. The next stage is to make the class type of the objects dynamic and configurable at design-time. And then there's no way I could expose those event on the component because they wouldn't be known at compile-time.

 

So instead of a single class that handles many different button styles (like I have now - see screenshot):

 

  property ButtonOptions: TButtonOptions read FButtonOptions write SetButtonOptions;

 

I will get one class for each button style:

 

  property ButtonOptions: TCustomButtonOptions read FButtonOptions write SetButtonOptions;

  property ButtonOptionsClass: TButtonOptionsClass read FButtonOptionsClass write SetButtonOptionsClass;

 

and each class will expose the properties that are relevant to that style.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×