由于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ì)比效果如圖所示

