qtwinapimfcqt6nativewindow

Qt child native window blocks native event messages for a native window parent


I have a top-level frameless window, and I want my application to have the Aero Snap Windows feature. So I needed to mix Qt and WinAPI. I used the approach from here https://github.com/Bringer-of-Light/Qt-Nice-Frameless-Window, which is based on overriding nativeEvent. So my frameless window is based on QMainWindow and I override nativeEvent.

bool CFramelessWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr* result) {
MSG* msg = static_cast<MSG*>(message);
auto tmp = msg->hwnd;
//msg->hwnd = (HWND)this->m_winId;

switch (msg->message)
{
case WM_NCCALCSIZE:
{
    NCCALCSIZE_PARAMS& params = *reinterpret_cast<NCCALCSIZE_PARAMS*>(msg->lParam);
    if (params.rgrc[0].top != 0)
        params.rgrc[0].top -= 1;

    return true;
}
case WM_NCHITTEST:
{
    *result = 0;

    const LONG border_width = m_borderWidth;
    RECT winrect;
    GetWindowRect(HWND(winId()), &winrect);

    long x = GET_X_LPARAM(msg->lParam);
    long y = GET_Y_LPARAM(msg->lParam);

    if (m_bResizeable)
    {

        bool resizeWidth = minimumWidth() != maximumWidth();
        bool resizeHeight = minimumHeight() != maximumHeight();

        if (resizeWidth)
        {
            //left border
            if (x >= winrect.left && x < winrect.left + border_width)
            {
                *result = HTLEFT;
            }
            //right border
            if (x < winrect.right && x >= winrect.right - border_width)
            {
                *result = HTRIGHT;
            }
        }
        if (resizeHeight)
        {
            //bottom border
            if (y < winrect.bottom && y >= winrect.bottom - border_width)
            {
                *result = HTBOTTOM;
            }
            //top border
            if (y >= winrect.top && y < winrect.top + border_width)
            {
                *result = HTTOP;
            }
        }
        if (resizeWidth && resizeHeight)
        {
            //bottom left corner
            if (x >= winrect.left && x < winrect.left + border_width &&
                y < winrect.bottom && y >= winrect.bottom - border_width)
            {
                *result = HTBOTTOMLEFT;
            }
            //bottom right corner
            if (x < winrect.right && x >= winrect.right - border_width &&
                y < winrect.bottom && y >= winrect.bottom - border_width)
            {
                *result = HTBOTTOMRIGHT;
            }
            //top left corner
            if (x >= winrect.left && x < winrect.left + border_width &&
                y >= winrect.top && y < winrect.top + border_width)
            {
                *result = HTTOPLEFT;
            }
            //top right corner
            if (x < winrect.right && x >= winrect.right - border_width &&
                y >= winrect.top && y < winrect.top + border_width)
            {
                *result = HTTOPRIGHT;
            }
        }
    }
    if (*result) return true;

    //*result still equals 0, that means the cursor locate OUTSIDE the frame area
    //but it may locate in titlebar area
    if (!m_titlebar) 
        return false;

    //support highdpi
    double dpr = this->devicePixelRatioF();
    QPoint pos = m_titlebar->mapFromGlobal(QPoint(x / dpr, y / dpr));

    if (!m_titlebar->rect().contains(pos)) return false;
    QWidget* child = m_titlebar->childAt(pos);
    if (!child)
    {
        *result = HTCAPTION;
        return true;
    }
    else 
    {
        if (m_whiteList.contains(child))
        {
            *result = HTCAPTION;
            return true;
        }
    }
    return false;
} 
case WM_GETMINMAXINFO:
{
    if (::IsZoomed(msg->hwnd)) 
    {
        RECT frame = { 0, 0, 0, 0 };
        AdjustWindowRectEx(&frame, WS_OVERLAPPEDWINDOW, FALSE, 0);

        //record frame area data
        double dpr = this->devicePixelRatioF();

        m_frames.setLeft(abs(frame.left) / dpr + 0.5);
        m_frames.setTop(abs(frame.bottom) / dpr + 0.5);
        m_frames.setRight(abs(frame.right) / dpr + 0.5);
        m_frames.setBottom(abs(frame.bottom) / dpr + 0.5);

        QMainWindow::setContentsMargins(m_frames.left() + m_margins.left(), \
            m_frames.top() + m_margins.top(), \
            m_frames.right() + m_margins.right(), \
            m_frames.bottom() + m_margins.bottom());
        m_bJustMaximized = true;
    }
    else 
    {
        if (m_bJustMaximized)
        {
            QMainWindow::setContentsMargins(m_margins);
            m_frames = QMargins();
            m_bJustMaximized = false;
        }
    }
    return false;
}
default:
    return false;
}

}

It works like a charm, if the central widget of my frameless wrapper isn't native as well. If it is (I need it to be native), the reimplementation of nativeEvent of the top-level window doesn't get the required messages, so the widget becomes unresizable and unmovable. If I override nativeEvent for the central widget, I see that I get the correct message when I hit the child, but I get nothing when I hit the frameless parent window. I found that I could installNativeEventFilter and then call CFramelessWindow::nativeEvent directly inside nativeEventFilter(), so that I would actually pass the required message into the right place, but the result looks definetely broken. It looks like I resize only the child of the frameless wrapper, not the whole main window.
resizing problems

Then I found, that every child element got it's own window descriptor (HWND), so, for example, if I hit the menubar, it has a different HWND than the main window has. I tried to use msg->hwnd = (HWND)this->m_winId;, and it indeed fixes the frameless features and aerosnap also, but it breakes all other events of the application.

I'm really stuck. What am I doing wrong?

Thanks in advance. Just in case, I apologize for the incorrect terminology.


Solution

  • This is what solved my problem with descriptors of any child of the frameless window:

    int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    a.setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
    //...
    

    }