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

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

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

См. также пример приложения Простой 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. Объявите методы для инициализации класса, создания и отмены ресурсов, обработки цикла сообщений, отрисовки содержимого и процедуры 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-indpendent 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::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_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::D iscardDeviceResources , чтобы освободить его; он будет повторно создан при следующем получении окна 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 и рисовать основные фигуры. Вы также узнали, как структурировать приложение для повышения производительности путем минимизации создания ресурсов.