Condividi tramite


Creare una semplice applicazione Direct2D

Questo argomento illustra il processo di creazione della classe DemoApp , che crea una finestra e usa Direct2D per disegnare contenuto. In questa esercitazione si apprenderà come creare risorse Direct2D e disegnare forme di base. Si apprenderà anche come strutturare l'applicazione per migliorare le prestazioni riducendo al minimo la creazione di risorse.

Per seguire l'esercitazione, è possibile usare Microsoft Visual Studio per creare un progetto Win32 e quindi sostituire il codice nell'intestazione e .cpp nel file dell'applicazione principale con il codice descritto in questa esercitazione.

Vedere anche l'app di esempio di applicazione Direct2D semplice in GitHub.

Nota

Se vuoi creare un'app piattaforma UWP (Universal Windows Platform) (UWP) che usa Direct2D, vedi l'argomento Avvio rapido di Direct2D per Windows 8.

Per una panoramica delle interfacce che è possibile usare per creare contenuto Direct2D, vedere panoramica dell'API Direct2D.

Al termine dell'esercitazione, la classe DemoApp produce l'output illustrato nella figura seguente.

illustrazione di due rettangoli su uno sfondo griglia

Parte 1: Creare l'intestazione DemoApp

In questo passaggio si configura l'applicazione per l'uso di Direct2D aggiungendo le intestazioni e le macro necessarie. Si dichiarano anche i metodi e i membri dati che verranno usati nelle parti successive di questa esercitazione.

  1. Nel file di intestazione dell'applicazione includere le intestazioni usate di frequente seguenti.

    // 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. Dichiarare funzioni aggiuntive per il rilascio di interfacce e macro per la gestione degli errori e il recupero dell'indirizzo di base del modulo.

    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. Dichiarare i metodi per inizializzare la classe, creare ed eliminare risorse, gestire il ciclo dei messaggi, il contenuto di rendering e la routine 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. Come membri della classe, dichiarare puntatori per un oggetto ID2D1Factory , un oggetto ID2D1HwndRenderTarget e due oggetti ID2D1SolidColorBrush .

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

Parte 2: Implementare l'infrastruttura di classe

In questa parte si implementa il costruttore DemoApp e il distruttore, i relativi metodi di inizializzazione e ciclo di messaggi e la funzione WinMain . La maggior parte di questi metodi ha lo stesso aspetto di quelli trovati in qualsiasi altra applicazione Win32. L'unica eccezione è il metodo Initialize , che chiama il metodo CreateDeviceIndependentResources (che verrà definito nella parte successiva), che crea diverse risorse Direct2D.

  1. Nel file di implementazione della classe implementare il costruttore e il distruttore della classe. Il costruttore deve inizializzare i relativi membri in NULL. Il distruttore deve rilasciare tutte le interfacce archiviate come membri della classe.

    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. Implementare il metodo DemoApp::RunMessageLoop , che converte e invia messaggi.

    void DemoApp::RunMessageLoop()
    {
        MSG msg;
    
        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    
  3. Implementare il metodo Initialize , che crea la finestra, lo mostra e chiama il metodo DemoApp::CreateDeviceIndependentResources . Il metodo CreateDeviceIndependentResources verrà implementato nella sezione successiva.

    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. Implementare il metodo WinMain , che funge da punto di ingresso dell'applicazione. Inizializzare un'istanza della classe DemoApp, e iniziare il ciclo di messaggi.

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

Parte 3: Creare risorse Direct2D

In questa parte vengono create le risorse Direct2D usate per disegnare. Direct2D offre due tipi di risorse, ovvero risorse indipendenti dal dispositivo che possono durare per tutta la durata dell'applicazione e risorse dipendenti dal dispositivo. Le risorse dipendenti dal dispositivo sono associate a un dispositivo di rendering specifico e smetteranno di funzionare se il dispositivo viene rimosso.

  1. Implementare il metodo DemoApp::CreateDeviceIndependentResources . Nel metodo creare un ID2D1Factory, ovvero una risorsa indipendente dal dispositivo per la creazione di altre risorse Direct2D. Usare il membro della m_pDirect2DdFactory classe per archiviare la factory.

    HRESULT DemoApp::CreateDeviceIndependentResources()
    {
        HRESULT hr = S_OK;
    
        // Create a Direct2D factory.
        hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pDirect2dFactory);
    
        return hr;
    }
    
  2. Implementare il metodo DemoApp::CreateDeviceResources . Questo metodo crea le risorse dipendenti dal dispositivo della finestra, una destinazione di rendering e due pennelli. Recuperare le dimensioni dell'area client e creare un OGGETTO ID2D1HwndRenderTarget delle stesse dimensioni di cui viene eseguito il rendering nel HWND della finestra. Archiviare la destinazione di rendering nel membro della m_pRenderTarget classe.

    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. Usare la destinazione di rendering per creare un ID2D1SolidColorBrush grigio e un ID ID2D1SolidColorBrush blu di cornflower.

    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. Poiché questo metodo verrà chiamato ripetutamente, aggiungere un'istruzione if per verificare se la destinazione di rendering (m_pRenderTarget) esiste già. Il codice seguente mostra il metodo CreateDeviceResources completo.

    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. Implementare il metodo DemoApp::D iscardDeviceResources . In questo metodo rilasciare la destinazione di rendering e i due pennelli creati nel metodo DemoApp::CreateDeviceResources .

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

Parte 4: Rendering del contenuto Direct2D

In questa parte si implementa la routine windows, il metodo OnRender (che disegna il contenuto) e il metodo OnResize (che regola le dimensioni della destinazione di rendering quando la finestra viene ridimensionata).

  1. Implementare il metodo DemoApp::WndProc per gestire i messaggi della finestra. Per il messaggio di WM_SIZE , chiamare il metodo DemoApp::OnResize e passarlo alla nuova larghezza e altezza. Per i messaggi WM_PAINT e WM_DISPLAYCHANGE , chiamare il metodo DemoApp::OnRender per disegnare la finestra. I metodi OnRender e OnResize verranno implementati nei passaggi seguenti.

    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. Implementare il metodo DemoApp::OnRender . Definire prima di tutto un valore HRESULT. Chiamare quindi il metodo CreateDeviceResource . Tale metodo viene chiamato ogni volta che viene disegnata la finestra. Tenere presente che, nel passaggio 4 della parte 3, è stata aggiunta un'istruzione if per impedire al metodo di eseguire operazioni se la destinazione di rendering esiste già.

    HRESULT DemoApp::OnRender()
    {
        HRESULT hr = S_OK;
    
        hr = CreateDeviceResources();
    
  3. Verificare che il metodo CreateDeviceResource sia riuscito. In caso contrario, non eseguire alcun disegno.

    if (SUCCEEDED(hr))
    {
    
  4. All'interno dell'istruzione appena aggiunta, avviare il if disegno chiamando il metodo BeginDraw della destinazione di rendering. Impostare la trasformazione della destinazione di rendering sulla matrice di identità e cancellare la finestra.

    m_pRenderTarget->BeginDraw();
    m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());
    m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
    
  5. Recuperare le dimensioni dell'area di disegno.

    D2D1_SIZE_F rtSize = m_pRenderTarget->GetSize();
    
  6. Disegnare uno sfondo della griglia usando un for ciclo e il metodo DrawLine della destinazione di rendering per disegnare una serie di linee.

    // 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. Creare due primitive rettangolo centrate sullo schermo.

    // 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. Utilizzare il metodo FillRectangle della destinazione di rendering per disegnare l'interno del primo rettangolo con il pennello grigio.

    // Draw a filled rectangle.
    m_pRenderTarget->FillRectangle(&rectangle1, m_pLightSlateGrayBrush);
    
  9. Utilizzare il metodo DrawRectangle della destinazione di rendering per disegnare il contorno del secondo rettangolo con il pennello blu del flusso di cornflower.

    // Draw the outline of a rectangle.
    m_pRenderTarget->DrawRectangle(&rectangle2, m_pCornflowerBlueBrush);
    
  10. Chiamare il metodo EndDraw della destinazione di rendering. Il metodo EndDraw restituisce un valore HRESULT per indicare se le operazioni di disegno hanno avuto esito positivo. Chiudere l'ambito dell'istruzione if avviata nel passaggio 3.

        hr = m_pRenderTarget->EndDraw();
    }
    
  11. Controllare il valore HRESULT restituito da EndDraw. Se indica che la destinazione di rendering deve essere ricreata, chiamare il metodo DemoApp::D iscardDeviceResources per rilasciarlo; verrà ricreata la volta successiva che la finestra riceve un messaggio di WM_PAINT o WM_DISPLAYCHANGE .

    if (hr == D2DERR_RECREATE_TARGET)
    {
        hr = S_OK;
        DiscardDeviceResources();
    }
    
  12. Restituire HRESULT e chiudere l'ambito del metodo.

        return hr;
    }
    
  13. Implementare il metodo DemoApp::OnResize in modo che ridimensiona la destinazione di rendering alla nuova dimensione della finestra.

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

L'esercitazione è stata completata.

Nota

Per usare Direct2D, assicurarsi che l'applicazione includa il d2d1.h file di intestazione e che venga compilata in base alla d2d1.lib libreria. È possibile trovare d2d1.h e d2d1.lib in Windows SDK.

Riepilogo

In questa esercitazione si è appreso come creare risorse Direct2D e disegnare forme di base. Si è anche appreso come strutturare l'applicazione per migliorare le prestazioni riducendo al minimo la creazione di risorse.