4
votes

My program uses dynamically created frames and sometimes I get an issue that their controls are improperly aligned.

I use my own container control inherited from TPanel, but the same problem can be found when using the GridPanel as well.

Here is the source of a test program that reproduces the problem (with compiled exe).

The key code snippets:

in the main form:

//creating Frame from the main form
procedure TForm1.FormCreate(Sender: TObject);
begin
  f := TFrame2.Create(Self);
  f.Parent := Self;
end;

in the frame:

//constructor of the Frame
constructor TFrame2.Create(AOwner: TComponent);
begin
  inherited;
  Edit1.Clear;// the problem appears
end;

The frame and all its controls are aligned and must have the width of the main form, but Edit1 and ComboBox1 are visually not aligned until you resize the form manually (Sending WM_SIZE has no effect).

But if you comment the Edit1.Clear line everything will work fine from the program start. This code is not specific for the error and you can enter here e.g. ComboBox1.Items.Add('') etc.

If the frame is created statically or the GridPanel is changed to Panel the problem disappears.

I have made a new test2 version thanks to @quasoft, it works better - now the controls are horizontally aligned proper but vertically combobox is not in the right place that can be seen by changing the form size.

1
If you just replace Edit1.Clear with Edit1.Text := '' it works as you would expectquasoft
I see you project and all is fine , and as @quasoft say jut replace Edit1.Clear;Ilyes
"If the frame is created statically" check the frame's WindowHandle in the TFrame2.Create in both cases (static and dynamic). You might want to init your "Handle dependent" controls in the Frame CreateWnd handler, where you know the controls have valid window handle. (I can't test this right now).kobik
Yes, there are no alignment problems with the frames created statically. For the frames created dynamically I use HandleAllocated and HandleNeeded functions to make sure that the handle is valid (e.g. when sending messages). Isn't that enough?Molochnik

1 Answers

3
votes

Quick fix:

The quick solution to your problem is to use Text property of TEdit, instead of the Clear method - as already said, replacing Edit1.Clear with Edit1.Text := ''.

Understanding the problem

But you need to understand this problem better if you plan to use frames in Delphi in the long term, or they will haunt you while you sleep (joking).

The real problem is that you are modifying the state of the frame before a Parent has been assigned to it.

procedure TForm1.FormCreate(Sender: TObject);
begin
  f := TFrame2.Create(Self);    // <--- Your text edit is cleared inside
  f.Parent := Self;             // <--- Your frame is attached to the form here
end;

Doing so does not allow the TGridPanel component to take in account the width, height and position of parent when calculating the size of its columns and rows.

Using Text property works, because the property setter does not change the text of the control directly, but sends a message for the purpose to the message queue:

Except from Controls.pas:

...
procedure TControl.SetTextBuf(Buffer: PChar);
begin
  Perform(WM_SETTEXT, 0, Longint(Buffer));
  Perform(CM_TEXTCHANGED, 0, 0);
end;

procedure TControl.SetText(const Value: TCaption);
begin
  if GetText <> Value then SetTextBuf(PChar(Value));
end;
...

Which in effect, causes the text to actually change after you have assigned the Parent of the frame - as the message queue will be processed a little bit after the form create method completes.

Clear method on the other hand directly changes the text:

Excerpt from StdCtrls.pas:

...
procedure TCustomEdit.Clear;
begin
  SetWindowText(Handle, '');
end;
...

A better solution

As you already learned, the quick fix works only in the specific example you provided.

A better solution is to create an Init method in your frame and call this method from the main form after Parent has been assigned:

Your frame:

procedure TFrame2.Init;
begin
  Edit1.Clear;
  ComboBox1.Items.Add('Foo Bar');
end;

Your main form:

procedure TForm1.FormCreate(Sender: TObject);
begin
  f := TFrame2.Create(Self);
  f.Parent := Self;             // <--- Your frame is attached to the form here
  f.Init;                       // <--- Calls initialization code of frame
end;