Dieser Artikel wurde maschinell übersetzt.

Windows mit C++

DirectComposition: Eine Retained Mode-API, die alles kontrolliert

Kenny Kerr

Laden Sie die Codebeispiele herunter

Kenny KerrGrafik-APIs tendenziell fallen in eine der zwei sehr unterschiedlichen Lagern. Es gibt den Direktmodus APIs mit bekannten Beispielen, einschließlich Direct2D und Direct3D. Dann gibt es die retained Mode-APIs, z. B. Windows Presentation Foundation (WPF) oder XAML oder deklarativen API. Moderne Browser bieten eine klare Unterscheidung zwischen den zwei Grafik-Modi mit Scalable Vector Graphics eine retained Mode-API und das Canvas-Element bietet eine API Direktmodus.

Retained Mode wird davon ausgegangen, dass die Grafik-API erhalten bleiben, werden einige Darstellung der Szene, wie ein Diagramm oder ein Baum von Objekten, die dann im Laufe der Zeit bearbeitet werden kann. Dies ist praktisch und vereinfacht die Entwicklung von interaktiven Anwendungen. Im Gegensatz dazu Direktmodus API eine integrierte Szenendiagramm beinhalten nicht und stattdessen stützt sich auf die Anwendung, die Szene mit einer Folge von Zeichenbefehle zu konstruieren. Dies bringt enorme Leistungsvorteile mit sich. Eine unmittelbarer Modus-API wie z. B. Direct2D wird in der Regel Puffer-Vertexdaten aus mehrere Geometrien, zusammengefügten zahlreiche Zeichenbefehle und vieles mehr. Dies ist besonders vorteilhaft mit einem Text-Rendering-Pipeline, als Symbole müssen zunächst in eine Textur geschrieben werden und nach unten in die Stichprobe vor dem Clear-Typ Filtern angewendet wird und der Text seinen Weg in die Renderingziel macht. Dies ist einer der Gründe, warum viele andere Graphiken APIs und zunehmend viele Drittanbieteranwendungen, jetzt auf Direct2D und DirectWrite für das Rendern von Text verlassen.

Die Wahl zwischen unmittelbarer Modus und retained Mode fiel traditionell auf einen Kompromiss zwischen Leistung und Produktivität. Entwickler könnte die Direct2D Direktmodus API für absolute Leistung oder die WPF-retained Mode-API für Produktivität oder Komfort wählen. DirectComposition ändert diese Gleichung durch so dass Entwickler, die beiden weit natürlichere zu mischen. Es verwischt die Grenze zwischen unmittelbarer Modus und retained Mode-APIs, da es eine retained Mode für Grafiken, aber ohne Speicher oder Leistung Aufwand bietet. Es erreicht dieses Kunststück durch Fokussierung auf Bitmap-Komposition, anstatt zu versuchen, mit anderen Grafik-APIs zu konkurrieren. DirectComposition bietet einfach die visuelle Struktur und die Zusammensetzung-Infrastruktur so, dass Bitmaps mit anderen Technologien dargestellt kann leicht manipuliert und komponiert zusammen. Und im Gegensatz zu WPF, DirectComposition ist ein integraler Bestandteil der OS-Grafiken-Infrastruktur und vermeidet alle Leistungs- und Luftraum Fragen, die WPF-Anwendungen traditionell prägten.

Wenn Sie meine zwei vorherigen Spalten auf DirectComposition gelesen habe (msdn.microsoft.com/magazine/dn745861 und msdn.microsoft.com/magazine/dn786854), Sie sollte schon ein Gefühl von, was die Komposition-Engine fähig ist. Jetzt will ich zu viel deutlicher machen, dass durch zeigt Ihnen, wie Sie DirectComposition verwenden können, um Visuals mit Direct2D gezeichnet wird, in einer Weise, die sehr attraktiv für Entwickler gewöhnt retained Mode-APIs zu bearbeiten. Ich werde Ihnen zeigen, wie ein einfaches Fenster erstellen, das Kreise präsentiert als "Objekte" können werden erstellt und verschoben, mit voller Unterstützung für Treffertests und in Z-Reihenfolge geändert. Sie können sehen, wie das aussieht, im Beispiel in Abbildung 1.

ziehen umkreist
Abbildung 1 ziehen umkreist

Obwohl die Kreise in Abbildung 1 mit Direct2D, der Anwendung zieht einen Kreis nur einmal zu einer Komposition Oberfläche gezeichnet werden. Diese Zusammensetzung-Oberfläche ist dann die Zusammensetzung Visuals in einer visuellen Struktur, die an das Fenster gebunden freigegeben. Jedes Visual definiert einen Offset relativ Fenster an dem inhaltlich — die Zusammensetzung-Oberfläche — positioniert ist, und letztlich von der Komposition-Engine gerendert. Der Benutzer kann neue Kreise erstellen und sie mit einer Maus, Stift oder Finger bewegen. Jedes Mal, wenn ein Kreis aktiviert ist, verschiebt es an die Spitze der Z-Reihenfolge, so dass es über alle anderen Kreisen im Fenster angezeigt wird. Während ich sicherlich keine retained Mode-API benötigen, um solch eine einfache Wirkung zu erzielen, dient es als ein gutes Beispiel für die Funktionsweise der DirectComposition-API zusammen mit Direct2D, einige leistungsfähige visuelle Effekte zu erreichen. Ziel ist es, eine interaktive Anwendung zu erstellen, deren WM_PAINT-Handler ist nicht verantwortlich für die Führung des Fensters Pixel auf dem neuesten Stand.

Ich beginne mit einer neuen SampleWindow-Klasse, die Vorlage der Window-Klasse abgeleitet, die ich in meinem früheren Artikel vorgestellt. Die Fenster-Klassenvorlage vereinfacht nur Nachricht Versand in C++:

struct SampleWindow : Window<SampleWindow>
{
};

Mit jeder modernen Windows-Anwendung muss ich behandeln, dynamische DPI-Skalierung, so füge ich werde zwei Gleitkommazahlen Mitglieder zu verfolgen die DPI Skalierungsfaktoren für die X- und Y-Achse:

float m_dpiX = 0.0f;
float m_dpiY = 0.0f;

Sie können diese bei Bedarf, initialisieren, wie ich in meinem vorigen Artikel oder innerhalb Ihrer WM_CREATE-Meldungshandler dargestellt. In beiden Fällen müssen Sie rufen Sie die MonitorFromWindow-Funktion, um den Monitor zu bestimmen, der die größte Fläche schneiden das neue Fenster hat. Dann rufen Sie einfach die GetDpiForMonitor-Funktion, um die effektiven DPI-Werte abrufen. Ich habe dies einige Male in früheren Artikeln und Kursen dargestellt, so dass ich es hier noch einmal wiederholen werden nicht.

Ich werde ein Direct2D-Ellipse-Geometry-Objekt verwenden, um den Kreis gezeichnet werden soll, so kann ich später diese gleiche Geometry-Objekt für Treffertests verwenden zu beschreiben. Es zwar effizienter, eine D2D1_ELLIPSE-Struktur als ein Geometry-Objekt zu zeichnen, das Geometrieobjekt bietet Treffertests und die Zeichnung wird einbehalten. Ich werde weiter verfolgen die Direct2D-Fabrik und die Ellipse Geometrie der:

ComPtr<ID2D1Factory2> m_factory;
ComPtr<ID2D1EllipseGeometry> m_geometry;

In meinem letzten Artikel habe ich Ihnen gezeigt, wie ein Direct2D-Geräteobjekt nicht mithilfe einer Direct2D-Fabrik, sondern direkt mit der D2D1CreateDevice-Funktion erstellt. Dies ist sicherlich eine akzeptable Möglichkeit weiterhin, aber es gibt einen Haken. Direct2D Fabrik Ressourcen, können während sie geräteunabhängige und nicht wiederhergestellt werden, brauchen wenn ein Gerät auftritt, nur mit Direct2D-Geräten, die von der gleichen Direct2D-Factory erstellten verwendet werden. Da ich vorne die Ellipse Geometrie erstellen möchten, benötige ich eine Direct2D-Factory-Objekt zu erstellen. Ich könnte vielleicht warten, bis ich das Direct2D-Gerät mit der D2D1CreateDevice-Funktion erstellt haben dann die zugrunde liegende Fabrik mit der GetFactory-Methode abrufen und dann diese Factory-Objekt verwenden, um die Geometrie zu erzeugen, aber das scheint eher gekünstelt. Stattdessen werde ich erstellen eine Direct2D-Fabrik und die Ellipse Geometrie und das Geräteobjekt nach Bedarf erstellen. Abbildung 2 die Direct2D-Fabrik und Geometrie-Objekte erstellen veranschaulicht.

Abbildung 2 die Direct2D Factory und Geometry-Objekte erstellen

void CreateFactoryAndGeometry()
{
  D2D1_FACTORY_OPTIONS options = {};
  #ifdef _DEBUG
  options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
  #endif
  HR(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
                       options,
                       m_factory.GetAddressOf()));
  D2D1_ELLIPSE const ellipse = Ellipse(Point2F(50.0f, 50.0f),
                                       49.0f,
                                       49.0f);
  HR(m_factory->CreateEllipseGeometry(ellipse,
                                      m_geometry.GetAddressOf()));
}

Die CreateFactoryAndGeometry-Methode kann dann aufgerufen werden, durch die SampleWindow-Konstruktor auf diese Gerät - Vorbereitung­unabhängige Ressourcen. Wie Sie sehen können, ist die Ellipse um eine Center Nummer 50 Pixel entlang der X- und y-Achse sowie einem Radius von 49 Pixel für die X- und die Y-Radius, machen diese Ellipse in einen Kreis definiert. Ich werde eine Fläche von 100 x 100 Komposition erstellen. Ich habe einen Radius von 49 Pixel gewählt, weil der Standard-Strich gezeichnet von Direct2D den Perimeter überspannt und es sonst abgeschnitten werden würde.

Als nächstes sind die gerätespezifische Ressourcen. Ich brauche eine Unterstützung Direct3D-Gerät, ein Komposition-Gerät für die Änderungen der visuellen Struktur, eine Komposition, die visuelle Struktur lebendig zu halten, eine visuelle Stammelement, das das übergeordnete Element des alle die Kreis-Visuals und eine gemeinsame Komposition-Oberfläche dargestellt werden:

ComPtr<ID3D11Device> m_device3D;
ComPtr<IDCompositionDesktopDevice> m_device;
ComPtr<IDCompositionTarget> m_target;
ComPtr<IDCompositionVisual2> m_rootVisual;
ComPtr<IDCompositionSurface> m_surface;

Ich habe diese verschiedenen Objekte in meiner früheren DirectX-Artikeln und vor allem meine vorherigen zweispaltig auf DirectComposition vorgestellt. Ich auch diskutiert und dargestellt, wie Sie Device-Erstellung und Verlust umgehen sollte, damit ich das hier nicht wiederholen werde. Ich nenne nur die CreateDevice2D-Methode, die aktualisiert werden, um die zuvor erstellten Direct2D-Factory verwenden muss:

ComPtr<ID2D1Device> CreateDevice2D()
{
  ComPtr<IDXGIDevice3> deviceX;
  HR(m_device3D.As(&deviceX));
  ComPtr<ID2D1Device> device2D;
  HR(m_factory->CreateDevice(deviceX.Get(), 
    device2D.GetAddressOf()));
  return device2D;
}

Jetzt werde ich die gemeinsame Oberfläche erstellen. Ich muss vorsichtig sein, die ComPtr-Klassenvorlage ReleaseAndGetAddressOf-Methode verwenden, um sicherzustellen, dass die Oberfläche sicher nach Gerät Verlust oder aufgrund einer Änderung der DPI-Skalierung wiederhergestellt werden kann. Ich muss auch darauf achten, das logische Koordinatensystem zu erhalten, das meine Bewerbung beim Übersetzen der Dimensions in physischen Pixel für die DirectComposition-API verwendet wird:

HR(m_device->CreateSurface(
  static_cast<unsigned>(LogicalToPhysical(100, m_dpiX)),
  static_cast<unsigned>(LogicalToPhysical(100, m_dpiY)),
  DXGI_FORMAT_B8G8R8A8_UNORM,
  DXGI_ALPHA_MODE_PREMULTIPLIED,
  m_surface.ReleaseAndGetAddressOf()));

Ich rufe dann die Zusammensetzung Oberfläche BeginDraw-Methode, um einen Gerätekontext Direct2D mit dem Zeichnungsbefehle Puffer zu erhalten:

HR(m_surface->BeginDraw(
  nullptr,
  __uuidof(dc),
  reinterpret_cast<void **>(dc.GetAddressOf()),
  &offset));

Und dann muss ich Direct2D erklären, wie man alle Zeichenbefehle skalieren:

dc->SetDpi(m_dpiX,
           m_dpiY);

Und ich brauche, um die Ausgabe an den Offset, bereitgestellt durch DirectComposition zu transformieren:

dc->SetTransform(Matrix3x2F::Translation(PhysicalToLogical(offset.x, m_dpiX),
                                         PhysicalToLogical(offset.y, m_dpiY)));

PhysicalToLogical ist eine Hilfsfunktion verwende ich routinemäßig für DPI-Skalierung beim Kombinieren von APIs, die unterschiedliche Ebenen der Unterstützung für DPI-Skalierung (oder überhaupt) zu haben. Kann man die PhysicalToLogical-Funktion und die entsprechende LogicalToPhysical-Funktion in Abbildung 3.

Abbildung 3 Konvertierung zwischen logischen und physischen Pixel

template <typename T>
static float PhysicalToLogical(T const pixel,
                               float const dpi)
{
  return pixel * 96.0f / dpi;
}
template <typename T>
static float LogicalToPhysical(T const pixel,
                               float const dpi)
{
  return pixel * dpi / 96.0f;
}

Jetzt kann ich einfach einen blauen Kreis mit einem einfarbigen Pinsel nur hierfür geschaffenen zeichnen:

ComPtr<ID2D1SolidColorBrush> brush;
D2D1_COLOR_F const color = ColorF(0.0f, 0.5f, 1.0f, 0.8f);
HR(dc->CreateSolidColorBrush(color,
                             brush.GetAddressOf()));

Als nächstes muss ich das Renderingziel löschen, bevor füllen die Ellipse Geometrie und dann streicheln oder zeichnen seiner Kontur mit dem geänderten Pinsel:

dc->Clear();
dc->FillGeometry(m_geometry.Get(),
                 brush.Get());
brush->SetColor(ColorF(1.0f, 1.0f, 1.0f));
dc->DrawGeometry(m_geometry.Get(),
                 brush.Get());

Schließlich muss ich rufen die EndDraw-Methode, um anzugeben, dass die Oberfläche bereit für Komposition ist:

HR(m_surface->EndDraw());

Jetzt ist es Zeit, Kreise zu erstellen. In meinem vorherigen Spalten ich habe nur einen einzigen Stamm visual, aber diese Anwendung zu erstellen Visuals on-Demand, so dass ich nur, dass in eine bequeme Hilfsmethode einwickeln werden muss:

ComPtr<IDCompositionVisual2> CreateVisual()
{
  ComPtr<IDCompositionVisual2> visual;
  HR(m_device->CreateVisual(visual.GetAddressOf()));
  return visual;
}

Einer der interessantesten Aspekte der DirectComposition-API ist, dass es sich effektiv um eine nur-schreiben Schnittstelle für die Komposition-Engine handelt. Während es eine visuelle Struktur für Ihr Fenster behält, nicht es Getter zur Verfügung, die Sie verwenden können, um die visuelle Struktur zu verhören. Weitere Informationen, wie einer visuelles Position oder Z-Reihenfolge muss direkt von der Anwendung beibehalten werden. Dies vermeidet unnötigen Speicherbedarf und vermeidet auch potenzielle Racebedingungen zwischen der Anwendung Sicht auf die Welt und die Komposition-Engine transaktionale Staat. Also werde ich weitermachen und erstellen Sie eine Circle-Struktur zu verfolgen jeden Kreis Stellung:

struct Circle
{
  ComPtr<IDCompositionVisual2> Visual;
  float LogicalX = 0.0f;
  float LogicalY = 0.0f;
};

Die visuelle Komposition stellt effektiv den Kreis Setter während der LogicalX und machte Felder sind die Getter. Ich kann das visuelle Position mit der IDCompositionVisual2-Schnittstelle festlegen und ich behalten und später seine Position mit den anderen Bereichen abrufen können. Dies ist notwendig für Treffertests und zum Wiederherstellen der Kreise nach Gerät Verlust. Um diese getting out of Sync zu vermeiden, werde ich einfach eine Hilfsmethode für das visual-Objekt anhand der logischen Position bereitstellen. Die DirectComposition-API hat keine Ahnung, wie der Inhalt möglicherweise positioniert und skaliert, also muss ich die notwendigen DPI-Berechnungen selbst machen:

void UpdateVisualOffset(float const dpiX,
                        float const dpiY)
{
  HR(Visual->SetOffsetX(LogicalToPhysical(LogicalX, dpiX)));
  HR(Visual->SetOffsetY(LogicalToPhysical(LogicalY, dpiY)));
}

Ich werde eine andere Hilfsmethode für tatsächlich fest des Kreises logische Versatz hinzufügen. Diese stützt sich auf UpdateVisualOffset um sicherzustellen, dass die Kreis-Struktur und das visuelle Objekt synchronisiert sind:

void SetLogicalOffset(float const logicalX,
                      float const logicalY,
                      float const dpiX,
                      float const dpiY)
{
  LogicalX = logicalX;
  LogicalY = logicalY;
  UpdateVisualOffset(dpiX,
                       dpiY);
}

Schließlich Kreisen die Anwendung hinzugefügt werden, benötigen ich einen einfachen Konstruktor initialisiert die Struktur übernehmen des Besitzes von einer IDCompositionVisual2-Referenz:

Circle(ComPtr<IDCompositionVisual2> && visual,
       float const logicalX,
       float const logicalY,
       float const dpiX,
       float const dpiY) :
  Visual(move(visual))
{
  SetLogicalOffset(logicalX,
                   logicalY,
                   dpiX,
                   dpiY);
}

Ich kann jetzt verfolgen die Anwendung Kreise mit einer Standardliste-Container zu:

list<Circle> m_circles;

Während ich hier bin, werde ich auch Mitglied um alle ausgewählten Kreis verfolgen hinzufügen:

Circle * m_selected = nullptr;
float m_mouseX = 0.0f;
float m_mouseY = 0.0f;

Der Maus-Offset helfen auch um natürliche Bewegung zu produzieren. Ich werde den Haushalt aus dem Weg, bevor ich die eigentlichen Mausinteraktion, die letztlich die Kreise erstellen und gestatten Sie mir betrachten, sie bewegen. Die CreateDeviceResources-Methode muss keinerlei visuelle Objekte neu Gerät verloren gehen sollte, basierend auf einer zuvor erstellten Kreise. Es würde nicht tun, wenn die Kreise verschwinden. Also direkt nach dem Erstellen oder Neuerstellen der Stamm visual und der gemeinsamen Oberfläche, ich werde diese Liste durchlaufen neue visuelle Objekte erstellen, und sie entsprechend den bestehenden Zustand neu anordnen. Abbildung 4 veranschaulicht, wie dies zusammen kommt alles mit, was ich bereits festgestellt habe.

Abbildung 4 Erstellen der Device-Stack und die visuelle Struktur

void CreateDeviceResources()
{
  ASSERT(!IsDeviceCreated());
  CreateDevice3D();
  ComPtr<ID2D1Device> const device2D = CreateDevice2D();
  HR(DCompositionCreateDevice2(
      device2D.Get(),
      __uuidof(m_device),
      reinterpret_cast<void **>(m_device.ReleaseAndGetAddressOf())));
  HR(m_device->CreateTargetForHwnd(m_window,
                                   true,
                                   m_target.ReleaseAndGetAddressOf()));
  m_rootVisual = CreateVisual();
  HR(m_target->SetRoot(m_rootVisual.Get()));
  CreateDeviceScaleResources();
  for (Circle & circle : m_circles)
  {
    circle.Visual = CreateVisual();
    HR(circle.Visual->SetContent(m_surface.Get()));
    HR(m_rootVisual->AddVisual(circle.Visual.Get(), false, nullptr));
    circle.UpdateVisualOffset(m_dpiX, m_dpiY);
  }
  HR(m_device->Commit());
}

Das andere Bit Hauswirtschaft hat zu tun mit DPI-Skalierung. Die Zusammensetzung-Oberfläche, die den Kreis Pixel enthält infolge Direct2D muß maßstabsgetreu neu erstellt, und die Visuals selbst müssen auch neu positioniert also proportional zueinander und zum Fenster besitzenden Versätze. WM_DPICHANGED-Meldungshandler zuerst ahmt die Zusammensetzung-Oberfläche — mit Hilfe der Methode CreateDeviceScaleResources — und aktualisiert dann den Inhalt und die Position für jedes der Kreise:

if (!IsDeviceCreated()) return;
CreateDeviceScaleResources();
for (Circle & circle : m_circles)
{
  HR(circle.Visual->SetContent(m_surface.Get()));
  circle.UpdateVisualOffset(m_dpiX, m_dpiY);
}
HR(m_device->Commit());

Jetzt werde ich die Zeiger-Interaktion beschäftigen. Ich lasse den Benutzer neue Kreise erstellen, wenn die linke Maustaste gedrückt wird, während die STRG-Taste gedrückt wird. Der Meldungshandler WM_LBUTTONDOWN sieht ungefähr so aus:

if (wparam & MK_CONTROL)
{
  // Create new circle
}
else
{
  // Look for existing circle
}
HR(m_device->Commit());

Vorausgesetzt, dass ein neuer Kreis erstellt werden muss, werde ich beginnen, indem Sie erstellen eine neue Visual und freigegebene Inhalte bevor Sie sie als untergeordnetes Element des visuellen Stamm hinzufügen:

ComPtr<IDCompositionVisual2> visual = CreateVisual();
HR(visual->SetContent(m_surface.Get()));
HR(m_rootVisual->AddVisual(visual.Get(), false, nullptr));

Das neue Visual wird vor jeder vorhandenen Bilder hinzugefügt. Das ist die zweite Parameter der AddVisual-Methode bei der Arbeit. Wenn ich diese auf True gesetzt hatte würde dann das neue Visual auf der Rückseite vorhandenen Geschwister gelegt habe. Als nächstes muss ich hinzufügen, dass eine Kreis-Struktur in der Liste, so dass ich später unterstützen kann Treffertests, Gerät Verlust und DPI-Skalierung:

m_circles.emplace_front(move(visual),
       PhysicalToLogical(LOWORD(lparam), m_dpiX) - 50.0f,
       PhysicalToLogical(HIWORD(lparam), m_dpiY) - 50.0f,
       m_dpiX,
       m_dpiY);

Ich bin vorsichtig, den neu geschaffenen Kreis an der Spitze der Liste zu platzieren, deshalb unterstütze ich natürlich können Treffertests in der gleichen Reihenfolge, die die visuelle Struktur impliziert. I Platz auch zunächst das visuelle, so dass es auf der Position des Mauszeigers zentriert ist. Schließlich angenommen, der Benutzer sofort die Maustaste loslassen nicht, ich auch die Maus erfassen Bestellungen und haben immer welche Kreis möglicherweise verschoben werden, um:

SetCapture(m_window);
m_selected = &m_circles.front();
m_mouseX = 50.0f;
m_mouseY = 50.0f;

Der Maus-Offset kann ich problemlos jeder Kreis unabhängig davon, wo auf den Kreis ziehen der Mauszeiger zunächst ausfällt. Auf der Suche nach einer bestehenden Gruppe ist ein wenig komplizierter. Hier, muss ich wieder manuell DPI Bewusstsein anwenden. Glücklicherweise macht Direct2D dies ein Kinderspiel. Zuerst muss ich die Kreise in der natürlichen Z-Reihenfolge durchlaufen. Glücklicherweise legte ich bereits neue Kreise an der Spitze der Liste so ist dies eine einfache Sache von Anfang bis Ende durchlaufen:

for (auto circle = begin(m_circles); circle != end(m_circles); ++circle)
{
}

Ich bin keiner bereichsbasierten für Anweisung benutze weil es bequemer wird zu Iteratoren praktisch in diesem Fall haben. Wo sind jetzt die Kreise? Nun, verfolgt von jeder Kreis seine logische Position relativ zur oberen linken Ecke des Fensters. Die Maus-Nachricht LPARAM enthält auch den Zeiger körperliche Position relativ zur oberen linken Ecke des Fensters. Aber es ist nicht genug, um sie zu einem gemeinsamen Koordinatensystem zu übersetzen, weil die Form, der ich brauche zu testen für ein einfaches Rechteck nicht. Die Form wird durch ein Geometry-Objekt definiert und Direct2D bietet die FillContainsPoint-Methode, um Treffertests implementieren. Der Trick ist, dass das Geometrieobjekt nur die Form des Kreises und nicht seine Position bietet. Für Treffertests um effektiv zu arbeiten, muss ich zuerst die Mausposition zu übersetzen, so dass er sich gegenüber dem Geometry-Objekt befindet. Das ist einfach genug:

D2D1_POINT_2F const point =
  Point2F(LOWORD(lparam) - LogicalToPhysical(circle->LogicalX, m_dpiX),
          HIWORD(lparam) - LogicalToPhysical(circle->LogicalY, m_dpiY));

Aber ich bin nicht ganz bereit, die FillContainsPoint-Methode aufrufen. Das andere Problem ist, dass das Geometrieobjekt nichts über das Renderingziel weiß. Wenn ich das Geometry-Objekt verwendet, um den Kreis zu zeichnen, war es das Renderingziel, das die Geometrie um die DPI-Werte des Ziels übereinstimmen skaliert. Also ich brauche eine Möglichkeit zur Skalierung der Geometrie vor dem Durchführen von Treffertests, so dass es die Größe des Kreises, widerspiegeln, was der Benutzer tatsächlich auf dem Bildschirm sehen, entspricht. Wieder einmal kommt Direct2D zur Rettung. FillContainsPoint akzeptiert eine optionale 3 x 2-Matrix zum Transformieren der Geometrie vor der Prüfung, ob der angegebene Punkt innerhalb der Form enthalten ist. Ich kann einfach eine Skalierungstransformation angesichts des Fensters DPI-Werte definieren:

D2D1_MATRIX_3X2_F const transform = Matrix3x2F::Scale(m_dpiX / 96.0f,
                                                      m_dpiY / 96.0f);

Die FillContainsPoint-Methode wird mir dann sagen, ob der Punkt innerhalb des Kreises enthalten ist:

BOOL contains = false;
HR(m_geometry->FillContainsPoint(point,
                                 transform,
                                 &contains));
if (contains)
{
  // Reorder and select circle
  break;
}

Wenn der Punkt innerhalb des Kreises enthalten ist, muss ich die Zusammensetzung-Visuals neu anordnen, so dass die ausgewählten Kreises Visual an der Spitze der Z-Reihenfolge ist. Ich kann das tun indem Entfernen von untergeordneten visuellen und in den Vordergrund der alle vorhandenen Bilder:

HR(m_rootVisual->RemoveVisual(circle->Visual.Get()));
HR(m_rootVisual->AddVisual(circle->Visual.Get(), false, nullptr));

Ich muss auch meine Liste aktuell zu halten, durch Verschieben des Kreises an die Front der Liste:

m_circles.splice(begin(m_circles), m_circles, circle);

Ich nehme dann an, der Benutzer möchte den Kreis um zu ziehen:

SetCapture(m_window);
m_selected = &*circle;
m_mouseX = PhysicalToLogical(point.x, m_dpiX);
m_mouseY = PhysicalToLogical(point.y, m_dpiY);

Hier bin ich darauf bedacht, den Offset der Mausposition relativ zum ausgewählten Kreis berechnen. Auf diese Weise der Kreis optisch "in die Mitte des Mauszeigers schnappen nicht" wie es gezogen wird, die nahtlose Bewegung. Reaktion auf die WM_MOUSEMOVE-Nachricht kann alle ausgewählten Kreis weiterhin diese Bewegung so lange ein Kreis ausgewählt ist:

if (!m_selected) return;
m_selected->SetLogicalOffset(
  PhysicalToLogical(GET_X_LPARAM(lparam), m_dpiX) - m_mouseX,
  PhysicalToLogical(GET_Y_LPARAM(lparam), m_dpiY) - m_mouseY,
  m_dpiX,
  m_dpiY);
HR(m_device->Commit());

Die Kreis-Struktur-SetLogicalOffset-Methode aktualisiert die logische Position beibehalten durch den Kreis, als auch die physikalische Position der visuelle Komposition. Ich bin auch darauf achten, die GET_X_LPARAM und GET_Y_LPARAM-Makros, LPARAM, anstatt der üblichen LOWORD zu knacken und HIWORD Makros verwenden. Während die Position, die durch die WM_MOUSEMOVE-Nachricht berichtet relativ zur oberen linken Ecke des Fensters ist, wird dies negative Koordinaten gehören, wenn die Maus erfasst wird und der Kreis, oben oder auf der linken Seite des Fensters gezogen wird.  Wie üblich, müssen die Änderungen an der visuellen Struktur für sie realisiert werden übernommen. Jede Bewegung hat ein Ende in den WM_LBUTTONUP-Meldungshandler durch die Maustaste loszulassen und Zurücksetzen des M_selected-Zeigers:

ReleaseCapture();
m_selected = nullptr;

Schließlich werde ich mit den besten Teil schließen. Der überzeugendste Beweis dafür, dass dies bezeichnend für retained Mode-Grafik ist ist, wenn man bedenkt, dass den WM_PAINT-Meldungshandler in Abbildung 5.

Abbildung 5 Retained Mode WM_PAINT-Meldungshandler

void PaintHandler()
{
  try
  {
    if (IsDeviceCreated())
    {
      HR(m_device3D->GetDeviceRemovedReason());
    }
    else
    {
      CreateDeviceResources();
    }
    VERIFY(ValidateRect(m_window, nullptr));
  }
  catch (ComException const & e)
  {
    ReleaseDeviceResources();
  }
}

Die CreateDeviceResources-Methode erstellt den Device-Stack nach vorne. Solange nichts schief geht, keine weiteren Arbeiten die WM_PAINT-Meldungshandler außer erfolgt durch um das Fenster zu überprüfen. Wenn Gerät erkannt wird, werden die verschiedenen Catch-Blöcke lassen Sie das Gerät und das Fenster bei Bedarf für ungültig erklären. Die nächste WM_PAINT-Nachricht ankommen wird wieder die Geräteressourcen neu erstellen. In meinem nächsten Artikel zeige ich Ihnen, wie Sie visuelle Effekte produzieren können, die direkt auf Benutzereingaben sind nicht. Da die Zusammensetzung-Engine mehr des Renderings führt ohne Einbeziehung der Anwendung, ist es möglich, dass Gerät Datenverlust auftreten kann, ohne es zu wissen. Deshalb ist die GetDeviceRemoved­Grund-Methode wird aufgerufen. Wenn die Komposition-Engine Gerät Verlust erkennt sendet des Anwendungsfensters eine WM_PAINT-Nachricht er rein, damit es für Gerät Verlust überprüfen kann, indem das Direct3D-Gerät GetDeviceRemovedReason-Methode. Nehmen Sie DirectComposition für eine Probefahrt mit dem begleitenden Beispielprojekt!


Kenny Kerr ist Programmierer aus Kanada sowie Autor bei Pluralsight und Microsoft MVP. Er veröffentlicht Blogs unter kennykerr.ca, und Sie können ihm auf Twitter unter twitter.com/kennykerr folgen.

Unser Dank gilt den folgenden technischen Experten von Microsoft für die Durchsicht dieses Artikels: Leonardo Blanco (Leonardo.Blanco@microsoft.com) und James Clarke (James.Clarke@Microsoft.com)