How do I refer to components created at runtime rather than in the form designer?
Asked Answered
J

3

8

i have a little problem. I'm trying to create in Delphi7 a list of components at run-time and to resize them with form's .OnResize event but no use... i can't figure out how to do it.

Here's my code:

procedure TForm1.Button1Click(Sender: TObject);
var
  //ExtCtrls
  panel: TPanel;
  memo: TMemo;
  splitter: TSplitter;
  list: TListBox;
begin
  panel := TPanel.Create(Self);
  list := TListBox.Create(Self);
  splitter := TSplitter.Create(Self);
  memo := TMemo.Create(Self);

  with panel do
  begin
    Parent := Form1;
    BevelOuter := bvNone;
    Top := 12;
    Left := 12;
    Height := Form1.Clientheight - 24;
    Width := Form1.Clientwidth - 24;
  end;

  with list do
  begin
    Parent := panel;
    Align := alClient;
    Top := 0;
    Height := panel.Height;
  end;

  with splitter do
  begin
    Parent := panel;
    Top := 0;
    Width := 12;
    Align := alLeft;
  end;

  with memo do
  begin
    Parent := panel;
    Top := 0;
    Left := 0;
    Width := round(panel.Width / 4) * 3;
    Height := panel.Height;
    Align := alLeft;
  end;
end;

Do i have to somehow register their names in order to use them in form's event? Or maybe, to create a class and include them?

Any kind of help is really appreciated! Thank you in advance.

Jeanajeanbaptiste answered 17/12, 2011 at 19:56 Comment(0)
M
9

Your variables are local to the procedure where they are created so you can't refer to them using those variables when outside that procedure. The solution is to make them fields of the form class.

type
  TForm1 = class(TForm)
    procedure FormResize(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    FPanel: TPanel;
    FMemo: TMemo;
    FSplitter: TSplitter;
    FList: TListBox;
  end;

Then your FormResize event handler can refer to them.

procedure TForm1.FormResize(Sender: TObject);
begin
  if Assigned(FPanel) then
  begin
    ...
  end;
end;

Don't forget to remove the local variables from Button1Click and use the fields instead.

procedure TForm1.Button1Click(Sender: TObject);
begin
  FPanel := TPanel.Create(Self);
  ...
end;
Malone answered 17/12, 2011 at 20:15 Comment(2)
That did the trick David. Now i'll just have to study how this can affect few other things like, an array of tpanels, etc. Thanks alot!Jeanajeanbaptiste
@DimitrisNats Arrays of controls are handled similarly. You just need a field of your class of the appropriate type, e.g. FPanels: array of TPanel.Malone
I
4

Although David's answer is also very correct, I thought I would take a moment and go into some more detail. By the looks of it, you seem to be very new with Delphi. There is a very common issue with beginners, which David doesn't address in his answer, pertaining to creating and freeing these objects. Any and every time you ever call 'Create' on a class, at some point, when you're done with it, you have to also 'Free' that class. Failure to free anything will result in a memory leak, and no one wants that. Freeing is just as simple as creating - until you get into the subject of keeping a list of objects (which you don't need right now).

Let's say you wanted to create a text box (TEdit) control and place it in the center of your form. Now first of all, the Delphi IDE allows you to simply drop these controls in your form, just making sure you know. You don't necessarily need to create/free them yourself, unless there's some special scenario. But doing this is dangerous. For the sake of this example, we're assuming that this TEdit control will be there for the entire duration of your application.

First, you need to declare a variable somewhere for this control. The most reasonable place for this is inside the class where it will be used (in this case, your form which we'll call Form1). When working with variables (aka Fields) in your form, make sure you do not put anything above the private section. Everything above private is intended for auto-generated code by Delphi for anything which has been dropped (and is visual) in your form. Otherwise, any manually created things must go under either private or under public. The public area would be a good place for your control...

type
  TForm1 = class(TForm)
  private

  public
    MyEdit: TEdit;
  end;

Now that it's declared, we have to create (and free) it. It's a good practice that any and every time you ever create something, that you immediately put the code to also free it before you continue working. Make an event handler for your form's OnCreate and OnDestroy events...

procedure TForm1.FormCreate(Sender: TObject);
begin
  MyEdit:= TMyEdit.Create(nil);
  MyEdit.Parent:= Self;
  MyEdit.Left:= (ClientWidth div 2) - (Width div 2);
  MyEdit.Top:= (ClientHeight div 2) - (Height div 2);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  if assigned(MyEdit) then MyEdit.Free;
end;

If this object is not created (before creation or after destruction), then you will get an "Access Violation" when trying to use it. This is because your application tries to access an area of the computer's memory which is not allocated or not matching with the type you meant to get.

Well, that's the basics to fix your scenario. However one more thing to show you. Suppose you need to just create an object for a short time, for the duration of a procedure. There's a different approach for this. In your code above, you declared your variable directly within the procedure. This example will show you when it is necessary to do this...

procedure TForm1.Button1Click(Sender: TObject);
var
  MyObject: TMyObject;
begin
  MyObject:= TMyObject.Create;
  try
    MyObject.DoSomething;
    Caption:= MyObject.GetSomething;
  finally
    MyObject.Free;
  end;
end;

You see, as long as MyObject will only be used in this one call to this procedure, then you can declare it here. But if the object is expected to stay in memory after this procedure is over and done with, then things get more complicated. Again, in your case, stick with putting this in the form's class until you're more familiar with dynamically creating objects.

A final note, as mentioned above, you do have the ability to place the TEdit control directly on your form in design-time without writing your own code. If you do this, you need to remember NOT to try to create or free these ones. This is also the case when Delphi will automatically put the code above the private section - is when there's something which you're not supposed to play with.

Impoverish answered 17/12, 2011 at 20:56 Comment(11)
@Nick I'm surprised, I thought you would have objected to the wide scope introduced by making these fields public.... ;-)Malone
Also, please don't ever write if assigned(MyEdit) then MyEdit.Free. You can call Free on a nil object reference. Do so because the alternative is unreadable.Malone
@DavidHeffernan I have to say I actually did not know that. I've created things like this before and haven't had issues. Then again, I've pondered why it requires setting both 'owner' and 'parent' - that would explain it. Changing from Self to nil. Plus, I typed this in the website, so I haven't compiled it to confirm its validity.Impoverish
@Jerry If you pass nil as the owner then you are saying, "I'm in charge of the lifetime, I will take responsibility for calling Free". Otherwise you are stating that the owner is responsible for calling Free. Although you can call Free, it's a litle pointless and you may as well have passed nil. Either way works but it all renders your answer somewhat beside the point I am afraid to say. It was a very well written answer, I commend you on that, but you just missed one crucial point.Malone
@DavidHeffernan point understood about if assigned but it doesn't hurt anything to add a layer of protection, does it?Impoverish
@Jerry In this case it does hurt. Your code is effectively like this: if Assigned(o) then if Assigned(o) then o.Free; which you will agree is silly. It's an incredibly widely used idiom and you'll soon get used to it. The difference in readability is immense. Consider what it looks like in a destructor which is freeing 10 objects. Yuch!Malone
PS - I have a reason for mentioning to declare it in public and not private - because since this is a beginner, they will most likely want to try to access this variable (or control) from another unit, which if it's in the private, it won't work without implementing a public property to wrap it (which is irrelevant in this example).Impoverish
Are we supposed to be critics here, or are we supposed to be answering questions? Neither of these mentioned "issues" in my answer are really issues, but seem more like suggestions or personal preference.Impoverish
The problem with your original answer, before you edited, was that it was factually incorrect. That is going to attract downvotes. That's just how the site works. Now that you have removed the comments about memory leaks, the remaining text is largely orthogonal to the question as asked. That also is liable to attract downvotes. What you say is basically sound but it does not really answer the question as asked.Malone
@DavidHeffernan OK from here on out I'm writing source for my answers in Delphi and not in this website :P I made the answer even worse, and would have quickly caught it if I had my debugger handy!Impoverish
Great info Jerry and David. I've read all of your comments and you made me understand lots of things i had no idea about. Thank you so much.Jeanajeanbaptiste
A
2

I don't think I am eligible to "comment", so I'm phrasing this as an "answer". If you want to resize your runtime components when their parent changes size, take a good look at the Anchors property. It can save you a lot of work.

Acceptation answered 19/12, 2011 at 2:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.