0

I'm trying to create a VCL component like TImage, that lets me add a variable amount of different sized TPictures. The Goal is to be able to assign that number of TPictures through the VCL editor in the property list.

delphi component property: TObjectList<TPicture> here we came to the conclusion, that a TCollection with TCollectionItems should be used. This is what I'm trying to do now, but as many times before i end up with the compiler error: "Published property 'Pictures' can not be of Type ARRAY" in this line: property Pictures[Index: Integer]: TPic read GetPic write SetPic;

unit ImageMultiStates;

interface

uses
  Vcl.Graphics, Vcl.StdCtrls, System.SysUtils, System.Classes, Vcl.Controls, Vcl.ExtCtrls, Forms, Generics.Collections;

type

  TPic = class(TCollectionItem)
  private
    FPicture: TPicture;
  public
    procedure Assign(Source: TPersistent); override;
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
  published
    property Picture: TPicture read FPicture write FPicture;
  end;

  TPictures = class(TCollection)
  private
    function GetPic(Index: Integer): TPic;
    procedure SetPic(Index: Integer; APicture: TPic);
  public
    constructor Create;
  published
    property Pictures[Index: Integer]: TPic read GetPic write SetPic;
  end;

  TImageMultiStates = class(TImage)
  private
    FPictures: TPictures;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Activate(Index: Integer);
  end;

procedure Register;

implementation

constructor TPic.Create(Collection: TCollection);
begin
  inherited Create(Collection);
end;

destructor TPic.Destroy;
begin
  FPicture.Free;
  inherited Destroy;
end;

procedure TPic.Assign(Source: TPersistent);
begin
  FPicture.Assign(Source);
end;


constructor TPictures.Create;
begin
  inherited Create(TPic);
end;

procedure TPictures.SetPic(Index: Integer; APicture: TPic);
begin
  Items[Index].Assign(APicture);
end;

function TPictures.GetPic(Index: Integer): TPic;
begin
  Result := TPic(inherited Items[Index]);
end;


constructor TImageMultiStates.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
end;

destructor TImageMultiStates.Destroy;
begin
  FPictures.Free;
  inherited Destroy;
end;

procedure TImageMultiStates.Activate(Index: Integer);
begin
  Picture.Assign(FPictures.Items[Index]);
end;

procedure Register;
begin
  RegisterComponents('Standard', [TImageMultiStates]);
end;

end.

Since noone seems to expect this error to be thrown, maybe it's related to my installed components? I used the internal GetIt Package-Manager to install the Jedi Code Library 2.8, Jedi Visual Component Library and PNGComponents 1.0. I guess that's about it as far as TImage-related components are concerned. Maybe one of these overrides some of my TImage contents with funky stuff...

Community
  • 1
  • 1
hzzmj
  • 121
  • 11
  • If you want to assign multiple images at design-time using `Object Inspector` like editing any other property then I'm afraid you won't be able to do this. Well not directly with `Object Inspector`. What you will have to do is create your own custom property editor. I suggest checking the source code of `TImageList` component for better clue of how it is done. – SilverWarior Jul 21 '16 at 15:12
  • It has been a while since the last time I used it, but IIRC, this is not how TCollection works. Take a look at how a status bar manages its panels or how a listview manages its column headers. AFAIK, both use descendants of TCollection. – Rudy Velthuis Jul 21 '16 at 17:05
  • One nice explanation here: http://www.atug.com/andypatterns/collections.htm#TCollectionMerits . The best part of a Tcollection is that it works nicely in the object inspector and has a property editor that lets you add items. – Rudy Velthuis Jul 21 '16 at 17:43
  • You forgot to initialize FPicture in the constructor of TPic. – Rudy Velthuis Jul 21 '16 at 19:47

2 Answers2

3

I experimented a little and derived a TPicturePanel from TPanel. It has a Pictures property, which is a TPictures, a descendant of TOwnedCollection and which contains TPics. Each TPic has a Picture property. I can install this component, and it allows me to edit the Pictures collection using the so called Collection editor, which allows you to add or remove TPic instances. If you select a TPic in the Collection editor, you can assign a picture to its Picture property, i.e. load from file, etc.

Here is the working code for TPicturePanel. You can model your component after this:

unit PicturePanels;

interface

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

type
  TPic = class(TCollectionItem)
  private
    FPicture: TPicture;
    procedure SetPicture(const Value: TPicture);
  public
    procedure Assign(Source: TPersistent); override;
    constructor Create(AOwner: TCollection); override;
    destructor Destroy; override;
  published
    property Picture: TPicture read FPicture write SetPicture;
  end;

  TPictures = class(TOwnedCollection)
  private
    function GetItem(Index: Integer): TPic;
    procedure SetItem(Index: Integer; const Value: TPic);
  public
    constructor Create(AOwner: TPersistent);
    property Items[Index: Integer]: TPic read GetItem write SetItem;
  end;

  TPicturePanel = class(TPanel)
  private
    FPictures: TPictures;
    procedure SetPictures(const Value: TPictures);
  published
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property Pictures: TPictures read FPictures write SetPictures;
  end;

procedure Register;

implementation

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

{ TPicturePanel }

constructor TPicturePanel.Create(AOwner: TComponent);
begin
  inherited;
  FPictures := TPictures.Create(Self);
end;

destructor TPicturePanel.Destroy;
begin
  FPictures.Free;
  inherited;
end;

procedure TPicturePanel.SetPictures(const Value: TPictures);
begin
  FPictures.Assign(Value);
end;

{ TPic }

procedure TPic.Assign(Source: TPersistent);
begin
  inherited;
  if Source is TPic then
    FPicture.Assign(TPic(Source).FPicture);
end;

constructor TPic.Create(AOwner: TCollection);
begin
  inherited;
  FPicture := TPicture.Create;
end;

destructor TPic.Destroy;
begin
  FPicture.Free;
  inherited;
end;

procedure TPic.SetPicture(const Value: TPicture);
begin
  FPicture.Assign(Value);
end;

{ TPictures }

constructor TPictures.Create(AOwner: TPersistent);
begin
  inherited Create(AOwner, TPic);
end;

function TPictures.GetItem(Index: Integer): TPic;
begin
  Result := inherited GetItem(Index) as TPic;
end;

procedure TPictures.SetItem(Index: Integer; const Value: TPic);
begin
  inherited SetItem(Index, Value);
end;

end.
Rudy Velthuis
  • 28,387
  • 5
  • 46
  • 94
  • I copied the exact code; I get into the CollectionEditor for a start, so it's great. Aside it throws an error "TPic can not be assigned to TPic" on opening the editor and assgning a picture, but what matters here that i can lay eyes on a working example that actually brings me into the Collection editor. Many thanks! – hzzmj Jul 22 '16 at 10:33
  • Hah! Based on your code I got the editor starting for my component as well. With the same error that TPic can not be assigned to TPic though – hzzmj Jul 22 '16 at 10:55
  • The problem is TPic.Assign: if you inherit it first, the train is gone when you try to FPicture.Assign(TPic(Source).FPicture); So something like "begin if source is TPic then FPicture := TPic(Source).FPicture else inherited; end;" does the job – hzzmj Jul 22 '16 at 11:05
  • I modified the code above, but the original code I posted works quite well in Seattle, and I don't get the error. – Rudy Velthuis Jul 22 '16 at 12:01
0

Your indexed property uses syntax that looks like it returns an array, but it doesn't do that. The pictures property returns an indexed TPic. It can only ever return one TPic at a time.

If you want to return an array you'll have to say so:

function GetPictures: TArray<TPicture>;
procedure SetPictures(const value: TArray<TPicture>);
property Pictures: TArray<TPicture> read GetPictures write SetPictures;  

//GetPictures might look something like this:
function TMyClass.GetPictures: TArray<TPicture>;
var
  i: integer;
begin
  SetLength(Result, Self.FPictureCount);
  for i:= 0 to FPictureCount - 1 do begin
    Result[i]:= GetMyPicture[i];
  end;
end;

I'm not sure how your TPic collection works, so you'll have to adjust it to suit your needs.
Obviously you can have an TArray<TArray<TPicture>> (aka: array of array of TPicture) if you so desire.

Johan
  • 74,508
  • 24
  • 191
  • 319
  • I found http://www.kadner-online.de/delphi/collection.html and http://stackoverflow.com/questions/1050724/using-townedcollection-descendant-in-delphi which both seem to successfully use this syntax without any Arrays involved – hzzmj Jul 21 '16 at 15:40
  • 1
    In both cases `Array properties` also known as `Indexed properties` are only declared as public and not published. The reason why `Object Inspector` is not able to work with published `Array properties` is because in order for `Object Inspector` to manipulate them you would need to be able to input two parameters (item index and item value) at the same time. Or in other words you would need two edit fields for such property in `Object Inspector`. – SilverWarior Jul 21 '16 at 15:48