Delphi 2010 DirectWrite "Hello World" Example

Posted by on in Blogs
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".

[caption id="attachment_38873" align="alignnone" width="300" caption="Delphi 2010 Hello World DirectWrite sample app"]Delphi 2010 Hello World DirectWrite sample app[/caption]

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.
About
Gold User, Rank: 9, Points: 364
Crazy about Delphi Programming!
Comments are not available for public users. Please login first to view / add comments.