将CEF官方Demo中的tests/cefclient/browser/root_window_win.cc下列代码拷贝到自己的离屏渲染项目中,并做修改。

核心原理则是通过OnDraggableRegionsChanged函数获得当前可拖拽区域,再通过劫持窗口消息,实现窗口拖拽。

kDraggableControl是一个自己扩展的属性,用来存储当前的控件指针,这样在劫持窗口消息的时候可以获取到当前控件的信息。使用__try的目的是防止程序释放顺序错误导致控件优先释放掉,导致程序崩溃。

当在WM_NCHITTEST消息中,如果是在拖拽区域内,则返回HTCAPTION,这样就可以实现窗口拖拽了。


namespace
{

LPCWSTR kParentWndProc = L"CefParentWndProc";
LPCWSTR kDraggableRegion = L"CefDraggableRegion";
LPCWSTR kDraggableControl = L"CefDraggableControl";

LRESULT CALLBACK SubClassedWindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    const auto hParentWndProc = reinterpret_cast<WNDPROC>(::GetPropW(hWnd, kParentWndProc));
    const auto hRegion = static_cast<HRGN>(::GetPropW(hWnd, kDraggableRegion));
    const auto pControl = static_cast<SCefBrowserBaseEx *>(::GetPropW(hWnd, kDraggableControl));

    if (message == WM_NCHITTEST)
    {
        const LRESULT hit = CallWindowProc(hParentWndProc, hWnd, message, wParam, lParam);
        if (hit == HTCLIENT)
        {
            CRect rect;
            __try
            {
                rect = pControl->GetClientRect();
            }
            __except (EXCEPTION_EXECUTE_HANDLER)
            {
                return hit;
            }
            const auto [x, y] = MAKEPOINTS(lParam);
            POINT point = { x - rect.left, y - rect.top };
            ::ScreenToClient(hWnd, &point);
            if (::PtInRegion(hRegion, point.x, point.y))
            {
                return HTCAPTION;
            }
        }
        return hit;
    }

    return CallWindowProc(hParentWndProc, hWnd, message, wParam, lParam);
}

void SubclassWindow(HWND hWnd, HRGN hRegion, const SCefBrowserBaseEx *control)
{
    if (::GetPropW(hWnd, kParentWndProc))
    {
        return;
    }

    SetLastError(0);
    const LONG_PTR hOldWndProc = SetWindowLongPtr(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(SubClassedWindowProc));
    if (hOldWndProc == 0 && GetLastError() != ERROR_SUCCESS)
    {
        return;
    }
    ::SetPropW(hWnd, kParentWndProc, reinterpret_cast<HANDLE>(hOldWndProc));
    ::SetPropW(hWnd, kDraggableRegion, reinterpret_cast<HANDLE>(hRegion));
    ::SetPropW(hWnd, kDraggableControl, (HANDLE)control);
}

void UnSubclassWindow(const HWND hWnd)
{
    if (const auto hParentWndProc = reinterpret_cast<LONG_PTR>(::GetPropW(hWnd, kParentWndProc)))
    {
        const auto hPreviousWndProc = SetWindowLongPtr(hWnd, GWLP_WNDPROC, hParentWndProc);
        ALLOW_UNUSED_LOCAL(hPreviousWndProc);
        DCHECK_EQ(hPreviousWndProc, reinterpret_cast<LONG_PTR>(SubClassedWindowProc));
    }
    ::RemovePropW(hWnd, kParentWndProc);
    ::RemovePropW(hWnd, kDraggableRegion);
    ::RemovePropW(hWnd, kDraggableControl);
}

BOOL CALLBACK SubclassWindowsProc(const HWND hWnd, HRGN hRegion, const SCefBrowserBaseEx *control)
{
    SubclassWindow(hWnd, hRegion, control);
    return TRUE;
}

BOOL CALLBACK UnSubclassWindowsProc(const HWND hWnd, HRGN hRegion, const SCefBrowserBaseEx *control)
{
    UnSubclassWindow(hWnd);
    return TRUE;
}

} // namespace 支持-webkit-app-region CSS属性

void SCefBrowserBaseEx::OnDraggableRegionsChanged(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, const std::vector<CefDraggableRegion> &regions)
{
    ::SetRectRgn(m_draggableRegion, 0, 0, 0, 0);
    std::vector<CefDraggableRegion>::const_iterator it = regions.begin();
    for (; it != regions.end(); ++it)
    {
        const HRGN region = ::CreateRectRgn(it->bounds.x, it->bounds.y, it->bounds.x + it->bounds.width, it->bounds.y + it->bounds.height);
        ::CombineRgn(m_draggableRegion, m_draggableRegion, region, it->draggable ? RGN_OR : RGN_DIFF);
        ::DeleteObject(region);
    }
    if (const auto hWnd = GetHostHwnd())
    {
        const auto proc = !regions.empty() ? SubclassWindowsProc : UnSubclassWindowsProc;
        proc(hWnd, m_draggableRegion, this);
    }
}