Поделиться через


Создание простого приложения Direct2D

В этом разделе рассматривается процесс создания класса DemoApp, который создает окно и использует Direct2D для рисования содержимого. В этом руководстве вы узнаете, как создать ресурсы Direct2D и нарисовать основные фигуры. Вы также узнаете, как структурировать приложение для повышения производительности путем минимизации создания ресурсов.

Чтобы следовать руководству, вы можете использовать Microsoft Visual Studio для создания проекта Win32, а затем заменить код в заголовке основного приложения и .cpp файл с кодом, описанным в этом руководстве.

Также см. пример приложения Simple Direct2D наGitHub.

Заметка

Если вы хотите создать приложение для универсальной платформы Windows (UWP) с использованием Direct2D, обратитесь к разделу быстрого начала работы с Direct2D для Windows 8.

Общие сведения об интерфейсах, которые можно использовать для создания содержимого Direct2D, см. в обзоре API Direct2D.

По завершении учебника класс 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. Этот метод создает ресурсы, зависящие от устройств окна, целевой объект отрисовки и две кисти. Получите размер клиентской области и создайте ID2D1HwndRenderTarget того же размера, который отображается в HWND окна. Сохраните целевой объект отрисовки в элементе класса 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::DiscardDeviceResources. В этом методе выпустите целевой объект отрисовки и две кисти, созданные в методе 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_PAINT и WM_DISPLAYCHANGE вызовите метод DemoApp::OnRender для рисования окна. Вы реализуете методы OnRender и OnResize в следующих шагах.

    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. Этот метод вызывается каждый раз, когда окно перерисовывается. Помните, что на шаге 4 части 3 вы добавили инструкцию 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, чтобы указать, были ли операции рисования успешными. Закройте область инструкции if, начатой на шаге 3.

        hr = m_pRenderTarget->EndDraw();
    }
    
  11. Проверьте возвращённый HRESULT с помощью EndDraw. Если указано, что целевой объект отрисовки необходимо создать заново, вызовите метод DemoApp::DiscardDeviceResources, чтобы освободить его; он будет повторно создан при следующем получении сообщения WM_PAINT или WM_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. Вы можете найти d2d1.h и d2d1.lib в Windows SDK .

Сводка

В этом руководстве вы узнали, как создать ресурсы Direct2D и нарисовать основные фигуры. Вы также узнали, как структурировать приложение для повышения производительности путем минимизации создания ресурсов.