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 principale dell'applicazione e .cpp file con il codice descritto in questa esercitazione.

Vedi anche la semplice applicazione Direct2D di esempio su GitHub.

Nota

Se vuoi creare un'app UWP (Universal Windows Platform) che usa Direct2D, vedi l'argomento di avvio rapido 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 seguenti intestazioni usate di frequente.

    // 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 l'inizializzazione della classe, la creazione e l'eliminazione di risorse, la gestione del ciclo di messaggi, il rendering del contenuto e la routine di 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, dichiarate i 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 implementano il costruttore e il distruttore DemoApp, i suoi metodi di inizializzazione e di ciclo dei 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 (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 per 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. Nella sezione successiva si implementerà il metodo 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. Implementare il metodo WinMain, che funge da punto di ingresso dell'applicazione. Inizializza un'istanza della classe DemoApp e inizia il loop dei 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 si creano le risorse Direct2D usate per disegnare. Direct2D offre due tipi di risorse, ovvero risorse indipendenti dal dispositivo che possono durare per la durata dell'applicazione e le 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, che è una risorsa indipendente dal dispositivo per la creazione di altre risorse Direct2D. Usare il membro della classe m_pDirect2DdFactory 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, un target di rendering e due pennelli. Recuperare le dimensioni dell'area client e creare un ID2D1HwndRenderTarget delle stesse dimensioni di cui viene eseguito il rendering nel HWNDdella finestra. Archivia la destinazione di rendering nel membro della classe 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. Usare la destinazione di rendering per creare un grigio ID2D1SolidColorBrush e un blu fiordaliso 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. 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 completo 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. Implementare il metodo DemoApp::DiscardDeviceResources. In questo metodo, rilascia 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 WM_SIZE, chiamare il metodo DemoApp::OnResize e passare la nuova larghezza e altezza. Per i messaggi WM_PAINT e WM_DISPLAYCHANGE, chiamare il metodo DemoApp::OnRender per disegnare la finestra. Verranno implementati i metodi OnRender e OnResize 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. Innanzitutto, definisci un HRESULT. Chiamare quindi il metodo CreateDeviceResource. Questo metodo viene chiamato ogni volta che viene dipinta 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 abbia avuto esito positivo. Se non lo fa, allora non eseguire alcun disegno.

    if (SUCCEEDED(hr))
    {
    
  4. All'interno dell'istruzione if appena aggiunta, iniziare a disegnare chiamando il metodo BeginDraw del target 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 utilizzando un ciclo for e il metodo DrawLine del target 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. Crea due rettangoli primitivi centrati 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. Usare 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 dell'oggetto di rendering per disegnare il contorno del secondo rettangolo con il pennello blu fiordaliso.

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

        hr = m_pRenderTarget->EndDraw();
    }
    
  11. Controllare il HRESULT restituito da EndDraw. Se viene indicato che la destinazione di rendering deve essere ricreata, bisogna chiamare il metodo DemoApp::DiscardDeviceResources per rilasciarla; sarà ricreata la prossima volta che la finestra riceverà un messaggio WM_PAINT o WM_DISPLAYCHANGE.

    if (hr == D2DERR_RECREATE_TARGET)
    {
        hr = S_OK;
        DiscardDeviceResources();
    }
    
  12. Restituisci il HRESULTe delimita l'ambito del metodo.

        return hr;
    }
    
  13. Implementare il metodo DemoApp::OnResize in modo da ridimensionare la destinazione di rendering alle nuove dimensioni 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));
        }
    }
    

Hai completato l'esercitazione.

Nota

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

Sommario

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 delle risorse.