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

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

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


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


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



代码封装


systemtrayicon.h文件:


#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文件:


#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的区别是:


//简单的上下文属性,对应的值为QVariant类型。
void QQmlContext::setContextProperty(const QString &name, const QVariant &value)
//相对来说稍微复杂一些,QObject*对象类型。
void QQmlContext::setContextProperty(const QString &name, QObject *value)


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


  MainController mainController;  
  engine.rootContext()->setContextProperty("MainController", &mainController);


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


  MainController mainController = new MainController;  
  engine.rootContext()->setContextProperty("MainController", mainController);


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


#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:


#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文件中使用


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教程 微雪课堂

相关文章
|
2月前
|
存储 Windows
(13) Qt事件系统(two)
文章详细介绍了Qt事件系统,包括事件分发、自定义事件、事件传播机制、事件过滤以及事件与信号的区别。
104 3
(13) Qt事件系统(two)
|
2月前
|
编解码 程序员
(12)Qt事件系统(one)
本文详细介绍了Qt事件系统,包括各种系统事件、鼠标事件、键盘事件、定时器等的处理方法和示例代码。
94 0
(12)Qt事件系统(one)
|
4月前
|
存储 C++
【C++】C++ 基于QT实现散列表学生管理系统(源码+数据+课程论文)【独一无二】
【C++】C++ 基于QT实现散列表学生管理系统(源码+数据+课程论文)【独一无二】
103 1
【C++】C++ 基于QT实现散列表学生管理系统(源码+数据+课程论文)【独一无二】
|
4月前
|
API
Qt绘图之Paint系统
Qt绘图之Paint系统
68 2
|
4月前
|
开发者
【Qt 学习笔记】Qt系统相关 | Qt事件 | 事件的介绍及基本概念
【Qt 学习笔记】Qt系统相关 | Qt事件 | 事件的介绍及基本概念
233 4
|
4月前
QT6使用CMamke将qml打包成dll
QT6使用CMamke将qml打包成dll
89 0
|
4月前
从源码角度分析Qt元对象系统2
从源码角度分析Qt元对象系统
61 0
|
4月前
|
存储
从源码角度分析Qt元对象系统1
从源码角度分析Qt元对象系统
88 0
|
4月前
|
数据安全/隐私保护
【qt】考试系统项目
【qt】考试系统项目
48 0
|
4月前
|
数据安全/隐私保护
【qt】获取主机信息系统
【qt】获取主机信息系统
19 0