写这篇文章的时候应该不算晚,因为qml中并没有正式的系统托盘组件。
但是,在Qt Labs Platform模块中(Qt 5.8引入),已经有了SystemTrayIcon,相信以后会作为正式的模块进入qml。
即便如此,本文仍提供了一种在qml中使用QSystemTrayIcon的方法,或者也可以作为qml与c++交互的例子来看。
先上完成后的qml代码:
main.qml
import QtQuick 2.7 import QtQuick.Window 2.2 import an.utility 1.0 Window { id: root visible: true width: 400 height: 200 title: qsTr("Hello World") Rectangle { anchors.fill: parent color: "red" } SystemTray { id: systemTray menu: menu visible: true icon: "qrc:/winIcon.png" toolTip: "托盘运行中..." onTrigger: { root.requestActivate(); root.show(); } MyMenu { id: menu MyAction { text: "我在线上" icon: "qrc:/winIcon.png" } MyAction { text: "Q我吧" icon: "qrc:/winIcon.png" } MySeparator {} MyAction { text: "离开" icon: "qrc:/winIcon.png" } MyAction { text: "忙碌" icon: "qrc:/winIcon.png" } } } }
效果图:
可以看到,效果和widget下使用是一样的,但用qml更简单更方便。
自定义的SystemTrayIcon我注册到an.utility 1.0中。下面讲解其实现。
systemtrayicon.h
#ifndef SYSTEMTRAYICON_H #define SYSTEMTRAYICON_H #include <QAction> #include <QQuickItem> #include <QSystemTrayIcon> class MyAction : public QAction { Q_OBJECT Q_PROPERTY(QUrl icon READ icon WRITE setIcon NOTIFY iconChanged) //Q_PROPERTY宏提供在qml中访问的信号槽等等 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
要将c++类导入到qml必须继承自QObject或其派生类,并使用Q_OBJECT宏。
而继承QQuickItem是为了得到item中的其他一些属性和信号,以及重实现 void componentComplete()。
该函数在组件完成时调用,重新实现来添加必要的操作。
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.cpp
#include <QApplication> #include <QQmlApplicationEngine> #include "systemtrayicon.h" int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication app(argc, argv); QQmlApplicationEngine engine; qmlRegisterType<MyMenu>("an.utility", 1, 0, "MyMenu"); //注册到qml中 qmlRegisterType<MyAction>("an.utility", 1, 0, "MyAction"); qmlRegisterType<MySeparator>("an.utility", 1, 0, "MySeparator"); qmlRegisterType<SystemTrayIcon>("an.utility", 1, 0, "SystemTrayIcon"); engine.load(QUrl(QLatin1String("qrc:/main.qml"))); if (engine.rootObjects().isEmpty()) return -1; return app.exec(); }
好吧,还有很多地方没有完善,比如 快捷键。
其实写自定义组件和写widget差不多。。不过写好之后,在qml中使用起来就非常方便了。
当然,最后我自己试了试Qt.labs.platform 1.0 中的 SystemTrayIcon,嗯基本上差不多吧。。所以期待以后的版本正式加入咯。