Udostępnij przez


Tworzenie prostej aplikacji Direct2D

W tym temacie opisano proces tworzenia klasy DemoApp, która tworzy okno i używa funkcji Direct2D do rysowania zawartości. Z tego samouczka dowiesz się, jak tworzyć zasoby Direct2D i rysować podstawowe kształty. Dowiesz się również, jak utworzyć strukturę aplikacji w celu zwiększenia wydajności, minimalizując tworzenie zasobów.

Aby wykonać czynności opisane w samouczku, możesz użyć programu Microsoft Visual Studio do utworzenia projektu Win32, a następnie zastąpić kod w głównym nagłówku aplikacji i .cpp pliku kodem opisanym w tym samouczku.

Zobacz również przykładową aplikację Simple Direct2D w witrynie GitHub.

Notatka

Jeśli chcesz utworzyć aplikację platformy uniwersalnej systemu Windows (UWP), która korzysta z funkcji Direct2D, zobacz przewodnik Szybki start dotyczący Direct2D dla systemu Windows 8 tematu.

Aby zapoznać się z omówieniem interfejsów, których można użyć do tworzenia zawartości Direct2D, zobacz omówienie interfejsu API Direct2D.

Po ukończeniu samouczka klasa DemoApp generuje dane wyjściowe przedstawione na poniższej ilustracji.

ilustracja dwóch prostokątów na tle siatki

Część 1. Tworzenie nagłówka demoapp

W tym kroku skonfigurujesz aplikację tak, aby korzystała z funkcji Direct2D, dodając niezbędne nagłówki i makra. Również zadeklarujesz metody i pola danych, których później użyjesz w kolejnych częściach tego samouczka.

  1. W pliku nagłówka aplikacji dołącz następujące często używane nagłówki.

    // 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. Zadeklaruj dodatkowe funkcje do wydawania interfejsów i makr na potrzeby obsługi błędów i pobierania adresu podstawowego modułu.

    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. Zadeklaruj metody inicjowania klasy, tworzenia i odrzucania zasobów, obsługi pętli komunikatów, renderowania zawartości i procedury systemu 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. Jako elementy członkowskie klasy zadeklaruj wskaźniki dla obiektu ID2D1Factory, dla obiektu ID2D1HwndRenderTarget oraz dla dwóch obiektów ID2D1SolidColorBrush.

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

Część 2. Implementowanie infrastruktury klas

W tej części zaimplementujesz konstruktor i destruktor aplikacji DemoApp, jej metody inicjalizacji i pętli komunikatów, a także funkcję WinMain. Większość tych metod wygląda tak samo jak w przypadku każdej innej aplikacji Win32. Jedynym wyjątkiem jest metoda Initialize, która wywołuje metodę CreateDeviceIndependentResources (która zostanie zdefiniowana w następnej części), która tworzy kilka zasobów Direct2D.

  1. W pliku implementacji klasy zaimplementuj konstruktor klasy i destruktor. Konstruktor powinien zainicjować swoje składowe do wartości NULL. Destruktor powinien zwolnić wszystkie interfejsy przechowywane jako składowe klasy.

    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. Zaimplementuj metodę DemoApp::RunMessageLoop, która tłumaczy i wysyła komunikaty.

    void DemoApp::RunMessageLoop()
    {
        MSG msg;
    
        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    
  3. Zaimplementuj metodę Initialize, która tworzy okno, wyświetla je i wywołuje metodę DemoApp::CreateDeviceIndependentResources. W następnej sekcji zaimplementujesz metodę 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. Zaimplementuj metodę WinMain, która służy jako punkt wejścia aplikacji. Zainicjuj wystąpienie klasy DemoAppi rozpocznij jej pętlę komunikatów.

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

Część 3. Tworzenie zasobów Direct2D

W tej części utworzysz zasoby Direct2D używane do rysowania. Funkcja Direct2D udostępnia dwa typy zasobów — zasoby niezależne od urządzenia, które mogą trwać przez czas trwania aplikacji i zasoby zależne od urządzenia. Zasoby zależne od urządzenia są skojarzone z konkretnym urządzeniem renderowania i przestaną działać, jeśli to urządzenie zostanie usunięte.

  1. Zaimplementuj metodę DemoApp::CreateDeviceIndependentResources. W metodzie utwórz ID2D1Factory, która jest zasobem niezależnym od urządzenia do tworzenia innych zasobów Direct2D. Użyj składowej klasy m_pDirect2DdFactory do przechowywania fabryki.

    HRESULT DemoApp::CreateDeviceIndependentResources()
    {
        HRESULT hr = S_OK;
    
        // Create a Direct2D factory.
        hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pDirect2dFactory);
    
        return hr;
    }
    
  2. Zaimplementuj metodę DemoApp::CreateDeviceResources. Ta metoda tworzy zasoby okna zależne od urządzenia, cel renderowania i dwa pędzle. Pobierz rozmiar obszaru klienta i utwórz ID2D1HwndRenderTarget o takim samym rozmiarze, który jest renderowany do HWND okna . Przechowuj cel renderowania w członku klasy 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. Użyj celu renderowania, aby utworzyć szary ID2D1SolidColorBrush i chaberowy niebieski 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. Ponieważ ta metoda zostanie wywołana wielokrotnie, dodaj instrukcję if, aby sprawdzić, czy obiekt docelowy renderowania (m_pRenderTarget) już istnieje. Poniższy kod przedstawia kompletną metodę 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. Zaimplementuj metodę DemoApp::DiscardDeviceResources. W tej metodzie zwolnij element docelowy renderowania i dwa pędzle utworzone w metodzie DemoApp::CreateDeviceResources.

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

Część 4. Renderowanie zawartości Direct2D

W tej części zaimplementujesz procedurę windows, metodę OnRender (która maluje zawartość) i metodę OnResize (która dostosowuje rozmiar obiektu docelowego renderowania po zmianie rozmiaru okna).

  1. Zaimplementuj metodę DemoApp::WndProc do obsługi komunikatów okien. W przypadku komunikatu WM_SIZE wywołaj metodę DemoApp::OnResize i przekaż nową szerokość i wysokość. W przypadku komunikatów WM_PAINT i WM_DISPLAYCHANGE wywołaj metodę DemoApp::OnRender, aby namalować okno. W kolejnych krokach zaimplementujesz metody OnRender i 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. Zaimplementuj metodę DemoApp::OnRender. Najpierw zdefiniuj HRESULT. Następnie wywołaj metodę CreateDeviceResource. Ta metoda jest wywoływana za każdym razem, gdy okno jest malowane. Pamiętaj, że w kroku 4 części 3 dodano instrukcję if, aby uniemożliwić metodę wykonywania jakiejkolwiek pracy, jeśli obiekt docelowy renderowania już istnieje.

    HRESULT DemoApp::OnRender()
    {
        HRESULT hr = S_OK;
    
        hr = CreateDeviceResources();
    
  3. Sprawdź, czy metoda CreateDeviceResource powiodła się. Jeśli tak nie jest, nie wykonuj żadnego rysunku.

    if (SUCCEEDED(hr))
    {
    
  4. Wewnątrz właśnie dodanej instrukcji if zainicjuj rysunek, wywołując metodę BeginDraw obiektu docelowego renderowania. Ustaw przekształcenie elementu docelowego renderowania na macierz tożsamości i wyczyść okno.

    m_pRenderTarget->BeginDraw();
    m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());
    m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
    
  5. Pobierz rozmiar obszaru rysunku.

    D2D1_SIZE_F rtSize = m_pRenderTarget->GetSize();
    
  6. Rysuj tło siatki, używając pętli for oraz metody DrawLine obiektu docelowego renderowania, aby narysować serię linii.

    // 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. Utwórz dwa prostokątne elementy pierwotne, które są wyśrodkowane na ekranie.

    // 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. Użyj metody FillRectangle obiektu docelowego renderowania, aby namalować wnętrze pierwszego prostokąta za pomocą szarego pędzla.

    // Draw a filled rectangle.
    m_pRenderTarget->FillRectangle(&rectangle1, m_pLightSlateGrayBrush);
    
  9. Użyj metody DrawRectangle docelowego elementu renderowania, aby namalować kontur drugiego prostokąta pędzlem w kolorze chabrowym.

    // Draw the outline of a rectangle.
    m_pRenderTarget->DrawRectangle(&rectangle2, m_pCornflowerBlueBrush);
    
  10. Wywołaj metodę EndDraw elementu docelowego renderowania. Metoda EndDraw zwraca HRESULT, aby wskazać, czy operacje rysunku zakończyły się pomyślnie. Zamknij zakres instrukcji if, rozpoczętą w kroku 3.

        hr = m_pRenderTarget->EndDraw();
    }
    
  11. Sprawdź HRESULT zwrócone przez EndDraw. Jeśli wskazuje, że element docelowy renderowania musi zostać ponownie utworzony, wywołaj metodę DemoApp::DiscardDeviceResources, aby ją zwolnić. Zostanie ona ponownie utworzona przy następnym odebraniu komunikatu WM_PAINT lub WM_DISPLAYCHANGE.

    if (hr == D2DERR_RECREATE_TARGET)
    {
        hr = S_OK;
        DiscardDeviceResources();
    }
    
  12. Zwróć HRESULTi zamknij zakres metody.

        return hr;
    }
    
  13. Zaimplementuj metodę DemoApp::OnResize, aby zmienić rozmiar obiektu docelowego renderowania na nowy rozmiar okna.

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

Ukończyłeś samouczek.

Notatka

Aby użyć direct2D, upewnij się, że aplikacja zawiera plik nagłówka d2d1.h i kompiluje się względem biblioteki d2d1.lib. d2d1.h i d2d1.lib można znaleźć w Windows SDK .

Streszczenie

W tym samouczku przedstawiono sposób tworzenia zasobów Direct2D i rysowania podstawowych kształtów. Przedstawiono również sposób tworzenia struktury aplikacji w celu zwiększenia wydajności przez zminimalizowanie tworzenia zasobów.