QtQuick實(shí)現(xiàn)無邊框窗口的拉伸,拖拽和自定義標(biāo)題欄

由于Qt的原生窗口帶有的標(biāo)題欄無法定制,然而通常情況下我們需要自定義窗體上的關(guān)閉、最小化等按鈕、背景,甚至需要不需要標(biāo)題欄。在QtQuick實(shí)現(xiàn)去除標(biāo)題欄,也即無邊框很簡(jiǎn)單,只需要在Qml-Window中設(shè)置:

flags: Qt.Window | Qt.FramelessWindowHint | Qt.WindowMinimizeButtonHint

但是這個(gè)時(shí)候的窗體不能拖拽,也不能在窗體的邊緣進(jìn)行拉伸。所以需要實(shí)現(xiàn)窗體的邊緣事件進(jìn)行拉伸,同時(shí)在這里也實(shí)現(xiàn)了一個(gè)帶有最小化,最大化和關(guān)閉的自定義標(biāo)題欄

  • 首先看自定義標(biāo)題欄代碼TitleBar.qml
import QtQuick 2.7
import QtQuick.Controls 2.13
import QtQuick.Templates 2.12 as T
import QtQuick.Window 2.2

Rectangle{
    property bool  isMaximized: false
    Connections{
        target: mainWindow
        onVisibilityChanged: {//解決Qt窗口最大化的時(shí)候最小化,再恢復(fù)窗口變?yōu)槠胀ù翱诘腷ug
            if(isMaximized && visibility === 2) {
                mainWindow.showMaximized()
            }
        }
    }
    MouseArea{
        anchors.fill: parent
        acceptedButtons: Qt.LeftButton //只處理鼠標(biāo)左鍵
        property bool   isDoubleClicked:false
        property point clickPos: "0,0"
         onPressed:
         {
             isDoubleClicked = false;
             clickPos = Qt.point(mouse.x,mouse.y)
         }
         onPositionChanged: {
             if(!isDoubleClicked && pressed && mainWindow.visibility !== Window.Maximized && mainWindow.visibility !== Window.FullScreen) {
                 var delta = Qt.point(mouse.x-clickPos.x, mouse.y-clickPos.y)
                 mainWindow.x += delta.x
                 mainWindow.y += delta.y
             }
             if(mainWindow.visibility === Window.Maximized && pressed && !isDoubleClicked)
             {
                 isMaximized = false;
                 mainWindow.showNormal();
                 normBtnBg.source  = "qrc:/res/maximinze_btn.png"
             }
         }
         onDoubleClicked :
         {
             isDoubleClicked = true; // 這個(gè)時(shí)候一定不能響應(yīng)onPositionChanged不然會(huì)一直置頂。
             if(isMaximized){
                 isMaximized = false;
                 mainWindow.showNormal();
                 normBtnBg.source  = "qrc:/res/maximinze_btn.png"
             }else{
                 isMaximized = true;
                 mainWindow.showMaximized();
                 normBtnBg.source = "qrc:/res/norm_btn.png"
             }
         }
     }

    Button{
        id:closeBtn
        anchors.right: parent.right
        width: parent.height
        height: parent.height - 1
        hoverEnabled : true
        background: Rectangle{
            color: closeBtn.hovered ? (closeBtn.pressed ? "#DE0000" : "#F91515") : "transparent"
            Image{
                id: closeBtnImg
                width: parent.height * 0.3; height: parent.height * 0.3
                anchors.centerIn: parent
                source: "qrc:/res/close_btn.png"
            }
        }
        onClicked: {
            mainWindow.close()
        }
    }
    Button{
        id: normBtn
        anchors.right: closeBtn.left
        width: parent.height
        height: parent.height - 1
        hoverEnabled : true
        background: Rectangle{
            color: normBtn.hovered ? (normBtn.pressed ? "#4E4E4E" : "#666666") : "transparent"
            //color: "black"
            Image{
                id: normBtnBg
                width: parent.height * 0.3; height: parent.height * 0.3
                anchors.centerIn: parent
                source: "qrc:/res/maximinze_btn.png"
            }
        }
        onClicked:{
            if(isMaximized){
                isMaximized = false;
                mainWindow.showNormal();
                normBtnBg.source = "qrc:/res/maximinze_btn.png"
            }else{
                isMaximized = true;
                mainWindow.showMaximized();
                normBtnBg.source = "qrc:/res/norm_btn.png"
            }
        }
    }
    Button{
        id: minBtn
        anchors.right: normBtn.left
        width: parent.height
        height: parent.height - 1
        hoverEnabled : true
        background: Rectangle{
            color: minBtn.hovered ? (minBtn.pressed ? "#4E4E4E" : "#666666") : "transparent"
            Image{
                width: parent.height * 0.3; height: parent.height * 0.3
                anchors.centerIn: parent
                source: "qrc:/res/min_btn.png"
            }
        }
        onClicked:{
            mainWindow.showMinimized();
        }
    }
}

標(biāo)題欄實(shí)現(xiàn)起來比較簡(jiǎn)單,主要有幾個(gè)功能:最大化,最小化、普通化(normal)以及雙擊最大化、雙擊恢復(fù)普通窗口。這里用一個(gè)兩個(gè)變量:isMaximized和isDoubleClicked來記錄窗體狀態(tài)和雙擊狀態(tài)。然后分別實(shí)現(xiàn)其事件邏輯即可。這里需要主要的是Qt的一個(gè)bug,也就是在窗口時(shí)最大化,然后再恢復(fù)窗口,這時(shí)窗體變成普通窗口了(正常應(yīng)該還是最大窗口),這個(gè)bug在5.13和5.13.1還沒被修復(fù),bug請(qǐng)看這里
在這里我們可以用自己用代碼修復(fù),即我們可以判斷窗體在最小化恢復(fù)時(shí)候,如果這個(gè)時(shí)候窗體是最大化并且是Windowed(值為2)這個(gè)時(shí)候?qū)⑵渥畲蠡纯?。具體代碼可看上面onVisibilityChanged這一行代碼

  • 窗體的邊緣拉伸和拖拽主要實(shí)現(xiàn)在ResizeItem和ResizeQmlWindow,ResizeQmlWindow類主要實(shí)現(xiàn)設(shè)置鼠標(biāo)的指針狀態(tài)而已,具體檢測(cè)鼠標(biāo)的邊緣事件在ResizeItem.qml里,如下所示
import QtQuick 2.0
import QtQuick.Window 2.2

Item {
    property int enableSize: 4
    property bool isPressed: false
    property point customPoint

    //左上角
    Item {
        id: leftTop
        width: enableSize
        height: enableSize
        anchors.left: parent.left
        anchors.top: parent.top
        z: 1000
        MouseArea {
            anchors.fill: parent
            hoverEnabled: true

            onPressed: press(mouse)
            onEntered: enter(1)
            onReleased: release()
            onPositionChanged: positionChange(mouse, -1, -1)
        }
    }

    //Top
    Item {
        id: top
        height: enableSize
        anchors.left: leftTop.right
        anchors.right: rightTop.left
        anchors.top: parent.top
        z: 1000
        MouseArea {
            anchors.fill: parent
            hoverEnabled: true

            onPressed: press(mouse)
            onEntered: enter(2)
            onReleased: release()

            onMouseYChanged: positionChange(Qt.point(customPoint.x, mouseY), 1, -1)
        }
    }

    //右上角
    Item {
        id: rightTop
        width: enableSize
        height: enableSize
        anchors.right: parent.right
        anchors.top: parent.top
        z: 1000
        MouseArea {
            anchors.fill: parent
            hoverEnabled: true

            onPressed: press(mouse)
            onEntered: enter(3)
            onReleased: release()
            onPositionChanged: positionChange(mouse, 1, -1)
        }
    }

    //Left
    Item {
        id: left
        width: enableSize
        anchors.left: parent.left
        anchors.top: leftTop.bottom
        anchors.bottom: leftBottom.top
        z: 1000
        MouseArea {
            anchors.fill: parent
            hoverEnabled: true

            onPressed: press(mouse)
            onEntered: enter(4)
            onReleased: release()

            onMouseXChanged: positionChange(Qt.point(mouseX, customPoint.y), -1, 1)
        }
    }

    //Center - 5
    Item {
        id: center
        anchors.left: left.right
        anchors.right: right.left
        anchors.top: top.bottom
        anchors.bottom: bottom.top
        MouseArea {
            anchors.fill: parent

            property point clickPos
            onPressed: clickPos = Qt.point(mouse.x,mouse.y)
            onPositionChanged: {
                if(pressed && mainWindow.visibility !== Window.Maximized && mainWindow.visibility !== Window.FullScreen) {
                    var delta = Qt.point(mouse.x-clickPos.x, mouse.y-clickPos.y)
                    mainWindow.x += delta.x
                    mainWindow.y += delta.y
                }
            }
        }
    }

    //Right
    Item {
        id: right
        width: enableSize
        anchors.right: parent.right
        anchors.top: rightTop.bottom
        anchors.bottom: rightBottom.top
        z: 1000
        MouseArea {
            anchors.fill: parent
            hoverEnabled: true

            onPressed: press(mouse)
            onEntered: enter(6)
            onReleased: release()

            onMouseXChanged: positionChange(Qt.point(mouseX, customPoint.y), 1, 1)
        }
    }

    //左下角
    Item {
        id: leftBottom
        width: enableSize
        height: enableSize
        anchors.left: parent.left
        anchors.bottom: parent.bottom
        z: 1000
        MouseArea {
            anchors.fill: parent
            hoverEnabled: true

            onPressed: press(mouse)
            onEntered: enter(7)
            onReleased: release()

            onPositionChanged: positionChange(mouse, -1, 1)
        }
    }

    //bottom
    Item {
        id: bottom
        height: enableSize
        anchors.left: leftBottom.right
        anchors.right: rightBottom.left
        anchors.bottom: parent.bottom
        z: 1000
        MouseArea {
            anchors.fill: parent
            hoverEnabled: true

            onPressed: press(mouse)
            onEntered: enter(8)
            onReleased: release()

            onMouseYChanged: positionChange(Qt.point(customPoint.x, mouseY), 1, 1)
        }
    }

    //右下角
    Item {
        id:rightBottom
        width: enableSize
        height: enableSize
        anchors.right: parent.right
        anchors.bottom: parent.bottom
        z: 1000
        MouseArea {
            anchors.fill: parent
            hoverEnabled: true

            onPressed: press(mouse)
            onEntered: enter(9)
            onReleased: release()

            onPositionChanged: positionChange(mouse,1,1)
        }
    }

    function enter(direct) {
        Resize.setMyCursor(direct)
    }

    function press(mouse) {
        isPressed = true
        customPoint = Qt.point(mouse.x, mouse.y)
    }

    function release() {
        isPressed = false
        //customPoint = undefined
    }

    function positionChange(newPosition, directX/*x軸方向*/, directY/*y軸方向*/) {
        if(!isPressed) return

        var delta = Qt.point(newPosition.x-customPoint.x, newPosition.y-customPoint.y)
        var tmpW,tmpH

        if(directX >= 0)
            tmpW = mainWindow.width + delta.x
        else
            tmpW = mainWindow.width - delta.x

        if(directY >= 0)
            tmpH = mainWindow.height + delta.y
        else
            tmpH = mainWindow.height - delta.y

        if(tmpW < mainWindow.minimumWidth) {
            if(directX < 0)
                mainWindow.x += (mainWindow.width - mainWindow.minimumWidth)
            mainWindow.width = mainWindow.minimumWidth
        }
        else {
            mainWindow.width = tmpW
            if(directX < 0)
                mainWindow.x += delta.x
        }

        if(tmpH < mainWindow.minimumHeight) {
            if(directY < 0)
                mainWindow.y += (mainWindow.height - mainWindow.minimumHeight)
            mainWindow.height = mainWindow.minimumHeight
        }
        else {
            mainWindow.height = tmpH
            if(directY < 0)
                mainWindow.y += delta.y
        }
    }
}

ResizeQmlWindow.cpp

#include "ResizeQmlWindow.h"


ResizeQmlWindow::ResizeQmlWindow(QObject *parent) : QObject(parent)
{}


void ResizeQmlWindow::setWindow(QWindow *win)
{
    m_win = win;
}


void ResizeQmlWindow::setMyCursor(int direct)
{
    switch (direct) {
    case 1:
        m_win->setCursor(QCursor(Qt::SizeFDiagCursor));
        break;
    case 2:
        m_win->setCursor(QCursor(Qt::SizeVerCursor));
        break;
    case 3:
        m_win->setCursor(QCursor(Qt::SizeBDiagCursor));
        break;
    case 4:
        m_win->setCursor(QCursor(Qt::SizeHorCursor));
        break;
    case 5:
        m_win->setCursor(QCursor(Qt::ArrowCursor));
        break;
    case 6:
        m_win->setCursor(QCursor(Qt::SizeHorCursor));
        break;
    case 7:
        m_win->setCursor(QCursor(Qt::SizeBDiagCursor));
        break;
    case 8:
        m_win->setCursor(QCursor(Qt::SizeVerCursor));
        break;
    case 9:
        m_win->setCursor(QCursor(Qt::SizeFDiagCursor));
        break;
    }
}

因?yàn)槲覀円贑++設(shè)置鼠標(biāo)的狀態(tài),所以我們還須將ResizeQmlWindow注冊(cè)傳遞到Qml中,如下

    QObject * obj = engine.rootObjects().at(0);
    QWindow * w = qobject_cast<QWindow *>(obj);
    ResizeQmlWindow resize;
    if(w) {
        resize.setWindow(w);
        engine.rootContext()->setContextProperty("Resize", &resize);
    }

對(duì)比效果如圖所示


原生邊框
定制邊框

示例工程下載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容