- 前言
- 总体设计
- 详细设计
- Qt的一些小点
我之前为了找工作,花了几天时间了解了下Qt。当时,练手文档在这里:QT小实验-翻金币小游戏 。
前段时间,修改了公司的托盘代码,原来的代码结构设计是很技巧性的。
最近,又看了另一个同事编写的托盘代码,他将界面和功能相分离,代码的质量总体还不错。
我手痒痒,所以空闲时间编写了一个Qt托盘demo。
完整代码见仓库:Qt/tray。该代码编写参考了:Qt官方的托盘代码示例、feiyangqingyun-QWidgetDemo-trayicon
运行结果如下图所示:
总体设计
- 托盘带有右键菜单。
- 托盘有图标。
- 托盘和一个界面相关联。
详细设计
有示例代码可以参考,剩下的是调用API,难度不大。重点代码见下方。
编写托盘可能是下面三个要点:
- 托盘本身的设置。包括图标切换,提示信息等。
- 右键菜单。
- 界面与托盘之间的代码结构关系。
// tray.h
class tray : public QDialog
{
Q_OBJECT
private:
std::shared_ptr trayIcon; // 菜单图标
std::shared_ptr trayMenu; // 菜单上下文
std::shared_ptr showAction; // 显示选项
std::shared_ptr versionAction; // 菜单中版本选项
std::shared_ptr quitAction; // 菜单中退出选项
std::shared_ptr windowInterface; // 主窗口界面
bool isShowVersion = false; // 当前正在展示版本界面
private slots:
void show_version(void);
void exit(void);
public:
tray(QWidget *parent = nullptr);
void createAction(void);
void createIcon(void);
~tray();
};
// tray.cpp
#include "tray.h"
tray::tray(QWidget *parent)
: QDialog(parent)
{
QApplication::setQuitOnLastWindowClosed(false); // 程序别因为没有窗口显示而隐式退出,点击托盘中的退出选项才给退出
// 避免connect的时候连接不到这个对象,先把第一层所有的对象创建出来
windowInterface.reset(new mainWindow(this));
createAction();
createIcon();
setVisible(false); // 由于继承了QDialog,如果没有这行,运行会弹出一个对话框
trayIcon->show(); // 显示托盘图标
windowInterface->show();
}
void tray::createAction()
{
showAction.reset(new QAction(QString("显示界面"), this));
connect(showAction.get(), &QAction::triggered, windowInterface.get(), &mainWindow::show);
versionAction.reset(new QAction(QString("版本"), this));
connect(versionAction.get(), &QAction::triggered, this, &tray::show_version);
quitAction.reset(new QAction(QString("退出"),this));
connect(quitAction.get(), &QAction::triggered, this, &tray::exit);
}
void tray::createIcon()
{
trayMenu.reset(new QMenu(this));
trayMenu->addAction(showAction.get());
trayMenu->addAction(versionAction.get());
trayMenu->addAction(quitAction.get());
trayIcon.reset(new QSystemTrayIcon(this));
trayIcon.reset(new QSystemTrayIcon(this));
trayIcon->setIcon(QIcon(":/image/flow.ico"));
trayIcon->setContextMenu(trayMenu.get());
}
void tray::show_version()
{
if(isShowVersion) {
return;
}
isShowVersion = true;
QMessageBox versionInterface = QMessageBox(this);
versionInterface.setText("Tray's version is 0.0.0");
// 如果只有一个按钮,关闭和escap key也会默认使用这个按钮。所以我们再添加一个不显示的no按钮
versionInterface.setStandardButtons(QMessageBox::Ok);
versionInterface.button(QMessageBox::Ok)->setText("Qt版本");
versionInterface.addButton(QMessageBox::No)->hide();
int ret = versionInterface.exec(); // 使用模态对话框
if(ret == QMessageBox::Ok) {
QApplication::aboutQt();
}
isShowVersion = false;
}
void tray::exit()
{
trayIcon->hide(); // QSystemTrayIcon没有close函数,调用hide
QApplication::quit(); // 退出程序
}
tray::~tray()
{
}
Qt的一些小点
目前没有建立起对Qt的面的认识,用的时候去查下。
目前没有系统的去看Qt的书籍,似乎也没有必要。因为,我不关心某个对话框或者图标这样细枝末节的知识点,这些内容用的时候查下就好。我需要的是高屋建瓴式的对于Qt本身结构方面的介绍。(似乎也没有那个必要,工具能用就好。)
推荐资料:feiyangqingyun/QWidgetDemo
下面,我介绍点上面代码细节。
- 上面代码我使用了很多智能指针。在C++中,在堆上创建的对象,使用智能指针管理,是个好的习惯。但是在Qt中,似乎不必完全如此。我们可以多多的使用对象树 。
- 编写tray类的时候,我们让它继承了QDialog类。反过来想下,如果我们不继承QDialog类会怎么样?我们编写的是托盘代码,似乎不需要对话框的功能。确实我们不需要对话框的功能,但我们需要QObject的功能。可以参考QObject详解
- Q_OBJECT 宏必须出现在声明自己的信号和槽或使用 Qt 元对象系统提供的其他服务的类定义的私有部分中。如果使用cmake作为构建系统,我们会看到set(CMAKE_AUTOMOC ON)。我们到安装Qt的目录中也能找见moc.exe可执行程序。可以参考Qt 元对象系统
- QMessageBox中,我使用了exec方法,而没有使用show方法。这两个的区别是阻塞与非阻塞。可以参考: modal dialog。而关于 QMessageBox的使用,我不喜欢读英文,可以参考Qt QMessageBox用法详解
- 对于有托盘的程序,当最后一个页面关闭的时候,程序也不退出,需要设置setQuitOnLastWindowClosed为false。
- 当使用信号和槽函数的时候,我们需要确保槽函数的对象已经创建出来,否则默认为空指针。信号和槽函数的介绍,可以参考Signals & Slots
- 在设置QMessageBox按钮的时候,如果没有QMessageBox::No按钮的时候,建议添加一个隐藏的该按钮。这也是无奈的做法。因为,当没有设置QMessageBox::escapeButton函数的时候,按下esc键,如果此时仅仅设定了一个按钮,会默认使用这个这个按钮功能。关闭按钮,测试了下,也是如此。



