8

クライアントを他の座標に移動する win コントロール オブジェクトがあります。問題は、子が多すぎる場合 (たとえば 500 個のコントロール)、コードが非常に遅くなることです。これは、Left プロパティと Top プロパティを設定するたびに各コントロールが再描画されるためです。したがって、WinControl オブジェクトに再描画を停止し、すべてのオブジェクトを新しい位置に移動した後、再度描画できるようにしたいと思います (BeginUpdateメモやリスト オブジェクトのようなもの)。これどうやってするの?オブジェクトを移動するコードは次のとおりです。それは非常に簡単です:

for I := 0 to Length(Objects) - 1 do begin
  with Objects[I].Client do begin
    Left := Left + DX;
    Top := Top + DY;
  end;
end;
4

4 に答える 4

14

Cosmin Prundが説明しているように、長時間の原因は再描画の影響ではなく、コントロールの移動で必要な VCL の再調整の影響です。(実際にそれほど時間がかかる場合は、すぐに再描画を要求する必要があるかもしれません)。

再整列とすべてのチェックを一時的に防止し、アンカーに対して機能するようにするには、設定と Z オーダーを整列し、 と を使用DisableAlignEnableAlignます。SetBoundsそして、それを直接呼び出すことにより、呼び出しの数を半分にします。

procedure TForm1.FormCreate(Sender: TObject);
var
  I: Integer;
  Control: TControl;
begin
  for I := 0 to 499 do
  begin
    Control := TButton.Create(Self);
    Control.SetBounds((I mod 10) * 40, (I div 10) * 20, 40, 20);
    Control.Parent := Panel1;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  I: Integer;
  C: TControl;
begin
  // Disable Panel1 paint
  SendMessage(Panel1.Handle, WM_SETREDRAW, Integer(False), 0);  
  Panel1.DisableAlign;
  try
    for I := 0 to Panel1.ControlCount - 1 do
    begin
      C := Panel1.Controls[I];
      C.SetBounds(C.Left + 10, C.Top + 5, C.Width, C.Height);
    end;
  finally
    Panel1.EnableAlign;
    // Enable Panel1 paint  
    SendMessage(Panel1.Handle, WM_SETREDRAW, Integer(True), 0);
    // Update client area   
    RedrawWindow(Panel1.Handle, nil, 0, RDW_INVALIDATE or RDW_UPDATENOW or RDW_ALLCHILDREN); 
  end;
end;
于 2013-01-25T12:46:49.580 に答える
7

遅さはコントロールの再描画に起因するというあなたの仮定はおそらく真実ですが、すべてではありません。コントロールの移動を処理するデフォルトの Delphi コードは、次のメッセージが受信されるまで描画を遅らせWM_PAINTます。これは、すべてのコントロールの移動が完了した後、メッセージ キューがポンピングされるときに発生します。残念ながら、これには多くのことが関係しており、Delphi や Windows 自体を含む多くの場所でデフォルトの動作が変更される可能性があります。次のコードを使用して、実行時にコントロールを移動するとどうなるかをテストしました。

var i: Integer;
begin
  for i:=1 to 100 do
  begin
    Panel1.Left := Panel1.Left + 1;
    Sleep(10); // Simulate slow code.
  end;
end; 

挙動はコントロール次第!A TControl(例: TLabel) は Delphi の規則に従って動作しますが、aTWinControlはあまりにも多くの要因に依存しています。私のマシンでは背景のみが再描画され、 aTPanelは完全に再描画されます。David のマシンでは も完全に再描画されており、これは多くの要因に依存していることを証明しています。最も可能性の高い要因の場合は、Windows のバージョンです。私は Windows 8 でテストし、David は Windows 7 でテストしました。TButton TCheckBoxTButtonTButton

アラインコントロール雪崩

とにかく、考慮すべきもう 1 つの非常に重要な要素があります。実行時にコントロールを移動するときは、すべてのコントロールの配置と固定に関するすべての規則を考慮する必要があります。AlignControlsこれにより、 / AlignControl/UpdateAnchorRules呼び出しのなだれが発生する可能性があります。これらの呼び出しはすべて同じものを再帰的に呼び出す必要があるため、呼び出しの数は指数関数的になります (したがって、a で多くのオブジェクトを移動するのTWinControlが遅いという観察結果があります)。

最も簡単な解決策は、David が提案するように、すべてを Panel に配置し、パネルを 1 つに移動することです。それが不可能で、すべてのコントロールが実際にTWinControl(つまり、ウィンドウ ハンドルを持っている) 場合は、次を使用できます。

BeginDeferWindowPosDeferWindowPosEndDeferWindowPos

于 2013-01-25T11:40:58.723 に答える
6

すべてのコントロールをパネルに配置してから、コントロールではなくパネルを移動します。そうすれば、1 回の操作でシフトを実行できます。

コンテナ内でコントロールを移動したい場合は、 を使用できますTWinControl.ScrollBy

それだけの価値があるため、コードを個別の行にSetBounds変更して使用するよりもLeft効率的です。Top

SetBounds(Left+DX, Top+DY, Width, Height);
于 2013-01-25T11:21:24.323 に答える
0

高速化するには、再描画を避けるために子の移動中に youのVisibleプロパティを に設定する必要があります。WinControlFalse

あなたと一緒SetBoundsに子コントロールを動かすことから最高のものを得るでしょう。

procedure TForm1.MoveControls( AWinControl : TWinControl; ADX, ADY : Integer );
var
  LIdx : Integer;
begin
  AWinControl.Visible := False;
  try
    for LIdx := 0 to Pred( AWinControl.ControlCount ) do
      with AWinControl.Controls[LIdx] do
        begin
          SetBounds( Left + ADX, Top + ADY, Width, Height );
        end;
  finally
    AWinControl.Visible := True;
  end;
end;

ところで、David が示唆したように、親を移動することは、各子を移動することよりもはるかに高速です。

于 2013-01-25T14:11:24.727 に答える