我正在研究在我的应用程序中一些部分将GDI替换为Direct2D。
为此,我阅读了Embarcadero官方文档并创建了这个最小的Direct2D应用程序:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Direct2D, D2D1;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormPaint(Sender: TObject);
private
FCanvas: TDirect2DCanvas;
protected
procedure CreateWnd; override;
procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
procedure WMSize(var Message: TWMSize); message WM_SIZE;
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
public
destructor Destroy; override;
property Canvas: TDirect2DCanvas read FCanvas;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.CreateWnd;
begin
inherited;
FreeAndNil(FCanvas);
FCanvas := TDirect2DCanvas.Create(Handle);
end;
destructor TForm1.Destroy;
begin
FreeAndNil(FCanvas);
inherited;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ReportMemoryLeaksOnShutdown := True;
end;
procedure TForm1.FormPaint(Sender: TObject);
var
R: TRect;
S: string;
begin
Canvas.RenderTarget.Clear(D2D1ColorF(clWhite));
R := ClientRect;
S := 'Hello, Direct2D!';
Canvas.TextRect(R, S, [tfSingleLine, tfVerticalCenter, tfCenter]);
Canvas.MoveTo(0, 0);
Canvas.LineTo(ClientWidth, ClientHeight);
Canvas.MoveTo(0, ClientHeight);
Canvas.LineTo(ClientWidth, 0);
end;
procedure TForm1.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
Message.Result := 1;
end;
procedure TForm1.WMPaint(var Message: TWMPaint);
var
PaintStruct: TPaintStruct;
begin
BeginPaint(Handle, PaintStruct);
try
if Assigned(FCanvas) then
begin
FCanvas.BeginDraw;
try
Paint;
finally
FCanvas.EndDraw;
end;
end;
finally
EndPaint(Handle, PaintStruct);
end;
end;
procedure TForm1.WMSize(var Message: TWMSize);
var
S: TD2DSizeU;
begin
if Assigned(FCanvas) then
begin
S := D2D1SizeU(ClientWidth, ClientHeight);
ID2D1HwndRenderTarget(FCanvas.RenderTarget).Resize(S);
end;
Invalidate;
inherited;
end;
end.
这段内容直接摘自文档,但有一些改进:
- 在
CreateWnd
中重新创建画布前,我更喜欢使用FreeAndNil
清空画布。 - 我更喜欢确保在
WMPaint
中分配了画布。 - 由于
ID2D1HwndRenderTarget.Resize
方法使用了一个var
参数,因此文档中的版本甚至无法编译,需要进行调整。 - 我想在调整大小时使表单失效。
- 我响应
WM_ERASEBKGND
以避免闪烁。 - 我更喜欢在表单销毁时释放画布。
- 我打开了内存泄漏报告。
- 我绘制了一些视觉上令人印象深刻的图形。
当我使用 Delphi 10.3.2 编译它并在启用了 Aero 的 Microsoft Windows 7 (64 位,125% DPI) 系统上运行它时,我得到了以下结果: 虽然我被这些线条令人惊叹的抗锯齿效果所迷住了,但显然,这不是我想要的图像。
问题似乎与 DPI 缩放有关,以下简单的调整似乎解决了问题:
procedure TForm1.WMPaint(var Message: TWMPaint);
var
PaintStruct: TPaintStruct;
begin
BeginPaint(Handle, PaintStruct);
try
if Assigned(FCanvas) then
begin
FCanvas.BeginDraw;
try
// BEGIN ADDITION
var f := 96 / Screen.PixelsPerInch;
Canvas.RenderTarget.SetTransform(TD2DMatrix3x2F.Scale(f, f, D2D1PointF(0, 0)));
// END ADDITION
Paint;
finally
FCanvas.EndDraw;
end;
end;
finally
EndPaint(Handle, PaintStruct);
end;
end;
但这种方法能够在所有情况下都奏效吗?这样做会让人们无法在OnPaint
中正常使用变换功能,对吧?是否有更好的解决方案?什么是正确(最佳实践)的解决方案?
更新
一个在“我的系统”上运行的不同的解决方案是
procedure TForm1.CreateWnd;
begin
inherited;
FreeAndNil(FCanvas);
FCanvas := TDirect2DCanvas.Create(Handle);
FCanvas.RenderTarget.SetDpi(96, 96); // <-- Add this!
end;
但是,我不确定这是否是“正确”的方法。