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