nyyzpp |
2014-03-03 00:09 |
更加“Native”的方式实现无边框可拖动调整大小的阴影窗口
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的实现方法,本人感激不尽。
提供附件下载:附件的名称是我要实现的一个伟大计划,相信大家根据名字也知道了:[attachment=11774]
|
|