欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

使用qml编写桌面悬浮窗

程序员文章站 2022-03-09 13:35:25
...

1、前言

  跨平台的桌面桌面应用有Qt、Electron等,但是Electron这些的视觉效果感觉不太好,网页质感,而且安装包大(Chromium这浏览器来展示),使用qt的话,其他质感强一点和安装包小点。而qt中,对于qWidget和qml,qml没用过,所以选择qml来写,事实证明,这是错误的,因为这在linux下展示很卡,并且抖动严重。所以还加了个动画让过度好点。而使用qWidget,完美兼容,看看qt自带案例Shaped Clock Example,完全不是一个级别。这是在linux下如此,windows下很流畅。

2、实现

import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.4 as Controls
import Qt.labs.platform 1.0


//包含系统托盘的导包
Window {
    id: mainWindow
    visible: true
    //大小根据屏幕计算,宽高比为6:14
    minimumHeight: 50
    minimumWidth: 120
    width: Screen.desktopAvailableWidth / 14
    height: width * 3 / 7
    title: qsTr("tiny monitor")
    //无边框的window flags
    flags: Qt.FramelessWindowHint | Qt.WindowSystemMenuHint
           | Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint
    //灰色0.9透明度
    color: Qt.rgba(0.5, 0.5, 0.5, 0.9)

    Rectangle {
        id: rectangle
        x: 0
        y: 0
        width: mainWindow.height
        height: width
        color: Qt.rgba(0.2, 1.0, 0.0, 0.7)
    }

    //混合动画效果(这里混合xy轴平移
    ParallelAnimation {
        id: moveAnimation
        running: false
        PropertyAnimation {
            target: mainWindow
            property: 'x'
            easing.type: Easing.Linear
            duration: 100
        }
        PropertyAnimation {
            target: mainWindow
            property: 'y'
            easing.type: Easing.Linear
            duration: 100
        }
    }

    //鼠标可控制区域
    MouseArea {
        property point clickPos: "0,0"
        id: dragRegion
        anchors.fill: parent
        drag.minimumX: 0
        drag.maximumX: Screen.desktopAvailableWidth - mainWindow.width
        drag.minimumY: 0
        drag.maximumY: Screen.desktopAvailableHeight - mainWindow.heigh
        onPressed: {
            mainWindow.requestActivate()
            clickPos = Qt.point(mouseX, mouseY)
        }

        onPositionChanged: {
            moveAnimation.stop()
            //鼠标偏移量
            var delta = Qt.point(mouse.x - clickPos.x, mouse.y - clickPos.y)
            console.log(delta.x + "  " + delta.y)
            mainWindow.x += delta.x
            mainWindow.y += delta.y
            moveAnimation.start()
        }
        //添加右键菜单
        acceptedButtons: Qt.LeftButton | Qt.RightButton // **右键(别落下这个)
        onClicked: {
            if (mouse.button === Qt.RightButton) {
                // 右键菜单
                contentMenu.popup()
            }
        }
    }
    //不是托盘的菜单类
    Controls.Menu {
        id: contentMenu
        // 右键菜单
        Controls.MenuItem {
            id:hideItem
            text: qsTr("隐藏")
            onTriggered: {
                if(trayIcon==null){
                    console.log("系统托盘不存在");
                    contentMenu.removeItem(hideItem);
                    return;
                }else{
                    if(trayIcon.available){
                        console.log("系统托盘存在");
                    }else{
                        console.log("系统托盘不存在");
                        contentMenu.removeItem(hideItem)
                    }
                }
                mainWindow.hide()
            }
        }
        Controls.MenuItem {
            text: qsTr("退出")
            onTriggered: Qt.quit()
        }
    }

    //使用系统托盘的菜单组件
    Menu {
        id: systemTrayMenu
        // 右键菜单
        MenuItem {
            text: qsTr("隐藏")
            shortcut: "Ctrl+z"
            onTriggered: mainWindow.hide()
        }
        MenuItem {
            text: qsTr("退出")
            onTriggered: Qt.quit()
        }
    }
    //系统托盘
    SystemTrayIcon {
        id:trayIcon
        visible: true
        iconSource: "qrc:/images/TraffickingIn.svg"
        tooltip: "tiny-流量监控软件"
        onActivated: {
            mainWindow.show()
            mainWindow.raise()
            mainWindow.requestActivate()
        }
        menu: systemTrayMenu
    }
}

效果如此:
使用qml编写桌面悬浮窗
做了如下功能:

  • 1、使用flag实现无菜单栏的悬浮窗:
    //无边框的window flags
    flags: Qt.FramelessWindowHint | Qt.WindowSystemMenuHint
           | Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint

其中添加Qt.X11BypassWindowManagerHint这个flag可以明显加强linux下的流畅度。

  • 2、设置大小根据分辨率计算。
    minimumHeight: 50
    minimumWidth: 120
    width: Screen.desktopAvailableWidth / 14
    height: width * 3 / 7

根据屏幕的长、宽根据屏幕宽度计算,防止高分屏。最小为宽120、高50,反之屏幕太小还如此计算悬浮窗太小。

  • 3、使用ParallelAnimation动画来是过度自然。

  • 4、MouseArea设置如下属性,使悬浮窗在屏幕内:

        drag.minimumX: 0
        drag.maximumX: Screen.desktopAvailableWidth - mainWindow.width
        drag.minimumY: 0
        drag.maximumY: Screen.desktopAvailableHeight - mainWindow.heigh
  • 5、悬浮窗移动,通过每次位置变化来加减悬浮窗的x,y绝对位置来实现::
    onPressed: {
            mainWindow.requestActivate()
            clickPos = Qt.point(mouseX, mouseY)
        }

        onPositionChanged: {
            moveAnimation.stop()
            //鼠标偏移量
            var delta = Qt.point(mouse.x - clickPos.x, mouse.y - clickPos.y)
            console.log(delta.x + "  " + delta.y)
            mainWindow.x += delta.x
            mainWindow.y += delta.y
            moveAnimation.start()
        }
  • 6、添加右键菜单:
    //添加右键菜单
        acceptedButtons: Qt.LeftButton | Qt.RightButton // **右键(别落下这个)
        onClicked: {
            if (mouse.button === Qt.RightButton) {
                // 右键菜单
                contentMenu.popup()
            }
        }

contentMenu是下面菜单的id名。

  • 7、区别托盘菜单和旧的菜单,因为同名类,所以会有bug,所以需要起别名:
import QtQuick.Controls 1.4 as Controls 
import Qt.labs.platform 1.0  //包含系统托盘的包

Qt.labs.platform这个包的系统托盘是实验用的,可能在未来的系统版本中去除,我为了方便,先用用,这个不是标准实现所以对于linux有兼容性问题,可能会不显示系统托盘,但是用旧的系统托盘不用qml方法还是可以正常使用的。使用qml的系统托盘需要使用QGuiApplication加载,旧的必须使用QApplication来加载qml。

  • 8、在常规菜单中我们加了个没有系统托盘就去除隐藏菜单的操作:
        Controls.MenuItem {
            id:hideItem
            text: qsTr("隐藏")
            onTriggered: {
                if(trayIcon==null){
                    console.log("系统托盘不存在");
                    contentMenu.removeItem(hideItem);
                    return;
                }else{
                    if(trayIcon.available){
                        console.log("系统托盘存在");
                    }else{
                        console.log("系统托盘不存在");
                        contentMenu.removeItem(hideItem)
                    }
                }
                mainWindow.hide()
            }
        }

这是隐藏菜单项功能。trayIcon是系统托盘的id名,如果系统托盘对象不存在旧去除(remove)。或者系统托盘不可用野去除(remove)。

界面全去qml实现。


顺便贴一下main.cpp:

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);//支持高分屏

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;
    int state = app.exec();
    return state;
}
相关标签: qml