创建简单的 Direct2D 应用程序

本主题将引导你完成创建 DemoApp 类的过程,该类将创建一个窗口,并使用 Direct2D 来绘制内容。 本教程介绍如何创建 Direct2D 资源和绘制基本形状。 你还将了解如何构建应用程序,以通过最大限度地减少资源创建来增强性能。

若要遵循本教程,可以使用 Microsoft Visual Studio 创建 Win32 项目,然后将 main 应用程序标头和.cpp文件中的代码替换为本教程中所述的代码。

另请参阅 GitHub 上的简单 Direct2D 应用程序示例应用

注意

如果要创建使用 Direct2D 的通用 Windows 平台 (UWP) 应用,请参阅 Direct2D 快速入门,了解Windows 8主题。

有关可用于创建 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. 声明用于初始化 类、创建和丢弃资源、处理消息循环、呈现内容和 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
            );
    };
    
  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 内容

在本部分中,你将实现 windows 过程、 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 资源和绘制基本形状。 你还了解了如何构建应用程序,以通过尽量减少资源创建来提高性能。