Search code examples
formsdelphivcl

Mistakes in forms' position calculations in Delphi


I have stuck with small problem.
Imagine two things: form, that should be covered - Cover-Form; and forms that will cover Cover-Form - Tiles. My main goal is to cover my Cover-Form with Tiles. So it will looks like the tiles. I illustrate this idea with image below
enter image description here
Yellow color is a Cover-Form, brown forms - Tiles. On this image you can see that forms are positioned too close each other - there is no free space between them. That's what I need.
But when I try to reach the same effect, I just get non-satisfying result. It is presented on picture below
enter image description here
Second image has an offset after the last tile. It is happens because of different size of form. I don't know exactly what width my Cover-Form will have. I simply divide the whole width of Cover-Form into three parts. But if Cover-Form has width, for example, 173 pixels, each of my Tiles will have width equal 173/3=57.6 pixels, that will be round to 58, but 58*3=174 and it is bad.

Code below runs situation as on second image.

type
  TTileArray = Array of Array of TPoint;

// This routine comes here from David's answer below and were changed by me
procedure EvenlySpacedTiles(PixelCountH, PixelCountV, TileCount: Integer; var ArrayOut: TTileArray);
var
  X: Integer;
  Y: Integer;
  OldH: Integer;
  OldV: Integer;
  OldCount: Integer;
  OldCount1: Integer;
  TempInt: Integer;
begin
  if (PixelCountH) or (PixelCountV) or(TileCount) = 0 then
    Exit;

  OldH := PixelCountH;
  OldCount1 := TileCount;
  for X:=Low(ArrayOut) to High(ArrayOut) do
    begin
      OldV := PixelCountV;
      OldCount := TileCount;

      TempInt := OldH div OldCount1;
      Dec(OldH, TempInt);
      Dec(OldCount1);
      for Y:=Low(ArrayOut) to High(ArrayOut) do
        begin
          ArrayOut[X, Y] := Point(TempInt, OldV div OldCount);
          Dec(OldV, ArrayOut[X, Y].Y);
          Dec(OldCount);
        end;
    end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  F: TForm;
  P: TForm;
  Delta: Integer;
  PrevLeft: Integer;
  PrevTop: Integer;
  X:Integer;
  Y: Integer;
  Arr: TTileArray;
  IncLeft: Integer;
begin
  Delta := 3;

  F := TForm.Create(Application);
  F.BorderStyle := Forms.bsNone;
  F.SetBounds(0, 0, 173, 115);
  F.Position := poDesktopCenter;
  F.Color := $11DFEE;
  F.Show;

  SetLength(Arr, Delta, Delta);
  EvenlySpacedTiles(F.Width, F.Height, Delta, Arr);
  PrevLeft := F.Left;
  PrevTop := F.Top;
  IncLeft := 0;
  for X:=Low(Arr) to High(Arr) do
    begin
      PrevTop := F.Top;
      Inc(PrevLeft, IncLeft);
      for Y:=Low(Arr) to High(Arr) do
        begin
          P := TForm.Create(Application);
          P.FormStyle := fsStayOnTop;
          P.BorderStyle := Forms.bsNone;
          P.Color := Random($FFFFFF);//clSkyBlue;
          P.Show;
          P.Width := Arr[X, Y].X;
          P.Height := Arr[X, Y].Y;
          P.Left := PrevLeft;
          P.Top := PrevTop;
          P.Canvas.Rectangle(P.ClientRect);
          Inc(PrevTop, Arr[X, Y].y);
          IncLeft := Arr[X, Y].X;
        end;
    end;
end;

So there is my question: how can I adjust width of all tiles (3 per row) independently of cover form's width?

Thanks in advance.

Edited

P.S. I modified some parts of code above. Now it works perfectly even with extremely small and large Cover-Form width - from 67 px. to 1237 px. Of course there is a way to improve this code, but the main goal is achieved. I think I will able to finish vertical Tiles placing tomorrow and publish this part there.
In many ways the comment by David gives me an idea how to do this. Thank you, David!

P.S.S.
I have read David's first comment diagonally, so I update code to work in another way, but the result still not good. You can see it on the picture below.
The first Tile has 57 px. width; the second one - 59 px.; the third Tile - only 31 px.
I just can't get how to place Tiles correctly using an algorithm suggested in David's comment. enter image description here

P.S.S.S.
And again there is no result.
enter image description here
Right red line demonstrates a big size of the last tile. Each tile has width 58 px.
David wrote this:

173/3=58. 173-58=115. 115/2=58. 115-58=57. 57/1=57

I am able to calculate it in real life, but I am not able to implement it in the code.
Source code is updated.

P.S.S.S.S.
David's procedure doesn't do what it should do. Picture below illustrates it.
enter image description here
There are a gap between the first and the second Tile, and red line on the right side as on previous picture.

P.S.S.S.S.S.
Well, at this time the first part of my task is accomplished. The second one - is adding more tiles, but I don't sure if I really need them. And I am thankful for this to David Heffernan!! He spend so much time to explain me some things and I don't know how to say him more than simlply 'Thank you very much'. I am afraid, I am able just increase his reputation and accept his post as an answer. It really does the job! On picture we can see the result I needed
enter image description here

P.S.S.S.S.S.S.
I have updated source code, so it can place tile and vertically too.
enter image description here


Solution

  • I would use a simple algorithm like this:

    function EvenlySpacedColumns(PixelCount, ColumnCount: Integer): TArray<Integer>;
    var
      i: Integer;
    begin
      Assert(PixelCount>0);
      Assert(ColumnCount>0);
      SetLength(Result, ColumnCount);
      for i := low(Result) to high(Result) do begin
        Result[i] := PixelCount div ColumnCount;
        dec(PixelCount, Result[i]);
        dec(ColumnCount);
      end;
    end;
    

    Here I use div which in effect uses division followed by truncation. But you could equally use Round(PixelCount / ColumnCount) if you would prefer. It's somewhat arbitrary so I personally would opt for integer arithmetic on the grounds that one should avoid floating point arithmetic if it is not necessary.