Search code examples
winapigdidirect2ddxgi

Force Win32 common controls to draw on `ID2D1HwndRenderTarget`?


I draw most of my UI using ID2D1HwndRenderTarget, but I want some classic window controls: button, edit. How to

ID2D1HwndRenderTarget * canvas = nullptr; // it's global object
HWND button = nullptr; // it's global object
HWND edit = nullptr; // it's global object
HWND custom = nullptr; // it's global object

// mainWindow WinPproc
case WM_CREATE:
    button = CreateWindowExW(0, L"button", L"Send", WS_CHILD | WS_VISIBLE, 10, 10, 120, 30, hWnd, BUTTON_ID, hInstance, 0); // win32 control
    edit = CreateWindowExW(0, L"edit", L"Edit", WS_CHILD | WS_VISIBLE, 10, 50, 120, 30, hWnd, BUTTON_ID, hInstance, 0); // win32 control
    custom = CreateWindowExW(0, L"custom", L"Custom", WS_CHILD | WS_VISIBLE, 10, 90, 120, 30, hWnd, BUTTON_ID, hInstance, 0); // it's my custom class
    break;

case WM_PAINT:
    BeginPaint(hWnd, nullptr);
    render_target->BeginPaint();
    ... GUI rendering stuff ....
    HRESULT result = render_target->EndDraw();
    if(result != S_OK)
    {
       // Error handling
       ...
    }
    EndPaint(hWnd, nullptr);
    break;
// custom WinProc
case WM_PAINT:
    BeginPaint(hWnd, nullptr);
    render_target->BeginPaint();
    ... rendering stuff ....
    HRESULT result = render_target->EndDraw();
    if(result != S_OK)
    {
       // Error handling
       ...
    }
    EndPaint(hWnd, nullptr);
    break;

Only things painted with render_target are visible. I understand why: becausebutton and edit are default win32 controls, internally drawn using PAINTSTRUCT->HDC context. I read Direct2D and GDI Interoperability Overview and get the concept, but still don't know where this HDC intrecpet should take place? I don't want touch default control WM_PAINT. I have to supclass all default win32 controls?

How to force those Win32 controls to draw onto my render_target?


Solution

  • If you want to share a device context (HDC) between GDI and Direct2D, you can use a ID2D1DCRenderTarget and Bind to this HDC when you want to render on it.

    This is demonstrated in this official sample : GDI/Direct2D Interoperability Sample

    Note, as is, it doesn't compile/work with today's Visual Studio. So, here is a similar code here with a simple button and textbox:

    #include <windows.h>
    #include <stdlib.h>
    #include <math.h>
    #include <d2d1.h>
    
    template<class Interface>
    inline void
    SafeRelease(Interface** ppInterfaceToRelease)
    {
        if (*ppInterfaceToRelease != NULL)
        {
            (*ppInterfaceToRelease)->Release();
            (*ppInterfaceToRelease) = NULL;
        }
    }
    
    EXTERN_C IMAGE_DOS_HEADER __ImageBase;
    
    class DemoApp
    {
    public:
        DemoApp();
        ~DemoApp();
    
        HRESULT Initialize();
    
    private:
        HRESULT CreateDeviceIndependentResources();
        HRESULT CreateDeviceResources();
        void DiscardDeviceResources();
        HRESULT OnRender(const PAINTSTRUCT& ps);
        static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
    
    private:
        HWND m_hwnd;
        ID2D1Factory* m_pD2DFactory;
        ID2D1DCRenderTarget* m_pDCRT;
        ID2D1SolidColorBrush* m_pBlackBrush;
    };
    
    int WINAPI WinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int /*nCmdShow*/)
    {
        if (SUCCEEDED(CoInitialize(NULL)))
        {
            DemoApp app;
            if (SUCCEEDED(app.Initialize()))
            {
                MSG msg;
                while (GetMessage(&msg, NULL, 0, 0))
                {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
            }
            CoUninitialize();
        }
        return 0;
    }
    
    DemoApp::DemoApp() :
        m_hwnd(NULL),
        m_pD2DFactory(NULL),
        m_pDCRT(NULL),
        m_pBlackBrush(NULL)
    {
    }
    
    DemoApp::~DemoApp()
    {
        SafeRelease(&m_pD2DFactory);
        SafeRelease(&m_pDCRT);
        SafeRelease(&m_pBlackBrush);
    }
    
    HRESULT DemoApp::Initialize()
    {
        HRESULT hr;
    
        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 = (HINSTANCE)&__ImageBase;
            wcex.hbrBackground = NULL;
            wcex.lpszMenuName = NULL;
            wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
            wcex.lpszClassName = L"D2DDemoApp";
            RegisterClassEx(&wcex);
    
            // Create the application window.
            // Because the CreateWindow function takes its size in pixels, we obtain the system DPI and use it to scale the window size.
            FLOAT dpiX, dpiY;
            m_pD2DFactory->GetDesktopDpi(&dpiX, &dpiY);
    
            m_hwnd = CreateWindow(
                L"D2DDemoApp",
                L"Direct2D Demo App",
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                static_cast<UINT>(ceil(640.f * dpiX / 96.f)),
                static_cast<UINT>(ceil(480.f * dpiY / 96.f)),
                NULL,
                NULL,
                (HINSTANCE)&__ImageBase,
                this
            );
    
            hr = m_hwnd ? S_OK : E_FAIL;
            if (SUCCEEDED(hr))
            {
                ShowWindow(m_hwnd, SW_SHOWNORMAL);
    
                UpdateWindow(m_hwnd);
            }
        }
    
        return hr;
    }
    
    HRESULT DemoApp::CreateDeviceIndependentResources()
    {
        // Create D2D factory
        return D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pD2DFactory);
    }
    
    HRESULT DemoApp::CreateDeviceResources()
    {
        HRESULT hr = S_OK;
        if (!m_pDCRT)
        {
            // Create a DC render target.
            D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties(
                D2D1_RENDER_TARGET_TYPE_DEFAULT,
                D2D1::PixelFormat(
                    DXGI_FORMAT_B8G8R8A8_UNORM,
                    D2D1_ALPHA_MODE_IGNORE),
                0,
                0,
                D2D1_RENDER_TARGET_USAGE_NONE,
                D2D1_FEATURE_LEVEL_DEFAULT
            );
    
            hr = m_pD2DFactory->CreateDCRenderTarget(&props, &m_pDCRT);
            if (SUCCEEDED(hr))
            {
                // Create a black brush.
                hr = m_pDCRT->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black), &m_pBlackBrush);
            }
        }
    
        return hr;
    }
    
    void DemoApp::DiscardDeviceResources()
    {
        SafeRelease(&m_pDCRT);
        SafeRelease(&m_pBlackBrush);
    }
    
    HRESULT DemoApp::OnRender(const PAINTSTRUCT& ps)
    {
        HRESULT hr;
        RECT rc;
    
        // Get the dimensions of the client drawing area.
        GetClientRect(m_hwnd, &rc);
    
        // Draw the pie chart with Direct2D.
    
        // Create the DC render target.
        hr = CreateDeviceResources();
    
        if (SUCCEEDED(hr))
        {
            // Bind the DC to the DC render target.
            hr = m_pDCRT->BindDC(ps.hdc, &rc);
    
            m_pDCRT->BeginDraw();
            m_pDCRT->SetTransform(D2D1::Matrix3x2F::Identity());
            m_pDCRT->Clear(D2D1::ColorF(D2D1::ColorF::White));
    
            m_pDCRT->DrawEllipse(D2D1::Ellipse(D2D1::Point2F(150.0f, 150.0f), 100.0f, 100.0f), m_pBlackBrush, 3.0);
    
            m_pDCRT->DrawLine(
                D2D1::Point2F(150.0f, 150.0f),
                D2D1::Point2F((150.0f + 100.0f * 0.15425f), (150.0f - 100.0f * 0.988f)), m_pBlackBrush, 3.0
            );
    
            m_pDCRT->DrawLine(
                D2D1::Point2F(150.0f, 150.0f),
                D2D1::Point2F((150.0f + 100.0f * 0.525f), (150.0f + 100.0f * 0.8509f)), m_pBlackBrush, 3.0
            );
    
            m_pDCRT->DrawLine(
                D2D1::Point2F(150.0f, 150.0f),
                D2D1::Point2F((150.0f - 100.0f * 0.988f), (150.0f - 100.0f * 0.15425f)), m_pBlackBrush, 3.0
            );
    
            hr = m_pDCRT->EndDraw();
    
            if (SUCCEEDED(hr))
            {
                // Draw the pie chart with GDI.
    
                // Save the original object.
                HGDIOBJ original = NULL;
                original = SelectObject(ps.hdc, GetStockObject(DC_PEN));
    
                HPEN blackPen = CreatePen(PS_SOLID, 3, 0);
                SelectObject(ps.hdc, blackPen);
    
                Ellipse(ps.hdc, 300, 50, 500, 250);
    
                POINT pntArray1[2];
                pntArray1[0].x = 400;
                pntArray1[0].y = 150;
                pntArray1[1].x = static_cast<LONG>(400 + 100 * 0.15425);
                pntArray1[1].y = static_cast<LONG>(150 - 100 * 0.9885);
    
                POINT pntArray2[2];
                pntArray2[0].x = 400;
                pntArray2[0].y = 150;
                pntArray2[1].x = static_cast<LONG>(400 + 100 * 0.525);
                pntArray2[1].y = static_cast<LONG>(150 + 100 * 0.8509);
    
    
                POINT pntArray3[2];
                pntArray3[0].x = 400;
                pntArray3[0].y = 150;
                pntArray3[1].x = static_cast<LONG>(400 - 100 * 0.988);
                pntArray3[1].y = static_cast<LONG>(150 - 100 * 0.15425);
    
                Polyline(ps.hdc, pntArray1, 2);
                Polyline(ps.hdc, pntArray2, 2);
                Polyline(ps.hdc, pntArray3, 2);
    
                DeleteObject(blackPen);
    
                // Restore the original object.
                SelectObject(ps.hdc, original);
            }
        }
    
        if (hr == D2DERR_RECREATE_TARGET)
        {
            hr = S_OK;
            DiscardDeviceResources();
        }
    
        return hr;
    }
    
    LRESULT CALLBACK DemoApp::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        if (message == WM_CREATE)
        {
            LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
            SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pcs->lpCreateParams);
    
            auto button = CreateWindowExW(0, L"button", L"Send", WS_CHILD | WS_VISIBLE, 10, 10, 120, 30, hwnd, (HMENU)1, (HINSTANCE)&__ImageBase, 0); // win32 control
            auto edit = CreateWindowExW(0, L"edit", L"Edit", WS_CHILD | WS_VISIBLE, 10, 50, 120, 30, hwnd, (HMENU)2, (HINSTANCE)&__ImageBase, 0); // win32 control
            return 1;
        }
    
        LRESULT result = 0;
        DemoApp* pDemoApp = (DemoApp*)(GetWindowLongPtr(hwnd, GWLP_USERDATA));
        bool wasHandled = false;
        if (pDemoApp)
        {
            switch (message)
            {
            case WM_PAINT:
            case WM_DISPLAYCHANGE:
            {
                PAINTSTRUCT ps;
                BeginPaint(hwnd, &ps);
                pDemoApp->OnRender(ps);
                EndPaint(hwnd, &ps);
            }
            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;
    }
    

    And here is how it renders (left circle is Direct2D, right is aliased GDI):

    enter image description here