首页 > 其他分享 >QML 自定义窗口简易实现:使用过滤 WINDOW 事件的方式

QML 自定义窗口简易实现:使用过滤 WINDOW 事件的方式

时间:2023-01-31 22:45:51浏览次数:66  
标签:自定义 geometry void window pos WINDOW bool QML FramelessHelper

 

 

1.前言

QML 自定义窗口目前看到的主要有两种方式,一种是纯 QML 实现,使用 MouseArea 来处理鼠标相关事件;另一种是事件过滤,用系统本地 API 进行操作。前两天看了涛哥的自定义窗口(https://github.com/jaredtao/TaoQuick),是继承 QQuickWindow + 本地 API 的方式实现的。我本来也想借鉴下,但是发现 QML 的 Window 在 Qt5 后面的版本改为了 QQuickWindow 的子类 QQuickWindowQmlImpl ,还是个没导出的类。所以我就改了下自己的思路,由继承 Window 改为过滤其事件。

2.实现

我使用的方式是:创建一个 QObject 子类过滤 Window 的事件,过滤到移动和拉伸等操作的时候就去设置 Window 相关参数。

目前实现的功能只有拖拽标题栏和边框缩放,最终效果如下:

这里面也遇到不少问题:

整个 Window 上的鼠标事件都会被传递进来,即使上面放了个 MouseArea 或者 Button ,而 QWidget 就不会接收小部件的事件。

鼠标在不同的子部件移动时,Window 里没有特殊的信号,有时会有 CursorChange 的事件。

MouseArea 来作为标题栏的区域绑定,不然用 Rectangle 也不知道是点击在了按钮还是标题栏空白处,毕竟都会传递到 Window。

QML 拉伸的时候窗口会闪烁,这是原生就有的问题,做个橡皮筋效果应该会舒服点。

3.代码 

完整代码链接:https://github.com/gongjianbo/MyTestCode/tree/master/Qml/QmlFramelessWindow

这里贴出过滤器类和 QML 使用示例的代码:

#ifndef FRAMELESSHELPER_H

#define FRAMELESSHELPER_H

 

#include <QQuickItem>

#include <QQuickWindow>

#include <QEvent>

 

/**

 * @brief 一个简易的无边框辅助类

 * @author 龚建波

 * @date 2020-11-15

 * @details

 * 之前看有网友用的Window+本地事件来做的

 * Qt5 QML 中的 Window类型

 * 可能是QQuickWindow或者QQuickWindowQmlImpl(子类),后者未导出

 * 所以思路由重写事件改为过滤,但最终的实现效果感觉不大好,resize得时候一样会有闪烁

 * (拉伸时闪烁也是老问题了)

 * 感觉解决拉伸时闪烁问题还是用橡皮筋好一点

 *

 * 使用说明:

 * 1.注册为QML类型

 * 2.锚定window(Window)和title(MouseArea)两个区域,且设置borderWidth边距

 * FramelessHelper {

 *      window: root

 *      title: title_area

 *      borderWidth: 6

 * }

 * 3.Window设置flags为无边框

 * flags: Qt.Window|Qt.FramelessWindowHint|Qt.WindowMinMaxButtonsHint

 */

class FramelessHelper : public QObject

{

    Q_OBJECT

    //边距

    Q_PROPERTY(int borderWidth READ getBorderWidth WRITE setBorderWidth NOTIFY borderWidthChanged)

    //拖动使能

    Q_PROPERTY(bool moveEnable READ getMoveEnable WRITE setMoveEnable NOTIFY moveEnableChanged)

    //正在拖动

    Q_PROPERTY(bool moving READ getMoving NOTIFY movingChanged)

    //拉伸缩放使能

    Q_PROPERTY(bool resizeEnable READ getResizeEnable WRITE setResizeEnable NOTIFY resizeEnableChanged)

    //正在拖边框改变大小

    Q_PROPERTY(bool resizing READ getResizing NOTIFY resizingChanged)

    //绑定主窗口,Window类型

    Q_PROPERTY(QQuickWindow* window MEMBER window WRITE setWindow NOTIFY windowChanged)

    //绑定标题栏,MouseArea类型

    Q_PROPERTY(QQuickItem* title MEMBER title WRITE setTitle NOTIFY titleChanged)

private:

    //区域划分-九宫格,便于判断当前点击位置

    //竖向上中下0x01-0x02-0x04

    //横向左中右0x10-0x20-0x40

    //判断时分别取pos-x和y判断区域进行叠加

    enum FramelessArea

    {

        FContentArea = 0x00 //内容区域

        ,FLeftArea = 0x10 //左侧

        ,FRightArea = 0x20 //右侧

        ,FTopArea = 0x01 //顶部

        ,FBottomArea = 0x02 //底部

        ,FLeftTopCorner = 0x11 //左上角

        ,FRightTopCorner = 0x21 //右上角

        ,FLeftBottomCorner = 0x12 //左下角

        ,FRightBottomCorner = 0x22 //右下角

    };

public:

    explicit FramelessHelper(QObject *parent = nullptr);

 

    int getBorderWidth() const;

    void setBorderWidth(int width);

 

    bool getMoveEnable() const;

    void setMoveEnable(bool enable);

 

    bool getMoving() const;

    void setMoving(bool state);

 

    bool getResizeEnable() const;

    void setResizeEnable(bool enable);

 

    bool getResizing() const;

    void setResizing(bool state);

 

    void setWindow(QQuickWindow *newWindow);

    void setTitle(QQuickItem *newTitle);

 

protected:

    bool eventFilter(QObject *watched, QEvent *event) override;

 

private:

    //处理窗口相关事件

    void filterWindowEvent(QEvent *event);

    //处理标题栏相关事件

    void filterTitleEvent(QEvent *event);

    //鼠标移动

    void mouseMoveEvent(QMouseEvent *event);

    //判断是否最大化

    bool windowIsMaxed() const;

    //更新鼠标位置

    void updatePosition(const QPoint &pos);

    //判断鼠标位置更新鼠标形状

    void updateCursor(int area);

    //重置为默认鼠标形状

    void resetCuror();

 

signals:

    void borderWidthChanged();

    void moveEnableChanged();

    void movingChanged();

    void resizeEnableChanged();

    void resizingChanged();

    void windowChanged();

    void titleChanged();

 

private:

    //边框

    int borderWidth=6;

    //移动标志

    bool moveEnable=true;

    bool moving=false;

    //缩放标志

    bool resizeEnable=true;

    bool resizing=false;

    //暂存鼠标位置信息

    QPoint screenPosTemp=QPoint(0, 0);

    //暂存窗体位置、大小信息

    QRect geometryTemp;

    //当前区域

    FramelessArea cursorArea=FContentArea;

    //窗口-需要绑定Window

    QQuickWindow *window=nullptr;

    //标题栏-需要绑定MouseArea

    QQuickItem *title=nullptr;

};

 

#endif // FRAMELESSHELPER_H

#include "FramelessHelper.h"

 

#include <QCursor>

#include <QEnterEvent>

#include <QMouseEvent>

#include <QDebug>

 

FramelessHelper::FramelessHelper(QObject *parent)

    : QObject(parent)

{

 

}

 

int FramelessHelper::getBorderWidth() const

{

    return borderWidth;

}

 

void FramelessHelper::setBorderWidth(int width)

{

    if(borderWidth!=width){

        borderWidth=width;

        emit borderWidthChanged();

    }

}

 

bool FramelessHelper::getMoveEnable() const

{

    return moveEnable;

}

 

void FramelessHelper::setMoveEnable(bool enable)

{

    if(moveEnable!=enable){

        moveEnable=enable;

        emit moveEnableChanged();

    }

}

 

bool FramelessHelper::getMoving() const

{

    return moveEnable&&moving;

}

 

void FramelessHelper::setMoving(bool state)

{

    if(moving!=state){

        moving=moveEnable&&state;

        emit movingChanged();

    }

}

 

bool FramelessHelper::getResizeEnable() const

{

    return resizeEnable;

}

 

void FramelessHelper::setResizeEnable(bool enable)

{

    if(resizeEnable!=enable){

        resizeEnable=enable;

        emit resizeEnableChanged();

    }

}

 

bool FramelessHelper::getResizing() const

{

    return resizeEnable&&resizing;

}

 

void FramelessHelper::setResizing(bool state)

{

    if(resizing!=state){

        resizing=resizeEnable&&state;

        emit resizingChanged();

    }

}

 

void FramelessHelper::setWindow(QQuickWindow *newWindow)

{

    if(newWindow&&newWindow!=window){

        if(window)

            window->removeEventFilter(this);

        window=newWindow;

        window->installEventFilter(this);

        emit windowChanged();

    }

}

 

void FramelessHelper::setTitle(QQuickItem *newTitle)

{

    if(newTitle&&newTitle!=title){

        if(title)

            title->removeEventFilter(this);

        title=newTitle;

        title->installEventFilter(this);

        emit titleChanged();

    }

}

 

bool FramelessHelper::eventFilter(QObject *watched, QEvent *event)

{

    //qDebug()<<watched<<event;

    if(watched==window){

        filterWindowEvent(event);

    }else if(watched==title){

        filterTitleEvent(event);

    }

    return false;

}

 

void FramelessHelper::filterWindowEvent(QEvent *event)

{

    if(!window)

        return;

    switch (event->type()) {

    //case QEvent::CursorChange: break;

    case QEvent::Enter:

        //根据位置更新鼠标样式

        updatePosition(static_cast<QEnterEvent*>(event)->pos());

        break;

    case QEvent::Leave:

        //恢复鼠标样式

        resetCuror();

        break;

    case QEvent::CursorChange:

        //跑到别的区域上了

    case QEvent::UpdateRequest:

        //QExposeEvent的时候会设置为默认样式,这里重置回来

        if(getResizing()||getMoving())

            updateCursor(cursorArea);

        break;

    case QEvent::MouseButtonPress:

        //screenPosTemp = static_cast<QMouseEvent*>(event)->screenPos().toPoint();

        screenPosTemp=QCursor::pos();

        geometryTemp = window->geometry();

        //非边框区域FContentArea

        if (getResizeEnable()&&cursorArea!=FContentArea&&!windowIsMaxed()) {

            //非最大化时点击了边框,且允许缩放

            setResizing(true);

        }

        break;

    case QEvent::MouseButtonRelease:

        geometryTemp = window->geometry();

        //非拖动标题栏时释放鼠标

        if(!getMoving()){

            setResizing(false);

            updatePosition(static_cast<QMouseEvent*>(event)->pos());

        }

        break;

    case QEvent::MouseMove:

        mouseMoveEvent(static_cast<QMouseEvent*>(event));

        break;

    default: break;

    }

}

 

void FramelessHelper::filterTitleEvent(QEvent *event)

{

    if(!window||!title)

        return;

    switch (event->type()) {

    case QEvent::MouseButtonPress:

        //点击标题栏

        if (getMoveEnable()) {

            if (windowIsMaxed()) {

                //最大化状态下点击标题栏

            }else if(cursorArea==FContentArea){

                //非边框区域时可以拖动

                setMoving(true);

            }

        }

        break;

    case QEvent::MouseButtonDblClick:

        //双击标题栏,切换最大和普通大小

        if(windowIsMaxed()){

            window->showNormal();

        }else{

            window->showMaximized();

        }

        break;

    case QEvent::MouseButtonRelease:

        //拖动标题栏时释放鼠标

        if(getMoving()){

            setMoving(false);

            updatePosition(static_cast<QMouseEvent*>(event)->pos());

            QRect geometry=window->geometry();

            //如果拖到了标题栏就最大化

            if(geometry.y()<0){

                geometry.moveTop(0);

                window->setGeometry(geometry);

                window->showMaximized();

            }

        }

        break;

    case QEvent::MouseMove:

        //最大化时拖动标题栏就恢复为普通大小并可拖动

        if (getMoveEnable()&&windowIsMaxed()) {

            QMouseEvent *mouse_event=static_cast<QMouseEvent*>(event);

            const int old_width=window->width();

            const int old_pos=mouse_event->pos().x();

            window->showNormal();

            QRect geometry=window->geometry();

            const QPoint cursor_pos=QCursor::pos();

            //标题栏上,鼠标所在x按照原来的比例设置,y不变

            geometry.moveLeft(cursor_pos.x()-geometry.width()*(old_pos/(double)old_width));

            geometry.moveTop(cursor_pos.y()-title->height()/2);

            window->setGeometry(geometry);

            geometryTemp=geometry;

            setMoving(true);

        }

        break;

    default: break;

    }

}

 

void FramelessHelper::mouseMoveEvent(QMouseEvent *event)

{

    if(getMoving()){

        //按住标题栏拖动

        event->accept();

        const QPoint move_vec=QCursor::pos()-screenPosTemp;

        window->setGeometry(QRect(geometryTemp.topLeft()+move_vec,geometryTemp.size()));

    }else if(getResizing()){

        //按住边框拖拽大小

        event->accept();

        const QPoint move_vec=QCursor::pos()-screenPosTemp;

        //每个方向单独计算

        //然后判断计算出来的pos和size是否有效,大于最小尺寸

        //因为每个方向固定的边不一样,所以单独处理

        QRect new_geometry=geometryTemp;

        //横项调整

        switch (cursorArea&0xF0) {

        case FLeftArea: //左侧

            new_geometry.setLeft(geometryTemp.left()+move_vec.x());

            if (new_geometry.width()<window->minimumWidth()){

                new_geometry.setLeft(geometryTemp.right()-window->minimumWidth());

            }

            break;

        case FRightArea: //右侧

            new_geometry.setRight(geometryTemp.right()+move_vec.x());

            if (new_geometry.width()<window->minimumWidth()){

                new_geometry.setRight(geometryTemp.left()+window->minimumWidth());

            }

            break;

        default: break;

        }

        //竖向调整

        switch (cursorArea&0x0F) {

        case FTopArea: //顶部

            new_geometry.setTop(geometryTemp.top()+move_vec.y());

            if(new_geometry.height()<window->minimumHeight())

                new_geometry.setTop(geometryTemp.bottom()-window->minimumHeight());

            break;

        case FBottomArea: //底部

            new_geometry.setBottom(geometryTemp.bottom()+move_vec.y());

            if(new_geometry.height()<window->minimumHeight())

                new_geometry.setBottom(geometryTemp.top()+window->minimumHeight());

            break;

        default: break;

        }

        window->setGeometry(new_geometry);

    }else if(getResizeEnable()){

        //根据位置更新鼠标样式

        updatePosition(event->pos());

    }

}

 

bool FramelessHelper::windowIsMaxed() const

{

    //判断窗口是否最大化

    return (window&&(window->visibility()==QWindow::Maximized

                     ||window->visibility()==QWindow::FullScreen));

}

 

void FramelessHelper::updatePosition(const QPoint &pos)

{

    if(!window||windowIsMaxed())

        return;

    //根据鼠标坐标判断所在区域

    int pos_area=cursorArea;

    //可拖拽大小时才判断

    if (resizeEnable)

    {

        if (pos.x()<borderWidth) {

            pos_area=0x10;

        }else if (pos.x()>window->width()-borderWidth) {

            pos_area=0x20;

        }else {

            pos_area=0x00;

        }

        if (pos.y()<borderWidth) {

            pos_area+=0x01;

        }else if (pos.y()>window->height()-borderWidth) {

            pos_area+=0x02;

        }else {

            pos_area+=0x00;

        }

    }

    if (pos_area == cursorArea)

        return;

    cursorArea=(FramelessArea)pos_area;

    updateCursor(cursorArea);

}

 

void FramelessHelper::updateCursor(int area)

{

    //根据鼠标悬停位置更换鼠标形状

    switch (area) {

    case FLeftArea:

    case FRightArea:

        window->setCursor(Qt::SizeHorCursor);

        break;

    case FTopArea:

    case FBottomArea:

        window->setCursor(Qt::SizeVerCursor);

        break;

    case FLeftTopCorner:

    case FRightBottomCorner:

        window->setCursor(Qt::SizeFDiagCursor);

        break;

    case FRightTopCorner:

    case FLeftBottomCorner:

        window->setCursor(Qt::SizeBDiagCursor);

        break;

    default:

        window->setCursor(Qt::ArrowCursor);

        break;

    }

}

 

void FramelessHelper::resetCuror()

{

    if(!window)

        return;

    //重置为默认鼠标样式

    cursorArea=FContentArea;

    window->setCursor(Qt::ArrowCursor);

}

import QtQuick 2.15

import QtQuick.Window 2.15

import QtQuick.Controls 2.15

import GongJianBo 1.0

 

//演示FramelessHelper的使用

Window {

    id: root

    width: 640

    height: 480

    minimumHeight: 200

    minimumWidth: 500

    visible: true

    title: qsTr("Qml 简易无边框 (by 龚建波)")

    flags: Qt.Window

           |Qt.FramelessWindowHint

           |Qt.WindowMinMaxButtonsHint

 

    FramelessHelper{

        window: root

        title: title_area

        borderWidth: 6

    }

 

    //边框

    Rectangle{

        anchors.fill: parent

        border.color: "gray"

        border.width: 6

    }

 

    //标题栏

    MouseArea{

        id: title_area

        height: 50

        width: root.width

        Rectangle{

            anchors.fill: parent

            color: "darkCyan"

            opacity: 0.5

        }

 

        Text{

            anchors{

                left: parent.left

                verticalCenter: parent.verticalCenter

                margins: 20

            }

            font.pixelSize: 20

            color: "white"

            text: root.title

        }

 

        Row{

            anchors.right: parent.right

            anchors.verticalCenter: parent.verticalCenter

            anchors.margins: 10

            spacing: 10

 

            Button{

                width: 70

                height: 30

                text: "min"

                onClicked: root.showMinimized()

            }

            Button{

                width: 70

                height: 30

                text: "max"

                onClicked: {

                    if(root.visibility==Window.Maximized)

                        root.showNormal()

                    else

                        root.showMaximized()

                }

            }

            Button{

                width: 70

                height: 30

                text: "close"

                onClicked: Qt.quit()

            }

        }

    }

 

    ListView{

        anchors.fill: parent

        anchors.margins: 20

        anchors.topMargin: 70

        clip: true

        model: 50

        spacing: 5

        delegate: Rectangle{

            height: 40

            width: ListView.view.width

            border.color: "gray"

            Text {

                anchors.centerIn: parent

                text: index

            }

        }

    }

}

 

 

     

标签:自定义,geometry,void,window,pos,WINDOW,bool,QML,FramelessHelper
From: https://www.cnblogs.com/im18620660608/p/17081073.html

相关文章

  • QML全局按键监视、拦截
    最近尝试使用Qt做android应用,一路不顺,满地都是坑,不过开发的应用不复杂,坑不算深,都一步步走过来了,唯独一个问题解决不了——Back按钮返回功能,不过今天总算解决了......  ......
  • QML实现换肤(主题样式切换)
    QtQuick没有像QtWidgets那样的QSS样式表机制,只能通过自定义组件或者设置Controls主题来实现样式定制。目前网上搜到的换肤功能,大多是定义一个全局的样式文件,在自定......
  • 自定义ConditionalOnXX注解(二)
    一、前言  在之前的文章《自定义ConditionalOnXX注解》中,介绍了Conditional注解的实现原理和实现自定义Conditional注解的基础方法,但是有些场景我们需要用一个Condition......
  • Windows Server 2022 中文版、英文版下载 (updated Jan 2023)
    WindowsServer2022正式版,2023年1月更新,持续更新中...请访问原文链接:https://sysin.org/blog/windows-server-2022/,查看最新版。原创作品,转载请保留出处。作者主页......
  • Windows 11 22H2 中文版、英文版 (x64、ARM64) 下载 (updated Jan 2023)
    Windows11,version22H2,2023年1月更新,持续更新中...请访问原文链接:https://sysin.org/blog/windows-11/,查看最新版。原创作品,转载请保留出处。作者主页:www.sysin.......
  • windows下实现服务端和客户端程序
    一、服务端//初始化WSAWORDsocketVersion=MAKEWORD(2,2);WSADATAwsaData;//WSADATA结构体变量的地址值//成功时返回0if(WSAStartup(socketVers......
  • 通过自定义注解和反射实现策略模式
    通过自定义注解和反射实现策略模式​ 今天在写一个工单系统时,工单审批通过后,需要根据不同的工单类型选择不同的处理方式非常适合用自定义注解+反射来实现,研究了一番,......
  • 【KAWAKO】在windows上用CMake和MinGW编译c++工程
    目录安装CMake安装MinGW编写CMakeLists.txt编译一条龙安装CMake在网上随便找个教程照着安装就行了,不再赘述。安装MinGW参考这篇博客。从MinGW官网下载的安装包在安装的......
  • windows下安装ES显示需要jdk11
    安装ES的过程中,会发现它要求jdk11,但现在开发基本都是用jdk8. 解决方法:1、下载jdk11(不推荐)2、修改ES安装目录下bin目录下的elasticsearch-env.bat(推荐)......
  • windows安装ES遇到各种问题
    刚刚在windows里安装ES,但就是一直启动不了,百度来百度去,看了各种解决方法,炸了锅就是解决不了!!!遇到的错误有:不能用jdk8,只能使用jdk11.  改了各种文件,.env.yml......