QT之Qml使用QSystemTrayIcon实现系统托盘

2022-05-11 08:40:31 浏览数 (1)

系统托盘图标,现代操作系统通常在桌面上提供一个特殊区域,称为系统托盘或通知区域,长时间运行的应用程序可以在其中显示图标和短消息。网上找到的例子大多太凌乱,这里总结下提供个代码封装,方便后续用到了简单使用。

 QT中实现这一功能使用QSystemTrayIcon,它为应用程序在系统托盘中提供一个图标。现代操作系统通常在桌面上提供一个特殊区域,称为系统托盘或通知区域,长时间运行的应用程序可以在其中显示图标和短消息。

下面是一个SystemTrayIcon类的封装,后面介绍它在Qml中的简单使用。

代码封装

systemtrayicon.h文件:

代码语言:javascript复制
#ifndef SYSTEMTRAYICON_H
#define SYSTEMTRAYICON_H

#include <QObject>
#include <QActionEvent>
#include <QAction>
#include <QQuickItem>
#include <QSystemTrayIcon>

class MyAction : public QAction
{
    Q_OBJECT
    //Q_PROPERTY宏提供在qml中访问的信号槽等等
    Q_PROPERTY(QUrl icon READ icon WRITE setIcon NOTIFY iconChanged)

public:
    MyAction(QObject *parent = nullptr);
    ~MyAction();

    QUrl icon() const;

signals:
    void iconChanged();

public slots:
    void setIcon(const QUrl &arg);

private:
    QUrl m_icon;
};


class MySeparator : public QObject
{
public:
    MySeparator(QObject *parent = nullptr);
    ~MySeparator();
};

class SystemTray;
class MyMenu : public QQuickItem
{
    Q_OBJECT
    Q_PROPERTY(int width READ width WRITE setWidth NOTIFY widthChanged)
    Q_PROPERTY(int height READ height WRITE setHeight NOTIFY heightChanged)

public:
    MyMenu(QQuickItem *parent = nullptr);
    ~MyMenu();

    int width() const;
    int height() const;
    void clear();

signals:
    void widthChanged();
    void heightChanged();

public slots:
    void setWidth(int arg);
    void setHeight(int arg);
    void addSeparator();
    void addAction(MyAction *action);
    void addMenu(MyMenu *menu);

protected:
    void componentComplete();

private:
    friend class SystemTrayIcon;    //让SystemTray能够直接访问m_menu
    QMenu *m_menu;
};

class SystemTrayIcon : public QQuickItem
{
    Q_OBJECT
    Q_PROPERTY(int x READ x CONSTANT)
    Q_PROPERTY(int y READ y CONSTANT)
    Q_PROPERTY(QUrl icon READ icon WRITE setIcon NOTIFY iconChanged)
    Q_PROPERTY(QString toolTip READ toolTip WRITE setToolTip NOTIFY toolTipChanged)
    Q_PROPERTY(MyMenu* menu READ menu WRITE setMenu NOTIFY menuChanged)

public:
    SystemTrayIcon(QQuickItem *parent = nullptr);
    ~SystemTrayIcon();

    int x() const;
    int y() const;
    QUrl icon() const;
    QString toolTip() const;
    MyMenu* menu() const;

signals:
    void trigger();
    void iconChanged();
    void toolTipChanged();
    void menuChanged();

public slots:
    void setIcon(const QUrl &arg);
    void setToolTip(const QString &arg);
    void setMenu(MyMenu *arg);
    void onVisibleChanged();
    void onActivated(QSystemTrayIcon::ActivationReason reason);
    void onExit();

private:
    QSystemTrayIcon *m_systemTray;
    MyMenu *m_menu;
    QString m_toolTip;
    QUrl m_icon;
};

#endif // SYSTEMTRAYICON_H

systemtrayicon.cpp文件: 

代码语言:javascript复制
#include <QApplication>
#include <QMenu>
#include <QAction>
#include "systemtrayicon.h"

MyAction::MyAction(QObject *parent)
    :   QAction(parent)
{
    setObjectName("MyAction");
}

MyAction::~MyAction()
{

}

QUrl MyAction::icon() const
{
    return m_icon;
}

void MyAction::setIcon(const QUrl &arg)
{
    if(m_icon != arg)
    {
        QString str = arg.toLocalFile();
        if(str == "") str = arg.toString();     //如果转换失败
        if( str.mid (0, 3) == "qrc")
            str = str.mid (3, str.count() - 3);
        QAction::setIcon(QIcon(str));
        m_icon = arg;
        emit iconChanged();
    }
}

MySeparator::MySeparator(QObject *parent)
    :   QObject(parent)
{
    setObjectName("MySeparator");
}

MySeparator::~MySeparator()
{

}

MyMenu::MyMenu(QQuickItem *parent)
    :   QQuickItem(parent)
{
    setObjectName("MyMenu");
    m_menu = new QMenu();
}

MyMenu::~MyMenu()
{

}

int MyMenu::width() const
{
    return m_menu->width();
}

int MyMenu::height() const
{
    return m_menu->height();
}

void MyMenu::clear()    //清空caidan
{
    m_menu->clear();
}

void MyMenu::setWidth(int arg)
{
    if (m_menu->width() != arg)
    {
        m_menu->setFixedWidth(arg);
        emit widthChanged();
    }
}

void MyMenu::setHeight(int arg)
{
    if (m_menu->height() != arg)
    {
        m_menu->setFixedHeight(arg);
        emit heightChanged();
    }
}

void MyMenu::addAction(MyAction *action)
{
    m_menu->addAction(action);
}

void MyMenu::addSeparator()
{
    m_menu->addSeparator();
}

void MyMenu::addMenu(MyMenu *menu)
{
    m_menu->addMenu(menu->m_menu);
}

void MyMenu::componentComplete()        //在菜单完成构建后调用,将自定义Action,Menu,Separator通过objectName判断加入
{
    QQuickItem::componentComplete();
    QObjectList list = children();
    for (auto it : list)
    {
        if (it->objectName() == "MyAction")
        {
            MyAction *action = qobject_cast<MyAction *>(it);
            m_menu->addAction(action);
        }
        else if (it->objectName() == "MySeparator")
        {
            m_menu->addSeparator();
        }
        else if (it->objectName() == "MyMenu")
        {
            MyMenu *menu = qobject_cast<MyMenu *>(it);
            m_menu->addMenu(menu->m_menu);
        }
    }
}

SystemTrayIcon::SystemTrayIcon(QQuickItem *parent)
    :   QQuickItem(parent)
{
    m_systemTray = new QSystemTrayIcon(this);
    connect(m_systemTray, &QSystemTrayIcon::activated, this, &SystemTrayIcon::onActivated);
    connect(this, &SystemTrayIcon::visibleChanged, this, &SystemTrayIcon::onVisibleChanged);
    setVisible(false);              //给visible一个初始值,否则会不显示
}

SystemTrayIcon::~SystemTrayIcon()
{

}

int SystemTrayIcon::x() const
{
    return m_systemTray->geometry().x();
}

int SystemTrayIcon::y() const
{
    return m_systemTray->geometry().y();
}

QUrl SystemTrayIcon::icon() const
{
    return m_icon;
}

QString SystemTrayIcon::toolTip() const
{
    return m_systemTray->toolTip();
}

MyMenu *SystemTrayIcon::menu() const
{
    return m_menu;
}

void SystemTrayIcon::setIcon(const QUrl &arg)
{
    if(m_icon != arg)
    {
        QString str = arg.toLocalFile();
        if(str == "") str = arg.toString();
        if( str.mid (0, 3) == "qrc")
            str = str.mid (3, str.count() - 3);
        m_systemTray->setIcon(QIcon(str));
        m_icon = arg;
        emit iconChanged();
    }
}

void SystemTrayIcon::setToolTip(const QString &arg)
{
    if (m_toolTip != arg)
    {
        m_systemTray->setToolTip(arg);
        m_toolTip = arg;
        emit toolTipChanged();
    }
}


void SystemTrayIcon::setMenu(MyMenu *arg)
{
    if (m_menu != arg)
    {
        m_menu = arg;
        m_systemTray->setContextMenu(m_menu->m_menu);
        m_systemTray->installEventFilter(this);
        emit menuChanged();
    }
}

void SystemTrayIcon::onVisibleChanged()    //visible可见性改变时显示/隐藏托盘
{
    m_systemTray->setVisible(isVisible());
}

void SystemTrayIcon::onActivated(QSystemTrayIcon::ActivationReason reason)
{
    switch (reason)
    {
    case QSystemTrayIcon::DoubleClick:
    case QSystemTrayIcon::Trigger:
        emit trigger();            //单击双击托盘图标时发送trigger()信号, reason类似还有Context,MiddleClick,Unknow

    default:
        break;
    }
}

void SystemTrayIcon::onExit()    //应在程序退出时调用,防止图标不消失
{
    m_systemTray->hide();
    QApplication::exit(0);
}

简单使用

首先需要在main函数中把自定义的类注册到Qml中,使用qmlRegisterType。

qmlRegisterType 是一个可以将C 实现的类在QML中调用的,连接C 和QML的一个工具,是一个非常重要的函数。它总共4个参数:第一个参数* uri指的是QML中import后的内容,相当于头文件名,第二个第三个参数分别是主次版本号,第四个指的是QML中类的名字。 (注意第四个QML的类名首字母一定要大写,要不然会报错。)

它与setContextProperty的区别是:

代码语言:javascript复制
//简单的上下文属性,对应的值为QVariant类型。
void QQmlContext::setContextProperty(const QString &name, const QVariant &value)

//相对来说稍微复杂一些,QObject*对象类型。
void QQmlContext::setContextProperty(const QString &name, QObject *value)

如果要使用某个全局类的实例来访问QML或从QML访问,需要在这之前创建此类对象。再使用setContextProperty()注册进去,然后QML中就可以直接使用这个类的对象。如:

代码语言:javascript复制
  MainController mainController;  
  engine.rootContext()->setContextProperty("MainController", &mainController);

但是这种方式不太好,setContextProperty要求对象实例的生命期需要我们自己管理,所以对象需要在堆上创建,否则离开了当前作用域就被析构了 。在栈上分配的对象“mainController”将在"return app.exec()"之后不久析构。正确应该是:

代码语言:javascript复制
  MainController mainController = new MainController;  
  engine.rootContext()->setContextProperty("MainController", mainController);

另需注意的是,这些定义的类需继承自QObject。类实例的方法需要qml中调用时,需要在函数前面加上Q_INVOKABLE宏。如:

代码语言:javascript复制
#include <QObject>

class RDBRestore : public QObject
{
  Q_OBJECT
public:
  explicit RDBRestore(QObject* parent = nullptr);


public:
  Q_INVOKABLE int restoreRedis(const QString& fileNameWithPath);
  Q_INVOKABLE bool checkRdbFileExist();
  Q_INVOKABLE void removeRdbFile();

  Q_INVOKABLE bool getIsRdbFileExist();
private:
  bool isRdbFileExist;
};

 下面开始正式使用,main中这样使用,把相关类注册,使用qmlRegisterType:

代码语言:javascript复制
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "systemtrayicon.h"

int main(int argc, char *argv[])
{
  QApplication app(argc, argv);

  QQmlApplicationEngine engine;
  //auto restoreDb = new RDBRestore;
  //engine.rootContext()->setContextProperty("restoreDb", restoreDb);
  //系统托盘相关
  qmlRegisterType<MyMenu>("my.util", 1, 0, "MyMenu");        //注册到qml中
  qmlRegisterType<MyAction>("my.util", 1, 0, "MyAction");
  qmlRegisterType<MySeparator>("my.util", 1, 0, "MySeparator");
  qmlRegisterType<SystemTrayIcon>("my.util", 1, 0, "SystemTrayIcon");

  const QUrl url(QStringLiteral("qrc:/main.qml"));

  QObject::connect(
      &engine, &QQmlApplicationEngine::objectCreated, &app,
      [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
          QCoreApplication::exit(-1);
      },
      Qt::QueuedConnection);
  engine.load(url);
  return app.exec();
}

在Qml文件中使用

代码语言:javascript复制
import QtQuick 2.10
import QtQuick.Window 2.10
import my.util 1.0
Window {
    id: root
    width: 1280
    height: 1024
    color: "#e5e5e5"
    visible: true

    flags: Qt.Window | Qt.MSWindowsFixedSizeDialogHint

    Image {
        id: image
        anchors.right: parent.right
        anchors.rightMargin: 378
        anchors.left: parent.left
        anchors.leftMargin: 379
        anchors.bottom: parent.bottom
        anchors.bottomMargin: 578
        anchors.top: parent.top
        anchors.topMargin: 213
        source: "image/background.png"
    }
    Text {
        id: text2
        x: 660
        color: "#004695"
        text: qsTr("hello world")
        styleColor: "#000000"
        font.weight: Font.Bold
        font.family: "微软雅黑"
        anchors.top: image.bottom
        anchors.topMargin: 12
        font.pixelSize: 18
    }

    Timer {
        id: checkDownTimer
        interval: 100
        repeat: true
        running: false

        onTriggered: {
           //定时任务
        }
    }
    // 托盘图标
    SystemTrayIcon{
        id: systemTray
        menu: menu
        visible: true
        icon: "qrc:///image/myicon.ico"
        toolTip: "daemon is runing"
        onTrigger:{
            root.requestActivate();
            root.show();
        }

        MyMenu{
            id: menu
            MyAction{
                text: "显示界面"
                icon: "qrc:///image/myicon.ico"
                onTriggered: {
                    console.log("onTriggered 2")
                    root.requestActivate();
                    root.show();
                }
            }
            MyAction{
                text: "隐藏界面"
                icon: "qrc:///image/myicon.ico"
                onTriggered: {
                    console.log("onTriggered 3")
                    root.hide();
                }
            }

            MySeparator {}

            MyAction{
                id:exitItem
                icon: "qrc:///image/myicon.ico"
                text: qsTr("Exit")
                onTriggered: Qt.quit()
            }
        }
    }

    Component.onCompleted:  {
        checkDownTimer.start()
    }

    onClosing: {
                //点击关闭按钮时阻止关闭不退出而是最小化至托盘显示
                root.hide()
            }
}

引用

Qt中的系统托盘QSystemTrayIcon分析_@蓝枫的博客-CSDN博客

Qt之QSystemTrayIcon_weixin_34055910的博客-CSDN博客

Qt浅谈之三十系统托盘(QSystemTrayIcon)_乌托邦2号的博客-CSDN博客

qt 之 QSystemTrayIcon(托盘程序整个例子)_比卡丘不皮的博客-CSDN博客_qsystemtrayicon

在QML中使用QSystemTrayIcon(系统托盘)_梦起丶的博客-CSDN博客_qml 托盘

树莓派Qt系列教程29(下):Qml和C 混合编程 - 树莓派QT教程 微雪课堂

【QT】QML与C 混合编程详解_会飞的代码UP的博客-CSDN博客_qt和c 混合编程

QML与C 集成<二>——<使用C 属性及注册QML类型> - 走看看

树莓派Qt系列教程8: 信号与槽 - 树莓派QT教程 微雪课堂

0 人点赞