Delphi 2010 DirectWrite "Hello World" Example

by Dec 14, 2009

In my previous post I have translated Windows 7 SDK Direct2D "Advanced Geometries" example from C++ to Delphi 2010 code. That was a lot of fun, so I have decided to continue the adventure in the realm of Direct2D programming and this time converted one of the DirectWrite examples – DirectWrite sample "Hello World".

Delphi 2010 Hello World DirectWrite sample app

Delphi 2010 Hello World DirectWrite sample app

In order to avoid writing over and over again the same Direct2D-specific code for creating TDirect2DCanvas instance and implementing "Resize" and "WMEraseBkgnd" methods, I have decided to refactor this common code into a reusable base class called "TFormD2D".


unit D2DUtils;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, D2D1, Direct2D;

type
TFormD2D = class(TForm)
private
FD2DCanvas: TDirect2DCanvas;
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
protected
procedure Resize; override procedure Paint; override procedure CreateD2DResources; virtual procedure PaintD2D; virtual function rt: ID2D1RenderTarget; // conveniency function
public
constructor Create(AOwner: TComponent); override destructor Destroy; override property D2DCanvas: TDirect2DCanvas read FD2DCanvas;
end
implementation

constructor TFormD2D.Create(AOwner: TComponent);
begin
inherited
if not TDirect2DCanvas.Supported then
raise Exception.Create('Direct2D not supported!'
FD2DCanvas := TDirect2DCanvas.Create(Handle);

CreateD2DResources;
end
destructor TFormD2D.Destroy;
begin
FD2DCanvas.Free;
inherited end
procedure TFormD2D.CreateD2DResources;
begin
// create Direct2D resources in descendant class
end
function TFormD2D.rt: ID2D1RenderTarget;
begin
Result := D2DCanvas.RenderTarget;
end
procedure TFormD2D.Resize;
var
HwndTarget: ID2D1HwndRenderTarget;
begin
inherited
if Assigned(D2DCanvas) then
if Supports(
rt, ID2D1HwndRenderTarget, HwndTarget) then
HwndTarget.Resize(D2D1SizeU(ClientWidth, ClientHeight));

Invalidate;
end
procedure TFormD2D.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
// avoid flicker as described here:
// http://chrisbensen.blogspot.com/2009/09/touch-demo-part-i.html
Message.Result := 1;
end
procedure TFormD2D.Paint;
begin
inherited D2DCanvas.BeginDraw;
try
PaintD2D;
finally
D2DCanvas.EndDraw;
end end
procedure TFormD2D.PaintD2D;
begin
// implement painting code in descendant class
end
end.

Using this base Direct2D class is simple. If you want to "Direct2D-enable" a VCL form class, just add "D2DUtils" unit to your project, add "D2DUtils" to the "uses" clause in the interface section of your form's unit, and change your form's base class from "TForm" to "TForm2D".
Now you only need to override "CreateD2DResources" and "PaintD2D" virtual methods and declare private members for different resources used for painting. In the first step you should create all Direct2D resources needed for painting like brushes, fonts, pens, geometries, and assign them to private variables in your form class. The second step is to implement "PaintD2D" that will include your painting code, most likely using "rt" convenience method that returns "D2DCanvas.RenderTarget" interface.

This is the actual code that paints "'Hello World using DirectWrite in Delphi 2010". Note that the form class derives from "TFormD2" and not "TForm".


unit Unit38;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, D2DUtils, D2D1, Direct2D;

type
// http://msdn.microsoft.com/en-us/library/ee264320(VS.85).aspx
TForm38 = class(TFormD2D)
private
FBlackBrush: ID2D1SolidColorBrush;
FTextFormat: IDWriteTextFormat;
protected
procedure PaintD2D; override procedure CreateD2DResources; override public

{ Public declarations }
end
var
Form38: TForm38;

implementation

{$R *.dfm}

{ TForm38 }

procedure TForm38.CreateD2DResources;

begin
inherited
rt.CreateSolidColorBrush(
D2D1ColorF(clBlack, 1),
nil,
FBlackBrush
);

DWriteFactory.CreateTextFormat(
PWideChar('Gabriola'),
nil,
DWRITE_FONT_WEIGHT_REGULAR,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
72,
PWideChar('en-us'),
FTextFormat
);

FTextFormat.SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER);
FTextFormat.SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
end
procedure TForm38.PaintD2D;
var
aDisplayText: string aRect: TD2D1RectF;
begin

// fill with white color the whole window
rt.Clear(D2D1ColorF(clWhite));

aDisplayText := 'Hello World using DirectWrite in Delphi 2010'
rt.DrawText(
PWideChar(aDisplayText),
Length(aDisplayText),
FTextFormat,
D2D1RectF(0, 0, ClientWidth, ClientHeight),
FBlackBrush
);
end
end.

The "TForm38" class derives from "TFormD2" class. We need two Direct2D resources to render "Hello World": a text format reference and a black brush. That's why the first thing to do is to define two fields in our form class:

FBlackBrush: ID2D1SolidColorBrush;
FTextFormat: IDWriteTextFormat;

In the "D2DCreateResource" method both fields are initialized and they are used in "PaintD2D" for rendering text on the form. In order to simplify code that needs to be there for painting surrounding calls to "D2DCanvas.BeginDraw" and "D2DCanvas.EndDraw" have been moved to the ancestor class and very frequently used calls to "D2DCanvas.RenderTarget. …" have been replaced with "rt" function that returns the same thing, but with fewer lines of code.

The source code for this application can be downloaded from EDN CodeCentral.