Créer une simple application Direct2D

Cette rubrique vous guide tout au long du processus de création de la classe DemoApp , qui crée une fenêtre et utilise Direct2D pour dessiner du contenu. Dans ce tutoriel, vous allez apprendre à créer des ressources Direct2D et à dessiner des formes de base. Vous apprenez également à structurer votre application pour améliorer les performances en réduisant la création de ressources.

Pour suivre le tutoriel, vous pouvez utiliser Microsoft Visual Studio pour créer un projet Win32, puis remplacer le code dans l’en-tête et .cpp le fichier d’application main par le code décrit dans ce didacticiel.

Consultez également l’exemple d’application Simple Direct2D sur GitHub.

Notes

Si vous souhaitez créer une application plateforme Windows universelle (UWP) qui utilise Direct2D, consultez le guide de démarrage rapide Direct2D pour Windows 8 rubrique.

Pour obtenir une vue d’ensemble des interfaces que vous pouvez utiliser pour créer du contenu Direct2D, consultez vue d’ensemble de l’API Direct2D.

Une fois le tutoriel terminé, la classe DemoApp produit la sortie illustrée dans l’illustration suivante.

illustration de deux rectangles sur un arrière-plan de grille

Partie 1 : Créer l’en-tête DemoApp

Dans cette étape, vous configurez votre application pour utiliser Direct2D en ajoutant les en-têtes et les macros nécessaires. Vous déclarez également les méthodes et les membres de données que vous utiliserez dans les parties ultérieures de ce didacticiel.

  1. Dans votre fichier d’en-tête d’application, incluez les en-têtes fréquemment utilisés suivants.

    // 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. Déclarez des fonctions supplémentaires pour libérer des interfaces et des macros pour la gestion des erreurs et la récupération de l’adresse de base du module.

    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. Déclarez les méthodes d’initialisation de la classe, de création et d’abandon de ressources, de gestion de la boucle de message, de rendu du contenu et de la procédure 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. En tant que membres de la classe, déclarez des pointeurs pour un objet ID2D1Factory , un objet ID2D1HwndRenderTarget et deux objets ID2D1SolidColorBrush .

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

Partie 2 : Implémenter l’infrastructure de classe

Dans cette partie, vous implémentez le constructeur et le destructeur DemoApp , ses méthodes d’initialisation et de boucle de message, ainsi que la fonction WinMain . La plupart de ces méthodes ressemblent à celles trouvées dans n’importe quelle autre application Win32. La seule exception est la méthode Initialize , qui appelle la méthode CreateDeviceIndependentResources (que vous allez définir dans la partie suivante), qui crée plusieurs ressources Direct2D.

  1. Dans le fichier d’implémentation de classe, implémentez le constructeur et le destructeur de classe. Le constructeur doit initialiser ses membres en NULL. Le destructeur doit libérer toutes les interfaces stockées en tant que membres de 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. Implémentez la méthode DemoApp::RunMessageLoop , qui traduit et répartit les messages.

    void DemoApp::RunMessageLoop()
    {
        MSG msg;
    
        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    
  3. Implémentez la méthode Initialize , qui crée la fenêtre, l’affiche et appelle la méthode DemoApp::CreateDeviceIndependentResources . Vous allez implémenter la méthode CreateDeviceIndependentResources dans la section suivante.

    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. Implémentez la méthode WinMain , qui sert de point d’entrée de l’application. Initialisez une instance de la classe DemoApp, et commencez sa boucle de message.

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

Partie 3 : Créer des ressources Direct2D

Dans cette partie, vous créez les ressources Direct2D que vous utilisez pour dessiner. Direct2D fournit deux types de ressources : les ressources indépendantes de l’appareil qui peuvent durer pendant la durée de l’application et les ressources dépendantes de l’appareil. Les ressources dépendantes de l’appareil sont associées à un appareil de rendu particulier et cesseront de fonctionner si cet appareil est supprimé.

  1. Implémentez la méthode DemoApp::CreateDeviceIndependentResources . Dans la méthode, créez un ID2D1Factory, qui est une ressource indépendante de l’appareil pour créer d’autres ressources Direct2D. Utilisez le membre de m_pDirect2DdFactory classe pour stocker la fabrique.

    HRESULT DemoApp::CreateDeviceIndependentResources()
    {
        HRESULT hr = S_OK;
    
        // Create a Direct2D factory.
        hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pDirect2dFactory);
    
        return hr;
    }
    
  2. Implémentez la méthode DemoApp::CreateDeviceResources . Cette méthode crée les ressources dépendantes de l’appareil de la fenêtre, une cible de rendu et deux pinceaux. Récupérez la taille de la zone cliente et créez un ID2D1HwndRenderTarget de la même taille que celle affichée dans le HWND de la fenêtre. Stockez la cible de rendu dans le membre de 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. Utilisez la cible de rendu pour créer un ID2D1SolidColorBrush gris et un maïsflow bleu 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. Étant donné que cette méthode sera appelée à plusieurs reprises, ajoutez une if instruction pour case activée si la cible de rendu (m_pRenderTarget) existe déjà. Le code suivant montre la méthode CreateDeviceResources complète.

    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. Implémentez la méthode DemoApp::D iscardDeviceResources . Dans cette méthode, relâchez la cible de rendu et les deux pinceaux que vous avez créés dans la méthode DemoApp::CreateDeviceResources .

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

Partie 4 : Rendu du contenu Direct2D

Dans cette partie, vous implémentez la procédure windows, la méthode OnRender (qui peint le contenu) et la méthode OnResize (qui ajuste la taille de la cible de rendu lorsque la fenêtre est redimensionnée).

  1. Implémentez la méthode DemoApp::WndProc pour gérer les messages de fenêtre. Pour le message WM_SIZE , appelez la méthode DemoApp::OnResize et transmettez-lui la nouvelle largeur et la nouvelle hauteur. Pour les messages WM_PAINT et WM_DISPLAYCHANGE , appelez la méthode DemoApp::OnRender pour peindre la fenêtre. Vous allez implémenter les méthodes OnRender et OnResize dans les étapes suivantes.

    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. Implémentez la méthode DemoApp::OnRender . Tout d’abord, définissez un HRESULT. Appelez ensuite la méthode CreateDeviceResource . Cette méthode est appelée chaque fois que la fenêtre est peinte. Rappelez-vous que, à l’étape 4 de la partie 3, vous avez ajouté une if instruction pour empêcher la méthode d’effectuer un travail si la cible de rendu existe déjà.

    HRESULT DemoApp::OnRender()
    {
        HRESULT hr = S_OK;
    
        hr = CreateDeviceResources();
    
  3. Vérifiez que la méthode CreateDeviceResource a réussi. Si ce n’est pas le cas, n’effectuez aucun dessin.

    if (SUCCEEDED(hr))
    {
    
  4. Dans l’instruction if que vous venez d’ajouter, lancez le dessin en appelant la méthode BeginDraw de la cible de rendu. Définissez la transformation de la cible de rendu sur la matrice d’identité, puis effacez la fenêtre.

    m_pRenderTarget->BeginDraw();
    m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());
    m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
    
  5. Récupérez la taille de la zone de dessin.

    D2D1_SIZE_F rtSize = m_pRenderTarget->GetSize();
    
  6. Dessinez un arrière-plan de grille à l’aide d’une for boucle et de la méthode DrawLine de la cible de rendu pour dessiner une série de lignes.

    // 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. Créez deux primitives rectangle centrées sur l’écran.

    // 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. Utilisez la méthode FillRectangle de la cible de rendu pour peindre l’intérieur du premier rectangle avec le pinceau gris.

    // Draw a filled rectangle.
    m_pRenderTarget->FillRectangle(&rectangle1, m_pLightSlateGrayBrush);
    
  9. Utilisez la méthode DrawRectangle de la cible de rendu pour peindre le contour du deuxième rectangle avec le pinceau bleu de fleur de maïs.

    // Draw the outline of a rectangle.
    m_pRenderTarget->DrawRectangle(&rectangle2, m_pCornflowerBlueBrush);
    
  10. Appelez la méthode EndDraw de la cible de rendu. La méthode EndDraw retourne un HRESULT pour indiquer si les opérations de dessin ont réussi. Fermez l’étendue de l’instruction if que vous avez commencée à l’étape 3.

        hr = m_pRenderTarget->EndDraw();
    }
    
  11. Vérifiez le HRESULT retourné par EndDraw. S’il indique que la cible de rendu doit être recréée, appelez la méthode DemoApp::D iscardDeviceResources pour la libérer ; il sera recréé la prochaine fois que la fenêtre recevra un message WM_PAINT ou WM_DISPLAYCHANGE .

    if (hr == D2DERR_RECREATE_TARGET)
    {
        hr = S_OK;
        DiscardDeviceResources();
    }
    
  12. Retournez le HRESULT et fermez l’étendue de la méthode.

        return hr;
    }
    
  13. Implémentez la méthode DemoApp::OnResize afin qu’elle redimensionne la cible de rendu à la nouvelle taille de la fenêtre.

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

Vous avez maintenant terminé le tutoriel.

Notes

Pour utiliser Direct2D, assurez-vous que votre application inclut le fichier d’en-tête d2d1.h et qu’elle se compile sur la d2d1.lib bibliothèque. Vous pouvez trouver d2d1.h et d2d1.lib dans le Kit de développement logiciel (SDK) Windows.

Résumé

Dans ce tutoriel, vous avez appris à créer des ressources Direct2D et à dessiner des formes de base. Vous avez également appris à structurer votre application pour améliorer les performances en réduisant la création de ressources.