4

通常、DelphiでGDI +を使用すると、TPaintBoxを使用してOnPaintイベント中にペイントできます。

procedure TForm1.PaintBox1Paint(Sender: TObject);
var
   g: TGPGraphics;
begin
   g := TGPGraphics.Create(PaintBox1.Canvas.Handle);
   try
      g.DrawImage(FSomeImage, 0, 0);
   finally
      g.Free;
   end;
end;

このパラダイムの問題は、毎回グラフィックスオブジェクトを破棄することは無駄であり、パフォーマンスが低いことです。さらに、GDI +には、永続的なGraphicsオブジェクトがある場合にのみ使用できるいくつかの構成があります。

もちろん、問題はいつそのGraphicsオブジェクトを作成できるかということです。ハンドルがいつ使用可能になり、いつ無効になるかを知る必要があります。Graphicsオブジェクトを作成および破棄できるように、この情報が必要です。


ソリューションの試みNº1

作成の問題は、本当に必要なときに作成することで解決できます。最初にペイントサイクルが呼び出されたときは、次のようになります。

procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
   if FGraphics = nil then
      FGraphics := TGPGraphics.Create(PaintBox1.Canvas.Handle);

   FGraphics.DrawImage(FSomeImage, 0, 0);
end;

ただし、デバイスコンテキストが無効になる時期を知る必要があるため、FGraphcisオブジェクトを破棄して、次に必要になったときに再作成できるようにすることができます。何らかの理由でTPaintBoxのデバイスコンテキストが再作成された場合、次にOnPaintが呼び出されたときに、無効なデバイスコンテキストを利用します。

TPaintBoxのデバイスコンテキストハンドルがいつ作成、破棄、または再作成されるかを知るために、Delphiで意図されているメカニズムは何ですか?

4

4 に答える 4

3

TPaintBox には TControlCanvas 型の Canvas があるため、標準の TPaintBox を使用することはできません。この問題に関連するメンバーは次のとおりです。

TControlCanvas = class(TCanvas)
private
  ...
  procedure SetControl(AControl: TControl);
protected
  procedure CreateHandle; override;
public
  procedure FreeHandle;
  ...
  property Control: TControl read FControl write SetControl;
end;

問題は、FreeHandle と SetControl が仮想ではないことです。

ただし: TControlCanvas が作成され、ここに割り当てられます。

 constructor TGraphicControl.Create(AOwner: TComponent);
 begin
   inherited Create(AOwner);
   FCanvas := TControlCanvas.Create;
   TControlCanvas(FCanvas).Control := Self;
 end;

したがって、仮想メソッドを持つ降順の TMyControlCanvas と、次のように Canvas を割り当てる TMyPaintBox を作成することができます。

 constructor TMyPaintBox.Create(AOwner: TComponent);
 begin
   inherited Create(AOwner);
   FCanvas.Free;
   FCanvas := TMyControlCanvas.Create;
   TMyControlCanvas(FCanvas).Control := Self;
 end;

次に、TMyControlCanvas のメソッドを使用して、TGPGraphics を動的に作成および破棄できます。

それでうまくいくはずです。

--jeroen

于 2009-10-24T18:48:45.883 に答える
2

作成の検出は簡単です。Jeroen's answer が示すCreateHandleように、子孫をオーバーライドしてTControlCanvas、デフォルトの代わりに自分のものを配置するだけです。破壊の検出はより困難です。

この問題を回避する 1 つの方法は、TGpGraphics ハンドルがペイント ボックスのハンドルと等しいかどうかを確認することです。そのため、デバイス コンテキストが解放された瞬間を検出するのではなく、知る必要がある前に確認するだけです。

if not Assigned(FGraphics)
    or (FGraphics.GetHDC <> PaintBox1.Canvas.Handle) then begin
  FGraphics.Free;
  FGraphics := TGpGraphics.Create(PaintBox1.Canvas.Handle);
end;

ただし、これはおそらく信頼できません。ハンドル値は再利用される傾向があるため、HDC 値は 2 つのチェック間で同じである可能性がありますが、同じ OS デバイス コンテキスト オブジェクトを参照しているという保証はありません。


TCanvas基本クラスはそれ自体のプロパティをクリアしないためHandle、キャンバスを無効にするものはすべて外部で発生する必要があります。プロパティが再割り当てされたときにそのプロパティTControlCanvasをクリアしますが、インスタンスはめったに共有されないため、通常はコントロールが作成されたときにのみ発生します。ただし、インスタンスは、 に保持されているデバイス コンテキスト ハンドルのプールから機能します。それらのいずれかが DC を必要とするときはいつでも ( で)、作成しようとしているハンドル用にキャンバス キャッシュにスペースを作るために呼び出します。その関数は (非仮想)メソッドを呼び出します。キャッシュ サイズは 4 (「 」を参照) であるため、またはの子孫が複数ある場合HandleControlTControlCanvasTControlCanvasCanvasListTControlCanvas.CreateHandleFreeDeviceContextFreeHandleCanvasListCacheSizeTCustomControlTGraphicControlあなたのプログラムでは、一度に 4 つ以上のキャッシュを再描画する必要がある場合、キャッシュ ミスが発生する可能性が高くなります。

TControlCanvas.FreeHandle仮想ではなく、仮想メソッドを呼び出しません。そのクラスの子孫を作成してそれに仮想メソッドを与えることはできますが、VCL の残りの部分は非仮想メソッドの呼び出しを継続し、追加したことを忘れます。


デバイス コンテキストがいつ解放されたかを検出しようとする代わりに、別の TGpGraphics コンストラクターを使用する方がよい場合があります。たとえば、DC ハンドルの代わりにウィンドウ ハンドルを取るものを使用します。ウィンドウ ハンドルの破壊は、はるかに簡単に検出できます。1 回限りの解決策として、独自のメソッドをプロパティに割り当て、メッセージTPaintBox.WindowProcを監視しwm_Destroyます。これを頻繁に行う場合は、子孫クラスを作成してオーバーライドしますDestroyWnd

于 2009-10-26T06:47:12.787 に答える
1

グラフィックス オブジェクトの作成/破棄によるパフォーマンスへの影響は最小限です。そもそも gdi+ の描画コマンドを使用することによるパフォーマンス ヒットは、それをはるかに上回ります。いずれにせよ、ユーザー インターフェイスの描画に関しては、どちらも心配する価値はありません。ユーザーはとにかく気付かないからです。率直に言って、グラフィックス オブジェクトを持ち歩き、DC ハンドルへの変更を追跡しようとするのは非常に不便です (特に、独自のクラス セット内にグラフィックス ルーチンをカプセル化している場合)。

ビットマップをキャッシュする必要がある場合は、GDI+ を使用してキャッシュするビットマップを作成し (適切なサイズにして、必要なアンチエイリアス設定を使用)、tmemorystream に保存し、必要なときにそれを行うことを検討してください。 、ストリームからロードし、古き良きbitbltを使用して描画します。Graphics.DrawImage を使用するよりもはるかに高速です。私は桁違いに速く話している。

于 2009-10-30T21:59:25.790 に答える
-3
procedure TGraphicControl.WMPaint(var Message: TWMPaint);
begin
  if Message.DC <> 0 then
  begin
    Canvas.Lock;
    try
      Canvas.Handle := Message.DC;
      try
        Paint;
      finally
        Canvas.Handle := 0;
      end;
    finally
      Canvas.Unlock;
    end;
  end;
end;

Canvas.Handle := Message.DC;
于 2012-01-14T17:33:51.313 に答える