TEdit неправильно перерисовывает с Invalidate в Delphi 5

Существует проблема с TScrollBox в Delphi 5 при использовании Cirtix, в некоторых системах, когда пользователь прокручивает, нажимая кнопку в верхней или нижней части конца полосы прокрутки, все приложение зависает. Изначально у нас была проблема с предварительными версиями QucikReports, и мы решили ее, реализовав собственные полосы прокрутки в TScrollBox.

Теперь у нас есть заказная работа, в которой используется TScrollBox, и клиент сообщает о похожей проблеме, поэтому я работаю над ней таким же образом. Я скрываю полосы прокрутки TScrollBox и добавляю свои собственные. Когда они нажаты, я вызываю следующее.

Обратите внимание, что этот тестовый код в настоящее время не работает в Citrix, я тестировал его в XP и Window 7.

Я отключаю перерисовку элемента управления, перемещаю все дочерние элементы управления, затем снова включаю рисование и вызываю Invalidate. Я бы ожидал, что аннулирование полностью перерисует элемент управления, но этого не происходит.

procedure TScrollBoxScrollReplacement.ScrollControls(x: Integer; y: Integer);
var
  I: Integer;
begin
  if (x = 0) and (y = 0) then
    Exit;

  // Stop the control from repaining while we're updating it
  try
    SendMessage(FScrollBox.Handle, WM_SETREDRAW, 0, 0);

    for I := 0 to FScrollBox.ControlCount - 1 do
    begin
      if (FScrollBox.Controls[I] = FVScrollBar) or (FScrollBox.Controls[I] = FHScrollBar) then
        Continue;

      FScrollBox.Controls[I].Left := FScrollBox.Controls[I].Left + x;
      FScrollBox.Controls[I].Top := FScrollBox.Controls[I].Top + y;
    end;

  finally
    // Turn on painting again
    SendMessage(FScrollBox.Handle, WM_SETREDRAW, 1, 0);
  end;
  // Redraw everything
  InvalidateEverything(FScrollBox);
end;

Код для перерисовки элементов управления

procedure TScrollBoxScrollReplacement.InvalidateEverything(AControl: TControl);
var
  I: Integer;
begin
  AControl.Invalidate();

  if (AControl is TWinControl) then
    for I := 0 to TWinControl(AControl).ControlCount - 1 do
      InvalidateEverything(TWinControl(AControl).Controls[I]);
end;

Я добавил Invalidate, Refresh и Reapint и перебрал все дочерние элементы управления, чтобы заставить его работать, но все равно не повезло. Поля редактирования выглядят следующим образом:

введите здесь описание изображения

Если я установлю для параметра Visible значение false, а затем верну его в значение true, они будут правильно перерисовываться, но, очевидно, будет ужасное мерцание. Они также правильно перерисовываются, если я сворачиваю, разворачиваю окно или перетаскиваю его на экран.

Любая помощь приветствуется.

изменить: немного информации об ответах.

Пользователям, ищущим решение, я бы порекомендовал вам попробовать оба. Дэвида и Сертака. Согласно документации Microsoft, решение Дэвида выглядит правильным. Однако в полосе прокрутки Delphi метки, размещенные непосредственно в полосе прокрутки, мерцают, в то время как метки, размещенные в групповых полях в полосе прокрутки, совершенно гладкие. Я думаю, что это может быть проблемой для всех компонентов, которые не происходят от TWinControl. Прокрутка сама по себе более плавная с решением Дэвида, но меньше щелкает с помощью WM_SETREDRAW и RedrawWindow. Я хотел бы принять оба ответа, поскольку оба имеют свои преимущества и недостатки.

изменить: код для всего класса ниже. Чтобы протестировать, просто добавьте в форму полосу прокрутки с некоторыми элементами управления и вызовите

TScrollBoxScrollReplacement.Create(ScrollBox1);

.

unit ScrollBoxScrollReplacement;

interface

uses extctrls, stdctrls, SpScrollBox, forms, Controls, classes, Messages, Windows, Sysutils, Math;

type
  TScrollBoxScrollReplacement = class(TComponent)
  private
    FLastVScrollPos: Integer;
    FLastHScrollPos: Integer;
    FScrollBox: TScrollBox;
    FVScrollBar: TScrollBar;
    FHScrollBar: TScrollBar;
    FVScrollBarVisible: Boolean;
    FHScrollBarVisible: Boolean;
    FCornerPanel: TPanel;
    FMaxRight: Integer;
    FMaxBottom: Integer;
    FOriginalResizeEvent: TNotifyEvent;
    FOriginalCanResizeEvent: TCanResizeEvent;
    FInScroll: Boolean;
    function GetHScrollHeight: Integer;
    function GetVScrollWidth: Integer;
    procedure ReplaceScrollBars;
    function SetUpScrollBar(AControlScrollBar: TControlScrollBar; AKind: TScrollBarKind): TScrollBar;
    procedure ScrollBoxResize(Sender: TObject);
    procedure ScrollBarEnter(Sender: TObject);
    procedure PositionScrollBars;
    procedure Scroll(Sender: TObject; ScrollCode: TScrollCode; var ScrollPos: Integer);
    procedure ScrollControls(x, y: Integer);
    procedure CalculateControlExtremes();
    procedure ResetVScrollBarRange;
    procedure ResetHScrollBarRange;
    function IsReplacementControl(AControl: TControl): Boolean;
    property HScrollHeight: Integer read GetHScrollHeight;
    property VScrollWidth: Integer read GetVScrollWidth;
    procedure ScrollBoxCanResize(Sender: TObject; var NewWidth,
      NewHeight: Integer; var Resize: Boolean);
  public
    constructor Create(AScrollBox: TScrollBox); reintroduce; overload;
    destructor Destroy(); override;
    procedure ResetScrollBarRange();
    procedure BringReplacementControlsToFront();
  end;

implementation

{ TScrollBoxScrollReplacement }

constructor TScrollBoxScrollReplacement.Create(AScrollBox: TScrollBox);
begin
  // Set up the scrollbox as our owner so we're destroyed when the scrollbox is
  inherited Create(AScrollBox);

  FScrollBox := AScrollBox;
  ReplaceScrollBars();

  // We make a note of any existing resize and can resize events so we can call them to make sure we don't break anything
  FOriginalResizeEvent := FScrollBox.OnResize;
  FScrollBox.OnResize := ScrollBoxResize;

  FOriginalCanResizeEvent := FScrollBox.OnCanResize;
  FScrollBox.OnCanResize := ScrollBoxCanResize;
end;

// This is called (unintuitively) when controls are moved within the scrollbox. We can use this to reset our scrollbar ranges

procedure TScrollBoxScrollReplacement.ScrollBoxCanResize(Sender: TObject; var NewWidth,
  NewHeight: Integer; var Resize: Boolean);
begin
  if (not FInScroll) then
  begin
    ResetScrollBarRange();
    BringReplacementControlsToFront();
  end;

  if (Assigned(FOriginalCanResizeEvent)) then
    FOriginalCanResizeEvent(Sender, NewWidth, NewHeight, Resize);
end;


procedure TScrollBoxScrollReplacement.ScrollBoxResize(Sender: TObject);
begin
  if (Assigned(FOriginalResizeEvent)) then
    FOriginalResizeEvent(Sender);

  ResetScrollBarRange();
end;

// Hides the original scrollbars and adds in ours

procedure TScrollBoxScrollReplacement.ReplaceScrollBars();
begin
  FVScrollBar := SetUpScrollBar(FScrollBox.VertScrollBar, sbVertical);
  FVScrollBarVisible := FVScrollBar.Visible;
  FHScrollBar := SetUpScrollBar(FScrollBox.HorzScrollBar, sbHorizontal);
  FHScrollBarVisible := FHScrollBar.Visible;

  FCornerPanel := TPanel.Create(FScrollBox);
  FCornerPanel.Parent := FScrollBox;

  ResetScrollBarRange();
end;


procedure TScrollBoxScrollReplacement.PositionScrollBars();
begin
  // Align our scrollbars correctly
  FVScrollBar.Top := 0;
  FVScrollBar.Left := FScrollBox.ClientWidth - FVScrollBar.Width;
  FVScrollBar.Height := FScrollBox.ClientHeight - HScrollHeight;
  //  FVScrollBar.BringToFront();

  FHScrollBar.Left := 0;
  FHScrollBar.Top := FScrollBox.ClientHeight - FHScrollBar.Height;
  FHScrollBar.Width := FScrollBox.ClientWidth - VScrollWidth;
  //  FHScrollBar.BringToFront();

    // If both scrollbars are visible we'll put a panel in the corner so we can't see components through it
  if (FVScrollBar.Visible) and (FHScrollBar.Visible) then
  begin
    FCornerPanel.Left := FHScrollBar.Width;
    FCornerPanel.Top := FVScrollBar.Height;
    FCornerPanel.Width := FVScrollBar.Width;
    FCornerPanel.Height := FHScrollBar.Height;
    FCornerPanel.Visible := True;
    //    FCornerPanel.BringToFront();
  end
  else
    FCornerPanel.Visible := False;
end;


procedure TScrollBoxScrollReplacement.ResetScrollBarRange();
begin
  CalculateControlExtremes();

  ResetVScrollBarRange();
  ResetHScrollBarRange();

  PositionScrollBars();
end;

procedure TScrollBoxScrollReplacement.ResetVScrollBarRange();
var
  ScrollMax: Integer;
  ScrollAmount: Integer;
begin
  // If all the controls fit to the right of the screen, but there are controls off the left then we'll scroll right.
  ScrollMax := FMaxBottom - FScrollBox.ClientHeight + FHScrollBar.Height;
  if (ScrollMax < 0) and (FLastVScrollPos > 0) then
  begin
    ScrollAmount := Min(Abs(ScrollMax), FLastVScrollPos);
    ScrollControls(0, ScrollAmount);
    FLastVScrollPos := FLastVScrollPos - ScrollAmount;
    CalculateControlExtremes();
  end;

  FVScrollBar.Max := Max(FMaxBottom - FScrollBox.ClientHeight + FHScrollBar.Height + FLastVScrollPos, 0);
  FVScrollBar.Visible := (FVScrollBar.Max > 0) and FVScrollBarVisible;
end;


procedure TScrollBoxScrollReplacement.ResetHScrollBarRange();
var
  ScrollMax: Integer;
  ScrollAmount: Integer;
begin
  // If all the controls fit to the bottom of the screen, but there are controls off the top then we'll scroll up.
  ScrollMax := FMaxRight - FScrollBox.ClientWidth + FVScrollBar.Width;
  if (ScrollMax < 0) and (FLastHScrollPos > 0) then
  begin
    ScrollAmount := Min(Abs(ScrollMax), FLastHScrollPos);
    ScrollControls(ScrollAmount, 0);
    FLastHScrollPos := FLastHScrollPos - ScrollAmount;
    CalculateControlExtremes();
  end;

  FHScrollBar.Max := Max(FMaxRight - FScrollBox.ClientWidth + FVScrollBar.Width + FLastHScrollPos, 0);
  FHScrollBar.Visible := (FHScrollBar.Max > 0) and FHScrollBarVisible;
end;


function TScrollBoxScrollReplacement.SetUpScrollBar(AControlScrollBar: TControlScrollBar; AKind: TScrollBarKind): TScrollBar;
begin
  Result := TScrollBar.Create(FScrollBox);
  Result.Visible := AControlScrollBar.Visible;
  Result.Parent := FScrollBox;
  Result.Kind := AKind;
  Result.Ctl3D := False;
  Result.Max := AControlScrollBar.Range;
  Result.OnEnter := ScrollBarEnter;
  Result.OnScroll := Scroll;
  Result.SmallChange := 5;
  Result.LargeChange := 20;

  AControlScrollBar.Visible := False;
end;

destructor TScrollBoxScrollReplacement.Destroy;
begin
  inherited;
end;

procedure TScrollBoxScrollReplacement.ScrollBarEnter(Sender: TObject);
begin
  // We just call this here to make sure our ranges are set correctly - a backup in case things go wrong
  ResetScrollBarRange();
end;

procedure TScrollBoxScrollReplacement.Scroll(Sender: TObject;
  ScrollCode: TScrollCode; var ScrollPos: Integer);
var
  Change: Integer;
begin
  ResetScrollBarRange();

  if (Sender = FVScrollBar) then
  begin
    Change := FLastVScrollPos - ScrollPos;
    ScrollControls(0, Change);
    FLastVScrollPos := ScrollPos;
  end
  else if (Sender = FHScrollBar) then
  begin
    Change := FLastHScrollPos - ScrollPos;
    ScrollControls(Change, 0);
    FLastHScrollPos := ScrollPos;
  end;
end;

// Moves all the controls in the scrollbox except for the scrollbars we've added

{procedure TScrollBoxScrollReplacement.ScrollControls(x: Integer; y: Integer);
var
  I: Integer;
begin
  if (x = 0) and (y = 0) then
    Exit;

  // Stop the control from repaining while we're updating it
  SendMessage(FScrollBox.Handle, WM_SETREDRAW, 0, 0);
  FInScroll := True;
  try
    for I := 0 to FScrollBox.ControlCount - 1 do
    begin
      if IsReplacementControl(FScrollBox.Controls[I]) then
        Continue;

      FScrollBox.Controls[I].Left := FScrollBox.Controls[I].Left + x;
      FScrollBox.Controls[I].Top := FScrollBox.Controls[I].Top + y;
    end;

  finally
    // Turn on painting again
    FInScroll := False;
    SendMessage(FScrollBox.Handle, WM_SETREDRAW, 1, 0);
  end;

  // Redraw everything
  RedrawWindow(FSCrollBox.Handle, nil, 0, RDW_ERASE or RDW_INVALIDATE or RDW_ALLCHILDREN);
end;  }


procedure TScrollBoxScrollReplacement.ScrollControls(x: Integer; y: Integer);
var
  I: Integer;
  Control: TControl;
  WinControl: TWinControl;
  hWinPosInfo: HDWP;
begin
  if (x = 0) and (y = 0) then
    Exit;

  hWinPosInfo := BeginDeferWindowPos(0);
  Win32Check(hWinPosInfo<>0);
  try
    for I := 0 to FScrollBox.ControlCount - 1 do
    begin
      Control := FScrollBox.Controls[I];
      if (Control = FVScrollBar) or (Control = FHScrollBar) then
        Continue;
      if Control is TWinControl then
      begin
        WinControl := FScrollBox.Controls[I] as TWinControl;
        hWinPosInfo := DeferWindowPos(
          hWinPosInfo,
          WinControl.Handle,
          0,
          WinControl.Left + x,
          WinControl.Top + y,
          WinControl.Width,
          WinControl.Height,
          SWP_NOZORDER or SWP_NOOWNERZORDER or SWP_NOACTIVATE
        );
        Win32Check(hWinPosInfo<>0);
      end
      else
        Control.SetBounds(Control.Left + x, Control.Top + y, Control.Width, Control.Height);
    end;
  finally
    EndDeferWindowPos(hWinPosInfo);
  end;
end;



// works out where our right most and bottom most controls are so we can set the scrollbars correctly

procedure TScrollBoxScrollReplacement.CalculateControlExtremes();
var
  I: Integer;
  Right: Integer;
  Bottom: Integer;
begin
  FMaxRight := 0;
  FMaxBottom := 0;
  for I := 0 to FScrollBox.ControlCount - 1 do
  begin
    if IsReplacementControl(FScrollBox.Controls[I]) then
      Continue;

    Right := FScrollBox.Controls[I].Left + FScrollBox.Controls[I].Width;
    Bottom := FScrollBox.Controls[I].Top + FScrollBox.Controls[I].Height;

    FMaxRight := Max(FMaxRight, Right);
    FMaxBottom := Max(FMaxBottom, Bottom);
  end;
end;

function TScrollBoxScrollReplacement.GetHScrollHeight: Integer;
begin
  if (FHScrollBar.Visible) then
    Result := FHScrollBar.Height
  else
    Result := 0;
end;

function TScrollBoxScrollReplacement.GetVScrollWidth: Integer;
begin
  if (FVScrollBar.Visible) then
    Result := FVScrollBar.Width
  else
    Result := 0;
end;

// Returns true if the passed control is one of the controls we've added

function TScrollBoxScrollReplacement.IsReplacementControl(
  AControl: TControl): Boolean;
begin
  Result := (AControl = FVScrollBar) or (AControl = FHScrollBar) or (AControl = FCornerPanel);
end;

procedure TScrollBoxScrollReplacement.BringReplacementControlsToFront;
begin
  FVScrollBar.BringToFront();
  FHScrollBar.BringToFront();
  FCornerPanel.BringToFront();
end;

end.

person Will Calderwood    schedule 25.03.2014    source источник
comment
Случайно вызывать все Invalidate, Repaint и Refresh довольно бессмысленно. Последние два одинаковы, и оба вызывают Invalidate. Предположительно исходный код назывался просто Invalidate. Кажется бессмысленным перебирать дочерние элементы, поскольку это происходит автоматически. И приведение к TWinControl вызывает некоторое беспокойство. Пожалуйста, не приписывайте вещи тому, чем они не являются.   -  person David Heffernan    schedule 25.03.2014
comment
Да, это код, над которым я работал. Я бы предположил, что простой вызов Invalidate в ScrollBox сработает, но это не так. Я оставил этот код там, так как он показывает, что я пробовал. Как видно на скриншоте - это тестовое приложение, до релиза еще далеко.   -  person Will Calderwood    schedule 25.03.2014
comment
По сути, вам просто нужно FScrollBox.Invalidate, и вы можете выбросить InvalidateEverything. У вас действительно есть эта функция в вашем производственном коде? С циклом for и поддельным приведением? Наверняка это вызывает проблемы.   -  person David Heffernan    schedule 25.03.2014
comment
@DavidHeffernan Как уже было сказано, это тестовый код. Я просто звонил FScrollBox.Invalidate. Это не сработало. Как видно из Скриншота, там все TWinControl. Простой вызов FScrollBox.Invalidate не будет правильно перерисовывать панели. Зацикливание дочерних элементов исправляет это, но поля редактирования отображаются неправильно. Это не код релиза — это код для попытки решить проблему, которую я улажу позже. Если вы можете помочь решить проблему, которая будет оценена.   -  person Will Calderwood    schedule 25.03.2014
comment
@DavidHeffernan Refresh вызывает Repaint, но не Invalidate. Немедленное перерисовывание краски, в то время как Invalidate работает через сообщение и, таким образом, обрабатывается только тогда, когда приложение становится бездействующим или когда вызывается Application.ProcessMessages. В таком случае Invalide должен быть единственным необходимым, это лучшее решение, если вы хотите перерисовать что-то в ответ на действие пользователя, такое как щелчок или прокрутка. Кроме того, это перерисовывание распространяется на родителей, поэтому вам не нужно вызывать это рекурсивно для каждого родителя/потомка в вашем дереве элементов управления.   -  person GolezTrol    schedule 25.03.2014
comment
@GolezTrol В моем VCL Repaint реализовано как Invalidate; Update;. Какую версию VCL вы смотрите. Invalidate вызывает InvalidateRect, а затем, когда очередь сообщений в следующий раз очищается, синтезируется сообщение WM_PAINT.   -  person David Heffernan    schedule 25.03.2014
comment
Кажется, ты прав. Я немного смутился, глядя на TControl.Repaint и .Invalidate. Но в TWinControl он работает именно так, как вы описываете, и даже в TControl аналогично.   -  person GolezTrol    schedule 25.03.2014
comment
@GolezTrol Этот тестовый код работает в Windows XP - Citrix здесь не участвует (пока).   -  person Will Calderwood    schedule 26.03.2014
comment
@DavidHeffernan Я добавил полный класс, чтобы вы могли проверить свои теории самостоятельно, если хотите.   -  person Will Calderwood    schedule 26.03.2014
comment
@Will Вы понимаете мою точку зрения насчет актерского состава?   -  person David Heffernan    schedule 26.03.2014
comment
@DavidHeffernan Да, как я уже сказал, это тестовый код. Актерский состав не является проблемой для этого теста. Проблема в перерисовке.   -  person Will Calderwood    schedule 26.03.2014


Ответы (2)


Я обнаружил, что ваш код начал работать, как только я удалю два сообщения WM_SETREDRAW. Это ваша фундаментальная проблема. Вам нужно будет удалить WM_SETREDRAW сообщений.

Это, без сомнения, будет означать, что вам все еще нужно решить проблему с мерцанием, но это уже другая проблема. Мои быстрые эксперименты показывают, что DeferWindowPos может решить эту проблему. Например:

procedure TScrollBoxScrollReplacement.ScrollControls(x: Integer; y: Integer);
var
  I: Integer;
  Control: TControl;
  WinControl: TWinControl;
  hWinPosInfo: HDWP;
begin
  if (x = 0) and (y = 0) then
    Exit;

  hWinPosInfo := BeginDeferWindowPos(0);
  Win32Check(hWinPosInfo<>0);
  try
    for I := 0 to FScrollBox.ControlCount - 1 do
    begin
      Control := FScrollBox.Controls[I];
      if (Control = FVScrollBar) or (Control = FHScrollBar) then
        Continue;
      if Control is TWinControl then
      begin
        WinControl := FScrollBox.Controls[I] as TWinControl;
        hWinPosInfo := DeferWindowPos(
          hWinPosInfo,
          WinControl.Handle,
          0,
          WinControl.Left + x,
          WinControl.Top + y,
          WinControl.Width,
          WinControl.Height,
          SWP_NOZORDER or SWP_NOOWNERZORDER or SWP_NOACTIVATE
        );
        Win32Check(hWinPosInfo<>0);
      end
      else
        Control.SetBounds(Control.Left + x, Control.Top + y, Control.Width, Control.Height);
    end;
  finally
    EndDeferWindowPos(hWinPosInfo);
  end;
end;

Ваши неоконные элементы управления все равно будут мерцать, но вы можете сделать их оконными или поместить все содержимое полосы прокрутки в оконный элемент управления. Черт возьми, если бы вы просто сделали это, этого было бы достаточно, чтобы решить проблему!

Что бы это ни стоило, мои испытания показывают, что DeferWindowPos обеспечивает более плавную прокрутку, чем WM_SETREDRAW и RedrawWindow. Но эти тесты вряд ли были исчерпывающими, и вы могли найти другие результаты в своем приложении.


Некоторые замечания относительно вашего кода:

Вы используете try/finally неправильно. Узор должен быть:

BeginSomething;
try
  Foo;
finally
  EndSomething;
end;

Вы ошибаетесь, когда звоните SendMessage.

И вы используете неправильный состав в InvalidateEverything. Вы не можете вслепую разыграть TControl в TWinControl. Тем не менее, эта функция не приносит пользы. Вы можете удалить его вообще. То, что он пытается сделать, можно выполнить одним вызовом Invalidate родительского элемента управления.

person David Heffernan    schedule 25.03.2014
comment
Это я прекрасно знаю, проблема в том, что потом он тоже начинает мерцать - попробуй перетащить полосу прокрутки. - person Will Calderwood; 26.03.2014
comment
Может быть и так, но боюсь, что это и есть ответ на вопрос, который вы задали. Вот почему ваши элементы управления не перерисовываются. Если вы хотите решить мерцание, это другой вопрос. - person David Heffernan; 26.03.2014
comment
Вопрос, который я задал, заключается в том, почему Invalidate неправильно перерисовывает элементы управления - Invalidate не требуется, если вы удалите эти две строки. - person Will Calderwood; 26.03.2014
comment
Итак, вы отказываетесь рассматривать вопрос об удалении WM_SETREDRAW? - person David Heffernan; 26.03.2014
comment
Если есть другой способ остановить мерцание, то я их уберу, но другого способа я не знаю. Я добавил их по этой причине. - person Will Calderwood; 26.03.2014
comment
Что ж, это ваш путь вперед. Удалите их и устраните мерцание. Здесь нет мерцания. Как я могу видеть мерцание. - person David Heffernan; 26.03.2014
comment
Вы перетаскиваете полосу прокрутки, а не нажимаете кнопки? Основная причина, по которой меня беспокоит мерцание и ненужные перерисовки, заключается в том, что он будет работать через Citrix, и все эти перерисовки будут отправляться через Интернет. Вот почему я хотел бы устранить проблему. - person Will Calderwood; 26.03.2014
comment
Однако я работаю с полосой прокрутки, я не вижу мерцания. Возможно, вам нужны BeginDeferWindowPos и друзья. - person David Heffernan; 26.03.2014
comment
У вас есть какие-нибудь панели в вашем скроллбоксе? Попробуйте добавить панель или несколько панелей в панели с другими дочерними элементами управления. Мерцание наиболее очевидно для меня на панелях. - person Will Calderwood; 26.03.2014
comment
Только что протестировано на моем хост-компьютере с Windows 7, а не на моей виртуальной машине - мерцание действительно менее очевидно. Я вижу, как кто-то, глядя на это, может легко подумать, что это не проблема. Однако на Citrix все будет намного хуже. - person Will Calderwood; 26.03.2014
comment
Я вижу мерцание. Я, конечно, не говорю, что мерцание — это не проблема. Я просто говорю вам, что удаление мерцания путем полной поломки функционала не является правильным решением. - person David Heffernan; 26.03.2014
comment
Я действительно не понимаю, почему Invalidate неправильно перерисовывает элементы управления. Когда вы сворачиваете и разворачиваете окно или перетаскиваете его и обратно на экран, что он делает такого, чего не делает Invalidate? Если бы на этот вопрос можно было ответить, я подозреваю, что решение стало бы очевидным. - person Will Calderwood; 26.03.2014
comment
@WillCalderwood Медленный пользовательский интерфейс — распространенная проблема внутри виртуальной машины. У меня были списки, прокручиваемые медленно в первый раз, когда они прокручиваются среди других проблем. Они также связаны с настройками производительности пользовательского интерфейса Windows. Хотя я не уверен, я не думаю, что они включены в типичном сеансе Citrix, где предпочтительна производительность. - person Graymatter; 26.03.2014
comment
@WillCalderwood Если пользователь находится в удаленном сеансе, вы, конечно же, не отслеживаете поле прокрутки в реальном времени? - person David Heffernan; 26.03.2014
comment
@DavidHeffernan Если я смогу заставить это работать хорошо, я сделаю это - person Will Calderwood; 26.03.2014
comment
Я провел небольшой эксперимент с BeginDefWindowPos, и он намного чище. Но я бы также сказал, что ванильный свиток не мерцает. Вы уверены, что ваш общий подход является лучшим решением основной проблемы. - person David Heffernan; 26.03.2014
comment
Я не проводил первоначального исследования проблемы, но я знаю, что на это было потрачено много времени (дней), и основная причина не была найдена, и поэтому не могла быть устранена. Это решение, которое меня попросили реализовать, поэтому я иду по этому пути. Я ценю время, которое вы тратите на это. - person Will Calderwood; 26.03.2014
comment
@WillCalderwood Просто ссылка на старую статью о проектировании для удаленного рабочего стола blogs.msdn.com/b/oldnewthing/archive/2006/01/03/508694.aspx - person Graymatter; 26.03.2014
comment
Ну, я тоже потратил много времени на это сейчас. И я дал вам несколько решений. Надеюсь, вы оцените это. Простое быстрое и грязное решение - запихнуть все в одну панель! - person David Heffernan; 26.03.2014
comment
@Graymatter В этом блоге есть интересное замечание о двойной буферизации. - person Will Calderwood; 26.03.2014
comment
@WillCalderwood Надеюсь, вы не выполняете двойную буферизацию или плавную прокрутку для удаленных сеансов! - person David Heffernan; 26.03.2014
comment
@DavidHeffernan Я не могу сказать, что двойная буферизация — это то, о чем я когда-либо думал в удаленных сеансах, а вы? Я не уверен, пытается ли Citrix справиться с этим элегантным способом или нет. Я не писал основную часть этой кодовой базы, и изначально она не была написана для удаленных сеансов в качестве цели, но это то, что я изучу. - person Will Calderwood; 26.03.2014
comment
@DavidHeffernan Похоже, двойная буферизация форм Delphi отключена по умолчанию, так что это не проблема. - person Will Calderwood; 26.03.2014
comment
Я, конечно, занимался этим вопросом раньше. Мое приложение не выполняет рендеринг вне экрана, то есть двойную буферизацию, при работе в удаленном сеансе. Я много раз писал здесь на эту тему. Citrix не может решить эту проблему. Пришлось делать разработчику. - person David Heffernan; 26.03.2014
comment
Должен сказать, что я был несколько озадачен вашим комментарием. Я прекрасно знаю об этом комментарии. Если вы это знали, то почему не сказали об этом? И я хотел бы подчеркнуть, что вы неправильно используете WM_SETREDRAW. Похоже, вы решили использовать его во что бы то ни стало. Я думаю, что это ошибка. - person David Heffernan; 26.03.2014
comment
@DavidHeffernan Прошу прощения за то, что я хорошо осведомлен об этом комментарии. Это было резко. Я был разочарован этой проблемой, и я думал, что было бы ясно, что я пытался предотвратить перерисовку по какой-то причине. Как вы сказали, я должен был указать, что хочу предотвратить ненужные перерисовки в исходном вопросе. - person Will Calderwood; 26.03.2014
comment
@DavidHeffernan Я также попробую ваше решение DeferWindowPos сегодня и проведу сравнение. К сожалению, у меня нет доступа к настройке клиента Citrix, чтобы проверить это, так как это то, что я хотел бы сделать. - person Will Calderwood; 26.03.2014
comment
@DavidHeffernan Прочитав документы, похоже, что ваше решение правильное. Интересно, что с DeferWindowPos элементы управления, отрисовываемые непосредственно на полосе прокрутки, ужасно мерцают, но элементы управления в групповых полях на полосе прокрутки вообще не мерцают. Однако прокрутка, как правило, более плавная. - person Will Calderwood; 26.03.2014
comment
@WillCalderwood Я вообще не вижу мерцания для оконных элементов управления. Неоконные элементы управления будут мерцать, потому что вы не можете использовать их с DeferWindowPos. Итак, я думаю о TLabel. Два способа справиться с этим: поместить неоконный элемент управления в оконный контейнер. Не всегда может помочь. Или заменить на оконный элемент управления. Используйте TStaticText вместо TLabel. Я склоняюсь к последнему. - person David Heffernan; 26.03.2014
comment
@DavidHeffernan Спасибо за это. TStaticText прекрасно работает. Я никогда не знал, что он даже существует. - person Will Calderwood; 26.03.2014
comment
@DavidHeffernan Интересно, что компоненты на панелях мерцают, а компоненты в групповых окнах - нет. Сейчас у меня нет времени разбираться в этом - того, что у меня есть, достаточно. Спасибо за вашу помощь в этом. - person Will Calderwood; 26.03.2014
comment
@WillCalderwood Да, это один из самых больших инструментов, доступных для разработчиков мерцания. Элементы управления без окон — головная боль для мерцания. - person David Heffernan; 26.03.2014

Вы можете заменить свой

FScrollBox.Invalidate();

с участием

RedrawWindow(FSCrollBox.Handle, nil, 0,
    RDW_ERASE or RDW_INVALIDATE or RDW_ALLCHILDREN);

чтобы все элементы управления были признаны недействительными и обновлены должным образом. RDW_ERASE предназначен для стирания предыдущих позиций элементов управления, а RDW_ALLCHILDREN — для ухода за оконными элементами управления внутри. Невыигрышные элементы управления, такие как метки, уже должны быть перерисованы из-за RDW_INVALIDATE.

Хотя этот подход может помочь избежать мерцания, которое вы наблюдаете, он также может привести к некоторой потере плавности прокрутки при отслеживании большого пальца. Это связано с тем, что положение прокрутки может потребоваться обновлять чаще, чем обрабатывается цикл рисования. Чтобы обойти это, вместо аннулирования вы можете немедленно обновить контрольные позиции:

RedrawWindow(FSCrollBox.Handle, nil, 0,
    RDW_ERASE or RDW_INVALIDATE or RDW_UPDATENOW or RDW_ALLCHILDREN);
person Sertac Akyuz    schedule 25.03.2014
comment
Идеально - именно то, что я искал. Пора мне пойти и прочитать все о RedrawWindow. - person Will Calderwood; 26.03.2014
comment
@Sertac Это лучше, чем DeferWindowPos? Мне кажется, что WM_SETREDRAW работает намного хуже с точки зрения плавной прокрутки. Я знаю, что спрашивающий, кажется, настроен на то, что WM_SETREDRAW является единственным решением, но я никогда не был уверен в его полезности. - person David Heffernan; 26.03.2014
comment
@ Дэвид - я действительно не могу сказать. Я не пробовал DeferWindowPs и не видел, чтобы коробка работала в удаленном сеансе. - person Sertac Akyuz; 26.03.2014
comment
@SertacAkyuz Я не говорю об удаленном сеансе. Я говорю о локальной сессии. - person David Heffernan; 26.03.2014
comment
@David - Сейчас попытался сравнить с DeferWindowPos, я не заметил никакой разницы, некоторые элементы управления время от времени отображают легкое мерцание в обоих методах. - person Sertac Akyuz; 26.03.2014
comment
@Sertac Я поместил довольно много элементов управления в поле прокрутки, чтобы получить заметное мерцание. - person David Heffernan; 26.03.2014
comment
@David - С точки зрения плавности, конечно, вы правы, именно WM_SETREDRAW делает ее негладкой. В конце концов, какой из них лучше, зависит от того, как каждый из них помогает решить проблему, которую мы не наблюдаем (мерцание). Если бы они действовали одинаково, для отслеживания большого пальца лучше не использовать WM_SETREDRAW. - person Sertac Akyuz; 26.03.2014
comment
@Sertac Я также твердо уверен, что семантически DeferWindowPos является правильным решением. WM_SETREDRAW не предназначен для массового управления движением. - person David Heffernan; 26.03.2014
comment
Пользователям, ищущим решение, я бы порекомендовал вам попробовать оба. Дэвида и Сертака. Решение Дэвида похоже на «правильное» решение в соответствии с документацией Microsoft. Однако в полосе прокрутки Delphi метки, размещенные непосредственно в полосе прокрутки, мерцают, в то время как метки, размещенные в групповых полях в полосе прокрутки, совершенно гладкие. Прокрутка сама по себе более плавная с решением Дэвида, но с WM_SETREDRAW меньше щелкает. - person Will Calderwood; 26.03.2014
comment
Исходя из этого, TLabel не является оконным, поэтому DeferWindowPos не может помочь. Использование оконного элемента управления TStaticText — хороший способ избежать этой проблемы. - person David Heffernan; 26.03.2014
comment
@Will - я думаю, что решил проблему плавности. - person Sertac Akyuz; 26.03.2014
comment
@SertacAkyuz Я пробовал это - это, безусловно, улучшает плавность, но все же не так гладко, как другой подход. Преимущество вашего подхода заключается в том, что метки и другие элементы управления, не происходящие от TWinControl, не мерцают. - person Will Calderwood; 27.03.2014