Qt Version:Qt5.2.1
Platform:windows 7
实现目的:网上很多类似的教程,但最终都会产生或多或少的负面作用,例如
窗口闪烁、需要自定义最大化方法
显示方法(就需要使用多个变量来存储窗口状态)、计算调整窗口的边界在哪(逻辑过程复杂,并且需要和窗口拖动一起判断)等等,也许还会面临更多
问题,这里不一一列出。为了能够更加“native”的方式去实现无边框可以拖动和调整大小的阴影窗口,查阅各种资料(主要参考msdn library),小有成果,切不敢独享。
实现过程:
1. 去除边框,是背景透明:
- this->setWindowFlags(Qt::FramelessWindowHint);
- this->setAttribute(Qt::WA_TranslucentBackground);
背景透明的目的是为了能够重画阴影窗口
2. 重载paintEvent函数:
- void QShadowDialog::paintEvent(QPaintEvent *)
- {
- Q_D(QShadowDialog);
- QSize verticalShadowSize = QSize(d->shadowSize, this->height() - d->shadowSize*2);
- QSize horizontalShadowSize = QSize(this->width() - shadowSize()*2, d->shadowSize);
- QSize cornerShadowSize = QSize(d->shadowSize, d->shadowSize);
- QRect leftRectangle = QRect(QPoint(0, d->shadowSize), verticalShadowSize);
- QRect leftTopRectangle = QRect(QPoint(0, 0), cornerShadowSize);
- QRect topRectangle = QRect(QPoint(d->shadowSize, 0), horizontalShadowSize);
- QRect rightTopRectangle = QRect(QPoint(this->width() - d->shadowSize, 0), cornerShadowSize);
- QRect rightRectangle = QRect(QPoint(this->width() - d->shadowSize, d->shadowSize), verticalShadowSize);
- QRect rightBottomRectangle = QRect(QPoint(this->width() - d->shadowSize, this->height() - d->shadowSize), cornerShadowSize);
- QRect bottomRectangle = QRect(QPoint(d->shadowSize, this->height() - d->shadowSize), horizontalShadowSize);
- QRect leftBottomRectangle = QRect(QPoint(0, this->height() - d->shadowSize), cornerShadowSize);
- QPainter painter(this);
- painter.drawPixmap(leftRectangle, d->shadowPixmapList[0].scaled(verticalShadowSize));
- painter.drawPixmap(leftTopRectangle, d->shadowPixmapList[1].scaled(cornerShadowSize));
- painter.drawPixmap(topRectangle, d->shadowPixmapList[2].scaled(horizontalShadowSize));
- painter.drawPixmap(rightTopRectangle, d->shadowPixmapList[3].scaled(cornerShadowSize));
- painter.drawPixmap(rightRectangle, d->shadowPixmapList[4].scaled(verticalShadowSize));
- painter.drawPixmap(rightBottomRectangle, d->shadowPixmapList[5].scaled(cornerShadowSize));
- painter.drawPixmap(bottomRectangle, d->shadowPixmapList[6].scaled(horizontalShadowSize));
- painter.drawPixmap(leftBottomRectangle, d->shadowPixmapList[7].scaled(cornerShadowSize));
- painter.setPen(Qt::NoPen);
- painter.setBrush(Qt::white);
- painter.drawRect(d->shadowSize, d->shadowSize, this->width() - d->shadowSize*2, this->height() - d->shadowSize*2);
- }
这里是为了画出阴影背景,可以参考之前360实现的代码,并非来自本人的成果。
3.响应系统消息,重载
nativeEvent(Qt4.x中,应该重载winEvent) 这里开始跟网上的大多教程不一致了,网上大多教程叫你使用mouseMoveEvent,mousePressEvent和mouseReleaseEvent,但是这样做会产生很多副作用,具体什么副作用,上面有列出。于是从
其他想法入手,查找资料发现网上一个WPF的实现,于是突发奇想,Qt也可以响应WM_消息,为何不从这里入手,把窗口移动、调整大小的工作交给操作系统处理呢?于是就有了下列代码:
- bool QShadowDialog::nativeEvent(const QByteArray& eventType, void* message, long* result)
- {
- Q_UNUSED(eventType)
- Q_D(QShadowDialog);
- MSG* param = static_cast<MSG*>(message);
- switch (param->message) {
- case WM_NCHITTEST: {
- HWND hWnd = (HWND)this->winId();
- int x = GET_X_LPARAM(param->lParam) - this->geometry().x();
- int y = GET_Y_LPARAM(param->lParam) - this->geometry().y();
- //! [1] 指定标题栏区域
- if (this->childAt(x, y)) {
- return false;
- }
- else {
- *result = HTCAPTION;
- }
- //! [1]
- //! [2] 如果窗口最大化了,则不支持resize
- if (IsZoomed(hWnd)) return true;
- if (!d->resizable) return true;
- //! [2]
- if (x > 0 && x < d->shadowSize)
- *result = HTLEFT;
- if (x > this->width() - d->shadowSize && x < this->width())
- *result = HTRIGHT;
- if (y > 0 && y < d->shadowSize)
- *result = HTTOP;
- if (y > this->height() - d->shadowSize && y < this->height())
- *result = HTBOTTOM;
- if (x > 0 && x < d->shadowSize && y > 0 && y < d->shadowSize)
- *result = HTTOPLEFT;
- if (x > this->width() - d->shadowSize && x < this->width() && y > 0 && y < d->shadowSize)
- *result = HTTOPRIGHT;
- if (x > 0 && x < d->shadowSize && y > this->height() - d->shadowSize && y < this->height())
- *result = HTBOTTOMLEFT;
- if (x > this->width() - d->shadowSize && x < this->width() && y > this->height() - d->shadowSize && y < this->height())
- *result = HTBOTTOMRIGHT;
- return true;
- }
- case WM_NCLBUTTONDBLCLK:
- {
- //! [1] 如果窗口大小不可调整,则不处理
- if (!d->resizable) return false;
- //! [1]
- HWND hWnd = (HWND)this->winId();
- if (IsZoomed(hWnd)) {
- ShowWindow(hWnd, SW_RESTORE);
- }
- else {
- ShowWindow(hWnd, SW_MAXIMIZE);
- }
- return true;
- }
- case WM_GETMINMAXINFO:
- {
- QRect rect = QApplication::desktop()->availableGeometry(this);
- MINMAXINFO* info = reinterpret_cast<MINMAXINFO*>(param->lParam);
- info->ptMaxPosition.x = rect.x() - d->shadowSize;
- info->ptMaxPosition.y = rect.y() - d->shadowSize;
- info->ptMaxSize.x = rect.width() + d->shadowSize*2;
- info->ptMaxSize.y = rect.height() + d->shadowSize*2;
- return true;
- }
- }// end switch
- return false;
- }
你可以在msdn library上查看这几个消息所代表的意思,凡是以NC开头的消息,都代表非客户区域的消息(
not client)
I. WM_NCHITTEST表示鼠标在非客户区域的位置,result返回具体的位置,例如HTCAPTION表示鼠标在标题栏,HTLEFT表示鼠标在窗口左边界,接下来操作系统会自己去处理先关
事件,相信大家很快就会知道由操作系统去处理,比我们自己去处理的好处和代码简洁度的提高。
II. WM_NCLBUTTONDBLCLK表示非客户区域双击事件,这段代码很容易懂,不做详细解释。
III. WM_GETMINMAXINFO表示操作系统需要获取窗口最大化最小化的一些信息,为什么需要处理这个消息呢,事实是这样的,由于自绘了阴影
部分,算在客户区域,当不处理这个消息的时候,最大化的话会发现,窗口会遮挡任务栏,并且阴影在出现在最大化的状态当中,这样就不太符合要求了。幸运的是,微软提供了响应的处理来获取最大化信息。代码很容易懂,不做详细解释。
4.支持修改阴影大小
- void QShadowDialog::setShadowSize(int shadowSize)
- {
- Q_D(QShadowDialog);
- if (shadowSize > 0 && shadowSize < 20) {
- d->shadowSize = shadowSize;
- this->update();
- }
- else {
- qWarning() << "Shadow size bigger than 20px or less than 0px will be throw!";
- }
- }
实现这一功能看起来会更加的“人性化”吧。
后续:该类实现的还不是很完美,在我的计划中,我还需要支持指定背景色、支持Aero Snap功能等,关于Aero Snap功能,我怀疑这是Qt的一个bug,已经看到有人提交了Aero Snap的bug在Qt网站上,当然如果有大拿能够提供Aero Snap的实现方法,本人感激不尽。
提供附件
下载:附件的名称是我要实现的一个伟大计划,相信大家根据名字也知道了:
QtWidgetsExtend.rar (17 K) 下载次数:207