Search code examples
delphiresizeflowpanel

Calculating flow panel width based on its contents


I am dynamically populating frames into flow panels (as part of a horizontally oriented VCL Metropolis app). I need to resize each group's flow panel to fit all its items horizontally. I have a very simple formula which does the trick sometimes, but not all the time - specifically when adding an odd number of items. The flow panel's FlowStyle is set to fsTopBottomLeftRight and fits 2 frames vertically.

For example, adding 7 items automatically detects the correct width (4 items across). But adding 5 items does not detect the correct width (supposed to be 3 across but winds up detecting 2 across).

How can I make it correctly calculate the width for each group?

Here's the procedure that populates the items into each item group (some irrelevant stuff removed):

procedure TSplitForm.LoadScreen;
const
  FRAME_WIDTH = 170;    //Width of each frame
  FRAME_HEIGHT = 250;   //Height of each frame
  FRAME_MARGIN = 30;    //Margin to right of each group
  FRAME_VERT_COUNT = 2; //Number of frames vertically stacked
var
  CurGroup: TFlowPanel; //Flow panel currently being populated
  procedure ResizeGroup(FP: TFlowPanel);
  var
    Count, CountHalf, NewWidth, I: Integer;
  begin
    //Resize the specific flow panel's width to fit all items
    Count:= FP.ComponentCount;
    NewWidth:= FRAME_WIDTH + FRAME_MARGIN;  //Default width if no items
    if Count > 0 then begin
      //THIS IS WHERE MY CALCULATIONS DO NOT WORK
      CountHalf:= Round(Count / FRAME_VERT_COUNT);
      NewWidth:= (CountHalf * FRAME_WIDTH) + FRAME_MARGIN;
    end;
    if FP.Parent.Width <> NewWidth then
      FP.Parent.Width:= NewWidth;
    //Resize main flow panel's width to fit all contained group panels
    //(automatically extends within scroll box to extend scrollbar)
    Count:= TFlowPanel(FP.Parent.Parent).ControlCount;
    NewWidth:= 0;
    for I := 0 to Count-1 do begin
      NewWidth:= NewWidth + FP.Parent.Parent.Controls[I].Width;
    end;
    NewWidth:= NewWidth + FRAME_MARGIN;
    if FP.Parent.Parent.Width <> NewWidth then
      FP.Parent.Parent.Width:= NewWidth;
  end;
  procedure Add(const Name, Title, Subtitle: String);
  var
    Frame: TfrmItemFrame;
  begin
    Frame:= AddItemFrame(CurGroup, Name); //Create panel, set parent and name
    Frame.OnClick:= ItemClick;
    Frame.Title:= Title;
    Frame.Subtitle:= Subtitle;
    ResizeGroup(CurGroup);
  end;
begin
  CurGroup:= fpMainGroup;
  Add('boxMainItem1', 'Item 1', 'This is item 1');
  Add('boxMainItem2', 'Item 2', 'This is item 2');
  Add('boxMainItem3', 'Item 3', 'This is item 3');
  Add('boxMainItem4', 'Item 4', 'This is item 4');
  Add('boxMainItem5', 'Item 5', 'This is item 5');

  CurGroup:= fpInventoryGroup;
  Add('boxInventItem1', 'Item 1', 'This is item 1');
  Add('boxInventItem2', 'Item 2', 'This is item 2');
  Add('boxInventItem3', 'Item 3', 'This is item 3');
  Add('boxInventItem4', 'Item 4', 'This is item 4');
  Add('boxInventItem5', 'Item 5', 'This is item 5');
  Add('boxInventItem6', 'Item 6', 'This is item 6');
  Add('boxInventItem7', 'Item 7', 'This is item 7');
end;

This is a screenshot of what that code is producing:

Item width screenshot

As you can see, the first group with 5 items is hiding the 5th item, but the second group with 7 items is showing all 7 just fine.

The structure of parent/child relationships is like so (with flow panels in question bold):

  • SplitForm: TSplitForm (main form)
    • ScrollBox2: TScrollBox (container of main flow panel)
      • fpMain: TFlowPanel (container of all group panels)
        • pMainGroup: TPanel (container of flow panel and title panel)
          • fpMainGroup: TFlowPanel (container of item frames)
          • pMainGroupTitle: TPanel (title at top of group)
        • pInventoryGroup: TPanel (container of flow panel and title panel)
          • fpInventoryGroup: TFlowPanel (container of item frames)
          • pInventoryGroupTitle: TPanel (title at top of group)
        • (other panels for more groups)

I tried using each flow panel's AutoSize property, but it didn't acknowledge the heights (2 up) and made things even worse. I basically just need to properly detect the total number of columns within these flow panels.


Solution

  • Round(Count / FRAME_VERT_COUNT);
    

    Here FRAME_VERT_COUNT is 2. When Count is 5 your expression becomes

    Rount(2.5);
    

    The default rounding mode is bankers rounding and this evaluates to 2. When Count is 7 the expression is

    Round(3.5); 
    

    Bankers rounding means this is 4.

    You could do what Sertac suggests and use ceil. However, I would simply avoid floating point altogether. It is just not needed and as a general rule, integer arithmetic is always to be preferred if it is viable. Your expression should be

    (Count + FRAME_VERT_COUNT - 1) div FRAME_VERT_COUNT