-->

Set method of TPicture property doesn't get ca

2019-07-12 13:42发布

问题:

Here's my component overall structure:

  • My Component
    • property Categories: TCollection (of TCategory)
      • TCategory
        • property Icon: TPicture read FIcon write SetIcon;
        • property Example: Integer read FExample write SetExample;
        • (Other category properties...)
    • (Other properties...)

In the Object Inspector of the IDE, I choose a picture and it gets serialized successfully (I checked it in form view as text). I set a breakpoint on SetIcon method and when I compile and run the application, it doesn't get called at all. At the same time, SetExample gets called right as it should.

What's wrong with the TPicture property?

P.S: Set method is called in IDE, but not at runtime.

Here's a MCVE of the code:

unit MCVE;

interface

uses
  System.SysUtils, System.Classes, Vcl.Controls, Graphics, Dialogs;

type
  TMyCollectionItem = class(TCollectionItem)
  private
    FIcon: TPicture;
    procedure SetIcon(const Value: TPicture);
  public
    constructor Create(Collection: TCollection); override;
  published
    property Icon: TPicture read FIcon write SetIcon;
  end;

  TMyCollection = class(TCollection)
  end;

  TMCVE = class(TCustomControl)
  private
    FCollection: TMyCollection;
    procedure SetCollection(const Value: TMyCollection);
  public
    constructor Create(AOwner: TComponent); override;
  published
    property MyCollection: TMyCollection read FCollection write SetCollection;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Samples', [TMCVE]);
end;

{ TMyCollectionItem }

constructor TMyCollectionItem.Create(Collection: TCollection);
begin
  inherited;
  FIcon := TPicture.Create;
end;

procedure TMyCollectionItem.SetIcon(const Value: TPicture);
begin
  ShowMessage('SetIcon is called!');
  FIcon.Assign(Value);
end;

{ TMCVE }

constructor TMCVE.Create(AOwner: TComponent);
begin
  inherited;
  FCollection := TMyCollection.Create(TMyCollectionItem);
end;

procedure TMCVE.SetCollection(const Value: TMyCollection);
begin
  FCollection := Value;
end;

end.

回答1:

For simple types like Integer, Boolean, Double etc., streaming operates the way you describe. The streaming framework reads the value, and calls the property setter.

For a more complex type like TPicture, it does not happen that way. The streaming framework cannot call the property setter. In order to do so it would need to get hold of a fully formed TPicture. And it doesn't know how to do that, a priori.

So what happens instead, is that the streaming framework calls the getter of the property to obtain the TPicture instance that your component's constructor created. And then streams in the state of the TPicture stored in the .dfm file.

As it happens, TPicture has no published properties. The picture data is saved to the .dfm file under a property named Data. So, where does that come from. The answer is in the overridden DefineProperties method. The code looks like this:

procedure TPicture.DefineProperties(Filer: TFiler);

  function DoWrite: Boolean;
  begin
    // .... code removed for brevity
  end;

begin
  Filer.DefineBinaryProperty('Data', ReadData, WriteData, DoWrite);
end;

The WriteData and ReadData methods do the streaming.

So, to recap. You asked why the setter for a TPicture property was not called when streaming in the picture's properties. It is not called because the picture object that you created in your constructor streams in its properties.