4

プロジェクト(TFrameの子孫)にフレームがあり、その上に何かをペイントしたいと思います。

フォーラムからわかるように、それを行う一般的な方法はPaintWindowメソッドをオーバーライドすることです。

私はこれをクリーンなプロジェクトで試しました:

type
  TMyFrame = class(TFrame)
  private
    FCanvas: TCanvas;
  protected
    procedure PaintWindow(DC: HDC); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy(); override;
  end;

implementation

{$R *.dfm}

constructor TMyFrame.Create(AOwner: TComponent);
begin
  inherited;
  FCanvas := TCanvas.Create();
end;

destructor TMyFrame.Destroy();
begin
  FCanvas.Free();
  inherited;
end;

procedure TMyFrame.PaintWindow(DC: HDC);
begin
  inherited;
  FCanvas.Handle := DC;
  FCanvas.Pen.Width := 3;
  FCanvas.Pen.Color := clRed;
  FCanvas.MoveTo(0, 0);
  FCanvas.LineTo(ClientWidth, ClientHeight);
  FCanvas.Pen.Color := clGreen;
  FCanvas.MoveTo(ClientWidth, 0);
  FCanvas.LineTo(0, ClientHeight);
end;

ただし、フレームをメインフォームに配置した後、フレームのプロパティで有効にするまで、デバッガーはこのメソッドを入力しませんでしたDoubleBuffered。の値はParentBackground結果に影響しませんでした。

WM_PAINTハンドラーをオーバーライドすると、問題も解決されます。

type
  TMyFrame = class(TFrame)
  protected
    procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
  ...

procedure TMyFrame.WMPaint(var Message: TWMPaint);
begin
  inherited;
  FCanvas.Handle := GetDC(Handle);
  FCanvas.Pen.Width := 3;
  FCanvas.Pen.Color := clRed;
  FCanvas.MoveTo(0, 0);
  FCanvas.LineTo(ClientWidth, ClientHeight);
  FCanvas.Pen.Color := clGreen;
  FCanvas.MoveTo(ClientWidth, 0);
  FCanvas.LineTo(0, ClientHeight);
  ReleaseDC(Handle, FCanvas.Handle);
end;

DoubleBufferedこのコードは、またはに割り当てられた値に関係なく、常に交差線を描画しますParentBackground

しかし、/の代わりに/を使用しようとするとBeginPaintEndPaint問題GetDCReleaseDC返されました。

procedure TMyFrame.WMPaint(var Message: TWMPaint);
var
  PS: PAINTSTRUCT;
begin
  inherited;
  FCanvas.Handle := BeginPaint(Handle, PS);
  FCanvas.Pen.Width := 3;
  FCanvas.Pen.Color := clRed;
  FCanvas.MoveTo(0, 0);
  FCanvas.LineTo(ClientWidth, ClientHeight);
  FCanvas.Pen.Color := clGreen;
  FCanvas.MoveTo(ClientWidth, 0);
  FCanvas.LineTo(0, ClientHeight);
  EndPaint(Handle, PS);
end;

FCanvas.Handleはゼロ以外ですが、結果は空白のフレームになります。この場合、何も変更しないDoubleBufferedか設定します。ParentBackground

多分私はそれらを間違って呼んでいますか?

このフレームで有効にしたくないので、ここで/でWM_PAINTハンドラーを使用します。また、他のプログラマーが私のフレームをプロジェクトに組み込んだ後に誤って無効にし、私と同じ頭痛の種になるのではないかと心配しています。GetDCReleaseDCDoubleBufferedDoubleBuffered

しかし、フレームの表面にペイントするためのより安全で正しい方法があるのではないでしょうか。

4

2 に答える 2

5

テストフレームにコントロールを配置しない場合は、問題を複製できます(これは、おそらく、問題を複製できない理由でもあります。フレームがフォーム上にあることを視覚的に確認するために、コントロールをスローします)。

PaintHandlerコントロールがない場合は理由が呼び出されず、コントロールがないのにが設定されている場合に呼び出される理由は、のメッセージハンドラーがDoubleBufferedどのように設計されているかです。WM_PAINTTWinControl

procedure TWinControl.WMPaint(var Message: TWMPaint);
var
  ..
begin
  if not FDoubleBuffered or (Message.DC <> 0) then
  begin
    if not (csCustomPaint in ControlState) and (ControlCount = 0) then
      inherited
    else
      PaintHandler(Message);
  end
  else
  begin
    ..

ご覧のとおり、DoubleBufferedが設定されていない場合やコントロールがない場合PaintHandlerは呼び出されません(結局、ペイントするものはありません。カスタム描画ではなく(csCustomPaintフラグもありません)、表示するコントロールもありません)。がDoubleBuffered設定されると、を呼び出す別のコードパスがたどらWMPrintClientれ、次にがが呼び出されますPaintHandler


コントロールなしでフレームを使用することになった場合(可能性は低いですが)、上記のコード部分から修正が明らかです(知っている場合も賢明です):include csCustomPaintin ControlState

type
  TMyFrame = class(TFrame)
    ..
  protected
    procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
  ..

procedure TMyFrame.WMPaint(var Message: TWMPaint);
begin
  ControlState := ControlState + [csCustomPaint];
  inherited;
  ControlState := ControlState - [csCustomPaint];
end;

次に、継承されたWM_PAINTハンドラーがを呼び出しますPaintHandler


BeginPaintメッセージハンドラで/EndPaintを 使用してペイントWM_PAINTが機能しないように見える理由は、inheritedペイントコードの前の呼び出しで更新領域が検証されるためです。電話をかけた後、あなたのrcPaintメンバーを確認してください。(0、0、0、0)であることがわかります。PAINTSTRUCTBeginPaint

無効な領域が残っていないため、OSは次の描画呼び出しを無視します。これは、キャンバスに描画する前にフレームのクライアントの長方形を無効にすることで確認できます。

procedure TMyFrame.WMPaint(var Message: TWMPaint);
var
  PS: PAINTSTRUCT;
begin
  inherited;
  InvalidateRect(Handle, nil, False);      // <- here
  FCanvas.Handle := BeginPaint(Handle, PS);
  FCanvas.Pen.Width := 3;
  FCanvas.Pen.Color := clRed;
  FCanvas.MoveTo(0, 0);
  FCanvas.LineTo(ClientWidth, ClientHeight);
  FCanvas.Pen.Color := clGreen;
  FCanvas.MoveTo(ClientWidth, 0);
  FCanvas.LineTo(0, ClientHeight);
  EndPaint(Handle, PS);
end;

これで、描画が有効になります。もちろんinherited、代わりに電話をかけないか、描画する部分だけを無効にすることもできます。

于 2012-06-04T00:55:54.350 に答える
0

デザイナーにいるときにだけ呼び出されるように見えます

procedure TCustomFrame.PaintWindow(DC: HDC);
begin
   // Paint a grid if designing a frame that paints its own background
   if (csDesigning in ComponentState) and (Parent is TForm) then
   with TForm(Parent) do
      if (Designer <> nil) and (Designer.GetRoot = Self) and
         (not StyleServices.Enabled or not Self.ParentBackground) then
          Designer.PaintGrid
   end; 

特別なペイントを行う唯一の方法は、フレームにWM_PAINTを追加することです。

TFrame3 = class(TFrame)
protected
  procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
end;
于 2012-05-21T07:45:53.043 に答える