Qt quick实现无边框可拖拽风格

由来:

    现在大量的软件采用简洁的风格,即移除系统的标题栏及边框并自己实现标题栏以达到定制的目的,由于我们写要用Qt写一个类似IM的软件,故有此需求。

方法:

去除系统边框比较简单,setFlags(Qt::Window | Qt::FramelessWindowHint);即可,但下一步要实现窗体的拖拽和缩放。

1,最简单的方法:

    这种方法比较简单直观,即点击鼠标的时候记录下鼠标位置,然后在鼠标移动的时候对窗体进行移动。示例代码如下:

 MouseArea { //为窗口添加鼠标事件
    property int xMouse //存储鼠标x坐标
    property int yMouse //存储鼠标y坐标
    anchors.fill: parent
    acceptedButtons: Qt.LeftButton //只处理鼠标左键
    drag.filterChildren: true
    onPressed: { //接收鼠标按下事件
        xMouse = mouse.x
        yMouse = mouse.y
    }
    onPositionChanged: { //鼠标按下后改变位置
        loginScreen.x = loginScreen.x + (mouse.x - xMouse)
        loginScreen.y = loginScreen.y + (mouse.y - yMouse)
    }
}

    以上方法简单直观,但当窗体比较复杂的时候将发生窗体移动比较迟钝,甚至,当从上标题快速拖拽窗体时,鼠标移出窗体,而窗体不再移动的问题。
    网上的一个案例,大家可以参考http://blog.sina.com.cn/s/blog_a6fb6cc90101au8r.html

2,改进方案:

    观察系统默认的带标题栏的窗体,发现,拖动窗体时,窗体并不移动,而是由一个矩形框随着鼠标移动,当松开鼠标后窗体才进行一次性地移动。
    于是,有了一个可行的方案(因为后面有更优的方案,但是如果后面的方案不行,这个方案可能成为最后的方案),当拖拽的时候绘制一个移动的和原窗体等大小的矩形进行移动,最后再一次性地移动(相信系统的拖拽也是采用类似的方案)。具体实施略,可以自行测试。

3,继续改进方案,应该是一个较优方案:

    具体原理我也是参照这个博客http://blog.sina.com.cn/s/blog_6288219501015dwa.html,讲得比较详细,虽然是mfc程序,但毕竟也是windows程序,原理相同。
    摘抄一段:

当窗口确定鼠标位置时,Windows向窗口发送WM_NCHITTEST消息,可以处理该消息,使得只要鼠标在窗口内,Windows便认为鼠标在标题条上。这需要重载CWnd类处理WM_NCHITTEST消息的OnNcHitTest函数,在函数中调用父类的该函数,如果返回HTCLIENT,说明鼠标在窗口客户区内,使重载函数返回HTCAPTION,使Windows误认为鼠标处于标题条上。

UINT MyWndDlg::OnNcHitTest(CPoint point)
{
    // 取得鼠标所在的窗口区域
    UINT nHitTest = CDialog::OnNcHitTest(point);

    // 如果鼠标在窗口客户区,则返回标题条代号给Windows
    // 使Windows按鼠标在标题条上类进行处理,即可单击移动窗口
    return (nHitTest==HTCLIENT) ? HTCAPTION : nHitTest;
}

    知道了原理咱便可以开工了!!
    接着又发现了如下例子:http://blog.csdn.net/kfbyj/article/details/9284923,不过貌似博主文章是在Qt4时写的,现在早就没有MainWindow::winEvent(MSG *message, long *result)函数了呀!!
    不过既然Qt堵了一扇门,他一定会打开另一扇窗,于是有了nativeEvent函数~~
    参看该博客http://blog.csdn.net/tujiaw/article/details/43836039,哈哈,貌似一切都搞定了~ 确实,我们可以开始写自己的无边框窗体了~~
    上代码:

#include "framelessWindow.h"
#include 

FramelessWindow::FramelessWindow(QWindow *parent) : QQuickWindow(parent)
{
    setFlags(Qt::Window | Qt::FramelessWindowHint);
}

FramelessWindow::~FramelessWindow()
{

}

void FramelessWindow::setTitleBar(QQuickItem *titleBar)
{
    m_pTitleBar = titleBar;
}

bool FramelessWindow::nativeEvent(const QByteArray &eventType, void *message, long *result)
{
    Q_UNUSED(eventType);
    if ( windowState() && Qt::WindowMaximized) {
        return false;
    }

    const int HIT_BORDER = 8;
    const MSG *msg=static_cast(message);
    if(msg->message == WM_NCHITTEST) {
        int xPos = ((int)(short)LOWORD(msg->lParam)) - this->frameGeometry().x();
        int yPos = ((int)(short)HIWORD(msg->lParam)) - this->frameGeometry().y();

        if (m_pTitleBar && m_pTitleBar->contains(QPointF(xPos,yPos))) {
            *result = HTCAPTION;
        }
        if (m_pTitleBar) {
            if (m_pTitleBar->contains(QPointF(xPos,yPos)) && !m_pTitleBar->childAt(xPos,yPos)) {
                *result = HTCAPTION;
                return true;
            }
        } else {
            /*
            if(contentItem()->childItems()[0]->childAt(xPos,yPos) == 0)
            {
                *result = HTCAPTION;
            }*/
        }

        auto child = contentItem()->childAt(xPos,yPos);
        if(child)
        {
            if (child != m_pTitleBar) {
                return false;
            } else {
                if (m_pTitleBar && !m_pTitleBar->childAt(xPos,yPos)) {
                    *result = HTCAPTION;
                    return true;
                }
                return false;
            }
        }

        if(xPos > 0 && xPos < HIT_BORDER) {
            *result = HTLEFT;
        }
        if(xPos > (this->width() - HIT_BORDER) && xPos < (this->width() - 0)) {
            *result = HTRIGHT;
        }
        if(yPos > 0 && yPos < HIT_BORDER) {
            *result = HTTOP;
        }
        if(yPos > (this->height() - HIT_BORDER) && yPos < (this->height() - 0)) {
            *result = HTBOTTOM;
        }
        if(xPos > 0 && xPos < HIT_BORDER && yPos > 0 && yPos < HIT_BORDER) {
            *result = HTTOPLEFT;
        }
        if(xPos > (this->width() - HIT_BORDER) && xPos < (this->width() - 0) && yPos > 0 && yPos < HIT_BORDER) {
            *result = HTTOPRIGHT;
        }
        if(xPos > 0 && xPos < HIT_BORDER && yPos > (this->height() - HIT_BORDER) && yPos < (this->height() - 0)) {
            *result = HTBOTTOMLEFT;
        }
        if(xPos > (this->width() - HIT_BORDER) && xPos < (this->width() - 0) && yPos > (this->height() - HIT_BORDER) && yPos < (this->height() - 0)) {
            *result = HTBOTTOMRIGHT;
        }
        return true;
    }
    return false;
}

    大体和前人的代码类似,略有些小改动,下面会说这些改动的原因。

4,进一步,将c++类导出到qml中使用:

    其实下一步相对来说比较简单,套路即可:在main函数中加入如下代码:

qmlRegisterType(“com.FramelessWindow”, 1, 0, “FramelessWindow”);

话说要记得FramelessWindow得继承自QObject才能导出哦,还有,我们的类得是用在主窗体下,所以继承自QQuickWindow

#ifndef MYWINDOW_H
#define MYWINDOW_H

#include 

class FramelessWindow : public QQuickWindow
{
    Q_OBJECT
public:
    explicit FramelessWindow(QWindow *parent = 0);
    ~FramelessWindow();
    Q_INVOKABLE void setTitleBar(QQuickItem *titleBar);
signals:

public slots:

    // QWidget interface
protected:
    bool nativeEvent(const QByteArray &eventType, void *message, long *result);
private:
    QQuickItem *m_pTitleBar;
};

#endif // MYWINDOW_H

    于是一切搞定,最后附上具体使用的qml

import QtQuick.Window 2.2
import QtWebEngine 1.2
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtWebChannel 1.0
import com.FramelessWindow 1.0

FramelessWindow {
    width: 500
    height: 859
    id:window

    TitleBar {
        id: titleBar
        anchors.top: parent.top
        anchors.left: parent.left
        width: parent.width
        height: 100
        color: "blue"
    }

    Component.onCompleted: {
        window.setTitleBar(titleBar)
    }    
}

    看,我们指定了一个TitleBar(其实就是一个Rectangle),注意setTitleBar一定得是Q_INVOKABLE或者定义为slot,否则qml中无法调用!
还有这段

if (m_pTitleBar && m_pTitleBar->contains(QPointF(xPos,yPos))) {
    *result = HTCAPTION;
}
if (m_pTitleBar) {
    if (m_pTitleBar->contains(QPointF(xPos,yPos)) && !m_pTitleBar->childAt(xPos,yPos)) {
        *result = HTCAPTION;
        return true;
    }
} else {
    /*
    if(contentItem()->childItems()[0]->childAt(xPos,yPos) == 0)
    {
        *result = HTCAPTION;
    }*/
}

    用于判断是否设置了titlebar,而且点击位置十分在控件下,比较简单~~

你可能感兴趣的:(Qt)