Die Direct2D-Zeichenfläche

Aus RAD Studio
Wechseln zu: Navigation, Suche

Nach oben zu Verwenden von Zeichenflächen zum Zeichnen


Microsoft Windows 7 enthält ein neues Feature zum Erstellen von Zeichenflächenobjekten, die zum Rendern und Zeichen die Direct2D-Technologie verwenden. Direct2D ist unter gewissen Umständen von Vorteil, vor allem dann, wenn in einer Anwendung viele benutzerdefinierte Zeichenoperationen ausgeführt werden. Direct2D leitet alle Zeichenoperationen an den Grafikprozessor (GPU) anstatt an die CPU weiter, was Ihre Anwendung deutlich leistungsfähiger macht. Dieses Thema beschreibt, wie Sie die neue Direct2D-Zeichenfläche in Ihren Delphi-Anwendungen nutzen können.


Erste Schritte

Die Direct2D-Zeichenfläche wird nur unter Windows 7 unterstützt, daher müssen Sie Ihre Direct2D-fähige Anwendung auf dieser Windows-Version entwickeln. Für Direct2D müssen Sie die folgenden Units in Ihre Anwendung einbeziehen:

  • Vcl.Direct2D, die VCL-Kapselungsklassen, wie TDirect2DCanvas, bereitstellt.
  • D2D1, die Header-Übersetzungen für die Microsoft Direct2D-API enthält.

Bitte beachten Sie Folgendes: Obwohl die VCL die notwendigen Klassen bereitstellt, müssen Sie trotzdem Direct2D-Interfaces verwenden, um von den erweiterten Features der Direct2D-Zeichenfläche zu profitieren.

Normale VCL-Steuerelemente sind standardmäßig nicht Direct2D-fähig. Sie verwenden das alte, GDI-basierte Vcl.Graphics.TCanvas-Objekt. Für die neue Technologie müssen Sie Ihren Steuerelementen die Unterstützung für Vcl.Direct2D.TDirect2DCanvas explizit zu hinzufügen.

Prüfen, ob Direct2D-Zeichenflächen unterstützt werden

TDirect2DCanvas unterstützt eine Klassenmethode namens Supported, mit der festgestellt werden kann, ob das Betriebssystem, auf dem die Anwendung ausgeführt wird, die Direct2D-Zeichenfläche unterstützt. Trotzdem kann unter gewissen Umständen beim Erstellen der Direct2D-Zeichenfläche eine Exception ausgelöst werden. Am besten prüfen Sie die Direct2D-Verfügbarkeit, wenn die Direct2D-Zeichenfläche erstellt werden soll:

function Create2D2Canvas(): Boolean; 
begin 
 Result := false;

 if TDirect2DCanvas.Supported then
 begin 
   try
     FD2DCanvas := TDirect2DCanvas.Create(Handle); 
     Result := true;
   except 
   end;
 end;

end;

Verwendung der Direct2D-Zeichenfläche

Es gibt in der Praxis zwei Wege, die Vorteile von Direct2D zu nutzen:

  • Die Zeichenfläche wird von der ursprünglichen GDI-Zeichenfläche und der neuen Direct2D-Zeichenfläche gemeinsam genutzt.
  • Die Direct2D-Zeichenfläche wird exklusiv verwendet.

Beide Methoden haben Vor- und Nachteile, daher müssen Sie sich für die momentan beste Lösung entscheiden.


Verwendung der Direct2D-Zeichenfläche zusammen mit der GDI-Zeichenfläche

Der einfachste Weg, um mit der Verwendung von Direct2D zu beginnen, ist die Implementierung von benutzerdefinierten Zeichenoperationen in Ihrem Formular oder Steuerelement. Das erreichen Sie mit einer OnPaint-Ereignisbehandlungsroutine oder durch Überschreiben der Methode Paint:

procedure T2D2Form.FormPaint(Sender: TObject);
var 
  LCanvas: TDirect2DCanvas; 
begin 
  LCanvas := TDirect2DCanvas.Create(Canvas, ClientRect); 
  LCanvas.BeginDraw;

  try 
    { Drawing goes here } 
    LCanvas.Brush.Color := clRed;
    LCanvas.Pen.Color := clBlue;
    LCanvas.Rectangle(100, 100, 200, 200);

  finally 
    LCanvas.EndDraw; 
    LCanvas.Free; 
  end; 
end;

Das obige Beispiel verwendet die Ereignisbehandlungsroutine FormPaint, die dem Formularereignis OnPaint zugeordnet ist. OnPaint wird jedes Mal ausgelöst, wenn das Formular seine Oberfläche zeichnen muss. Beachten Sie bitte, dass für jedes Neuzeichnen eine neue TDirect2DCanvas-Instanz erstellt wird. Das ist deshalb erforderlich, weil der tatsächliche, dem Formular zugeordnete Gerätekontext (HDC) sich jedes Mal ändern und eine zuvor erstellte TDirect2DCanvas-Instanz unwirksam machen kann.

Obwohl die Methode die einfachste ist, könnte sie in der Praxis nicht akzeptabel sein, weil Direct2D-Ressourcen bei jedem Neuzeichnen belegt und freigegeben werden müssen. Die zugehörigen Pen-, Brush- und Font-Objekte müssen freigegeben werden, weil sie nur mit der TDirect2DCanvas-Instanz, die sie erstellt hat, verwendet werden können. Dass das ursprüngliche TCanvas zusammen mit TDirect2DCanvas eingesetzt werden kann, ist der Vorteil dieser Methode. Selbst wenn beide Zeichenflächenobjekte auf derselben Oberfläche zeichnen, können Sie beide gleichzeitig ohne große Schwierigkeiten verwenden.

Exklusive Verwendung der Direct2D-Zeichenfläche

Die zweite Methode bietet sich vor allem dann an, wenn Sie eine einzelne Instanz von TDirect2DCanvas erstellen möchten, deren Lebensdauer an die Eigentümerkomponente gebunden ist. In diesem Fall müssen Sie die Windows-Botschaften WM_PAINT und WM_SIZE abfangen, um Ihre eigenen Zeichenroutinen auszuführen:

type 
  T2D2Form = class(TForm)
  private
    FCanvas: TDirect2DCanvas;

  protected
    procedure CreateWnd; override; 

    procedure WMPaint(var Message: TWMPaint); message WM_PAINT; 
    procedure WMSize(var Message: TWMSize); message WM_SIZE; 
  public
    property Canvas: TDirect2DCanvas read FCanvas;

  end;

implementation

{$R *.dfm}

procedure T2D2Form.CreateWnd;
begin 
  inherited; 
  FCanvas := TDirect2DCanvas.Create(Handle); 
end;

procedure T2D2Form.WMPaint(var Message: TWMPaint); 
var 
  PaintStruct: TPaintStruct; 
begin 
 BeginPaint(Handle, PaintStruct);

  try
    FCanvas.BeginDraw; 
    try 
      Paint; 
    finally 
      FCanvas.EndDraw; 
    end; 
  finally 
    EndPaint(Handle, PaintStruct); 
  end; 
end;

procedure T2D2Form.WMSize(var Message: TWMSize);
begin
  if Assigned(FCanvas) then 
    ID2D1HwndRenderTarget(FCanvas.RenderTarget).Resize(D2D1SizeU(ClientWidth, ClientHeight)); 

  inherited;
end;

Im obigen Beispiel wurden benutzerdefinierte Behandlungsroutinen für die Windows-Botschaften WM_PAINT und WM_SIZE geschrieben. WM_PAINT muss benutzerdefiniert verarbeitet werden, weil die VCL-Zeichenmethoden direkt überschrieben werden. WM_SIZE muss die Größe der Direct2D-Zeichenfläche anpassen, bevor VCL-Methoden aufgerufen werden. Und schließlich wurde die Vcl.Direct2D.TDirect2DCanvas-Instanz in der Methode CreateWnd erstellt, weil ein gültiges Fenster-Handle für die Zuordnung zu der Zeichenfläche vorhanden sein muss.

API-Kompatibilität

TCanvas und TDirect2DCanvas sind von einem gemeinsamen Vorfahren, TCustomCanvas abgeleitet, der die High-Level-Funktionalität definiert, die beide Zeichenflächenimplementierungen unterstützen müssen. TDirect2DCanvas implementiert allerdings einige Funktionen nicht -- zum Beispiel die Eigenschaft TCustomCanvas.Pixels; es gibt keine Möglichkeit für Direct2D-Zeichenflächen Pixel-Informationen zu ermitteln. Sie sollten in der API-Dokumentation für Vcl.Direct2D.TDirect2DCanvas die unterstützten und nicht-unterstützten Funktionen nachschlagen.

Beide Zeichenflächen verfügen über das Konzept von zugeordneten Grafikobjekten wie Pen, Brush und Font. Obwohl beide Zeichenflächenimplementierungen diese Objekte bereitstellen, werden sie vollständig unterschiedlich implementiert, und deshalb stellt TCustomCanvas überhaupt keine Grafikobjekte bereit. Ein TCustomCanvas-Objekt bereitzustellen, falls Sie eine Komponenten entwerfen, die GDI und Direct2D unterstützen soll, wäre also äußerst unpraktisch. Das folgende Beispiel demonstriert diese Beschränkung:

procedure TForm1.MyBoxPaint(Sender: TObject);
begin 
  { Assuming the Canvas property of TMyPaintBox is TCustomCanvas,
    to support both canvas implementations }

  { Intermediate API to access graphic objects is different }
  if (MyBox.Canvas is TDirect2DCanvas) then 
    (MyBox.Canvas as TDirect2DCanvas).Pen.Color := clRed 
  else 
    (MyBox.Canvas as TCanvas).Pen.Color := clRed;

  { Normal high-level API is available } 
  MyBox.Canvas.MoveTo(0, 0); 
  MyBox.Canvas.LineTo(MyBox.Width, MyBox.Height); 
end;

Im obigen Beispiel musste die Farbe des Pen-Objekts gesetzt werden. Weil TCustomCanvas die Eigenschaft Pen nicht bereitstellt, musste auf die Objekttypumwandlung zurückgegriffen werden. Beachten Sie bitte, dass Pen bei GDI und bei Direct2D vollständig unterschiedlich ist. Sie haben keinen gemeinsamen Vorfahren; aber ein GDI-Pen kann beispielsweise einem Direct2D-Pen zugewiesen werden.

Entwickeln einer Direct2D-fähigen Komponente

Dieser Abschnitt zeigt, wie ein benutzerdefiniertes Zeichenfeld entwickelt wird, das entweder die Direct2D- oder die GDI-Zeichenfläche verwendet. Die Komponente, TAcceleratedPaintBox, ist nicht von Vcl.ExtCtrls.TPaintBox abgeleitet, weil TPaintBox selbst von TControl und nicht von TWinControl abgeleitet ist. Dies ist ein wichtiger Entscheidungsfaktor -- von TControl abgeleitete Steuerelemente haben kein Fenster-Handle, das zum Erstellen einer fest zugeordneten Direct2D-Zeichenfläche erforderlich ist.

Die Klassendefinition

Das entwickelte Steuerelement ist von Vcl.Controls.TCustomControl abgeleitet (das wiederum von Vcl.Controls.TWinControl abgeleitet ist) und sieht folgendermaßen aus:

 TCustomAcceleratedPaintBox = class(TCustomControl)
 private
   FOnPaint: TNotifyEvent; 
   FUseD2D: Boolean; 
   FD2DCanvas: TDirect2DCanvas;

   function CreateD2DCanvas: Boolean;

   { Catching paint events } 
   procedure WMPaint(var Message: TWMPaint); message WM_PAINT; 
   procedure WMSize(var Message: TWMSize); message WM_SIZE;

   { Set/Get stuff } 
   procedure SetAccelerated(const Value: Boolean); 
   function GetGDICanvas: TCanvas; 
   function GetOSCanvas: TCustomCanvas; 
 protected 
   procedure CreateWnd; override; 
   procedure Paint; override;

 public
   { Life-time management } 
   constructor Create(AOwner: TComponent); override; 
   destructor Destroy(); override;

   { Public properties } 
   property Accelerated: Boolean read FUseD2D write SetAccelerated; 
   property Canvas: TCustomCanvas read GetOSCanvas; property GDICanvas: TCanvas read GetGDICanvas; 
   property D2DCanvas: TDirect2DCanvas read FD2DCanvas;

   { The Paint event } 
   property OnPaint: TNotifyEvent read FOnPaint write FOnPaint; 
 end;

Das allgemeine Layout und das public-Interface versuchen, diejenigen von Vcl.ExtCtrls.TPaintBox bereitgestellten nachzuahmen, weil ein 1:1-Ersatz dafür entwickelt werden soll.

Implementieren der Initialisierungsfunktionalität

Die Methode CreateD2DCanvas versucht, eine TDirect2DCanvas-Instanz zu erstellen und gibt einen boolesche Wert zurück, der über Erfolg oder Misserfolg Auskunft gibt. Mit CreateD2DCanvas wird später die Direct2D-Zeichenfläche initialisiert.

function TCustomAcceleratedPaintBox.CreateD2DCanvas: Boolean; 
begin
  try 
    FD2DCanvas := TDirect2DCanvas.Create(Handle); 
 except 
   { Failed creating the D2D canvas, halt! }
   Exit(false); 
 end;

  Result := true;
end;

CreateWnd wird aus der Vorfahrklasse überschrieben. CreateWnd wird automatisch beim Erstellen der Komponente aufgerufen. Das ist die richtige Stelle, um die Erstellung der Direct2D-fähigen Zeichenfläche zu implementieren.

procedure TCustomAcceleratedPaintBox.CreateWnd; 
begin
  inherited;

  { Try to create the custom canvas }
  if (Win32MajorVersion >= 6) and (Win32Platform = VER_PLATFORM_WIN32_NT) then
    FUseD2D := CreateD2DCanvas 
  else
    FUseD2D := false;
end;

Mit dem booleschen Feld FUseD2D wird später in der Komponente entweder die GDI- oder die Direct2D-Zeichenfläche ausgewählt.

Methoden zum Abfragen von Eigenschaften

Mit GetGDICanvas wird intern auf die geerbte Eigenschaft Canvas zugegriffen, die die ursprüngliche GDI-Zeichenfläche zurückgibt. Sie dient auch als Methode zum Abfragen der Eigenschaft GDICanvas.

function TCustomAcceleratedPaintBox.GetGDICanvas: TCanvas; 
begin 
  if FUseD2D then
    Result := nil 
  else 
    Result := inherited Canvas;
end;

GetOSCanvas ist die Methode zum Abfragen der Eigenschaft Canvas, die von dieser Komponente bereitgestellt wird. Sie prüft, ob die Direct2D-Unterstützung verfügbar ist und gibt dann das entsprechende Zeichenflächenobjekt zurück.

function TCustomAcceleratedPaintBox.GetOSCanvas: TCustomCanvas;
begin 
  if FUseD2D then 
    Result := FD2DCanvas 
  else
    Result := inherited Canvas;
end;

SetAccelerated ist die Methode zum Setzen der Eigenschaft Accelerated. Der Benutzer kann die Unterstützung für die Direct2D-Zeichenfläche zur Laufzeit deaktivieren oder aktivieren.

procedure TCustomAcceleratedPaintBox.SetAccelerated(const Value: Boolean);
begin 
  { Same value? }
  if Value = FUseD2D then
    Exit;

  if not Value then
  begin
    FUseD2D := false; 
    Repaint;
  end else 
  begin
    FUseD2D := FD2DCanvas <> nil;

    if FUseD2D then
      Repaint;
  end; 
end;

Zeichnen und Anpassen der Größe

Die Methode Paint reproduziert im Grunde das Verhalten der Methode TPaintBox.Paint. Sie bereitet das Zeichenflächenobjekt für das Zeichnen vor und ruft die vom Benutzer bereitgestellte Ereignisbehandlungsroutine auf, die das eigentliche Zeichnen ausführt. In dieser Implementierung wird überprüft, ob Direct2D aktiviert ist, und es wird die D2DCanvas-Eigenschaft verwendet. Wenn Direct2D nicht unterstützt wird, wird auf GDICanvas zurückgegriffen.

procedure TCustomAcceleratedPaintBox.Paint;
begin
  if FUseD2D then
  begin
    D2DCanvas.Font.Assign(Font);
    D2DCanvas.Brush.Color := Color;
 
    if csDesigning in ComponentState then
    begin 
      D2DCanvas.Pen.Style := psDash;
      D2DCanvas.Brush.Style := bsSolid;
 
      D2DCanvas.Rectangle(0, 0, Width, Height);
    end;
  end else
  begin
    GDICanvas.Font.Assign(Font);
    GDICanvas.Brush.Color := Color;
 
    if csDesigning in ComponentState then
    begin
      GDICanvas.Pen.Style := psDash;
      GDICanvas.Brush.Style := bsSolid;
 
      GDICanvas.Rectangle(0, 0, Width, Height);
    end;
  end;
 
  if Assigned(FOnPaint) then FOnPaint(Self);
end;

Wie in den vorherigen Abschnitten beschrieben, ist es wichtig, eine Behandlungsroutine für die Windows-Botschaft WM_PAINT zu implementieren, damit die von der VCL bereitgestellten Zeichenroutinen überschrieben werden. Die WMPaint-Implementierung in diesem Beispiel verarbeitet die WM_PAINT-Botschaft nur, wenn die Direct2D-Zeichenfläche aktiviert ist; ansonsten wird die Botschaft an die geerbte Methode übergeben.

procedure TCustomAcceleratedPaintBox.WMPaint(var Message: TWMPaint);
var
  PaintStruct: TPaintStruct;
begin
  if FUseD2D then
  begin
    BeginPaint(Handle, PaintStruct);
    try
      FD2DCanvas.BeginDraw;
 
      try
        Paint;
      finally 
        FD2DCanvas.EndDraw;
      end;
 
    finally
      EndPaint(Handle, PaintStruct);
    end;
  end else
    inherited;
end;

Mit der Methode WMSize wird die Windows-Botschaft WM_SIZE verarbeitet. Diese Methode dient der Aktualisierung der Größe der Direct2D-Zeichenfläche.

procedure TCustomAcceleratedPaintBox.WMSize(var Message: TWMSize);
begin
  if FD2DCanvas <> nil then
    ID2D1HwndRenderTarget(FD2DCanvas.RenderTarget).Resize(D2D1SizeU(Width, Height));
 
  inherited;
end;

Siehe auch