Creación de aplicación Direct2D simpl
Este tema le guiará por el proceso de creación de la clase DemoApp , que crea una ventana y usa Direct2D para dibujar contenido. En este tutorial, aprenderá a crear recursos de Direct2D y a dibujar formas básicas. También aprenderá a estructurar la aplicación para mejorar el rendimiento al minimizar la creación de recursos.
Para seguir el tutorial, puede usar Microsoft Visual Studio para crear un proyecto de Win32 y, a continuación, reemplazar el código en el encabezado de aplicación principal y .cpp
el archivo por el código descrito en este tutorial.
Consulte también la aplicación de ejemplo de aplicación Simple Direct2D en GitHub.
Nota
Si quieres crear una aplicación de Plataforma universal de Windows (UWP) que usa Direct2D, consulta el inicio rápido de Direct2D para Windows 8 tema.
Para obtener información general sobre las interfaces que puedes usar para crear contenido de Direct2D, consulta la introducción a la API de Direct2D.
Una vez completado el tutorial, la clase DemoApp genera el resultado que se muestra en la ilustración siguiente.
Parte 1: Crear el encabezado DemoApp
En este paso, configurará la aplicación para usar Direct2D agregando los encabezados y macros necesarios. También declara los métodos y miembros de datos que usará en partes posteriores de este tutorial.
En el archivo de encabezado de la aplicación, incluya los siguientes encabezados usados con frecuencia.
// Windows Header Files: #include <windows.h> // C RunTime Header Files: #include <stdlib.h> #include <malloc.h> #include <memory.h> #include <wchar.h> #include <math.h> #include <d2d1.h> #include <d2d1helper.h> #include <dwrite.h> #include <wincodec.h>
Declare funciones adicionales para liberar interfaces y macros para controlar errores y recuperar la dirección base del módulo.
template<class Interface> inline void SafeRelease( Interface **ppInterfaceToRelease) { if (*ppInterfaceToRelease != NULL) { (*ppInterfaceToRelease)->Release(); (*ppInterfaceToRelease) = NULL; } } #ifndef Assert #if defined( DEBUG ) || defined( _DEBUG ) #define Assert(b) do {if (!(b)) {OutputDebugStringA("Assert: " #b "\n");}} while(0) #else #define Assert(b) #endif //DEBUG || _DEBUG #endif #ifndef HINST_THISCOMPONENT EXTERN_C IMAGE_DOS_HEADER __ImageBase; #define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase) #endif
Declare métodos para inicializar la clase, crear y descartar recursos, controlar el bucle de mensajes, representar contenido y el procedimiento de Windows.
class DemoApp { public: DemoApp(); ~DemoApp(); // Register the window class and call methods for instantiating drawing resources HRESULT Initialize(); // Process and dispatch messages void RunMessageLoop(); private: // Initialize device-independent resources. HRESULT CreateDeviceIndependentResources(); // Initialize device-dependent resources. HRESULT CreateDeviceResources(); // Release device-dependent resource. void DiscardDeviceResources(); // Draw content. HRESULT OnRender(); // Resize the render target. void OnResize( UINT width, UINT height ); // The windows procedure. static LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ); };
Como miembros de clase, declare punteros para un objeto ID2D1Factory , un objeto ID2D1HwndRenderTarget y dos objetos ID2D1SolidColorBrush .
private: HWND m_hwnd; ID2D1Factory* m_pDirect2dFactory; ID2D1HwndRenderTarget* m_pRenderTarget; ID2D1SolidColorBrush* m_pLightSlateGrayBrush; ID2D1SolidColorBrush* m_pCornflowerBlueBrush;
Parte 2: Implementación de la infraestructura de clases
En esta parte, implementará el constructor y el destructor DemoApp , sus métodos de inicialización y bucle de mensajes, y la función WinMain . La mayoría de estos métodos tienen el mismo aspecto que los que se encuentran en cualquier otra aplicación Win32. La única excepción es el método Initialize , que llama al método CreateDeviceIndependentResources (que definirá en la siguiente parte), que crea varios recursos de Direct2D.
En el archivo de implementación de clase, implemente el constructor de clase y el destructor. El constructor debe inicializar sus miembros en
NULL
. El destructor debe liberar las interfaces que se almacenan como miembros de clase.DemoApp::DemoApp() : m_hwnd(NULL), m_pDirect2dFactory(NULL), m_pRenderTarget(NULL), m_pLightSlateGrayBrush(NULL), m_pCornflowerBlueBrush(NULL) {} DemoApp::~DemoApp() { SafeRelease(&m_pDirect2dFactory); SafeRelease(&m_pRenderTarget); SafeRelease(&m_pLightSlateGrayBrush); SafeRelease(&m_pCornflowerBlueBrush); }
Implemente el método DemoApp::RunMessageLoop , que traduce y envía mensajes.
void DemoApp::RunMessageLoop() { MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } }
Implemente el método Initialize , que crea la ventana, lo muestra y llama al método DemoApp::CreateDeviceIndependentResources . Implementará el método CreateDeviceIndependentResources en la sección siguiente.
HRESULT DemoApp::Initialize() { HRESULT hr; // Initialize device-independent resources, such // as the Direct2D factory. hr = CreateDeviceIndependentResources(); if (SUCCEEDED(hr)) { // Register the window class. WNDCLASSEX wcex = { sizeof(WNDCLASSEX) }; wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = DemoApp::WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = sizeof(LONG_PTR); wcex.hInstance = HINST_THISCOMPONENT; wcex.hbrBackground = NULL; wcex.lpszMenuName = NULL; wcex.hCursor = LoadCursor(NULL, IDI_APPLICATION); wcex.lpszClassName = L"D2DDemoApp"; RegisterClassEx(&wcex); // In terms of using the correct DPI, to create a window at a specific size // like this, the procedure is to first create the window hidden. Then we get // the actual DPI from the HWND (which will be assigned by whichever monitor // the window is created on). Then we use SetWindowPos to resize it to the // correct DPI-scaled size, then we use ShowWindow to show it. m_hwnd = CreateWindow( L"D2DDemoApp", L"Direct2D demo application", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, NULL, NULL, HINST_THISCOMPONENT, this); if (m_hwnd) { // Because the SetWindowPos function takes its size in pixels, we // obtain the window's DPI, and use it to scale the window size. float dpi = GetDpiForWindow(m_hwnd); SetWindowPos( m_hwnd, NULL, NULL, NULL, static_cast<int>(ceil(640.f * dpi / 96.f)), static_cast<int>(ceil(480.f * dpi / 96.f)), SWP_NOMOVE); ShowWindow(m_hwnd, SW_SHOWNORMAL); UpdateWindow(m_hwnd); } } return hr; }
Implemente el método WinMain , que actúa como punto de entrada de la aplicación. Inicialice una instancia de DemoApp, clase y comience su bucle de mensajes.
int WINAPI WinMain( HINSTANCE /* hInstance */, HINSTANCE /* hPrevInstance */, LPSTR /* lpCmdLine */, int /* nCmdShow */ ) { // Use HeapSetInformation to specify that the process should // terminate if the heap manager detects an error in any heap used // by the process. // The return value is ignored, because we want to continue running in the // unlikely event that HeapSetInformation fails. HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0); if (SUCCEEDED(CoInitialize(NULL))) { { DemoApp app; if (SUCCEEDED(app.Initialize())) { app.RunMessageLoop(); } } CoUninitialize(); } return 0; }
Parte 3: Crear recursos de Direct2D
En esta parte, creará los recursos de Direct2D que se usan para dibujar. Direct2D proporciona dos tipos de recursos: recursos independientes del dispositivo que pueden durar durante la aplicación y recursos dependientes del dispositivo. Los recursos dependientes del dispositivo están asociados a un dispositivo de representación determinado y dejará de funcionar si se quita ese dispositivo.
Implemente el método DemoApp::CreateDeviceIndependentResources . En el método , cree un id2D1Factory, que es un recurso independiente del dispositivo para crear otros recursos de Direct2D. Use el miembro de
m_pDirect2DdFactory
clase para almacenar el generador.HRESULT DemoApp::CreateDeviceIndependentResources() { HRESULT hr = S_OK; // Create a Direct2D factory. hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pDirect2dFactory); return hr; }
Implemente el método DemoApp::CreateDeviceResources . Este método crea los recursos dependientes del dispositivo de la ventana, un destino de representación y dos pinceles. Recupere el tamaño del área de cliente y cree un ID2D1HwndRenderTarget del mismo tamaño que se representa en el HWND de la ventana. Almacene el destino de representación en el miembro de
m_pRenderTarget
clase.RECT rc; GetClientRect(m_hwnd, &rc); D2D1_SIZE_U size = D2D1::SizeU( rc.right - rc.left, rc.bottom - rc.top); // Create a Direct2D render target. hr = m_pDirect2dFactory->CreateHwndRenderTarget( D2D1::RenderTargetProperties(), D2D1::HwndRenderTargetProperties(m_hwnd, size), &m_pRenderTarget);
Use el destino de representación para crear un ID2D1SolidColorBrush gris y un id2D1SolidColorBrush azul de maíz.
if (SUCCEEDED(hr)) { // Create a gray brush. hr = m_pRenderTarget->CreateSolidColorBrush( D2D1::ColorF(D2D1::ColorF::LightSlateGray), &m_pLightSlateGrayBrush ); } if (SUCCEEDED(hr)) { // Create a blue brush. hr = m_pRenderTarget->CreateSolidColorBrush( D2D1::ColorF(D2D1::ColorF::CornflowerBlue), &m_pCornflowerBlueBrush ); }
Dado que se llamará repetidamente a este método, agregue una
if
instrucción para comprobar si el destino de representación (m_pRenderTarget
) ya existe. El código siguiente muestra el método CreateDeviceResources completo.HRESULT DemoApp::CreateDeviceResources() { HRESULT hr = S_OK; if (!m_pRenderTarget) { RECT rc; GetClientRect(m_hwnd, &rc); D2D1_SIZE_U size = D2D1::SizeU( rc.right - rc.left, rc.bottom - rc.top ); // Create a Direct2D render target. hr = m_pDirect2dFactory->CreateHwndRenderTarget( D2D1::RenderTargetProperties(), D2D1::HwndRenderTargetProperties(m_hwnd, size), &m_pRenderTarget ); if (SUCCEEDED(hr)) { // Create a gray brush. hr = m_pRenderTarget->CreateSolidColorBrush( D2D1::ColorF(D2D1::ColorF::LightSlateGray), &m_pLightSlateGrayBrush ); } if (SUCCEEDED(hr)) { // Create a blue brush. hr = m_pRenderTarget->CreateSolidColorBrush( D2D1::ColorF(D2D1::ColorF::CornflowerBlue), &m_pCornflowerBlueBrush ); } } return hr; }
Implemente el método DemoApp::D iscardDeviceResources . En este método, libere el destino de representación y los dos pinceles que creó en el método DemoApp::CreateDeviceResources .
void DemoApp::DiscardDeviceResources() { SafeRelease(&m_pRenderTarget); SafeRelease(&m_pLightSlateGrayBrush); SafeRelease(&m_pCornflowerBlueBrush); }
Parte 4: Representación del contenido de Direct2D
En esta parte, se implementa el procedimiento windows, el método OnRender (que pinta contenido) y el método OnResize (que ajusta el tamaño del destino de representación cuando se cambia el tamaño de la ventana).
Implemente el método DemoApp::WndProc para controlar los mensajes de la ventana. Para el mensaje WM_SIZE , llame al método DemoApp::OnResize y páselo el nuevo ancho y alto. Para los mensajes WM_PAINT y WM_DISPLAYCHANGE , llame al método DemoApp::OnRender para pintar la ventana. Implementará los métodos OnRender y OnResize en los pasos siguientes.
LRESULT CALLBACK DemoApp::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { LRESULT result = 0; if (message == WM_CREATE) { LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam; DemoApp *pDemoApp = (DemoApp *)pcs->lpCreateParams; ::SetWindowLongPtrW( hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pDemoApp) ); result = 1; } else { DemoApp *pDemoApp = reinterpret_cast<DemoApp *>(static_cast<LONG_PTR>( ::GetWindowLongPtrW( hwnd, GWLP_USERDATA ))); bool wasHandled = false; if (pDemoApp) { switch (message) { case WM_SIZE: { UINT width = LOWORD(lParam); UINT height = HIWORD(lParam); pDemoApp->OnResize(width, height); } result = 0; wasHandled = true; break; case WM_DISPLAYCHANGE: { InvalidateRect(hwnd, NULL, FALSE); } result = 0; wasHandled = true; break; case WM_PAINT: { pDemoApp->OnRender(); ValidateRect(hwnd, NULL); } result = 0; wasHandled = true; break; case WM_DESTROY: { PostQuitMessage(0); } result = 1; wasHandled = true; break; } } if (!wasHandled) { result = DefWindowProc(hwnd, message, wParam, lParam); } } return result; }
Implemente el método DemoApp::OnRender . En primer lugar, defina un HRESULT. A continuación, llame al método CreateDeviceResource . Se llama a ese método cada vez que se pinta la ventana. Recuerde que, en el paso 4 de la parte 3, ha agregado una
if
instrucción para evitar que el método realice cualquier trabajo si el destino de representación ya existe.HRESULT DemoApp::OnRender() { HRESULT hr = S_OK; hr = CreateDeviceResources();
Compruebe que el método CreateDeviceResource se realizó correctamente. Si no es así, no realice ningún dibujo.
if (SUCCEEDED(hr)) {
Dentro de la
if
instrucción que acaba de agregar, inicie el dibujo llamando al método BeginDraw del destino de representación. Establezca la transformación del destino de representación en la matriz de identidad y borre la ventana.m_pRenderTarget->BeginDraw(); m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity()); m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
Recupere el tamaño del área de dibujo.
D2D1_SIZE_F rtSize = m_pRenderTarget->GetSize();
Dibuje un fondo de cuadrícula mediante un
for
bucle y el método DrawLine del destino de representación para dibujar una serie de líneas.// Draw a grid background. int width = static_cast<int>(rtSize.width); int height = static_cast<int>(rtSize.height); for (int x = 0; x < width; x += 10) { m_pRenderTarget->DrawLine( D2D1::Point2F(static_cast<FLOAT>(x), 0.0f), D2D1::Point2F(static_cast<FLOAT>(x), rtSize.height), m_pLightSlateGrayBrush, 0.5f ); } for (int y = 0; y < height; y += 10) { m_pRenderTarget->DrawLine( D2D1::Point2F(0.0f, static_cast<FLOAT>(y)), D2D1::Point2F(rtSize.width, static_cast<FLOAT>(y)), m_pLightSlateGrayBrush, 0.5f ); }
Cree dos primitivos de rectángulo centrados en la pantalla.
// Draw two rectangles. D2D1_RECT_F rectangle1 = D2D1::RectF( rtSize.width/2 - 50.0f, rtSize.height/2 - 50.0f, rtSize.width/2 + 50.0f, rtSize.height/2 + 50.0f ); D2D1_RECT_F rectangle2 = D2D1::RectF( rtSize.width/2 - 100.0f, rtSize.height/2 - 100.0f, rtSize.width/2 + 100.0f, rtSize.height/2 + 100.0f );
Utilice el método FillRectangle del destino de representación para pintar el interior del primer rectángulo con el pincel gris.
// Draw a filled rectangle. m_pRenderTarget->FillRectangle(&rectangle1, m_pLightSlateGrayBrush);
Use el método DrawRectangle del destino de representación para pintar el contorno del segundo rectángulo con el pincel azul cornflower.
// Draw the outline of a rectangle. m_pRenderTarget->DrawRectangle(&rectangle2, m_pCornflowerBlueBrush);
Llame al método EndDraw del destino de representación. El método EndDraw devuelve un VALOR HRESULT para indicar si las operaciones de dibujo se realizaron correctamente. Cierre el ámbito de la
if
instrucción que inició en el paso 3.hr = m_pRenderTarget->EndDraw(); }
Compruebe el VALOR HRESULT devuelto por EndDraw. Si indica que es necesario volver a crear el destino de representación, llame al método DemoApp::D iscardDeviceResources para liberarlo; se volverá a crear la próxima vez que la ventana reciba un mensaje WM_PAINT o WM_DISPLAYCHANGE .
if (hr == D2DERR_RECREATE_TARGET) { hr = S_OK; DiscardDeviceResources(); }
Devuelve hrESULT y cierra el ámbito del método.
return hr; }
Implemente el método DemoApp::OnResize para que cambie el tamaño del destino de representación al nuevo tamaño de la ventana.
void DemoApp::OnResize(UINT width, UINT height) { if (m_pRenderTarget) { // Note: This method can fail, but it's okay to ignore the // error here, because the error will be returned again // the next time EndDraw is called. m_pRenderTarget->Resize(D2D1::SizeU(width, height)); } }
Ya ha completado el tutorial.
Nota
Para usar Direct2D, asegúrese de que la aplicación incluye el d2d1.h
archivo de encabezado y se compila en la d2d1.lib
biblioteca. Puede encontrar d2d1.h
y d2d1.lib
en Windows SDK.
Resumen
En este tutorial, ha aprendido a crear recursos de Direct2D y a dibujar formas básicas. También ha aprendido a estructurar la aplicación para mejorar el rendimiento al minimizar la creación de recursos.