모든 VCL 컨트롤에서 Canvas가 "숨겨진"이유는 무엇입니까?

Nov 28 2020

모든 컨트롤 (버튼, 패널 등) 캔버스에 무언가를 그리는 기본 절차를 수행하고 싶습니다 (단순화를 위해 삼각형이라고합시다).

procedure DrawTriangle(Control: TCustomControl);

이 함수에서 컨트롤의 크기를 알기 위해 Control.Width & Control.Height를 사용해야합니다. Canvas가 보호되어 있기 때문에 상상했던 것보다 더 어렵습니다.

해결책은 절차 내에서 컨트롤의 캔버스를 얻는 것입니다.

VAR
   ParentControl: TWinControl;
   canvas: TCanvas;
begin
 ParentControl:= Control.Parent;
 Canvas:= TCanvas.Create;
 TRY
  Canvas.Handle:= GetWindowDC(ParentControl.Handle);
  WITH Canvas DO
    xyz
 FINALLY
   FreeAndNil(canvas);
 END;
end;

그러나 무언가를 칠할 때마다 캔버스를 만들고 파괴하는 것은 CPU 낭비처럼 보입니다 ...

그래서 내 질문은 다음과 같습니다.

  1. 캔버스가 디자인에 의해 숨겨진 (보호) 이유는 무엇입니까?
  2. CPU를 낭비하지 않고 우아하게 (하나의 단일 매개 변수) 해결하는 방법

이제 Paint 메서드를 재정의하고 있지만 이것은 여러 위치에서 페인팅 코드를 복제하는 것을 의미합니다. 물론 DrawTriangle은 더 많은 매개 변수 (Canvas, Control Width / Height 등)를 수신 할 수 있습니다. ....하지만 잘 ... 노출 된 Paint 메서드를 사용하면 모든 것이 훨씬 더 우아해 졌을 것입니다.

답변

2 AndreasRejbrand Nov 28 2020 at 16:49

질문에 대한 의견에서

  1. 이 솔루션이 TCustomControl하위 항목 으로 제한되는 것으로 충분합니다.
  2. 그리기 프로 시저가 간단한 함수 호출로 인수 컨트롤에서 캔버스를 얻을 수 있다면 충분히 "우아하다".

그렇다면 다음 해결책이 가능합니다.

//
// Infrastructure needed
//

type
  TCustomControlCracker = class(TCustomControl)
  end;

function CustomControlCanvas(AControl: TCustomControl): TCanvas;
begin
  Result := TCustomControlCracker(AControl).Canvas;
end;

//
// My reusable drawing functions
// (Can only be used in TCustomControl descendants)
//

procedure DrawFrog(AControl: TCustomControl);
var
  Canvas: TCanvas;
begin
  Canvas := CustomControlCanvas(AControl);
  Canvas.TextOut(10, 10, 'Frog');
end;

공지 DrawFrog단 하나의 매개 변수, 컨트롤 자체를합니다. 그런 다음 CPU 오버 헤드가 극히 적은 간단한 함수 호출을 사용하여 컨트롤의 캔버스를 얻을 수 있습니다.

전체 예 :

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

type
  TTestControl = class(TCustomControl)
  protected
    procedure Paint; override;
  end;

type
  TCustomControlCracker = class(TCustomControl)
  end;

function CustomControlCanvas(AControl: TCustomControl): TCanvas;
begin
  Result := TCustomControlCracker(AControl).Canvas;
end;

procedure DrawFrog(AControl: TCustomControl);
var
  Canvas: TCanvas;
begin
  Canvas := CustomControlCanvas(AControl);
  Canvas.TextOut(10, 10, 'Frog');
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  with TTestControl.Create(Self) do
  begin
    Parent := Self;
    Top := 100;
    Left := 100;
    Width := 400;
    Height := 200;
  end;
end;

{ TTestControl }

procedure TTestControl.Paint;
begin
  inherited;
  Canvas.Brush.Color := clSkyBlue;
  Canvas.FillRect(ClientRect);
  DrawFrog(Self); // use my reusable frog-drawing function
end;

end.

그러나이 모든 것은 개인적 으로 컨트롤 대신 a TCanvas(또는 a HDC) 를 전달하는 표준 접근 방식을 일부 차원과 함께 계속 사용할 것입니다.

procedure DrawFrog(ACanvas: TCanvas; const ARect: TRect);

이렇게하면 다른 컨트롤 ( TCustomControl하위 항목뿐만 아니라)뿐만 아니라 프린터 캔버스 등에 사용할 수 있습니다.

fpiette Nov 28 2020 at 14:27

캔버스가 디자인에 의해 숨겨진 이유는 무엇입니까?

실제로 숨겨진 것은 아니지만 보호 된 섹션에 있습니다. 액세스하려면 관심있는 클래스에서 새 클래스를 파생하고 Canvas를 공용으로 선언해야합니다.

응용 프로그램 수준에서 액세스 할 수 없기 때문에 비공개입니다.

필요한 interposer소스에서 클래스 를 사용하는 경우 구성 요소를 설치할 필요가 없습니다 .

Paint메서드를 재정의하고 거기에 그리기 코드를 넣을 수도 있습니다 .