Criar um aplicativo Direct2D simples
Este tópico orienta você pelo processo de criação da classe DemoApp, que cria uma janela e usa o Direct2D para desenhar conteúdo. Neste tutorial, você aprenderá a criar recursos Direct2D e desenhar formas básicas. Você também aprende a estruturar seu aplicativo para melhorar o desempenho minimizando a criação de recursos.
Para seguir o tutorial, você pode usar o Microsoft Visual Studio para criar um projeto Win32 e, em seguida, substituir o código no cabeçalho do aplicativo principal e .cpp
arquivo com o código descrito neste tutorial.
Consulte também a aplicação de exemplo Direct2D simples no GitHub.
Observação
Se você quiser criar um aplicativo da Plataforma Universal do Windows (UWP) que use Direct2D, consulte o tópico início rápido do Direct2D para Windows 8.
Para obter uma visão geral das interfaces que você pode usar para criar conteúdo Direct2D, consulte a Visão geral da API Direct2D.
Após a conclusão do tutorial, a classe DemoApp produz a saída mostrada na ilustração a seguir.
Parte 1: Criar o cabeçalho do DemoApp
Nesta etapa, você configura seu aplicativo para usar Direct2D adicionando os cabeçalhos e macros necessários. Você também declara os métodos e membros de dados que usará em partes posteriores deste tutorial.
No arquivo de cabeçalho do aplicativo, inclua os seguintes cabeçalhos usados com freqüência.
// 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 funções adicionais para liberar interfaces e macros para tratamento de erros e recuperação do endereço base do 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 a classe, criar e descartar recursos, manipular o loop de mensagens, renderizar conteúdo e o procedimento do 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 elementos da classe, declare ponteiros para um objeto ID2D1Factory, um objeto ID2D1HwndRenderTarget e dois objetos ID2D1SolidColorBrush.
private: HWND m_hwnd; ID2D1Factory* m_pDirect2dFactory; ID2D1HwndRenderTarget* m_pRenderTarget; ID2D1SolidColorBrush* m_pLightSlateGrayBrush; ID2D1SolidColorBrush* m_pCornflowerBlueBrush;
Parte 2: Implementar a infraestrutura de classe
Nesta parte, você implementa o construtor e o destrutor do DemoApp, seus métodos de inicialização e ciclo de mensagens, e a função WinMain. A maioria desses métodos parece o mesmo que aqueles encontrados em qualquer outro aplicativo Win32. A única exceção é o método Initialize, que chama o método CreateDeviceIndependentResources (que será definido na próxima parte), o qual cria vários recursos Direct2D.
No arquivo de implementação de classe, implemente o construtor e o destruidor de classe. O construtor deve inicializar os seus membros com
NULL
. O destrutor deve liberar quaisquer interfaces que estejam armazenadas como membros da classe.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 o método DemoApp::RunMessageLoop, que traduz e despacha mensagens.
void DemoApp::RunMessageLoop() { MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } }
Implemente o método Initialize, que cria a janela, a mostra e chama o DemoApp::CreateDeviceIndependentResources método. Você implementará o método CreateDeviceIndependentResources na próxima seção.
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 o WinMain método, que serve como o ponto de entrada do aplicativo. Inicialize uma instância do DemoAppclasse , e comece seu loop de mensagem.
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: Criar recursos Direct2D
Nesta parte, você cria os recursos Direct2D que usa para desenhar. O Direct2D fornece dois tipos de recursos: recursos independentes do dispositivo que podem durar a duração do aplicativo e recursos dependentes do dispositivo. Os recursos dependentes do dispositivo estão associados a um dispositivo de renderização específico e deixarão de funcionar se esse dispositivo for removido.
Implemente o método DemoApp::CreateDeviceIndependentResources . No método, crie um ID2D1Factory, que é um recurso independente do dispositivo para criar outros recursos Direct2D. Utilize o membro de classe
m_pDirect2DdFactory
para armazenar a fábrica.HRESULT DemoApp::CreateDeviceIndependentResources() { HRESULT hr = S_OK; // Create a Direct2D factory. hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pDirect2dFactory); return hr; }
Implemente o DemoApp::CreateDeviceResources método. Esse método cria os recursos dependentes do dispositivo da janela, um destino de renderização e dois pincéis. Recupere o tamanho da área do cliente e crie um ID2D1HwndRenderTarget do mesmo tamanho que renderiza para o HWND da janela. Armazene o destino de renderização no membro da classe
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);
Use o destino de renderização para criar um ID2D1SolidColorBrush cinza e um ID2D1SolidColorBrush azul de centáurea .
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 ); }
Como esse método será chamado repetidamente, adicione uma instrução
if
para verificar se o destino de renderização (m_pRenderTarget
) já existe. O código a seguir mostra o método completo CreateDeviceResources.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 o método DemoApp::DiscardDeviceResources. Nesse método, libere o destino de renderização e os dois pincéis criados no método DemoApp::CreateDeviceResources.
void DemoApp::DiscardDeviceResources() { SafeRelease(&m_pRenderTarget); SafeRelease(&m_pLightSlateGrayBrush); SafeRelease(&m_pCornflowerBlueBrush); }
Parte 4: Renderizar conteúdo Direct2D
Nesta parte, você implementa o procedimento Windows, o método OnRender (que pinta o conteúdo) e o método OnResize (que ajusta o tamanho do destino de renderização quando a janela é redimensionada).
Implemente o método DemoApp::WndProc para manipular mensagens de janela. Para a mensagem WM_SIZE, chame o método DemoApp::OnResize, e passe-lhe a largura e altura novas. Para as mensagens WM_PAINT e WM_DISPLAYCHANGE, chame o método DemoApp::OnRender para pintar a janela. Você implementará os métodos OnRender e OnResize nas etapas a seguir.
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 o método DemoApp::OnRender. Primeiro, defina um HRESULT. Em seguida, chame o método CreateDeviceResource. Esse método é chamado toda vez que a janela é pintada. Lembre-se de que, na etapa 4 da Parte 3, você adicionou uma instrução
if
para impedir que o método execute qualquer trabalho se o destino de renderização já existir.HRESULT DemoApp::OnRender() { HRESULT hr = S_OK; hr = CreateDeviceResources();
Verifique se o método CreateDeviceResource teve êxito. Se não o fez, então não faça nenhum desenho.
if (SUCCEEDED(hr)) {
Dentro da instrução
if
que acabou de adicionar, inicie o desenho chamando o método BeginDraw do alvo de renderização . Defina a transformação do destino de renderização para a matriz de identidade e limpe a janela.m_pRenderTarget->BeginDraw(); m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity()); m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
Recupere o tamanho da área de desenho.
D2D1_SIZE_F rtSize = m_pRenderTarget->GetSize();
Desenhe um plano de fundo de grade usando um loop de
for
e o método DrawLine do destino de renderização para desenhar uma série de linhas.// 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 ); }
Crie duas primitivas de retângulo centralizadas na tela.
// 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 );
Utilize o métodoFillRectangle do destino de renderizaçãopara pintar o interior do primeiro retângulo com o pincel cinza.
// Draw a filled rectangle. m_pRenderTarget->FillRectangle(&rectangle1, m_pLightSlateGrayBrush);
Utilize o método DrawRectangle do destino de renderização para pintar o contorno do segundo retângulo com o pincel cor azul centáurea.
// Draw the outline of a rectangle. m_pRenderTarget->DrawRectangle(&rectangle2, m_pCornflowerBlueBrush);
Chame o do destino de renderização método EndDraw. O método EndDraw retorna um HRESULT para indicar se as operações de desenho foram bem-sucedidas. Feche o escopo da instrução
if
que você começou na Etapa 3.hr = m_pRenderTarget->EndDraw(); }
Confira o HRESULT devolvido por EndDraw. Se isso indicar que o destino de renderização precisa ser recriado, chame o método DemoApp::DiscardDeviceResources para liberá-lo; ele será recriado na próxima vez que a janela receber uma mensagem WM_PAINT ou WM_DISPLAYCHANGE.
if (hr == D2DERR_RECREATE_TARGET) { hr = S_OK; DiscardDeviceResources(); }
Retorne o HRESULTe feche o escopo do método.
return hr; }
Implemente o método DemoApp::OnResize para que ele redimensione o destino de renderização para o novo tamanho da janela.
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)); } }
Agora você concluiu o tutorial.
Observação
Para usar o Direct2D, verifique se o aplicativo inclui o arquivo de cabeçalho d2d1.h
e compila na biblioteca d2d1.lib
. Você pode encontrar d2d1.h
e d2d1.lib
no Windows SDK.
Resumo
Neste tutorial, você aprendeu como criar recursos Direct2D e desenhar formas básicas. Você também aprendeu como estruturar seu aplicativo para melhorar o desempenho minimizando a criação de recursos.