Сбивается позиция некоторых элементов в TFrame

Вывожу список видео канала ютуба. Для каждого видео создаётся отдельный экземппяр TFrame. Если вывести много таких видео, то в какой-то момент некоторые элементы фрейма начинают уезжать вверх.

Если вывести еще больше видео, то в следующих фреймах этих элементов становится вообще не видно.
Почему?

Так а как они создаются, может с высотой/позицией что-то не так?


Картинки лучше на форум загружать (например, Ctrl+V если она в буфере, или перетащить файл в область редактора), чтобы не смотреть на кучу рекламы (даже через юблок один баннер пробился :slight_smile: ) и чтобы внезапно не пропали.

Весь фрейм создаётся в дизайн-тайме. Потом при парсинге JSON’ов создаётся динамический массив таких фреймов. При создании происходит первый ресайз, чтобы подтянуть размер фрейма под форму. Потом при ресайзе формы, ресайзятся все фреймы. Но там всё ресайзится только в ширину. Позиция компонентов по высоте не теребонькается.

Но что именно? Высота нигде не меняется.

Ну тут сложно без кода гадать, может просто ошибка при ресайзе.

Это через Anchors/Align скорее всего проще сделать.

Если бы была ошибка при ресайзе, тогда бы все фреймы косячились.

procedure TYouTubeFrameVideo.FrameResize;
begin
  imgFav.Left := Parent.Width - imgFav.Width;
  lblVideoTitle.Width := Parent.Width - lblVideoTitle.Left - imgFav.Width;
  btnDownload.Left := Parent.Width - btnDownload.Width - 4;
  pbDownload.Width := btnDownload.Left - pbDownload.Left - 4;
  btnGetWebPage.Left := Parent.Width + 10;
  btnSetWebPage.Left := btnGetWebPage.Left;
  btnGetVideoInfo.Left := btnGetWebPage.Left;
  imgScrollBar.Left := -Left;
  imgScrollBar.Width := Parent.Width;
end;

procedure TForm1.FormResize(Sender: TObject);
var
  i : Integer;
begin
  gbFavorites.Left := Width - gbFavorites.Width - 30;
  gbFavorites.Height := Height - 40;
  pnlFavBkg.Height := gbFavorites.Height - pnlFavBkg.Top - 10;
  PageControl1.Left := 0;
  PageControl1.Top := 0;
  PageControl1.Width := gbFavorites.Left - 4;
  PageControl1.Height := Height - 40;
  ScrollBarSearch.Top := 0;
  ScrollBarSearch.Left := TabSheetResults.Width - ScrollBarSearch.Width;
  ScrollBarSearch.Height := TabSheetResults.Height;
  pnlSearchResults.Left := 0;
  pnlSearchResults.Top := 0;
  pnlSearchResults.Width := TabSheetResults.Width - ScrollBarSearch.Width;
  pnlSearchResults.Height := TabSheetResults.Height;
  btnRestoreFormat.Left := TabSheetSettings.Width - btnRestoreFormat.Width;
  EditOutputFileNameFormat.Width := btnRestoreFormat.Left - EditOutputFileNameFormat.Left;
  btnSelectBrowser.Left := TabSheetSettings.Width - btnSelectBrowser.Width;
  EditBrowser.Width := btnSelectBrowser.Left - EditBrowser.Left - 6;
  StackFrames;
end;

procedure TForm1.StackFrames;
var
  i, h, h2 : Integer;
begin
  if (Length(FramesVideo) > 0) or (Length(FramesChannel) > 0) then
  begin
    h := -ScrollBarSearch.Position;
    h2 := 0;
    for I := 0 to Length(FramesChannel) - 1 do
    begin
      FramesChannel[i].Top := h;
      h := h + FramesChannel[i].Height + 4;
      h2 := h2 + FramesChannel[i].Height + 4;
    end;
    for I := 0 to Length(FramesVideo) - 1 do
    begin
      FramesVideo[i].Top := h;
      FramesVideo[i].Width := pnlSearchResults.Width - ScrollBarSearch.Width + SLIDING_WIDTH;
      FramesVideo[i].FrameResize;
      h := h + FramesVideo[i].Height + 4;
      h2 := h2 + FramesVideo[i].Height + 4;
    end;

    if h2 > pnlSearchResults.Height then
    begin
      ScrollBarSearch.Max := h2;
      ScrollBarSearch.PageSize := pnlSearchResults.Height;
      ScrollBarSearch.LargeChange := pnlSearchResults.Height;
      ScrollBarSearch.Enabled := True;
    end else
    begin
      ScrollBarSearch.Position := 0;
      ScrollBarSearch.Enabled := False;
    end;
  end else
  ScrollBarSearch.Enabled := False;
end;

Мне не проще. Не люблю VCL.

Всем проще, пара кликов мышки и не нужен весь этот сложный код.

Вот сделал сейчас в Лазарусе за 20 минут, всё работает и не сбивается.

Во фрейме добавить anchor Right у прогрессбара, у кнопки поменять Left на Right.
На форме все стороны у пейджконтрола, и все кроме Left у групбокса справа.
На вкладку кинул скролбокс с align Client, и добавлять фреймы с align Top.

{$mode delphi}{$H+}

uses ..., myframe, Generics.Collections;
...

var
  frames: TList<TFrame1>;

procedure TForm1.FormCreate(Sender: TObject);
begin
  frames := TList<TFrame1>.Create;
end;

procedure TForm1.btnAppendClick(Sender: TObject);
var
  frame: TFrame1;
  lastPos: integer;
begin
  frame := TFrame1.Create(Self);
  frame.Name := 'myframe' + IntToStr(frames.Count);
  frame.Parent := ScrollBox1;
  frame.Align := alTop;
  if frames.Count mod 2 = 0 then frame.Color:= clCream else frame.Color := clSkyBlue;

  lastPos := 0;
  if (frames.Count > 0) then
  begin
    lastPos := frames[frames.Count - 1].Top + frames[frames.Count - 1].Height;
  end;

  frame.Top := lastPos;

  frames.Add(frame);
end;                                    


Кстати, при отладке подобных проблем бывает помогает задать цвет фона у элементов. Например, разный для четных/нечетных. Чтобы понять что на что залезает и т.п.
И выводить куда-нибудь позиции (например, OutputDebugString и смотреть View —> Debug Windows —> Event Log).

Как будто формы, фреймы и визуальные компоненты совсем не vcl ))

Вы показали только 5 фреймов. И что вы этим доказали? У меня при пяти фреймах тоже всё работает. А вот создайте фреймов столько же, сколько у меня (примерно 60-70), отключите автовыравнивание и т.д. и вот тогда посмотрим, что будет.

Не выйдет. У меня фрейм можно двигать влево-вправо. Он шире, чем его видимая область.

Не понял, чем именно это поможет? В моём случае ведь и так понятно, что улетает кнопка и прогрессбар. Но почему именно они и почему это происходит, начиная только примерно с 60-го фрейма?

Автовыравнивание - зло. Обёртки - зло. Зло - зло.

Естественно. Фреймы тоже зло, делфи тем более, и вообще программирование зло еще то) А если серьезно стараюсь с фреймами не работать из-за их глючности, особенно при наследовании. А если уж деваться некуда, то вообще на них не дышать и обращаться как с предметами искусства из тончайшего хрусталя )

Непонятно из какого фрейма, какая у чего получается высота/позиция и т.д.

с 200 тоже работает )

а вот на 300 выдало

[Debugger Exception Notification]

Project project1 raised exception class ‘Exception’ with message:
Position range overflow in myframe297.SendMoveSizeMessages: Left=0, Top=32780.

Может тут тоже Top переполнился? Надо в настройках проекта включить все рантайм проверки для отладочной сборки.

https://stackoverflow.com/a/14656731/964478

Вообще тут надо что-то с виртуализацией, чтоб рисовались только видимые элементы, а не все 100500. Какие-то компоненты для списков/таблиц умеют это.

Что даст знание этих параметров?

Это с отключенным автовыравниванием?

Top фрейма или Top компонентов во фрейме? Top же 32 бита вроде.

Из стандартных DrawGrid c этим справится. В OnDrawCell прорисовывать картинки с текстом и кнопку. Кнопку не создавать, а именно прорисовывать и ловить на этом месте клик в ячейке грида

У меня фрейм может скроллиться. По-этому, такие штуки не прокатят. Точнее, прокатят, но придётся сильно извращаться. Тогда уж эту GUI’ню проще на WinAPI написать.

нет

Параметра из сообщения винапи, которое передается каким-то из контролов-оберток над винапи. То есть Top фреймов нужны для вычисления позиций и вызова функций винапи.
Забавно, что Лазарус на Линуксе эмулирует это. :slight_smile:

See the documentation on the WM_SIZE message wherein width and height are passed together in a single 32 bit parameter:

So width and height values are limited to 16 bits. That is: when SetWindowPos or alike is involved, which is called by SetBounds , which is called by setting Top .

Subsequently, the Top value of a control, which can be negative, is limited to 15 bits and 1 sign bit, thus ±32,767. To be specific: that is where Control.ClientOrigin.X/Y is bound by. E.g. thus resolving in a maximum Top value of 32,167 for a control placed in the middle of a 1920x1200 pixel screen.

So this is why the last panels appear on the same spot within your scroll box.

Note that this limitation does not apply to VCL controls without Windows handle.

Там по ссылке вроде есть какое-то костыльное решение в первом куске кода, может поможет.

Да, в вкл/винформс сложно с этим, поэтому я там стараюсь ограничиваться стандартным поведением.

WPF/Qt/QML/JavaFX/HTML хороши для такого. Может быть FireMonkey в новых Дельфи, не пробовал.

В данном случае было бы видно, что сбивается Top после 32к.
UPD: хотя не, скорее всего только при создании сообщений для винапи.
В общем :arrow_down:

Тогда ваш эксперимент не имеет смысла. Тут смысл в том, чтобы вымутить из-за чего оно сбивается, если не влиять на их позицию.

Костыльное решеие и так известно. Если при ресайзе самому выставлять им Top, то всё работает. Правда, не пробовал создавать овер дофига фреймов. Но в пределах 120 точно работает.
Попробую вывести куда-нибудь их количество и позицию.

Дык при выравнивании тоже вычисляется позиция аналогично, просто самому не надо этот код писать )

-  frame.Align := alTop;
+  frame.Top := 33000;

та же ошибка в TWinControl.SendMoveSizeMessages

{-------------------------------------------------------------------------------
  Send Move and Size messages through the LCL message paths. This simulates the
  VCL behaviour and has no real effect.
-------------------------------------------------------------------------------}
procedure TWinControl.SendMoveSizeMessages(SizeChanged, PosChanged: boolean);
begin
  ...
  
  if PosChanged then
  begin
    with MoveMsg do
    begin
      Msg:= LM_MOVE;
      MoveType:= 1;
      if (FLeft < Low(Smallint)) or (FLeft > High(Smallint))
      or (FTop  < Low(Smallint)) or (FTop  > High(Smallint)) then
        raise Exception.CreateFmt('Position range overflow in %s.SendMoveSizeMessages:'
                                 +' Left=%d, Top=%d.', [Name, FLeft, FTop]);     
      XPos := FLeft;
      YPos := FTop;
      ...
    end;
    WindowProc(TLMessage(MoveMsg));

аналогично чему?

Ручному присваиванию Top.

При ручном присваивании я знаю, что я туда присвою. А автоалайнмент что туда присвоит?

Тоже самое, просто контрол сам вычислит. Ну выше я ж сам присвоил 33к, и еще выше описание причин.

В настройках проекта где-то в Delphi —> Compiling —> Runtime errors пункты про range или overflow, если включить, то должно выдать исключение.