Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is my Delphi form control cropped when my form is bigger than my screen?

The scenario is this:

  • I've created a Delphi (XE2) form.
  • On it is a single TGroupBox (or other control) stretched so it occupies the full width of the form with the top.
  • Right anchor (in addition to left and top) on TGroupBox is set.
  • Form width set to 1200px (to illustrate the point).

If I run this application on a monitor whose Screen.Width property is greater than 1200px (I'm running without any DPI virtualization AFAIK) then the TGroupBox renders as you'd expect.

However.. if the monitor's width is less than 1200px then the right hand portion of the control is missing from the screen regardless of how your resize the form.

I've overridden the Create() method of my form with the override; directive and verified that I'm setting the width property correctly, however the control is still cropped.

Can anyone advise either how to:

a) set the width property of the form such that it is affects the positioning of the child components or...

b) suggest a way to force a relayout of all child components once the form is rendered?

like image 788
Duncan Avatar asked Oct 18 '22 08:10

Duncan


1 Answers

Tracing the code to see what happens, I came up with the below adjustment.

procedure TForm1.WMWindowPosChanging(var Message: TWMWindowPosChanging);
var
  MessageWidth: Integer;
begin
  MessageWidth := Message.WindowPos.cx;
  inherited;
  if MessageWidth > Message.WindowPos.cx then
    GroupBox1.Width := GroupBox1.Width - MessageWidth + Message.WindowPos.cx;
end;

This is not a generalized solution, but it makes clear what the problem is. VCL asks for a window size for its form which is not granted by the OS since it is larger then the desktop. From then on the form resumes anchoring the child control with its design time specified width which is larger than the client width of the form, thus right side of the child control overflows.

Another solution can be to override handling of WM_GETMINMAXINFO message to let the OS grant the asked width.

procedure TForm1.WMGetMinMaxInfo(var Message: TWMGetMinMaxInfo);
begin
  inherited;
  Message.MinMaxInfo.ptMaxTrackSize.X := 1200;
end;

This may not be a good solution because then the form will be larger than the desktop.

Regarding your 'a' and 'b' items, I don't think 'b' is possible - or at least not possible to make the VCL relayout by itself - because VCL defers applying anchor rules until after the component (form) is done loading. By then, the form's width is different than the design time width but child controls' placement remain unaffected. No amount of forcing to layout will make them in sync again.

However it should possible to recalculate everything from scratch if your own code keeps a reference to the design time width. Below is not complete code.

type
  TForm1 = class(TForm)
    ..
  private
    FAdjustShrinkWidth, FAdjustShrinkHeight: Integer;
  protected
    procedure Loaded; override;
  public
    procedure SetBounds(ALeft: Integer; ATop: Integer; AWidth: Integer;
      AHeight: Integer); override;
  end;

...

procedure TForm1.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
var
  TrackWidth, TrackHeight: Boolean;
begin
  TrackWidth := AWidth = 1200;
  TrackHeight := AHeight = ??;
  inherited;
  if TrackWidth and (Width < AWidth) then
    FAdjustShrinkWidth := AWidth - Width;
  if TrackHeight and (Height < AHeight) then
    FAdjustShrinkHeight := AHeight - Height;
end;

procedure TForm1.Loaded;

  procedure ReadjustControlAnchors(Control: TWinControl);
  var
    i: Integer;
  begin
    for i := 0 to Control.ControlCount - 1 do
      if (akRight in Control.Controls[i].Anchors) or (akBottom in Control.Controls[i].Anchors) then begin
        Control.Controls[i].Left := // some complex calculation depending on the anchors set;
        Control.Controls[i].Top := // same as above;
        Control.Controls[i].Width := // same as above;
        Control.Controls[i].Height := // same as above;
        if (Control.Controls[i] is TWinControl) and (TWinControl(Control.Controls[i]).ControlCount > 0) then
          ReadjustControlAnchors(TWinControl(Control.Controls[i]));
      end;
  end;

begin
  inherited;
  ReadjustControlAnchors(Self);
end;

I have no idea how to fill in the blanks in the above code. Reading and tracing VCL code may be compulsory to imitate VCL anchoring.

I can't think of anything for 'a'.


Update:

VCL has actually left a backdoor for a control to lie to its immediate children about their parent's size while they are anchoring. Documentation explains it a bit different:

UpdateControlOriginalParentSize is a protected method that updates the original size of the parent control. It is used internally to update the anchor rules of the control.

We can use it to tell the groupbox the intended original size.

type
  TForm1 = class(TForm)
    ..
  private
    FWidthChange, FHeightChange: Integer;
  protected
    procedure UpdateControlOriginalParentSize(AControl: TControl;
      var AOriginalParentSize: TPoint); override;
  public
    procedure SetBounds(ALeft: Integer; ATop: Integer; AWidth: Integer;
      AHeight: Integer); override;
  end;

...

procedure TForm1.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
var
  RequestedWidth, RequestedHeight: Integer;
begin
  RequestedWidth := AWidth;
  RequestedHeight := AHeight;
  inherited;
  if csLoading in ComponentState then begin
    if RequestedWidth <> Width then
      FWidthChange := Width - AWidth;
    if RequestedHeight <> Height then
      FHeightChange := Height - AHeight;
  end;
end;

procedure TForm1.UpdateControlOriginalParentSize(AControl: TControl;
  var AOriginalParentSize: TPoint);
begin
  inherited;
  if akRight in AControl.Anchors then
    AOriginalParentSize.X := AOriginalParentSize.X - FWidthChange;
  if akBottom in AControl.Anchors then
    AOriginalParentSize.Y := AOriginalParentSize.Y - FHeightChange;
end;


I note again that this will affect the form's immediate children only. Should the groupbox hosts controls that anchors right and bottom, it also has to override the same method.

Also note that this will not undo the fact that the form's width has changed. That's if there was a left anchored control that's at the far right of the form, it will not replace itself to client boundary. It will act as if the form's width has been decreased, i.e. remain out of sight.

like image 79
Sertac Akyuz Avatar answered Oct 21 '22 05:10

Sertac Akyuz