查看完整版本: [-- 更加“Native”的方式实现无边框可拖动调整大小的阴影窗口 --]

QTCN开发网 -> Qt代码秀 -> 更加“Native”的方式实现无边框可拖动调整大小的阴影窗口 [打印本页] 登录 -> 注册 -> 回复主题 -> 发表主题

nyyzpp 2014-03-03 00:09

更加“Native”的方式实现无边框可拖动调整大小的阴影窗口

Qt Version:Qt5.2.1
Platform:windows 7
实现目的:网上很多类似的教程,但最终都会产生或多或少的负面作用,例如窗口闪烁、需要自定义最大化方法显示方法(就需要使用多个变量来存储窗口状态)、计算调整窗口的边界在哪(逻辑过程复杂,并且需要和窗口拖动一起判断)等等,也许还会面临更多问题,这里不一一列出。为了能够更加“native”的方式去实现无边框可以拖动和调整大小的阴影窗口,查阅各种资料(主要参考msdn library),小有成果,切不敢独享。
实现过程:
1. 去除边框,是背景透明:
  1. this->setWindowFlags(Qt::FramelessWindowHint);
    this->setAttribute(Qt::WA_TranslucentBackground);


    背景透明的目的是为了能够重画阴影窗口

2. 重载paintEvent函数:
  1. 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_消息,为何不从这里入手,把窗口移动、调整大小的工作交给操作系统处理呢?于是就有了下列代码:

  1. 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.支持修改阴影大小

  1. 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]

nyyzpp 2014-03-03 22:19
我今天又重新实现了改类,使用了更加合理的方式,稍后贴出来

windywater 2014-03-04 21:55
Mac和Linux下怎么使用?

woniu600 2014-03-07 20:20
Mac和Linux下怎么使用?

mxcai2005 2014-03-19 16:22
mark  ..............

gzfstudy 2014-04-08 17:59

hcaihao 2014-04-10 16:20
这种方法有个bug,困扰我很久了

http://www.qtcn.org/bbs/read-htm-tid-56224.html

姜小白 2016-04-23 10:15
  

tonyzhou1985 2016-05-24 09:44
如果往客户区添加一个qwebview,并加载了页面的话,就无效了。

sunboseo 2016-10-24 09:13
看起来很不错

qxiaoyu718 2016-11-11 11:10
给力帖子 顶

shokokawaii 2023-10-22 09:11


查看完整版本: [-- 更加“Native”的方式实现无边框可拖动调整大小的阴影窗口 --] [-- top --]



Powered by phpwind v8.7 Code ©2003-2011 phpwind
Gzip disabled