TFlowPanel alike in D5
Asked Answered
M

1

6

I am looking for an implementation of TFlowPanel (or similar) that will work with D5.
Basically I need the TFlowPanel to work inside a TScrollBox (with Vertical scroll bar), so the controls will wrap based on the Width of that TScrollBox.

The images basically show what I need:

enter image description here

After resizing the controls are automatically repositioned:

enter image description here

With Vertical scroll bar:

enter image description here

Madea answered 4/11, 2012 at 15:49 Comment(1)
It should be pretty easy to build, especially if you got the code of the FlowPanel of later versions. But if I did, I couldn't test it in D5. :-/Janik
J
8

Just a concept. No various FlowTypes, and no possibility to change the order of the controls. You could still move them around by changing the order in the DFM, I think, or by resetting the parent.

The panel sizes vertically to fit all controls. This means, that when you put it inside a scrollbox it will automatically work.

unit uAlignPanel;

interface

uses
  Windows, SysUtils, Classes, Controls, ExtCtrls;

type
  TAlignPanel = class(TPanel)
  protected
    procedure SetChildOrder(Child: TComponent; Order: Integer); overload; override;
    procedure SetZOrder(TopMost: Boolean); override;
  public
    procedure AlignControls(AControl: TControl; var Rect: TRect); override;
    procedure Insert(AControl: TControl);
    procedure Append(AControl: TControl);
    function GetChildOrder(Child: TControl): Integer;
    procedure SetChildOrder(Child: TControl; Order: Integer); reintroduce; overload; virtual;
    procedure MoveChildBefore(Child: TControl; Sibling: TControl); virtual;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('StackOverflow', [TAlignPanel]);
end;

{ TAlignPanel }

procedure TAlignPanel.AlignControls(AControl: TControl; var Rect: TRect);
var
  i: Integer;
  x, y: Integer;
  LineHeight: Integer;
begin
  x := 0; y := 0;
  LineHeight := 0;
  for i := 0 to ControlCount - 1 do
  begin
    if x + Controls[i].Width > ClientWidth then
    begin
      x := 0;
      y := y + LineHeight;
      LineHeight := 0;
    end;
    Controls[i].Top := y;
    Controls[i].Left := x;
    x := x + Controls[i].Width;
    if Controls[i].Height > LineHeight then
      LineHeight := Controls[i].Height;
  end;

  // Height + 1. Not only looks nices, but also prevents a small redrawing
  // problem of the bottom line of the panel when adding controls.
  ClientHeight := y + LineHeight + 1;

end;

procedure TAlignPanel.Append(AControl: TControl);
begin
  AControl.Parent := Self;
  AControl.BringToFront;
  Realign;
end;

function TAlignPanel.GetChildOrder(Child: TControl): Integer;
begin
  for Result := 0 to ControlCount - 1 do
    if Controls[Result] = Child then
      Exit;
  Result := -1;
end;

procedure TAlignPanel.Insert(AControl: TControl);
begin
  AControl.Parent := Self;
  AControl.SendToBack;
  Realign;
end;

procedure TAlignPanel.MoveChildBefore(Child, Sibling: TControl);
var
  CurrentIndex: Integer;
  NewIndex: Integer;
begin
  if Child = Sibling then
    raise Exception.Create('Child and sibling cannot be the same');

  CurrentIndex := GetChildOrder(Child);
  if CurrentIndex = -1 then
    raise Exception.CreateFmt( 'Control ''%s'' is not a child of panel ''%s''',
                               [Sibling.Name, Name]);

  if Sibling <> nil then
  begin
    NewIndex := GetChildOrder(Sibling);
    if NewIndex = -1 then
      raise Exception.CreateFmt( 'Sibling ''%s'' is not a child of panel ''%s''',
                                 [Sibling.Name, Name]);
    if CurrentIndex < NewIndex then
      Dec(NewIndex);
  end
  else
    NewIndex := ControlCount;

  SetChildOrder(Child, NewIndex);
end;

procedure TAlignPanel.SetChildOrder(Child: TComponent; Order: Integer);
begin
  inherited;
  Realign;
end;

procedure TAlignPanel.SetChildOrder(Child: TControl; Order: Integer);
begin
  SetChildOrder(TComponent(Child), Order);
end;

procedure TAlignPanel.SetZOrder(TopMost: Boolean);
begin
  inherited;
  Realign;
end;

end.
Janik answered 4/11, 2012 at 17:38 Comment(14)
BringToFront might work too (with regard to moving the controls).Pack
To do what? The controls don't overlap.Janik
I didn't check this but I was under impression that BringToFront changes the order of controls in the Controls property. At least, BringToFront seemed to work for me when I used it on a group of controls aligned the same way (i.e. all top-aligned, or all left-aligned etc.).Pack
@AndriyM You are very right indeed. Control.BringToFront makes it the last control on the panel. Control.SendToBack will make it the first. Rather contra-intuitive, but it will work. You will have to call Panel.Realign, though, to force the AlignControls method to re-do its work.Janik
I added two methods that do just that. Works for exising control (already on the panel) or to add new controls (instead of setting the parent).Janik
Should the AlignPanel have a specific alignment? It is certainly not specified anywhere in your answer and I can't find it implied in any way. In my tests, this works for me as expected in all aspects only when I set the panel's alignment as alTop (I'm testing this in Delphi 7). With alNone the alignment of the child controls doesn't change at all (they retain their initial position). With alClient, everything works the same as with alTop except the scrollbar doesn't show on the ScrollBox I put my AlignPanel into. Either way, I find this answer highly useful for me at least, good job!Pack
A small correction: when I said that with alNone the controls retained their initial positions, I meant the panel did align them at the beginning, but when I resized the form (and consequently the ScrollBox), the controls didn't move. I guess, this was never meant to work with alNone.Pack
Indeed. If you set align to alNone, the panel doesn't resize when the form or the scrollbox are resized. The panel seems to work in designtime as well (before I only tested it in runtime). It may be a little annoying that it forcefully autosizes itself, ignoring any attempts you may do to set its height. :) A small price for having such great functionality in Delphi 5. ;) (If it works in D5. I think it should, but I'm using Delphi XE at the moment.)Janik
@GolezTrol, is it somehow possible to change the order of the controls? (e.g: MoveBeforeControl)Madea
Yes, a bit. As you can see, I already added some methods to move the control to the first an last position. I'll see if it can move to other positions as well.Janik
@GolezTrol, That would be great. I am accepting your answer either way.Madea
That wasn't as easy as I hoped, because the main methods I wanted to override were private. I've made an attempt to override some methods to force realingment in cases where you call, for instance, BringToFront. I've also introduced MoveChildBefore, which moves the child in front of a sibling (or nil to move it to the end). Furthermore, I've introduced GetChildOrder and SetChildOrder, to help with that. I've posted the whole unit as I have it now. I hope it still works in D5. ;)Janik
Still poor design time support, I'm sorry. :-/Janik
Perfect! thank you. (p.s. I don't care much about design time)Madea

© 2022 - 2024 — McMap. All rights reserved.