建立簡單的 Direct2D 應用程式

本主題將逐步引導您完成建立 DemoApp 類別的程式,此類別會建立視窗,並使用 Direct2D 來繪製內容。 在本教學課程中,您將瞭解如何建立 Direct2D 資源,以及繪製基本圖形。 您也會瞭解如何將資源建立降至最低,以建構應用程式以增強效能。

若要遵循本教學課程,您可以使用 Microsoft Visual Studio 來建立 Win32 專案,然後將主要應用程式標頭和 .cpp 檔案中的程式碼取代為本教學課程中所述的程式碼。

另請參閱 GitHub 上的簡單 Direct2D 應用程式範例應用程式

注意

如果您想要建立使用 Direct2D 的 通用 Windows 平臺 (UWP) app,請參閱適用于 Windows 8 的 Direct2D 快速入門主題。

如需可用來建立 Direct2D 內容的介面概觀,請參閱 Direct2D API 概觀

完成本教學課程之後, DemoApp 類別會產生下圖所示的輸出。

格線背景上兩個矩形的圖例

第 1 部分:建立 DemoApp 標頭

在此步驟中,您會新增必要的標頭和宏,將應用程式設定為使用 Direct2D。 您也會宣告您將在本教學課程稍後部分使用的方法和資料成員。

  1. 在應用程式標頭檔中,包含下列常用標頭。

    // 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>
    
  2. 宣告發行介面的其他函式,以及用於錯誤處理和擷取模組基底位址的宏。

    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
    
  3. 宣告方法以初始化 類別、建立和捨棄資源、處理訊息迴圈、轉譯內容和視窗程式。

    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
            );
    };
    
  4. 做為類別成員,宣告 ID2D1Factory 物件的指標、 ID2D1HwndRenderTarget 物件,以及兩個 ID2D1SolidColorBrush 物件。

    private:
    HWND m_hwnd;
    ID2D1Factory* m_pDirect2dFactory;
    ID2D1HwndRenderTarget* m_pRenderTarget;
    ID2D1SolidColorBrush* m_pLightSlateGrayBrush;
    ID2D1SolidColorBrush* m_pCornflowerBlueBrush;
    

第 2 部分:實作類別基礎結構

在此部分中,您會實作 DemoApp 建構函式和解構函式、其初始化和訊息迴圈方法,以及 WinMain 函式。 這些方法大多看起來與任何其他 Win32 應用程式中找到的方法相同。 唯一的例外是 Initialize 方法,它會呼叫 CreateDeviceIndependentResources 方法 (您將在下一個部分定義) ,這會建立數個 Direct2D 資源。

  1. 在類別實作檔案中,實作類別建構函式和解構函式。 建構函式應該將其成員初始化為 NULL 。 解構函式應該釋放儲存為類別成員的任何介面。

    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);
    }
    
  2. 實作 DemoApp::RunMessageLoop 方法,此方法會轉譯和分派訊息。

    void DemoApp::RunMessageLoop()
    {
        MSG msg;
    
        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    
  3. 實作 Initialize 方法,這個方法會建立視窗、顯示視窗,並呼叫 DemoApp::CreateDeviceIndependentResources 方法。 您將在下一節中實作 CreateDeviceIndependentResources 方法。

    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;
    }
    
  4. 實作 WinMain 方法,做為應用程式進入點。 初始化 DemoApp的實例,類別並開始其訊息迴圈。

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

第 3 部分:建立 Direct2D 資源

在此部分中,您會建立用來繪製的 Direct2D 資源。 Direct2D 提供兩種類型的資源:裝置獨立資源,可在應用程式期間持續,以及裝置相依資源。 裝置相依資源與特定轉譯裝置相關聯,如果移除該裝置,將會停止運作。

  1. 實作 DemoApp::CreateDeviceIndependentResources 方法。 在 方法中,建立 ID2D1Factory,這是建立其他 Direct2D 資源的裝置獨立資源。 m_pDirect2DdFactory使用類別成員來儲存處理站。

    HRESULT DemoApp::CreateDeviceIndependentResources()
    {
        HRESULT hr = S_OK;
    
        // Create a Direct2D factory.
        hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pDirect2dFactory);
    
        return hr;
    }
    
  2. 實作 DemoApp::CreateDeviceResources 方法。 這個方法會建立視窗的裝置相依資源、轉譯目標,以及兩個筆刷。 擷取工作區的大小,並建立與視窗HWND相同大小的ID2D1HwndRenderTarget。 將轉譯目標儲存在類別成員中 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);
    
  3. 使用轉譯目標來建立灰色 ID2D1SolidColorBrush 和一個圓角花藍色 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
            );
    }
    
  4. 因為這個方法會重複呼叫,所以請新增 if 語句,以檢查轉譯目標 (m_pRenderTarget) 是否存在。 下列程式碼顯示完整的 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;
    }
    
  5. 實作 DemoApp::D iscardDeviceResources 方法。 在此方法中,釋放您在 DemoApp::CreateDeviceResources 方法中建立的轉譯目標和兩個筆刷。

    void DemoApp::DiscardDeviceResources()
    {
        SafeRelease(&m_pRenderTarget);
        SafeRelease(&m_pLightSlateGrayBrush);
        SafeRelease(&m_pCornflowerBlueBrush);
    }
    

第 4 部分:轉譯 Direct2D 內容

在此部分中,您會實作視窗程式、 OnRender 方法 (繪製內容) ,以及 OnResize 方法 (,以在視窗調整大小時調整轉譯目標的大小) 。

  1. 實作 DemoApp::WndProc 方法來處理視窗訊息。 針對 WM_SIZE 訊息,呼叫 DemoApp::OnResize 方法,並將新的寬度和高度傳遞。 針對 WM_PAINTWM_DISPLAYCHANGE 訊息,請呼叫 DemoApp::OnRender 方法來繪製視窗。 您將在後續步驟中實作 OnRenderOnResize 方法。

    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;
    }
    
  2. 實作 DemoApp::OnRender 方法。 首先,定義 HRESULT。 然後呼叫 CreateDeviceResource 方法。 每次繪製視窗時都會呼叫該方法。 回想一下,在第 3 部分的步驟 4 中,您已新增 if 語句,以防止方法在轉譯目標已經存在時執行任何工作。

    HRESULT DemoApp::OnRender()
    {
        HRESULT hr = S_OK;
    
        hr = CreateDeviceResources();
    
  3. 確認 CreateDeviceResource 方法成功。 如果未執行,則請勿執行任何繪圖。

    if (SUCCEEDED(hr))
    {
    
  4. 在您剛才新增的 if 語句內,呼叫轉譯目標的 BeginDraw 方法來起始繪圖。 將轉譯目標的轉換設定為識別矩陣,然後清除視窗。

    m_pRenderTarget->BeginDraw();
    m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());
    m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
    
  5. 擷取繪圖區域的大小。

    D2D1_SIZE_F rtSize = m_pRenderTarget->GetSize();
    
  6. 使用 for 迴圈和轉譯目標的 DrawLine 方法來繪製一數列線條,繪製格線背景。

    // 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
            );
    }
    
  7. 建立兩個以螢幕為中心的矩形基本類型。

    // 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
        );
    
  8. 使用轉譯目標的 FillRectangle 方法,以灰色筆刷繪製第一個矩形的內部。

    // Draw a filled rectangle.
    m_pRenderTarget->FillRectangle(&rectangle1, m_pLightSlateGrayBrush);
    
  9. 使用轉譯目標的 DrawRectangle 方法,使用菜花藍色筆刷繪製第二個矩形的外框。

    // Draw the outline of a rectangle.
    m_pRenderTarget->DrawRectangle(&rectangle2, m_pCornflowerBlueBrush);
    
  10. 呼叫轉譯目標的 EndDraw 方法。 EndDraw方法會傳回HRESULT,以指出繪圖作業是否成功。 關閉您在步驟 3 中開始的 if 語句範圍。

        hr = m_pRenderTarget->EndDraw();
    }
    
  11. 檢查EndDraw傳回的HRESULT。 如果它指出必須重新建立轉譯目標,請呼叫 DemoApp::D iscardDeviceResources 方法來釋放它;它會在下一次視窗收到 WM_PAINTWM_DISPLAYCHANGE 訊息時重新建立。

    if (hr == D2DERR_RECREATE_TARGET)
    {
        hr = S_OK;
        DiscardDeviceResources();
    }
    
  12. 傳回 HRESULT,並關閉方法的範圍。

        return hr;
    }
    
  13. 實作 DemoApp::OnResize 方法,以便將轉譯目標調整為視窗的新大小。

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

您現在已完成教學課程。

注意

若要使用 Direct2D,請確定您的應用程式包含 d2d1.h 標頭檔,並針對 d2d1.lib 程式庫進行編譯。 您可以在Windows SDK中找到 d2d1.hd2d1.lib

摘要

在本教學課程中,您已瞭解如何建立 Direct2D 資源,以及繪製基本圖形。 您也瞭解如何建構應用程式,藉由將資源建立降至最低來增強效能。