Compartir a través de


Este artículo proviene de un motor de traducción automática.

El factor DirectX

Formato y desplazamiento de texto con DirectWrite

Charles Petzold

Descargar el código de ejemplo

Charles PetzoldLa visualización de texto en gráficos de computadora siempre ha sido bastante torpe. Sería genial ' para mostrar texto con tanta facilidad como otros dos -­gráficos tridimensionales, como geometrías y mapas de bits, sin embargo, texto viene con cientos de años de bagaje y algunas necesidades concretas — el requisito fundamental de la legibilidad, por ejemplo.

En reconocimiento a la naturaleza especial de texto en gráficos, DirectX divide el trabajo de trabajar con texto en dos subsistemas principales, Direct2D y DirectWrite. La interfaz ID2D1RenderTarget declara los métodos para la visualización de texto, junto con otros gráficos 2D, mientras que las interfaces comenzando con IDWrite ayudan a preparar el texto para la exhibición.

En la frontera entre texto y gráficos son algunas técnicas interesantes, tales como obtención de líneas de caracteres de texto, o implementar la interfaz IDWriteTextRenderer para interceptar y manipulación de texto en su camino a la pantalla. Pero un informe preliminar necesario es una sólida comprensión de formato de texto básico — en otras palabras, la pantalla de texto que pretende ser leer más admirado con éxtasis estético.

La salida de texto simple

La interfaz de visualización de texto más fundamental es IDWriteTextFormat, que combina una familia de fuentes (Times New Roman o Arial, por ejemplo) junto con un estilo (cursiva u oblicua), peso (negrita o luz), tramo (estrecho o ampliado) y un tamaño de fuente. Probablemente se podrá definir un puntero de referencia-contado para el objeto IDWriteTextFormat como un miembro privado en un archivo de encabezado:

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

Se crea el objeto con un método definido por IDWriteFactory:

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);

En el caso general, la aplicación probablemente creará varios objetos IDWriteTextFormat para familias de diferentes fuentes, tamaños y estilos. Estos son recursos independientes del dispositivo, así que puede crear en cualquier momento después de llamar a DWriteCreateFactory para obtener un objeto IDWriteFactory y mantenerlos durante la duración de su aplicación.

Si se escribe incorrectamente el nombre de familia — o si no es una fuente con ese nombre en su sistema, usted obtendrá una fuente predeterminada. El segundo argumento indica la colección de fuentes en las que buscar una fuente de dicha. Especificar nullptr indica la colección de fuentes del sistema. También puedes tener colecciones de fuentes privadas.

El tamaño es en unidades independientes del dispositivo basadas en una resolución de 96 unidades por pulgada, para que un tamaño de 24 es equivalente a una fuente de 18 puntos. El indicador de lengua se refiere a la lengua del nombre de familia de la fuente y puede quedar como una cadena vacía.

Una vez que haya creado un objeto IDWriteTextFormat, toda la información que especificó es inmutable. Si necesitas cambiar la familia de fuentes, estilo o tamaño, necesitas volver a crear el objeto.

¿Qué más necesita representar el texto más allá de la IDWriteText­objeto de formato? Obviamente, el texto en sí, sino también la ubicación en la pantalla donde desea que se muestre y el color. Estos elementos se especifican cuando se procesa el texto: El destino del texto se indica no un punto pero con un rectángulo de tipo D2D1_RECT_F. El color del texto se especifica con un cepillo, que puede ser cualquier tipo de cepillo, como por ejemplo un pincel de degradado o un cepillo de imagen.

Este es un típico llamado DrawText:

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);

De forma predeterminada, el texto está dividido en líneas y envuelto basado en el ancho del rectángulo (o la altura del rectángulo en los idiomas que se leen de arriba hacia abajo). Si el texto es demasiado largo para mostrar dentro del rectángulo, continuará más allá de la parte inferior. El penúltimo argumento puede indicar banderas opcionales al texto clip cayendo fuera del rectángulo, o de no alinear caracteres en los límites de píxeles (que es útil si usted podrá realizar animaciones sobre el texto) o, en el punto 8.1 de Windows, para permitir caracteres fuente multicolor.

El código descargable para esta columna incluye un programa de Windows 8.1 que utiliza IDWriteTextFormat y DrawText para mostrar el capítulo 7 de Lewis Carroll "Aventuras de Alicia en el país de las maravillas". (Obtuvo el texto desde el sitio Web de proyecto Gutenberg, pero modifiqué un poco para hacerla más consistente con la tipografía de la edición original). El programa se llama PlainTextAlice, y he creado utilizando la plantilla de aplicación DirectX (XAML) en Visual Studio Express 2013 Preview para Windows 8.1. Esta plantilla de proyecto genera un archivo XAML que contiene un SwapChainPanel y toda la sobrecarga necesaria para la visualización de gráficos DirectX en él.

El archivo con el texto forma parte del contenido del proyecto. Cada párrafo es una sola línea, y cada uno está separado por una línea en blanco. La clase DirectXPage el texto en un controlador de eventos cargados de carga y transfiere a la clase PlainTextAliceMain (creada como parte del proyecto), que lo transfiere a la clase de PlainTextAliceRenderer — la clase contribuí al proyecto.

Porque los gráficos mostrados por este programa son bastante estáticos, he desactivado el bucle de renderizado en DirectXPage por no fijar un controlador para el evento CompositionTarget::Rendering. En cambio, PlainTextAliceMain determina cuando los gráficos deben ser rediseñados, que es solamente cuando el texto se carga o cuando la ventana de la aplicación cambia el tamaño o la orientación. En estos momentos, PlainTextAliceMain llama al método Render de PlainTextAlice­Renderer y el método presente en DeviceResources.

La parte de C++ de la clase PlainTextAliceRenderer se muestra en la figura 1. Para mayor claridad, he eliminado los controles HRESULT.

Figura 1 el archivo PlainTextAliceRenderer.cpp

#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());
}

Tenga en cuenta que el miembro de m_layoutRect se calcula basándose en el tamaño de la aplicación en la pantalla pero con un margen de 50 píxeles a la izquierda y derecha. El resultado se muestra en la figura 2.

The PlainTextAlice Program
Figura 2 el programa PlainTextAlice

Primero, encontrarás algunas cosas buenas que decir acerca de este programa: Claramente, con un mínimo de arriba, el texto es envuelto correctamente en los párrafos bien separados.

Las insuficiencias de la PlainText­Proyecto Alice también son obvios: El espacio entre los párrafos existe sólo porque el archivo de texto original tiene las líneas en blanco que he insertado para ese propósito. Si usted quiere un espaciado entre párrafos algo más pequeño o más amplio, no sería posible. Por otra parte, es imposible indicar palabras en cursiva o boldfaced dentro del texto.

Pero el mayor inconveniente es que se puede ver sólo el comienzo del capítulo. Desplazamiento lógica podría ser implementado, pero ¿cómo lo sabes lo lejos que desplazarse? El gran problema con IDWriteText­formato y DrawText es que la altura de los prestados texto formateado simplemente no está disponible.

En conclusión, usando DrawText tiene sentido sólo cuando el texto tiene formato uniforme y el rectángulo que especifica es suficiente introducir el texto o no te importa si hay un atropellamiento. Para los propósitos más sofisticados se requiere un enfoque mejor.

Antes de continuar, tome nota que la información especificada en el método CreateTextFormat es inmutable en el objeto IDWriteTextFormat, pero la interfaz declara varios métodos que permiten cambiar cómo se muestra el texto: Por ejemplo, SetParagraphAlignment altera la colocación vertical del texto dentro del rectángulo especificado en DrawText, mientras que SetTextAlignment permite especificar si las líneas del párrafo son izquierdas, derecha, centrado o justificado dentro del rectángulo. Los argumentos de estos métodos usan palabras como cerca, lejos, el líder y se arrastra a ser generalizado para el texto que se lee de arriba a abajo o de derecha a izquierda. También puede controlar interlineado, ajuste del texto y tabulaciones.

El enfoque de diseño de texto

El siguiente paso para arriba IDWriteTextFormat es grande, y es ideal para prácticamente todas las necesidades salida texto estándar, incluyendo pruebas de posicionamiento. Se trata de un objeto de tipo IDWriteTextLayout, que no sólo se deriva de IDWriteTextFormat, pero incorpora un objeto IDWriteTextFormat cuando se es ser instanciado.

Aquí es un puntero de referencia-contado a un objeto IDWriteTextLayout, probablemente declarado en un archivo de encabezado:

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

Crear el objeto así:

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

A diferencia de la interfaz IDWriteTextFormat, la IDWriteTextLayout incorpora el texto propiamente dicho y la altura deseada y anchura del rectángulo para dar formato al texto. El método DrawTextLayout que muestra un objeto IDWriteTextLayout necesita sólo un punto 2D para indicar donde aparezca la esquina superior izquierda del texto con formato:

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

Porque el objeto IDWriteTextLayout tiene toda la información que necesita para calcular saltos de línea antes de que se procesa el texto, también sabe cuán grande será el texto representado. IDWriteTextLayout tiene varios métodos — GetMetrics, GetOverhangMetrics, GetLineMetrics y GetClusterMetrics — que proporcionan una gran cantidad de información para ayudarle a trabajar eficazmente con este texto. Por ejemplo, GetMetrics proporciona la total anchura y la altura del texto con formato, así como el número de líneas y otra información.

Aunque el método de CreateTextLayout incluye argumentos de altura y ancho máximo, éstos pueden ajustarse a otros valores en un momento posterior. (El texto en sí es inmutable, sin embargo). Si el área de visualización cambia (por ejemplo, cumplas tu tableta de paisaje a modo de retrato), no tienes que volver a crear el objeto IDWriteTextLayout. Solo llame a los métodos SetMaxWidth y SetMaxHeight, declarados por la interfaz. De hecho, cuando primero se crea el objeto IDWriteTextLayout, puede establecer los argumentos de anchura y la altura máximos a cero.

El proyecto ParagraphFormattedAlice utiliza IDWriteTextLayout y DrawTextLayout, y los resultados se muestran en figura 3. Usted todavía no puede desplazarse para ver el resto del texto, pero notar que algunas líneas estén centradas, y muchos tienen primera línea guiones. Los títulos de utilizan un tamaño de fuente más grande que el resto y algunas palabras están en cursiva.

The ParagraphFormattedAlice Program
Figura 3 el programa de ParagraphFormattedAlice

El archivo de texto es un poco diferente en este proyecto el primer proyecto: Cada párrafo es todavía una línea separada, pero no hay líneas en blanco separan estos párrafos. En lugar de utilizar un único objeto IDWriteTextFormat para todo el texto, cada párrafo en ParagraphFormatted­Alice es un objeto IDWriteTextLayout independiente con una separada llamada DrawTextLayout. Por lo tanto, el espacio entre los párrafos puede ajustarse a cualquier cantidad deseada.

Para trabajar con el texto, definí una estructura llamada párrafo:

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

Una clase auxiliar llamada AliceParagraphGenerator genera una colección de objetos párrafo basado en las líneas de texto.

IDWriteTextLayout tiene un montón de métodos para establecer el formato en otros bloques de texto o palabras individuales. Por ejemplo, aquí es cómo los cinco caracteres comenzando en offset 23 en el texto están en cursiva:

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

Métodos similares están disponibles para la familia de fuentes, recolección, tamaño, peso, estiramiento, subrayado y tachado. En aplicaciones reales, formato de texto es generalmente definida por marcado (como HTML), pero para mantener las cosas simples, la clase de AliceParagraphGenerator funciona con un archivo de texto y contiene hardcoded ubicaciones para las palabras en cursiva.

AliceParagraphGenerator también tiene un método SetWidth para establecer la nueva anchura de la pantalla, como se muestra en la figura 4. (Para mayor claridad, las comprobaciones HRESULT han sido removidas del código en esta figura). El ancho de la pantalla cambia cuando cambia el tamaño de la ventana, o cuando una tableta cambia la orientación. SetWidth recorre todos los objetos de párrafo, llama a SetMaxWidth en el TextLayout y luego obtiene una nueva altura con formato del párrafo que guarda en TextHeight. Anteriormente, el campo SpaceAfter simplemente fue establecido en 12 píxeles para la mayoría de los párrafos, 36 píxeles para las partidas y 0 para un par de líneas del verso. Esto hace fácil obtener la altura de cada párrafo y la altura total de todo el texto en el capítulo.

Figura 4 el método SetWidth en 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;
}

El método Render en ParagraphFormattedAliceRenderer también recorre todos los objetos del párrafo y llama a DrawTextLayout con un origen diferente dependiendo de la altura acumulada del texto.

El problema de la sangría de primera línea

Como se puede ver en figura 3, el programa de ParagraphFormattedAlice aplica sangría a la primera línea de los párrafos. Quizás la forma más fácil de hacer tal una sangría está introduciendo algunos espacios en blanco al principio de la cadena de texto. El Unicode estándar define los códigos de un espacio (que tiene una anchura igual al tamaño de punto), espacio en (la mitad que), cuarto em y espacios más pequeños, así que usted puede combinar estas para el espacio deseado. La ventaja es que el guión es proporcional al tamaño del punto de la fuente.

Sin embargo, este enfoque no funcionará para una sangría de primera línea negativa — a veces se llama una sangría — que es una primera línea que comienza a la izquierda del resto del párrafo. Por otra parte, las sangrías de primera línea comúnmente se especifican en métricas (como media pulgada) que son independientes del tamaño del punto.

Por estas razones, he decidido en cambio aplicar sangrías de primera línea mediante el método SetInlineObject de la interfaz IDWriteTextLayout. Este método está pensado para permitirle poner cualquier objeto gráfico en línea con el texto se convierte en casi como una palabra separada cuyo tamaño es tenida en cuenta para envolver la línea.

Comúnmente se utiliza el método SetInlineObject para insertar pequeños mapas de bits en el texto. Para usarlo para ese o para cualquier otro propósito, debes escribir una clase que implementa la interfaz IDWriteInlineObject, que, además de los tres métodos estándar de IUnknown, declara el GetMetrics, GetOverhangMetrics, GetBreakConditions, y dibujar los métodos. Básicamente, la clase que usted suministra se llama mientras que el texto está siendo medido o prestados. Para mi clase de FirstLineIndent, definí un constructor con un argumento que indica el guión deseado en píxeles, y que es básicamente valor devuelto por la llamada GetMetrics para indicar el tamaño del objeto incrustado. Implementación de la clase de dibujo no hace nada. Los valores negativos funcionan bien para colgar las sangrías.

Llamar al SetInlineObject de IDWriteFontLayout como SetFontStyle y otros métodos basados en intervalos de texto, pero fue sólo cuando comencé a usar SetInlineObject que he descubierto que la gama no podía tener una longitud de cero. En otras palabras, no puede simplemente insertar un objeto en línea. El objeto en línea tiene que sustituir al menos un carácter de texto. Por esa razón, mientras se define los objetos de párrafo, el código inserta un carácter de espacio sin ancho ('\x200B') al principio de cada línea, con ningún aspecto visual, pero puede ser reemplazado cuando se llama a SetInlineObject.

Desplazamiento de bricolaje

El programa ParagraphFormattedAlice no desplazarse, pero tiene toda la información esencial para implementar desplazamiento, específicamente, la altura total del texto representado. El proyecto de ScrollableAlice demuestra una forma de desplazamiento: El programa de salidas todavía a un SwapChainPanel el tamaño de la ventana del programa, pero compensa el procesamiento basado en ratón del usuario o la entrada táctil. Creo que de este enfoque como "do it yourself" de desplazamiento.

He creado el proyecto ScrollableAlice en Visual Studio 2013 escuchar usando la misma plantilla de DirectX App (XAML) que utiliza para los proyectos anteriores, pero era capaz de tomar ventaja de otro aspecto interesante de esta plantilla. La plantilla contiene código en DirectXPage.cpp que crea un subproceso de ejecución para controlar los eventos de puntero de la SwapChainPanel secundario. Esta técnica evita atascarse el subproceso de la interfaz de usuario con esta entrada.

Por supuesto, introduciendo un hilo secundario complica las cosas así. Quería una inercia en mi desplazamiento, que significaba que quería eventos de manipulación en lugar de eventos de puntero. Esto requirió que utilizar DirectXPage para crear un objeto GestureRecognizer (también en aquel hilo secundario), que genera eventos de manipulación de eventos de puntero.

El anterior programa ParagraphFormattedAlice redibuja la ventana del programa cuando el texto está cargado y cuando la ventana cambia de tamaño. ScrollableAlice también hace eso, y ese rediseño persiste en el subproceso de la interfaz de usuario. ScrollableAlice también redibuja la ventana cuando ManipulationUpdated eventos son despedidos, pero esto ocurre en el hilo secundario creado para el puntero de entrada.

¿Qué pasa si das el texto una buena película con el dedo para continúa desplazamiento con la inercia, y mientras aún es desplazar el texto, cambiar el tamaño de la ventana? Hay una buena posibilidad que superpuestas llamadas DirectX se realizará de dos hilos diferentes al mismo tiempo, y eso es un problema.

Lo que se requiere es una sincronización de subprocesos, y una buena, fácil solución implica la clase critical_section en el espacio de nombres de simultaneidad. En un archivo de encabezado, se declara lo siguiente:

Concurrency::critical_section m_criticalSection;

La clase critical_section contiene una clase incorporada denominada scoped_lock. El siguiente comando crea un objeto denominado bloqueo de tipo scoped_lock llamando al constructor con el objeto critical_section:

critical_section::scoped_lock lock(m_criticalSection);

Este constructor asume la propiedad del objeto m_criticalSection, o ejecución de bloques si el objeto m_criticalSection es propiedad de otro subproceso. Lo bueno de esta clase de scoped_lock es que el destructor libera la propiedad de la m_criticalSection cuando el objeto de bloqueo va fuera de alcance, así que es muy fácil sólo espolvorear un puñado de estos alrededor de varios métodos que podrían llamarse concurrentemente.

Decidí que sería más fácil de implementar esta sección crítica en DirectXPage, que contiene algunas llamadas cruciales para la clase DeviceResources (como UpdateForWindowSizeChanged) que requieren otros subprocesos bloquearse para la duración. Aunque no es una buena idea para bloquear el subproceso de la interfaz de usuario (que pasa cuando se disparan los eventos de puntero), estos bloques son extremadamente cortos.

Por el momento la información de desplazamiento es entregada a los Scrollable­AliceRenderer clase, es en forma de un valor de punto flotante como una variable denominada m_scrollOffset, sujeta entre 0 y un valor máximo igual a la diferencia entre la altura del capítulo completo del texto y la altura de la ventana. El método Render utiliza este valor para determinar cómo comenzar y terminar la visualización de los párrafos, como se muestra en la figura 5.

Figura 5 implementación de desplazamiento en 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;
}

Desplazamiento con un rebote

Aunque implementa el programa de ScrollableAlice de desplazamiento tiene inercia táctil, no tiene el característica de Windows 8 rebote cuando intenta desplazarse más allá de la parte superior o inferior. Que (por ahora) rebote familiar se incorpora a ScrollViewer, y mientras que puede ser divertido para tratar de duplicar el rebote ScrollViewer en su propio código, no es el trabajo de hoy.

Porque texto desplazable puede ser largo, es mejor no intentar hacerla en una superficie o de mapa de bits. DirectX tiene restricciones sobre cuán grande pueden ser estas superficies. El programa ScrollableAlice tiene alrededor de estas restricciones limitando su exhibición a un SwapChainPanel el tamaño de la ventana del programa, y que funciona muy bien. Pero para que ScrollViewer trabajar, el contenido debe tener un tamaño en diseño que refleja la altura completa del texto formateado.

Afortunadamente, Windows 8 soporta un elemento que hace exactamente lo que se necesita. La clase VirtualSurfaceImageSource se deriva de SurfaceImageSource, que a su vez se deriva de ImageSource así puede servir como una fuente de mapa de bits para un elemento de la imagen en un ScrollViewer. VirtualSurfaceImageSource puede ser cualquier tamaño deseado (y puede redimensionarse sin volver a ser creado) y se pone alrededor de DirectX limitaciones de tamaño de virtualizar la superficie y aplicación de dibujo bajo demanda. (Sin embargo, SurfaceImageSource y VirtualSurfaceImageSource no son óptimos para animaciones basadas en marco de alto rendimiento.)

VirtualSurfaceImageSource es una clase de referencia de tiempo de ejecución de Windows. Para usarlo en conjunción con DirectX, se debe convertir a un objeto de tipo IVirtualSurfaceImageSourceNative, que revela los métodos utilizados para implementar dibujo bajo demanda. Estos métodos informan qué áreas rectangulares para actualizarse y permitir que el programa para ser notificado de la nueva actualización de rectángulos mediante el suministro de una clase que implementa IVirtualSurfaceUpdatesCallbackNative.

El proyecto de demostración de esta técnica es BounceScrollableAlice, y porque no requirió de un SwapChainPanel, he creado en la vista previa de Visual Studio 2013 basado en la plantilla en blanco de la aplicación (XAML). Para la clase requiere que implementa IVirtualSurface­UpdatesCallbackNative he creado una clase denominada VirtualSurface­ImageSourceRenderer y también proporciona gran parte de la sobrecarga de DirectX. La clase AliceVsisRenderer se deriva de VirtualSurface­ImageSourceRenderer para proporcionar el dibujo de Alice-específica.

Los rectángulos de actualización disponibles de IVirtualSurfaceImageSourceNative están en relación al tamaño completo de la VirtualSurfaceImageSource, pero dibujo coordenadas son relativas a los rectángulos de actualización. Esto significa que las llamadas a DrawTextLayout en BounceScrollableAlice son prácticamente las mismas que se muestra en la figura 5, excepto el origen inicial se establece en la negativa de la parte superior del rectángulo actualización más el desplazamiento de desplazamiento, y el valor de outputHeight es la diferencia entre la parte inferior y superior del rectángulo actualización.

Introduciendo ScrollViewer en la mezcla, la pantalla de texto siente verdaderamente Windows 8-like y usted puede incluso utilizar un gesto de pellizcar para hacerla más grande o más pequeño, subrayando aún más que la fusión de este Windows 8 de DirectX y XAML ofrece algo parecido a lo mejor de ambos mundos.

Charles Petzold es desde hace mucho tiempo colaborador de MSDN Magazine y el autor de "Programación Windows, 6ª edición" (Microsoft Press, 2012), un libro acerca de cómo escribir aplicaciones para Windows 8. Su sitio Web es charlespetzold.com.

Gracias a los siguientes expertos técnicos por su ayuda en la revisión de este artículo: Jim Galasyn (Microsoft) y Justin Panian (Microsoft)