TEdit没有在Delphi 5中使用Invalidate正确重绘

时间:2014-03-25 19:26:40

标签: delphi delphi-5

在某些系统上使用Cirtix时,Delphi 5中的TScrollBox存在问题,当用户通过单击滚动条末尾顶部或底部的按钮滚动整个应用程序时会冻结。我们最初在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中添加并循环遍历所有子控件以努力使其正常工作,但仍然没有运气。编辑框如下所示:

enter image description here

如果我将Visible设置为false并返回true,那么它们将正确重绘,但显然有一个可怕的闪烁。如果我最小化窗口最大化,或者将其拖到屏幕上,它们也会正确重绘。

非常感谢任何帮助。

编辑:有关答案的一些信息。

寻找解决方案的用户,我建议您尝试两者。大卫和塞塔克。根据微软的文档,David看起来是正确的解决方案。但是,使用Delphi滚动条时,标签直接放置在滚动框闪烁中,滚动框中放置在组框中的标签非常流畅。我认为这可能是所有不从TWinControl下降的组件的问题。使用David的解决方案滚动本身更顺畅,但使用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.

2 个答案:

答案 0 :(得分:5)

我发现一旦删除了两条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_SETREDRAWRedrawWindow更平滑。但这些测试并非详尽无遗,您可能会在应用中找到不同的结果。


关于您的代码的一些旁白:

您对try/finally的使用不正确。模式必须是:

BeginSomething;
try
  Foo;
finally
  EndSomething;
end;

您拨打SendMessage时出错了。

你在InvalidateEverything中使用了错误的演员表。你不能盲目地将TControl投射到TWinControl。也就是说,这个功能没有用。你可以完全删除它。只需调用父控件的Invalidate即可执行它尝试执行的操作。

答案 1 :(得分:5)

您可以替换

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);