Dieser Artikel wurde maschinell übersetzt.

DirectX-Faktor

Textformatierung und Bildlaufmodus mit DirectWrite

Charles Petzold

Charles PetzoldDie Anzeige von Text in der Computergrafik seit jeher eher peinlich.Es wäre toll " zum Anzeigen von Text mit so viel Leichtigkeit als andere zwei-­Dreidimensionale Grafiken, z. B. Geometrien und Bitmaps noch Text kommt mit Hunderten von Jahren von Gepäck und einige ganz bestimmten Anforderungen — die entscheidende Anforderung der Lesbarkeit, zum Beispiel.

In Anerkennung der Besonderheiten von Text in Grafiken teilt DirectX die Aufgabe des arbeiten mit Text in zwei wichtigen Subsysteme, Direct2D und DirectWrite.Die ID2D1RenderTarget-Schnittstelle deklariert die Methoden zum Anzeigen von Text, zusammen mit anderen 2D-Grafiken, während Schnittstellen mit IDWrite helfen den Text für die Anzeige vorbereiten.

An der Grenze zwischen Grafiken und Text sind einige interessanten Techniken, z. B. Beschaffung Umrisse von Textzeichen oder Implementierung der IDWriteTextRenderer-Schnittstelle für das Abfangen und Bearbeiten von Text auf dem Weg zur Anzeige.Aber eine notwendige Vorstufe ist ein solides Verständnis der grundlegenden Text-Formatierung – mit anderen Worten, die Anzeige von Text, die dazu bestimmt ist, zu lesen anstatt mit ästhetischer Verzückung bewundert.

Der einfachste Textausgabe

Die meisten grundlegenden Text-Display-Schnittstelle ist IDWriteTextFormat, verbindet eine Schriftfamilie (Times New Roman oder Arial, z. B.) zusammen mit einem Stil (kursiv oder schräg), Gewicht (Fett- oder Licht), Stretch (schmale oder erweitert), und einen Schriftgrad.Sie werden wahrscheinlich einen Verweiszählung Zeiger für das IDWriteTextFormat-Objekt als privaten Member in einer Headerdatei definieren:

Microsoft::WRL::ComPtr<IDWriteTextFormat> m_textFormat;

Das Objekt wird mit einer Methode, die festgelegten IDWriteFactory erstellt:

dwriteFactory->CreateTextFormat(
  L"Century Schoolbook", nullptr,
  DWRITE_FONT_WEIGHT_NORMAL,
  DWRITE_FONT_STYLE_ITALIC,
  DWRITE_FONT_STRETCH_NORMAL,
  24.0f, L"en-US", &m_textFormat);

Im allgemeinen Fall wird die Anwendung wahrscheinlich mehrere IDWriteTextFormat-Objekte für verschiedene Schriftfamilien, Größen und Stile erstellen. Dies sind geräteunabhängige Ressourcen, sodass Sie sie jederzeit, nachdem Sie DWriteCreateFactory rufen um ein IDWriteFactory-Objekt abzurufen, und sie für die Dauer der Anwendung halten erstellen können.

Wenn Sie den Familiennamen falsch buchstabieren – oder wenn auf Ihrem System eine Schriftart mit diesem Namen ist — erhalten Sie eine Standardschriftart. Das zweite Argument gibt der Font Collection in der für solche Schriftart gesucht. Nullptr angeben, zeigt die System Font Collection. Sie können auch private Font-Kollektionen haben.

Die Größe ist in geräteunabhängige Einheiten basiert auf einer Auflösung von 96 Einheiten pro Zoll, also eine Größe von 24 ein 18-Punkt-Schrift entspricht. Die Sprachanzeige kann bezieht sich auf die Sprache der Schriftfamilienname, und als eine leere Zeichenfolge.

Sobald Sie ein IDWriteTextFormat-Objekt erstellt haben, ist alle Informationen, die Sie angegeben haben unveränderlich. Wenn Sie die Schriftfamilie, Stil oder Größe ändern müssen, müssen Sie das Objekt neu erstellen.

Was Sie noch tun müssen zum Rendern von Text über die IDWriteText­Objekt formatieren? Natürlich, der Text selbst, sondern auch die Position auf dem Bildschirm angezeigt werden soll, und die Farbe. Diese Elemente werden angegeben, wenn der Text gerendert wird: Das Ziel des Textes ist nicht mit einem Punkt, sondern mit einem Rechteck des Typs D2D1_RECT_F angegeben. Die Farbe des Textes wird mit einem Pinsel angegeben, die jede Art von Pinsel, z. B. Farbverlaufspinsel oder Bildpinsel sein kann.

Hier ist ein typischer DrawText Aufruf:

deviceContext->DrawText(
  L"This is text to be displayed",
  28,    // Characters
  m_textFormat.Get(),
  layoutRect,
  m_blackBrush.Get(),
  D2D1_DRAW_TEXT_OPTIONS_NONE,
  DWRITE_MEASURING_MODE_NATURAL);

Standardmäßig ist der Text in Zeilen aufgeteilt und gewickelt anhand der Breite des Rechtecks (oder die Höhe des Rechtecks für Sprachen, die von oben nach unten gelesen). Wenn der Text zu lang, um innerhalb des Rechtecks angezeigt ist, wird es nach unten fortgesetzt. Das vorletzte Argument kann optional Flags Clip Text fallen außerhalb des Rechtecks oder nicht Zeichen auf Pixelbegrenzungen ausrichten (die ist nützlich, wenn Sie auf den Text Animationen durchführen werde) angeben oder in Windows-8.1, multicolor Schriftzeichen zu aktivieren.

Zum herunterladbare Code für diese Spalte enthält ein Windows 8.1-Programm, IDWriteTextFormat und DrawText zur Anzeige von Kapitel 7 der Carrolls verwendet "Alice's Adventures in Wonderland." (Ich den Text aus dem Project Gutenberg-Website gewonnen, aber es etwas zu machen, mehr im Einklang mit der Typografie der Originalausgabe geändert.) Das Programm heißt PlainTextAlice, und ich habe es mit der DirectX-App (XAML)-Vorlage in Visual Studio Express 2013 Preview für Windows 8.1. Diese Projektvorlage generiert eine XAML-Datei enthält eine SwapChainPanel und den erforderlichen Aufwand für Grafikdarstellung DirectX drauf.

Die Datei mit dem Text ist Teil des Projekts. Jeder Absatz ist eine einzige Zeile, und ist durch eine Leerzeile getrennt. Die DirectXPage-Klasse lädt den Text in einen Loaded-Ereignishandler und überträgt diese an die PlainTextAliceMain-Klasse (erstellt im Rahmen des Projekts), die es auf der PlainTextAliceRenderer-Klasse überträgt — der Klasse zum Projekt beigetragen.

Da die Grafik angezeigt, indem dieses Programm relativ statisch ist, deaktiviert ich die Rendering-Schleife in DirectXPage von nicht Anfügen eines Handlers für das CompositionTarget::Rendering-Ereignis. Stattdessen PlainTextAliceMain bestimmt, wann die Grafik gezeichnet werden sollte deshalb nur, wenn der Text geladen wird, oder wenn das Anwendungsfenster ändert Größe oder Ausrichtung. Zu diesen Zeiten PlainTextAliceMain Ruft die Render-Methode in PlainTextAlice­Renderer und die vorliegende Methode in DeviceResources.

Der C++-Teil der Klasse PlainTextAliceRenderer wird gezeigt, Abbildung 1. Aus Gründen der Übersichtlichkeit habe ich die HRESULT-Kontrollen entfernt.

Abbildung 1 die PlainTextAliceRenderer.cpp-Datei

#include "pch.h"
#include "PlainTextAliceRenderer.h"
using namespace PlainTextAlice;
using namespace D2D1;
using namespace Platform;
PlainTextAliceRenderer::PlainTextAliceRenderer(
  const std::shared_ptr<DeviceResources>& deviceResources) :
  m_text(L""),
  m_deviceResources(deviceResources)
{
  m_deviceResources->GetDWriteFactory()->
    CreateTextFormat(L"Century Schoolbook",
                     nullptr,
                     DWRITE_FONT_WEIGHT_NORMAL,
                     DWRITE_FONT_STYLE_NORMAL,
                     DWRITE_FONT_STRETCH_NORMAL,
                     24.0f,
                     L"en-US",
                     &m_textFormat);
  CreateDeviceDependentResources();
}
void PlainTextAliceRenderer::CreateDeviceDependentResources()
{
  m_deviceResources->GetD2DDeviceContext()->
    CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black),
                          &m_blackBrush);
  m_deviceResources->GetD2DFactory()->
    CreateDrawingStateBlock(&m_stateBlock);
}
void PlainTextAliceRenderer::CreateWindowSizeDependentResources()
{
  Windows::Foundation::Size windowBounds =
    m_deviceResources->GetOutputBounds();
  m_layoutRect = RectF(50, 0, windowBounds.Width - 50,
      windowBounds.Height);
}
void PlainTextAliceRenderer::ReleaseDeviceDependentResources()
{
  m_blackBrush.Reset();
  m_stateBlock.Reset();
}
void PlainTextAliceRenderer::SetAliceText(std::wstring text)
{
  m_text = text;
}
void PlainTextAliceRenderer::Render()
{
  ID2D1DeviceContext* context = 
    m_deviceResources->GetD2DDeviceContext();
  context->SaveDrawingState(m_stateBlock.Get());
  context->BeginDraw();
  context->Clear(ColorF(ColorF::White));
  context->SetTransform(m_deviceResources->GetOrientationTransform2D());
  context->DrawText(m_text.c_str(),
                    m_text.length(),
                    m_textFormat.Get(),
                    m_layoutRect,
                    m_blackBrush.Get(),
                    D2D1_DRAW_TEXT_OPTIONS_NONE,
                    DWRITE_MEASURING_MODE_NATURAL);
  HRESULT hr = context->EndDraw();
  context->RestoreDrawingState(m_stateBlock.Get());
}

Beachten Sie, dass das M_layoutRect-Element basierend auf der Anwendung Größe auf dem Bildschirm, sondern mit 50 Pixel Rand am linken und rechten Seite berechnet wird. Das Ergebnis ist in Abbildung 2 dargestellt.

The PlainTextAlice Program
Abbildung 2 das PlainTextAlice-Programm

Zuerst finde einmal ich einige gute Dinge über dieses Programm: Klar, mit minimalem Aufwand, ist der Text korrekt in schön getrennte Absätze umbrochen.

Die Unzulänglichkeiten der Klartext­Alice-Projekt liegen auch auf der Hand: Die Abstände zwischen den Absätzen besteht, nur weil die ursprünglichen Textdatei Leerzeilen hat genau für diesen Zweck eingefügtes. Wenn Sie einen etwas kleineren oder größeren Absatzabstand wollte, wäre das nicht möglich. Darüber hinaus gibt es keine Möglichkeit, anzugeben, kursiv oder fettgedruckten Wörter innerhalb des Textes.

Aber der größte Nachteil ist, dass Sie erst den Anfang des Kapitels sehen können. Werden Scrollen Logik umgesetzt könnte, aber wie können Sie erkennen, wie weit Scrollen? Das wirklich große Problem mit IDWriteText­Format und DrawText ist, dass die Höhe der formatierte gerenderter Text einfach nicht verfügbar ist.

Zusammenfassend ist die mit DrawText sinnvoll, nur dann, wenn der Text einheitliche Formatierung hat, und du, dass das Rechteck, das Sie angeben ist ausreichend weißt an den Text anpassen oder Sie egal, ob es ein kleines überfahrenen gibt. Für anspruchsvollere Zwecke ist ein besserer Ansatz erforderlich.

Vor dem weitergehen, nehmen Sie zur Kenntnis, dass die Angaben in CreateTextFormat Methode unveränderlich in das IDWriteTextFormat-Objekt ist, aber die Schnittstelle deklariert mehrere Methoden, mit denen Sie ändern, wie der Text angezeigt wird: Beispielsweise SetParagraphAlignment ändert die vertikale Platzierung des Textes innerhalb des Rechtecks in DrawText angegeben, während der SetTextAlignment können Sie angeben, ob die Zeilen des Absatzes, rechts, zentriert oder begründeten innerhalb des Rechtecks Links sind. Die Argumente für diese Methoden verwenden Sie Wörter wie nahe, weit, führende und nachfolgende um für Text verallgemeinert werden, die von oben nach unten oder von rechts nach links liest. Sie können auch die Möglichkeit, Zeilenabstand, Textumbruch und Tabstopps steuern.

Das Text-Layout-Konzept

Der nächste Schritt von IDWriteTextFormat ist ein großes, und es ist ideal für ziemlich alle standard-Text-Ausgabe-Anforderungen, einschließlich Treffertests. Es geht um ein Objekt vom Typ IDWriteTextLayout, die nicht nur von IDWriteTextFormat abgeleitet, sondern beinhaltet ein IDWriteTextFormat-Objekt, wenn es instanziiert wird.

Hier ist ein Verweiszählung Zeiger auf ein IDWriteTextLayout-Objekt, wahrscheinlich in einer Headerdatei deklariert:

Microsoft::WRL::ComPtr<IDWriteTextLayout> m_textLayout;

Erstellen Sie das Objekt wie folgt:

dwriteFactory->CreateTextLayout(
  pText, wcslen(pText),
  m_textFormat.Get(),
  maxWidth, maxHeight,
  &m_textLayout);

Im Gegensatz zu der IDWriteTextFormat-Schnittstelle enthält IDWriteTextLayout den Text selbst und die gewünschte Höhe und Breite des Rechtecks um den Text zu formatieren. Die DrawTextLayout-Methode, die ein IDWriteTextLayout-Objekt anzeigt benötigt nur einen 2D Punkt, um anzugeben, wo die linke obere Ecke des formatierten Textes erscheinen soll:

deviceContext->DrawTextLayout(
  point,
  m_textLayout.Get(),
  m_blackBrush.Get(),
  D2D1_DRAW_TEXT_OPTIONS_NONE);

Da das IDWriteTextLayout-Objekt verfügt über alle Informationen, die es braucht, um Zeilenumbrüche zu berechnen, bevor der Text gerendert wird, weiß es auch, wie groß der gerenderte Text sein soll. IDWriteTextLayout verfügt über mehrere Methoden — GetMetrics, GetOverhangMetrics, GetLineMetrics und GetClusterMetrics —, bieten eine Fülle von Informationen helfen Ihnen mit diesem Text effektiv zu arbeiten. GetMetrics bietet beispielsweise die Gesamtbreite und die Höhe des formatierten Textes sowie die Anzahl der Linien und andere Informationen.

Obwohl die CreateTextLayout-Methode maximale Breite und Höhe Argumente enthält, können dies zu einem späteren Zeitpunkt andere Werte festgelegt werden. (Der Text selbst ist unveränderlich, jedoch.) Wenn im Display-Bereich ändert (z. B. Sie drehen Ihre Tablette von Landschaft, Portrait-Modus), du musst nicht das IDWriteTextLayout-Objekt neu erstellen. Rufen Sie die Methoden SetMaxWidth und SetMaxHeight von der Schnittstelle deklariert. In der Tat, wenn Sie zuerst das IDWriteTextLayout-Objekt erstellen, können Sie die maximale Breite und Höhe Argumente auf NULL festlegen.

Das ParagraphFormattedAlice-Projekt verwendet IDWriteTextLayout und DrawTextLayout, und die Ergebnisse sind in Abbildung 3. Sie scrollen noch nicht zu sehen und den Rest des Textes, aber beachten, dass einige Linien sind zentriert, und viele erste Zeile Gedankenstrich haben. Die Titel verwenden einen größeren Schriftgrad als der Rest, und einige Wörter sind kursiv gedruckt.

The ParagraphFormattedAlice Program
Abbildung 3 das ParagraphFormattedAlice-Programm

Die Textdatei ist ein wenig anders in diesem Projekt als erstes Projekt: Jeder Absatz ist noch eine separate Zeile, aber keine Leerzeilen trennen diese Absätze. Anstatt ein einzelnes IDWriteTextFormat-Objekt für den gesamten Text, jeder Absatz in ParagraphFormatted­Alice ist ein separates IDWriteTextLayout-Objekt mit einen gesonderten Aufruf der DrawTextLayout gerendert. Der Abstand zwischen den Absätzen kann daher auf jeden gewünschten Betrag festgelegt werden.

Um das Arbeiten mit dem Text definiert eine Struktur, die mit dem Namen Absatz:

struct Paragraph
{
  std::wstring Text;
  ComPtr<IDWriteTextLayout> TextLayout;
  float TextHeight;
  float SpaceAfter;
};

Eine Helper-Klasse namens AliceParagraphGenerator erzeugt eine Auflistung von Absatz Objekten basierend auf die Textzeilen.

IDWriteTextLayout hat eine Reihe von Methoden legen Sie Formatierung auf einzelne Wörter oder anderen Textblöcke. Zum Beispiel, ist hier, wie die fünf Zeichen beginnend bei Offset 23 im Text kursiv geschrieben sind:

DWRITE_TEXT_RANGE range = { 23, 5 };
textLayout->SetFontStyle(DWRITE_FONT_STYLE_ITALIC, range);

Ähnliche Methoden sind verfügbar für die Schriftfamilie, Sammlung, Größe, Gewicht, Strecken, unterstrichen und durchgestrichen. In realen Anwendungen Text-Formatierung wird in der Regel durch Markup (z. B. HTML) definiert, aber um Dinge einfach zu halten, die AliceParagraphGenerator-Klasse arbeitet mit einer nur-Text-Datei und enthält Hardcoded Standorte für die kursiven Worte.

AliceParagraphGenerator hat auch eine SetWidth-Methode, um eine neue Display-Breite festzulegen, wie in Abbildung 4. (Aus Gründen der Übersichtlichkeit wurden die HRESULT-Kontrollen aus dem Code in dieser Abbildung entfernt.) Die Anzeigebreite ändert, wenn die Fenstergröße ändert oder ein Tablett Ausrichtung ändert. SetWidth durchläuft die Absatz-Objekte, das TextLayout SetMaxWidth fordert und erhält dann eine neue formatierte Höhe des Absatzes, das es im TextHeight speichert. Früher wurde das SpaceAfter-Feld einfach eingerichtet, um 12 Pixel für den meisten Punkten, 36 Pixel für die Überschriften und 0 für ein paar Zeilen des Verses. Dies erleichtert die Höhe des Absatzes und die Gesamthöhe des gesamten Textes im Kapitel zu erhalten.

Abbildung 4 die SetWidth-Methode in AliceParagraphGenerator

float AliceParagraphGenerator::SetWidth(float width)
{
  if (width <= 0)
    return 0;
  float totalHeight = 0;
  for (Paragraph& paragraph : m_paragraphs)
  {
    HRESULT hr = paragraph.TextLayout->SetMaxWidth(width);
    hr = paragraph.TextLayout->SetMaxHeight(FloatMax());
    DWRITE_TEXT_METRICS textMetrics;
    hr = paragraph.TextLayout->GetMetrics(&textMetrics);
    paragraph.TextHeight = textMetrics.height;
    totalHeight += paragraph.TextHeight + paragraph.SpaceAfter;
  }
  return totalHeight;
}

Die Render-Methode in ParagraphFormattedAliceRenderer auch durchläuft die Absatz-Objekte und ruft DrawTextLayout mit einer anderen Herkunft, die auf der Grundlage der kumulierten Höhe des Textes.

Der Erstzeileneinzug-Problem

Wie Sie, in sehen können Abbildung 3, das ParagraphFormattedAlice-Programm rückt die erste Zeile des Absatzes. Vielleicht ist die einfachste Möglichkeit, einen solchen Einzug machen indem Sie einige Leerzeichen am Anfang der Zeichenfolge einfügen. Die Unicode-Standard definiert codes für ein Geviert (mit einer Breite gleich der Punktgröße), Halbgeviert (die Hälfte, die), Viertel-Em und kleinere Räume, so dass Sie diese für den gewünschten Abstand kombinieren können. Der Vorteil ist, dass der Gedankenstrich die Punktgröße der Schriftart proportional ist.

Aber dieser Ansatz funktioniert nicht für einen negativen Erstzeileneinzug — bezeichnet einen hängenden Einzug — das ist eine erste Linie, die auf der linken Seite den Rest des Absatzes beginnt. Darüber hinaus sind Erstlinien-Einzüge häufig in Metriken (z. B. einen halben Zoll) angegeben, die unabhängig von der Größe sind.

Aus diesen Gründen entschied ich mich stattdessen Erstlinien-Einzügen, die mit der SetInlineObject-Methode der IDWriteTextLayout-Schnittstelle implementieren. Diese Methode soll ermöglichen Sie ein grafisches Objekt Inline mit dem Text zu setzen, so dass es fast wie ein alleinstehendes Wort wird deren Größe zur Verpackung von der Linie berücksichtigt wird.

Die SetInlineObject-Methode wird häufig verwendet, für das kleine Bitmaps in den Text einfügen. Um es für die oder andere Zwecke zu verwenden, müssen Sie eine Klasse, die die IDWriteInlineObject-Schnittstelle, die neben den drei standard IUnknown-Methoden, die GetMetrics, GetOverhangMetrics, GetBreakConditions implementiert erklärt, schreiben und Zeichnen Methoden. Grundsätzlich Klasse, die Sie bereitstellen aufgerufen wird, während der Text wird gemessen oder gerendert. Für meine Klasse FirstLineIndent ich definiert einen Konstruktor mit einem Argument, den gewünschten Einzug in Pixel angibt, und dass Wert grundsätzlich durch den Aufruf GetMetrics, das die Größe des eingebetteten Objekts zurückgegeben. Die Implementierung der Klasse ziehen tut nichts. Negative Werte funktionieren gut für hängende Einzüge.

Sie rufen SetInlineObject IDWriteFontLayout wie SetFontStyle und andere Methoden basierend auf Textbereiche, aber erst als ich begann mit SetInlineObject entdeckte, dass der Bereich eine Länge von NULL haben könnte. Mit anderen Worten, können nicht Sie einfach ein Inline-Objekt einfügen. Das Inlineobjekt muss mindestens ein Zeichen des Texts zu ersetzen. Aus diesem Grund fügt beim definieren die Absatz-Objekte, der Code Nein Breite Leerzeichen ('\x200B') am Anfang jeder Zeile, die keine visuelle Darstellung sondern kann ersetzt werden, wenn SetInlineObject aufgerufen wird.

DIY Scrollen

Das ParagraphFormattedAlice-Programm nicht scrollen, aber es hat alle wichtige Informationen zu scrollen, insbesondere die Gesamthöhe des gerenderten Textes zu implementieren. Das ScrollableAlice-Projekt zeigt ein Ansatz zum Scrollen: Das Programm gibt noch an einer SwapChainPanel die Größe des Programmfensters, aber es gleicht die Darstellung auf der Grundlage des Benutzers Maus oder Fingereingabe. Ich denke dieses Ansatzes, wie "do it yourself" scrollen.

Ich habe das ScrollableAlice-Projekt in Visual Studio 2013 Vorschau mit der gleichen DirectX App (XAML)-Vorlage habe ich für den früheren Projekten, aber ich konnte ein weiterer interessanter Aspekt dieser Vorlage nutzen. Die Vorlage enthält Code in DirectXPage.cpp, die von einem sekundären Thread der Ausführung zum Behandeln von Ereignissen der Zeiger von der SwapChainPanel erstellt. Diese Technik vermeidet bogging dem UI-Thread mit diesem Eingang.

Einführung in einem sekundären Thread erschwert natürlich Dinge auch. Ich wollte einige Trägheit in meine scrollen, was bedeutete, dass ich Manipulation Ereignisse anstelle von Zeiger Veranstaltungen wollte. Dies erforderte, dass ich DirectXPage verwenden, zum Erstellen eines GestureRecognizer-Objekts (auch in diesem sekundären Thread), das Manipulation Ereignisse vom Zeiger Ereignisse generiert.

Das vorherige ParagraphFormattedAlice-Programm zeichnet das Fenster des Programms, wenn der Text geladen wird, und wenn das Fenster Größe ändert. ScrollableAlice tut das auch, und das Neuzeichnen weiterhin in den UI-Thread auftritt. ScrollableAlice zeichnet auch das Fenster, wenn ManipulationUpdated Ereignisse werden ausgelöst, doch dies in der sekundären Thread erstellt für den Zeiger geben geschieht.

Was passiert, wenn Sie dem Text eine gute Flick mit dem Finger geben so weiter Scrollen mit Trägheit, und zwar der Text noch Scrollen ist, Größe des Fensters ändern? Es ist eine gute Möglichkeit, die überlappende DirectX Anrufe von zwei verschiedenen Threads gleichzeitig vorgenommen werden, und das ist ein Problem.

Einige Threadsynchronisierung ist was erforderlich ist und eine gute, einfache Lösung umfasst die Critical_section-Klasse im Namespace Parallelität. In einer Headerdatei ist Folgendes erklärt:

Concurrency::critical_section m_criticalSection;

Die Critical_section-Klasse enthält eine eingebettete Klasse mit dem Namen Scoped_lock. Die folgende Anweisung erstellt ein Objekt namens Verschluss des Typs Scoped_lock durch Aufrufen des Konstruktors mit dem Critical_section-Objekt:

critical_section::scoped_lock lock(m_criticalSection);

Dieser Konstruktor übernimmt den Besitz des M_criticalSection-Objekts, oder Blöcke Ausführung wenn das M_criticalSection-Objekt von einem anderen Thread gehört. Was ist nett zu dieser Scoped_lock-Klasse ist, dass der Destruktor releases Eigentum an den M_criticalSection, wenn das Lock-Objekt den Gültigkeitsbereich, verlässt, so ist es unglaublich einfach, nur ein paar von diesen um in verschiedene Methoden zu streuen, die gleichzeitig aufgerufen werden könnte.

Ich entschlossen, es wäre am einfachsten, diese kritischen Abschnitt in DirectXPage, zu implementieren, die einige entscheidende Aufrufe der DeviceResources-Klasse (z. B. UpdateForWindowSizeChanged) enthält, die müssen andere Threads für die Dauer blockiert werden. Obwohl es keine gute Idee um den UI-Thread blockieren (das geschieht, wenn Zeiger Ereignisse ausgelöst werden), sind diese Blöcke extrem kurz.

Mit der Zeit wird die durchlaufende Informationen geliefert, um die Scrollable­AliceRenderer Klasse, es hat die Form eines Gleitkommawerts als eine Variable namens M_scrollOffset, eingeklemmt zwischen 0 und dem Maximalwert entspricht der Differenz zwischen der Höhe des vollen Kapitels des Textes und die Höhe des Fensters gespeichert. Die Render-Methode verwendet diesen Wert um zu bestimmen, wie beginnen und enden die Anzeige von Ziffern, siehe Abbildung 5.

Figure 5 Implementieren von Blättern in ScrollableAlice

std::vector<Paragraph> paragraphs =
  m_aliceParagraphGenerator.GetParagraphs();
float outputHeight = m_deviceResources->GetOutputBounds().Height;
D2D1_POINT_2F origin = Point2F(50, -m_scrollOffset);
for (Paragraph paragraph : paragraphs)
{
  if (origin.y + paragraph.TextHeight + paragraph.SpaceAfter > 0)
    context->DrawTextLayout(origin, paragraph.TextLayout.Get(),
    m_blackBrush.Get());
  origin.y += paragraph.TextHeight + paragraph.SpaceAfter;
  if (origin.y > outputHeight)
    break;
}

Scrollen mit einem Bounce

Obwohl das ScrollableAlice-Programm implementiert scrollen, die Note Trägheit hat, es muss nicht die charakteristische Windows 8-Bounce, wenn Sie versuchen, führen Sie einen Bildlauf nach oben oder unten. (Inzwischen) vertraut Bounce ScrollViewer eingegliedert, und zwar es Spaß zu versuchen den ScrollViewer-Bounce in Ihrem eigenen Code zu duplizieren, das ist nicht die Aufgabe für heute.

Da scrollbaren Text lang sein könnte, es ist am besten, nicht zu versuchen, sie zu machen, alles auf einer Oberfläche oder Bitmap. DirectX hat Beschränkungen, wie groß diese Flächen werden können. Das ScrollableAlice-Programm umgeht diese Einschränkungen durch seine Anzeige zu einer SwapChainPanel zu begrenzen die Größe des Programmfensters, und das funktioniert prima. Aber für ScrollViewer-Element arbeiten, müssen der Inhalt eine Größe im Layout reflektiert die volle Höhe des formatierten Textes.

Glücklicherweise unterstützt Windows 8 ein Element, das ist genau was wir brauchen. Die VirtualSurfaceImageSource-Klasse ist von SurfaceImageSource, die wiederum von ImageSource abgeleitet ist, so dass es als eine Bitmapquelle für ein Image-Element in einem ScrollViewer dienen kann. VirtualSurfaceImageSource kann jede gewünschte Größe (und Größe geändert werden kann, ohne erneut erstellt wird), und umgeht DirectX Größenbeschränkungen durch die Virtualisierung der Fläche und Umsetzung Anforderungs-Zeichnung. (Allerdings sind SurfaceImageSource und VirtualSurfaceImageSource nicht optimal für Hochleistungs-Frame-basierte Animationen.)

VirtualSurfaceImageSource ist eine Windows-Runtime-Ref-Klasse. Für die Verwendung in Verbindung mit DirectX muss es in ein Objekt vom Typ IVirtualSurfaceImageSourceNative, die Methoden zum Implementieren von Anforderungs-Zeichnung zeigt umgewandelt werden. Diese Methoden berichten was rechteckige Bereiche müssen aktualisiert werden, und das Programm der neuen Update-Rechtecke durch Bereitstellen einer Klasse, die implementiert IVirtualSurfaceUpdatesCallbackNative benachrichtigt werden.

Das Projekt demonstriert diese Technik ist BounceScrollableAlice, und da es nicht ist eine SwapChainPanel erforderlich, ich habe es in Visual Studio 2013 Preview basierend auf der Vorlage leere App (XAML). Für die erforderliche Klasse, die implementiert IVirtualSurface­UpdatesCallbackNative ich habe eine Klasse mit dem Namen VirtualSurface­ImageSourceRenderer, und es bietet auch viel von den DirectX-Overhead. Die AliceVsisRenderer-Klasse wird von VirtualSurface­ImageSourceRenderer das Alice-spezifische zeichnen bereitgestellt.

Die Update-Rechtecke von IVirtualSurfaceImageSourceNative beziehen sich auf die volle Größe des der VirtualSurfaceImageSource, aber Zeichnung Koordinaten beziehen sich auf die Update-Rechtecke. Dies bedeutet, dass die DrawTextLayout-Aufrufe in BounceScrollableAlice sind nahezu identisch mit denen im Abbildung 5, außer die ursprüngliche Herkunft auf das negativ des oberen Teils des Update-Rechtecks anstatt den Bildlaufoffset festgelegt ist, und der OutputHeight-Wert der Unterschied zwischen der unteren und oberen Rand des Rechtecks Update ist.

Durch die Einführung von ScrollViewer in die Mischung, die Textanzeige fühlt wirklich Windows 8-artig, und können Sie sogar eine Prise Geste zu vergrößern oder zu verkleinern, betont einmal mehr, dass dieses Windows 8 Verschmelzung von DirectX und XAML bietet etwas in der Nähe von das Beste aus beiden Welten.

Charles Petzold ist ein langjähriger Beitrag zum MSDN Magazine und Autor von "Programming Windows, 6th Edition" (Microsoft Press, 2012), ein Buch über das Schreiben von Anwendungen für Windows 8. Seiner Website lautet charlespetzold.com.

Unser Dank gilt dem folgenden technischen Experten für die Durchsicht dieses Artikels: Jim Galasyn (Microsoft) und Justin Panian (Microsoft)