Skapa ett enkelt Direct2D-program
Det här avsnittet beskriver hur du skapar klassen DemoApp, som skapar ett fönster och använder Direct2D för att rita innehåll. I den här självstudien får du lära dig hur du skapar Direct2D-resurser och ritar grundläggande former. Du lär dig också hur du strukturerar ditt program för att förbättra prestanda genom att minimera resursskapandet.
Om du vill följa självstudien kan du använda Microsoft Visual Studio för att skapa ett Win32-projekt och sedan ersätta koden i huvudprogrammets huvudrubrik och .cpp
fil med koden som beskrivs i den här självstudien.
Se även exempelappen för Simple Direct2D-program på GitHub.
Notera
Om du vill skapa en UWP-app (Universal Windows Platform) som använder Direct2D kan du läsa snabbstarten Direct2D för Windows 8.
En översikt över de gränssnitt som du kan använda för att skapa Direct2D-innehåll finns i Direct2D API-översikt.
När handledningen har slutförts, kommer klassen DemoApp att generera resultatet som visas i bilden nedan.
Del 1: Skapa DemoApp-rubriken
I det här steget konfigurerar du programmet så att det använder Direct2D genom att lägga till nödvändiga rubriker och makron. Du definierar även de metoder och datamedlemmar som du ska använda i senare delar av den här guiden.
I programhuvudfilen innehåller du följande rubriker som används ofta.
// 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>
Deklarera ytterligare funktioner för att frigöra gränssnitt och makron för felhantering och hämtning av modulens basadress.
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
Deklarera metoder för att initiera klassen, skapa och ta bort resurser, hantera meddelandeloopen, återge innehåll och windows-proceduren.
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 ); };
Som klassmedlemmar ska du deklarera pekare för ett ID2D1Factory--objekt, ett ID2D1HwndRenderTarget--objekt och två ID2D1SolidColorBrush--objekt.
private: HWND m_hwnd; ID2D1Factory* m_pDirect2dFactory; ID2D1HwndRenderTarget* m_pRenderTarget; ID2D1SolidColorBrush* m_pLightSlateGrayBrush; ID2D1SolidColorBrush* m_pCornflowerBlueBrush;
Del 2: Implementering av klassens infrastruktur
I den här delen implementerar du konstruktorn och destruktorn för DemoApp, dess initierings- och meddelandeloopmetoder, samt funktionen WinMain. De flesta av dessa metoder ser likadana ut som de som finns i andra Win32-program. Det enda undantaget är metoden Initialize, som anropar metoden CreateDeviceIndependentResources (som du definierar i nästa del), som skapar flera Direct2D-resurser.
I klassimplementeringsfilen implementerar du klasskonstruktorn och destruktören. Konstruktorn bör initiera sina medlemmar till
NULL
. Destructor bör släppa alla gränssnitt som lagras som klassmedlemmar.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); }
Implementera metoden DemoApp::RunMessageLoop som översätter och skickar meddelanden.
void DemoApp::RunMessageLoop() { MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } }
Implementera metoden Initialize, som skapar fönstret, visar den och anropar metoden DemoApp::CreateDeviceIndependentResources. Du implementerar metoden CreateDeviceIndependentResources i nästa avsnitt.
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; }
Implementera metoden WinMain, som fungerar som startpunkt för programmet. Initiera en instans av klassen DemoApp, och starta dess meddelandeloop.
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; }
Del 3: Skapa Direct2D-resurser
I den här delen skapar du de Direct2D-resurser som du använder för att rita. Direct2D innehåller två typer av resurser – enhetsoberoende resurser som kan pågå under programmets varaktighet och enhetsberoende resurser. Enhetsberoende resurser är associerade med en viss återgivningsenhet och upphör att fungera om enheten tas bort.
Implementera metoden DemoApp::CreateDeviceIndependentResources. I metoden skapar du en ID2D1Factory, som är en enhetsoberoende resurs för att skapa andra Direct2D-resurser. Använd
m_pDirect2DdFactory
-klassmedlemsvariabeln för att lagra fabriken.HRESULT DemoApp::CreateDeviceIndependentResources() { HRESULT hr = S_OK; // Create a Direct2D factory. hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pDirect2dFactory); return hr; }
Implementera metoden DemoApp::CreateDeviceResources. Den här metoden skapar fönstrets enhetsberoende resurser, ett återgivningsmål och två penslar. Hämta storleken på klientområdet och skapa en ID2D1HwndRenderTarget av samma storlek som återges i fönstrets HWND. Lagra återgivningsmålet i
m_pRenderTarget
-klassmedlemmen.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);
Använd återgivningsmålet för att skapa en grå ID2D1SolidColorBrush och en blå majsblomma ID2D1SolidColorBrush.
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 ); }
Eftersom den här metoden anropas upprepade gånger lägger du till en
if
-instruktion för att kontrollera om återgivningsmålet (m_pRenderTarget
) redan finns. Följande kod visar den fullständiga metoden 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; }
Implementera metoden DemoApp::D iscardDeviceResources. I den här metoden släpper du återgivningsmålet och de två penslar som du skapade i metoden DemoApp::CreateDeviceResources.
void DemoApp::DiscardDeviceResources() { SafeRelease(&m_pRenderTarget); SafeRelease(&m_pLightSlateGrayBrush); SafeRelease(&m_pCornflowerBlueBrush); }
Del 4: Rendera Direct2D-innehåll
I den här delen implementerar du windows-proceduren, metoden OnRender (som målar innehåll) och metoden OnResize (som justerar storleken på återgivningsmålet när fönstret storleksändras).
Implementera metoden DemoApp::WndProc för att hantera fönstermeddelanden. För WM_SIZE-meddelandet anropar du metoden DemoApp::OnResize och skickar den den nya bredden och höjden. För WM_PAINT och WM_DISPLAYCHANGE meddelanden anropar du metoden DemoApp::OnRender för att måla fönstret. Du implementerar metoderna OnRender och OnResize i stegen nedan.
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; }
Implementera metoden DemoApp::OnRender. Definiera först en HRESULT-. Anropa därefter metoden CreateDeviceResource. Den metoden anropas varje gång fönstret målas. Kom ihåg att du i steg 4 i del 3 lade till en
if
-instruktion för att förhindra att metoden utför något arbete om återgivningsmålet redan finns.HRESULT DemoApp::OnRender() { HRESULT hr = S_OK; hr = CreateDeviceResources();
Kontrollera att metoden CreateDeviceResource lyckades. Om det inte stämde, utför då ingen ritning.
if (SUCCEEDED(hr)) {
I
if
-instruktionen som du precis lade till initierar du ritningen genom att anropa återgivningsmålets BeginDraw--metod. Ange renderingsmålets transformation till identitetsmatrisen och rensa fönstret.m_pRenderTarget->BeginDraw(); m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity()); m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
Hämta ritytans storlek.
D2D1_SIZE_F rtSize = m_pRenderTarget->GetSize();
Rita en rutnätsbakgrund med hjälp av en
for
-loop och återgivningsmålets metod DrawLine för att rita en serie linjer.// 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 ); }
Skapa två rektangelprimitanter som är centrerade på skärmen.
// 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 );
Använd återgivningsmålets FillRectangle metod för att måla det inre av den första rektangeln med den grå penseln.
// Draw a filled rectangle. m_pRenderTarget->FillRectangle(&rectangle1, m_pLightSlateGrayBrush);
Använd återgivningsmålets DrawRectangle--metoden för att måla konturen av den andra rektangeln med den kornblåa penseln.
// Draw the outline of a rectangle. m_pRenderTarget->DrawRectangle(&rectangle2, m_pCornflowerBlueBrush);
Anropa återgivningsmålets EndDraw--metod. Metoden EndDraw returnerar en HRESULT- som anger om ritningsåtgärderna lyckades. Stäng omfånget för den
if
-instruktion som du påbörjade i steg 3.hr = m_pRenderTarget->EndDraw(); }
Kontrollera HRESULT- som returneras av EndDraw. Om det anger att återgivningsmålet måste återskapas, anropar du metoden DemoApp::DiscardDeviceResources för att frigöra det; det kommer återskapas nästa gång applikationsfönstret tar emot ett WM_PAINT eller WM_DISPLAYCHANGE meddelande.
if (hr == D2DERR_RECREATE_TARGET) { hr = S_OK; DiscardDeviceResources(); }
Returnera HRESULT-och stäng metodens omfång.
return hr; }
Implementera metoden DemoApp::OnResize så att det ändrar storlek på återgivningsmålet till fönstrets nya storlek.
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)); } }
Nu har du slutfört självstudien.
Obs
Om du vill använda Direct2D kontrollerar du att programmet innehåller d2d1.h
-huvudfilen och kompilerar mot d2d1.lib
-biblioteket. Du hittar d2d1.h
och d2d1.lib
i Windows SDK.
Sammanfattning
I den här självstudien har du lärt dig hur du skapar Direct2D-resurser och ritar grundläggande former. Du har också lärt dig hur du strukturerar ditt program för att förbättra prestandan genom att minimera resursskapandet.