前言
我们今天实现一个无边框,带窗口阴影,可拖拽、伸缩的窗口。
第一步
去除qt窗口的边框、工具栏等属性。我们的窗口的类Window,继承于QWidget这个基础的控件类,然后直接在构造函数内一句函数搞定:
Window::Window(const QSize &initSize, QWidget *parent)
: QWidget(parent)
{
// 设置窗口大小
resize(initSize);
// 去除边框
setWindowFlags(Qt::Window |Qt::FramelessWindowHint);
}
在主程序内直接show这个窗口类:
int main(int argc, char **argv)
{
QApplication app(argc, argv);
Window *mainWindow = new Window(QSize(994,650);
mainWindow->show();
return app.exec();
}
效果如下(桌面背景为白色):
第二步,给它套个阴影
Window::Window(const QSize &initSize, QWidget *parent)
: QWidget(parent)
{
resize(initSize);
setWindowFlags(Qt::Window |Qt::FramelessWindowHint);
HWND hwnd = reinterpret_cast<HWND>(winId());
LONG style = static_cast<LONG>( WS_POPUP | WS_CAPTION | (true ? WS_MINIMIZEBOX : 0) | (true ? WS_MAXIMIZEBOX : 0) | WS_THICKFRAME | WS_CLIPCHILDREN );
::SetWindowLongPtr(hwnd, GWL_STYLE, style);
const MARGINS shadow = {1, 1, 1, 1};
DwmExtendFrameIntoClientArea(hwnd, &shadow);
}
效果如下:
第三步,加上实现可拖拽、伸缩效果
原理是重写nativeEvent函数,处理windows下的鼠标事件,其实也就是把窗口边界的事件转化为Windows的窗口事件
bool Window::nativeEvent(const QByteArray &eventType, void *message, long *result)
{
if (eventType != "windows_generic_MSG")
return false;
MSG* msg = reinterpret_cast<MSG*>(message);
QWidget* widget = QWidget::find(reinterpret_cast<WId>(msg->hwnd));
if (!widget)
return false;
switch (msg->message)
{
case WM_NCCALCSIZE:
{
*result = 0;
return true;
}
case WM_NCHITTEST:
{
const LONG borderWidth = 9;
RECT winrect;
::GetWindowRect(msg->hwnd, &winrect);
long x = GET_X_LPARAM(msg->lParam);
long y = GET_Y_LPARAM(msg->lParam);
if (m_resizeable && m_freeSizeable)
{
// bottom left
if (x >= winrect.left && x < winrect.left + borderWidth &&
y < winrect.bottom && y >= winrect.bottom - borderWidth)
{
*result = HTBOTTOMLEFT;
return true;
}
// bottom right
if (x < winrect.right && x >= winrect.right - borderWidth &&
y < winrect.bottom && y >= winrect.bottom - borderWidth)
{
*result = HTBOTTOMRIGHT;
return true;
}
// top left
if (x >= winrect.left && x < winrect.left + borderWidth &&
y >= winrect.top && y < winrect.top + borderWidth)
{
*result = HTTOPLEFT;
return true;
}
// top right
if (x < winrect.right && x >= winrect.right - borderWidth &&
y >= winrect.top && y < winrect.top + borderWidth)
{
*result = HTTOPRIGHT;
return true;
}
// left
if (x >= winrect.left && x < winrect.left + borderWidth)
{
*result = HTLEFT;
return true;
}
// right
if (x < winrect.right && x >= winrect.right - borderWidth)
{
*result = HTRIGHT;
return true;
}
// bottom
if (y < winrect.bottom && y >= winrect.bottom - borderWidth)
{
*result = HTBOTTOM;
return true;
}
// top
if (y >= winrect.top && y < winrect.top + borderWidth)
{
*result = HTTOP;
return true;
}
}
// 高分屏
double dpr = this->devicePixelRatioF();
QPoint pos = mapFromGlobal(QPoint(static_cast<int>(x/dpr),
static_cast<int>(y/dpr)));
// 全区域移动,前提是窗口内没有子控件,有的话需要加入m_ignoreList
if (m_fullAreaMoveable)
{
if (!rect().contains(pos)) return false;
QWidget *curParent = this;
while (1)
{
QWidget* child = curParent->childAt(pos);
if (!child)
{
*result = HTCAPTION;
return true;
}
else
{
// 当鼠标在m_ignoreList列表的窗口子控件内时才能拖动窗口
if (m_ignoreList.contains(child))
{
curParent = child;
continue;
}
else
break;
}
}
break;
}
// 标题栏移动
if (!m_titleBar) return false;
if (!m_titleBar->rect().contains(pos)) return false;
QWidget* child = m_titleBar->childAt(pos);
if (!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);
frame.left = abs(frame.left);
frame.top = abs(frame.bottom);
widget->setContentsMargins(frame.left, frame.top, frame.right, frame.bottom);
}
else
{
widget->setContentsMargins(0, 0, 0, 0);
}
// 限定最小及最大窗口
reinterpret_cast<MINMAXINFO *>(msg->lParam)->ptMinTrackSize.x = minimumWidth();
reinterpret_cast<MINMAXINFO *>(msg->lParam)->ptMinTrackSize.y = minimumHeight();
reinterpret_cast<MINMAXINFO *>(msg->lParam)->ptMaxTrackSize.x = maximumWidth();
reinterpret_cast<MINMAXINFO *>(msg->lParam)->ptMaxTrackSize.y = maximumHeight();
*result = ::DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam);
return true;
}
default:
break;
}
return QWidget::nativeEvent(eventType, message, result);
}
// 加入全局拖拽时的控件忽略列表
void Window::AddToMoveableIgnoreList(QWidget *widget)
{
m_ignoreList.append(widget);
}
// 使能全局拖拽
void Window::SetFullAreaMoveable(bool enable)
{
m_fullAreaMoveable = enable;
}
这样调用窗口
int main(int argc, char **argv)
{
QApplication app(argc, argv);
Window *mainWindow = new Window(QSize(994, 650));
mainWindow->SetFullAreaMoveable(true);
mainWindow->SetResizeable(true);
mainWindow->show();
return app.exec();
}
效果如下: