强制 Win32 公共控件在“ID2D1HwndRenderTarget”上绘制?

Force Win32 common controls to draw on `ID2D1HwndRenderTarget`?

我使用 ID2D1HwndRenderTarget 绘制大部分 UI,但我想要一些经典的 window 控件:buttonedit。如何

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;

只有用 render_target 绘制的东西是可见的。我明白为什么:因为 buttonedit 是默认的 win32 控件,内部使用 PAINTSTRUCT->HDC 上下文绘制。我阅读 Direct2D and GDI Interoperability Overview 并了解了概念,但仍然不知道这个 HDC 中断应该发生在哪里?我不想触摸默认控件 WM_PAINT。我必须 supclass 所有默认的 win32 控件?

如何强制这些 Win32 控件绘制到我的 render_target 上?

如何强制 Win32 控件绘制到我的 ID2D1Bitmap1 上?这可能吗?

是的。您可以将 GDI 内容写入 Direct2D GDI 兼容渲染目标。此方法对于主要使用 Direct2D 呈现但具有可扩展性模型或需要能够使用 GDI.

呈现的其他遗留内容的应用程序很有用

步骤:

To render GDI content to a Direct2D GDI-compatible render target, use an ID2D1GdiInteropRenderTarget, which provides access to a device context that can accept GDI draw calls. Unlike other interfaces, an ID2D1GdiInteropRenderTarget object is not created directly. Instead, use the QueryInterface method of an existing render target instance.

参考:Draw GDI Content to a Direct2D GDI-Compatible Render Target

如果你想共享 GDI 和 Direct2D 之间的设备上下文 (HDC),当你想在它上面渲染时,你可以对这个 HDC 使用 ID2D1DCRenderTarget and Bind .

这在这个官方示例中得到了证明:GDI/Direct2D Interoperability Sample

请注意,照原样,它 compile/work 与今天的 Visual Studio 不同。所以,这里有一个类似的代码,带有一个简单的按钮和文本框:

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

这是它的渲染方式(左圈是 Direct2D,右圈是别名 GDI):