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