Reputation: 131
I'm trying to create a component descending from TImage, with the difference that I can assign a variable number of TPictures in the property list (not assign the TPictures by code) and activate one of them by code to be displayed in the TImage.
It would not be a problem to have a property that sets the overall number of TPictures (length of the dynamic array), if that's necessary to have all the TPictures assignable within the properties.
unit ImageMultiStates;
interface
uses
Vcl.Graphics, Vcl.StdCtrls, System.SysUtils, System.Classes, Vcl.Controls, Vcl.ExtCtrls, Forms;
type
TPictures = Array of TPicture;
TImageMultiStates = class(TImage)
private
FPictures: TPictures;
procedure SetPicture(Which: Integer; APicture: TPicture);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Activate(Which: Integer);
published
property Images: TPictures read FPictures write FPictures; default;
end;
procedure Register;
implementation
constructor TImageMultiStates.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
for TPicture in FPictures do
TPicture := TPicture.Create;
end;
destructor TImageMultiStates.Destroy;
var
APicture: TPicture;
begin
for APicture in FPictures do
APicture.Free;
inherited Destroy;
end;
procedure TImageMultiStates.Activate(Which: Integer);
begin
Picture.Assign(FPictures[Which]);
end;
procedure TImageMultiStates.SetPicture(Which: Integer; APicture: TPicture);
begin // i would also like to use SetPicture instead of "write FPictures"
FPictures[Which].Assign(APicture);
if Which=0 then // because: First Picture will be displayed in the VCL editor
Picture.Assign(FPictures[Which]);
end;
procedure Register;
begin
RegisterComponents('Standard', [TImageMultiStates]);
end;
end.
I turned this code around in many ways, but i just can't get anything to work really.
I do have this exact same idea already working in my component 'Image2States'. Then I needed 'Image4States' and so on, until I decided that I absolutely need this with a variable amount of TPictures...
Upvotes: 1
Views: 346
Reputation: 23036
I would answer this by asking: What are you expecting this to do:
for TPicture in FPictures do
TPicture := TPicture.Create;
Firstly, as written, this simply does not compile. The TPicture loop variable is not declared (and being the same as the name of a type would cause subsequent compilation errors even if it were declared).
Even assuming that the loop were valid, compilable code, when the constructor executes FPictures is an empty array so this loop will execute 0 (zero) times. You do not show any code which tells the component how many pictures it should support so it always supports 0 (zero).
The problems don't end there.
If you did declare an appropriate variable for use as the loop variable, the loop code will still not compile since it involves an assignment to the loop variable which is not permitted.
This is true even in simple for-loops as well as iterator based loops such as the one you were attempting to use. In the case of simple loops, this is to enable the compiler to produce optimal code. In the case of iterator loops it also protects you from mistakes that might otherwise be easy to make.
Consider that in this case on each iteration of the loop, you would create a new TPicture instance and assign it to the loop variable but this does not assign it to the item in the array from which the loop variable is initialised.
Perhaps the easiest way to explain this is to "unroll the loop" (Unrolling a loop is re-writing the code explicitly for each iteration, as if there were no loop at all).
So consider if the loop contained 2 items, and let's also change the name of the loop variable to make things both valid as well as a bit clearer. We'll use simply pic. In other words we'll unroll a 2 iteration version of this loop:
for pic in FPictures do // fPictures contains 2 items
pic := TPicture.Create;
Remember, this does not compile and the reason why this helps prevent us making mistakes becomes apparent when we unroll the loop that it would create, if it were possible:
// Iteration #1
pic := fPictures[0];
pic := TPicture.Create;
// Iteration #2
pic := fPictures[1];
pic := TPicture.Create;
Hopefully you see the problem. You overwrite the value of the loop variable on each iteration, but this does not modify the array items themselves. Worse than that, as a result, each iteration of the loop is leaking a TPicture.
Hopefully this should now help you understand why your code isn't working (actually, can't work) and make the necessary corrections.
You need some mechanism to set the number of pictures supported by your component
You need to correctly initialise the items in the array holding those pictures
Upvotes: 1
Reputation: 28806
You told us that your approach "didn't work" (I assume you meant "didn't compile"): That is because of a few syntactical errors and because you are not using the right tool for the job.
If you only have pictures of the same size, then think about using the already existing, well tested and IDE-supported TImageList
and don't try to reinvent the wheel.
If, however, you must have a list of pictures of different sizes, then use a TObjectList
and not an array of TPicture
. TObjectLists allow you to add, remove, query, etc. objects and they can automatically free them, if you wish.
If your compiler supports generics, then include System.Generics.Collections and use a TObjectList<TPicture>
to manage your pictures. That way, you don't have to cast to TPicture
, because the generics lists are type safe.
If it doesn't support them, include the unit Contnrs and use a TObjectList
. When reading from that list, you will have to cast using as
, i.e. as TPicture
, but otherwise you can do similar things.
The name of your type makes me think you only need multiple states for a certain control. In that case, I think a TImageList
is the best tool for the job (and it already is for other controls with similar needs) and there is no need to make your own one. But if you want to make your own, don't use a dynamic array and don't produce loops like the one with for TPicture in FPictures do
. Do yourself a favour and use an object list.
Upvotes: 1