一个客户端程序, 有时候会需要提供退出到最小化托盘的功能, Qt 的 QSystemTrayIcon
类就刚好实现了这个功能, 这篇文章主要就是对它简单封装一下,实现一些额外的功能
功能点
- 最小化到托盘
- 例如微信收到消息, 托盘状态下闪烁
- 托盘下菜单功能
- 托盘下鼠标事件的响应
首先看一下简单封装后的 OwnSyetemTrayIcon
的头文件
class QMenu;
class QTimer;
class OwnSyetemTrayIcon : public QSystemTrayIcon
{
Q_OBJECT
public:
OwnSyetemTrayIcon();
~OwnSyetemTrayIcon();
// 托盘闪烁的控制
void startTrayFlash();
void stopTrayFlash();
private slots:
// 托盘下鼠标事件的响应的处理
void slotActivated(QSystemTrayIcon::ActivationReason reason);
signals:
// 托盘下菜单点击信号
void sigNormalShow();
void sigClose();
private:
// 托盘闪烁的实际实现
void iconFlash();
private:
// 托盘下菜单对象
QMenu *m_menu;
// 控制闪烁的定时器
QTimer *m_flashTimer;
// 决定是闪还是烁
bool m_flashStatus;
};
最小化到托盘
因为是继承自 QSystemTrayIcon
, 所以如果只要最小化到托盘只要简单的设置托盘状态下的图标即可
this->setIcon(QIcon(":/Icon/logo.ico"));
调用的时候也只需要实例化后,show()
即可
OwnSyetemTrayIcon *m_trayIcon = new OwnSyetemTrayIcon;
m_trayIcon->show();
托盘状态下闪烁
闪烁其实逻辑很简单,托盘的图标一会儿有样式一会儿没有样式,代码如下:
connect(m_flashTimer, &QTimer::timeout,this,&OwnSyetemTrayIcon::iconFlash);
// 开始闪烁,就是启动定时器
void OwnSyetemTrayIcon::startTrayFlash()
{
if (m_flashTimer && m_flashTimer->isActive())
return;
m_flashTimer->start(500);
}
// 停止闪烁,就是停止定时器,并设置可见的图标
void OwnSyetemTrayIcon::stopTrayFlash()
{
if (m_flashTimer && m_flashTimer->isActive())
m_flashTimer->stop();
if (m_flashStatus)
this->setIcon(QIcon(":/Icon/logo.ico"));
}
// 闪烁的时候设置的资源文件中存在的图标文件,相反则随便写一个找不到图标既可
void OwnSyetemTrayIcon::iconFlash()
{
if (m_flashStatus)
this->setIcon(QIcon(":/Icon/logo.ico"));
else
this->setIcon(QIcon(":/Icon/no_logo.ico"));
m_flashStatus = !m_flashStatus;
}
m_flashStatus
变量控制样式,m_flashTimer
定时器来启动闪烁
提到闪烁,想到另一个功能,任务栏图标闪烁, 但这有个条件,窗口不是活动窗口 的时候才有作用
QApplication::alert(this);
托盘下菜单功能
connect(m_menu->addAction(QIcon(), CN("主界面")), &QAction::triggered, this, &OwnSyetemTrayIcon::sigNormalShow);
m_menu->addSeparator();
connect(m_menu->addAction(QIcon(), CN("退出")), &QAction::triggered, this, &OwnSyetemTrayIcon::sigClose);
this->setContextMenu(m_menu);
这里的 CN("主界面")
其实一个宏,因为经常出现中文乱码的问题,而且也不想通过 “工具” -> “选项” -> “文本编辑器”(“行为”) 里修改文件编码
#ifdef __GNUC__
#define CN(ch) QString::fromLocal8Bit(ch)
#else
#define CN(ch) QStringLiteral(ch)
#endif
因为我一般只用 MINGW
或者 MSVC
这 2 种编译器,一般情况下也不考虑翻译的问题
MINGW
下一般都是使用QString::fromLocal8Bit
MSVC
则使用QStringLiteral
, 简单区分了一下
托盘下鼠标事件的响应
通过信号槽绑定 void activated(QSystemTrayIcon::ActivationReason reason);
信号就能很简单实现这个功能
其中 QSystemTrayIcon::ActivationReason
枚举如下,够用了
enum ActivationReason {
Unknown,
Context,
DoubleClick,
Trigger,
MiddleClick
};
简单看一下代码:
connect(this,&QSystemTrayIcon::activated,this,&OwnSyetemTrayIcon::slotActivated);
void OwnSyetemTrayIcon::slotActivated(QSystemTrayIcon::ActivationReason reason)
{
switch (reason)
{
case QSystemTrayIcon::Trigger:
this->showMessage(CN("托盘图标点击"),CN("鼠标左键~"));
emit sigNormalShow();
break;
default:
break;
}
}
调用自带的 showMessage()
系统弹窗的格式大概如下:
调试一下
// 初始化
void MainWindow::initTrayIcon()
{
// OwnSyetemTrayIcon *m_trayIcon; 头文件中声明了
if (!m_trayIcon)
m_trayIcon = new OwnSyetemTrayIcon;
connect(m_trayIcon, &OwnSyetemTrayIcon::sigNormalShow,
this, [this]{this->show();this->m_trayIcon->stopTrayFlash();});
connect(m_trayIcon, &OwnSyetemTrayIcon::sigClose,
this, [this]{ this->m_realClose=true;this->close();});
m_trayIcon->show();
}
void MainWindow::closeEvent(QCloseEvent *e)
{
// m_realClose 区分状态栏点击的退出和主界面下的退出
if (m_realClose) {
e->accept();
return;
}
QMessageBox msgbox;
msgbox.setWindowTitle(CN("关闭提示"));
msgbox.setText(CN("您点击了关闭按钮,您是想:"));
msgbox.addButton(QMessageBox::Yes)->setText(CN("最小化到托盘"));
msgbox.addButton(QMessageBox::No)->setText(CN("退出程序"));
if (msgbox.exec() == QMessageBox::No) {
e->accept();
} else {
e->ignore();
m_trayIcon->show();
this->hide();
// 模拟一下收到消息后通知托盘闪烁
QTimer::singleShot(1000, m_trayIcon, &OwnSyetemTrayIcon::startTrayFlash);
}
}
使用过程中其实也要根据实际情况,托盘和菜单栏是否同时存在,这就是涉及到 this->hide()
或者托盘什么时候就显示,当然这些都是小问题
完整代码地址
我使用的版本和编译环境:
Qt 5.7.1
- 编译器使用的是
MINGW 5.3.0 32bit
也就是默认自带的