通过一个实际案例来介绍开发程序的流程和QT的各个模块。
开发程序流程包括系统结构设计、模块设计、层次划分、模块实现、模块间通信、模块匹配。
QT模块包括在界面设计CSS样式、web网站文件的上传和下载、应用程序实例检测、外部动态链接库调用、系统托盘管理、查看网络状态、执行外部进程、进程间通过Windows消息通信、INI和JSON格式文件读写、使用install shield打包和发布程序。
目录
1.系统结构设计
以“远程传输与控制系统”为例从功能上分为面向底层硬件部分和顶层通信部分;在模块类别上,系统分为硬件层、软件层、网络层、跨语言通信层、数据层。
1.1 总体结构
软件层和硬件层分隔,因为硬件层要操作身份证读卡器,所以调用逻辑与软件层有区别。网络层与跨语言层本属于网络通信范畴。但在系统中一部分是传统的HTTP和TCP通信,另一部分是跨语言hessian协议调用远端Java服务。两者实现技术和手段差别很大,因此在系统设计中将它们分开。
数据层严格来说是传统C/S结构中的服务器端程序。
1.2 软件层
软件层实现系统的大部分功能。
界面登录:登录界面要进行CSS样式处理,登录后信息要保存到系统配置文件中。同时,登录界面不能通过关闭窗口退出,只有正确登录后,才可以最小化到托盘。
系统托盘:用户对硬件操作的结果以实时消息显示在托盘程序图标中的消息框,当用户需要时,单击即可重现消息。同时,单击图标右键,弹出的快捷菜单中包括硬件设备状态、业务状态、注销用户、退出程序。
后台执行:进入托盘状态后,系统会启动timer,定期进行业务轮询。
强制升级:使用网络层从指定的网址读取升级程序版本。升级前,系统自动关闭自身进程。
1.3 硬件层
首要任务是与硬件设备建立关联。采用动态库DLL建立关联。
然后是操作硬件设备。
1.4 网络层
先通过HTTP协议在指定网址读取升级程序,如果发现新版本,则下载相应升级程序。升级程序是打包好的EXE文件。
1.5 跨语言通信层
在操作数据库方面,基于JavaEE的web技术是十分成熟。因此在数据层采用JavaEE实现业务服务,在客户端通过QT。两者连接采用比较成熟的hessian协议。
1.6 数据层
数据库采用PostgreSQL。硬件设备读取后的数据经过处理,以JSON串形式存储在数据表中。
2. 软件层
开发一个软件登录界面,登录后界面最小化进入托盘管理,在后台运行。
2.1 程序窗口
2.1.1 对话框开发
首先创建一个QDialog类,然后在ui文件构建一个下面的界面。
2.1.2 图片资源管理
label控件可以通过加载图片来达到美化对话框效果。
首先,选择“文件”——“新建文件或项目”。然后在“文件与类”中选择QT,再选择Qt Resource File,自定义文件名。
然后, 右击.qrc文件,选择添加现有文件,把图片添加进去。
最后, 在ui文件中,对需要添加图片的label控件右击,选择“改变样式表”,然后点击“添加资源”,选择“image”或“background-image”,选择对应图片添加。
注:Qt虽然原则上支持PNG、JPEG等多种格式的图片,但对PNG支持最好。如果程序中使用JPEG等其他格式图片,则程序打包时要加上相应的DLL,否则图片在用户的计算机上无法显示。而且有时加载了支持JPEG的DLL,也无法正常显示。所以建议在Qt中使用PNG格式的图片。
2.1.3 CSS样式表
Qt允许使用CSS样式表优化界面。这是Qt在各种开发平台中所独有的特色。在ui界面空白处右击(不要单击label等控件),选择“改变样式表”,在编辑栏输入下面代码:
2.2 按钮响应
单击按钮应该有对应时间发生。
选择“登录”按钮右击,选择“转到槽”。然后选择第一个函数,表示响应单击事件。
接着在dialog.cpp中就会自动生成on_pushButton_clicked()函数。
最后,在函数编写下面代码。
2.3 托盘管理
程序登录后直接进入Windows系统托盘。Qt程序是跨平台程序,由于有些平台没有系统托盘的概念,因此需要针对Windows系统托盘进行特殊处理。
2.3.1 后台图标
增加托盘概念需要在Qt添加QSystemTrayIcon头文件,代码如下所示。
2.3.2 事件劫持
现在单击运行程序的X按钮,程序将直接退出。因为单击X按钮后程序进入系统托盘,然后接着执行Qt关闭对话框的默认动作,退出程序。
Qt中,处理关闭事件函数名为closeEvent(),在这个函数中“劫持”关闭事件,然后做自己的处理。
注:“关闭”按钮事件被拦截后,需要使用任务管理器将进程关闭。
2.4 菜单管理
程序进入后台运行后,用户对程序的所有操作都只能通过托盘图标进行,因此要对托盘图标设计菜单,实现用户接口功能。
单击和右击托盘图标一般都会出现不同的菜单。右击托盘图标显示系统的功能菜单;单击托盘图标显示系统消息菜单。
2.4.1 鼠标右键动作
鼠标右击会显示功能菜单。在Qt中显示功能菜单需要2个信息,对应头文件分别是<QAction>和<QMenu>。
实现菜单需要先定义QAction变量;然后将QAction变量当做参数生成QMenu变量,这个QMenu变量就是菜单项;最后使用定义的托盘图标变量,使用setContextMenu函数,将生成的菜单加入托盘图标中。
2.4.1 鼠标左键动作
鼠标左键的动作相比于右键更为复杂:第一步是响应鼠标左键的消息,Qt4之前是用SIGNAL(actived(QSystemTrayIcon::ActivationReason))消息,用户要自定义SLOT函数来处理SLOT(myIconActivated(QSystemTrayIcon::ActivationReason))消息;第二步是定义显示的消息内容;第三步是显示消息。
Windows10上的显示结果如下所示:
2.5 单实例管理
对于大多数程序来说,程序运行后,如果用户再次点击运行程序,那么程序会打开第二个实例,比如IE浏览器,每打开一个就会打开一个实例。对于某些程序来说,它不希望用户再次点击程序时执行新的进程,而是希望程序已单实例的形式运行,就比如远程控制与运输系统。
单实例管理的实现技术有很多种。比如,在系统目录下生产一个文件,里面加上配置信息,如APPStarted=yes,程序退出时再改成APPStarted=no。只有在no的情况下才启动进程。但在硬盘上配置文件容易出现错误。
远程传输与控制系统通过在内存中分配一块标识来完成单实例的判断。
3. web网络服务模块
3.1 网络模块类
早期Qt针对多种协议实现了相应的类,包括QtcpSocket,QFtp等,在Qt5之后,Qt将所有网络协议模块封装在一起,形成单独的网络接口,对应于QtNetwork模块中的QNetworkAccessManager类和QNetworkReply类。除非遇到要处理底层网络的情况,不建议再用QtcpSocket类。
QNetworkAccessManager类支持应用程序发送网络请求和接收网络应答。只要创建QNetworkAccessManager对象,并在创建后进行网络发哦送请求的通用配置和设置,如代理和缓存的配置以及相关的其他信号,就可以实现网络通信。
网络通信的实时状态和通信结果可以使用应答信号获取,这个应答信号就是QNetworkReply类。QNetworkReply类包含发送给QNetworkAccessManager请求的所有应答数据,包含一个URL和一些首部信息。QNetworkReply是一个顺序访问的QIODevice,一旦数据从对象中读取出来,那么对象就不再持有这些数据。
QNetworkReply类有几个关键函数用于操作网络通信和获取网络数据:
1)downloadprogress()表示当前网络数据传输数量(不一定准确);
2)finished()在网络通信结束时被触发,不再对应答数据进行更新;
3)read()和readAll()可以读取数据;
4)close()调用后放弃所有应答信息。
//当网络通信完成时,发出信号
connect(pReply, QNetworkReply::finished, this, myFinished);
/*在下载大文件时,需要为用户提供下载进度。
*myDownloadProgress提供qint64 bytesReceived和qint64 bytesTotal两个参数
*qint64 bytesReceived表示当前传输数据量
*qint64 bytesTotal表示总共应传输数据总量
*/
connect(pReply, QNetworkReply::downloadProgress, this, myDownloadProgress);
//当网络处理函数发生错误时,系统发出错误信号
connect(pReply, SIGNAL(QNetworkReply::NetworkError), this, SLOT(slotError(QNetworkReply::NetworkError)));
注:Qt5虽然可以继续使用SIGNAL和SLOT宏定义,但建议用新格式。
3.2 下载升级文件
远程控制与传输系统实现了程序自动升级功能,因此需要从网络上下载文件。但需要下载的文件除升级文件外,还有升级版本控制文件、配置文件等。因此,需要制作一个通用的下载类,将网络下载功能代码打包成一体。
//UpDateByNetwork.h
#ifndef UPDATEBYNETWORK_H
#define UPDATEBYNETWORK_H
#include <QObject>
#include <QtNetwork>
#include <QDir>
class UpDateByNetwork : public QObject
{
Q_OBJECT
public:
UpDateByNetwork();
~UpDateByNetwork();
void startDownload();
private slots:
void myFinished();
private:
bool bDownloaded;
QFile *pFile;
QNetworkAccessManager *pManager;
QNetworkReply *pReply;
QString baseAddress;
QString downloadFilename;
};
#endif // UPDATEBYNETWORK_H
//UpDateByNetwork.c
#include "updatebynetwork.h"
UpDateByNetwork::UpDateByNetwork()
{
}
UpDateByNetwork::~UpDateByNetwork()
{
}
void UpDateByNetwork::startDownload()
{
bDownloaded = false;
QUrl url = QUrl(baseAddress + downloadFilename);
QString strAppDir = QCoreApplication::applicationFilePath();
strAppDir = strAppDir.left(strAppDir.lastIndexOf("/"));
pFile = new QFile(strAppDir + downloadFilename);
if(!pFile->open(QIODevice::WriteOnly | QIODevice::Truncate))
{
qDebug() <<"本地文件无法创建,无法下载文件";
return;
}
pManager = new QNetworkAccessManager();
pReply = pManager->get(QNetworkRequest(url));
//创建信号机制
connect(pReply, QNetworkReply::finished, this, &UpDateByNetwork::myFinished);
}
void UpDateByNetwork::myFinished()
{
if(pFile)
{
pFile->write(pReply->readAll());
pFile->flush();
pFile->close();
delete pFile;
pFile = NULL;
}
pReply->deleteLater();
pReply = NULL;
bDownloaded = true;
}
void UpDateByNetwork::setBaseAddress(QString str)
{
baseAddress = str;
}
void UpDateByNetwork::setDownloadFileName(QString str)
{
downloadFilename = str;
}
bool UpDateByNetwork::isDownload()
{
return pReply == NULL?false:true;
}
3.3 强制升级
实现强制升级需要3个步骤:
1.程序启动时从网络读取程序新版本信息。
2.与本地版本信息对比,判断是否需要升级。
3.锁定其他按钮,只允许单击升级按钮。
3.3.1 设计INI文件信息
//config.ini
[program]
userZone =
userName =
userID =
version = 1.0
installDir =
[update]
baseUrl = http://xxx.cn/release/
exeFileName = _update_setup_
versionFileName = updateVersion.ini
updateLocalDir = update
【program】区块指定了程序相关的所有信息,包括用户区域、用户名、用户ID、当前程序版本等。
【update】区块定义从何处下载INI信息文件;versionFileName指定了需要下载的INI文件名;updateLocalDir指定了下载后的文件放在哪个目录下。这个值作为参数传入前面的mydir.mkdir(LOCALUPDATEDIR)函数;exeFileName 指定了要下载的EXE文件名。按照程序规则,会在文件名前加上用户区域信息段,在文件名后加上版本号和“.exe”后缀,形成类似“ALL_update_setup_2.0.exe”的文件名。
3.3.2 读写INI文件
INI文件通常放在应用程序所在目录,或者Windows常用的系统目录,如“我的文档”目录。
若是后者,则需提取Windows系统的目录:
//0.1 寻找系统INI my documents/x/config.ini
QStringList slist = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation);
QDir documentsDir = slist.at(0);
QString configIni = "/x/config.ini";
QString configIniWhole = documentsDir.path() + configIni;
Qt为读取INI文件提供QSettings类。使用时仅需将INI文件名作为参数实例化该类便可。
//0.1 寻找系统INI my documents/x/config.ini
QStringList slist = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation);
QDir documentsDir = slist.at(0);
QString configIni = "/x/config.ini";
QString configIniWhole = documentsDir.path() + configIni;
//0.2读入INI
QSettings configIniRead(configIniWhole, QSettings::IniFormat);
curUserName = configIniRead.value("/program/userName").toString();
if(curUserName.isEmpty())
isLogin = false;
else
isLogin = true;
curVersion = configIniRead.value("/program/version").toDouble();
curInstalledDir = configIniRead.value("/program/installedDir").toString();
QSettings configIniWrite(configIniWhole, QSettings::IniFormat);
configIniWrite.setValue("program/installedDir", QDir::currentPath().trimmed());
baseUrl = configIniRead.value("/update/baseUrl").toString();
//0.2.2 update
exeFileName = configIniRead.value("/update/exeFileName").toString();
versionFileName = configIniRead.value("/update/versionFileName").toString();
updateDir = configIniRead.value("/update/updateLocalDir").toString();
isThereNewUpdate = false;
qDebug() << "---" << curVersion << "==" << curUserName << "==" << isLogin;
3.3.3 逻辑判断
下载升级程序前先在网站上下载INI文件进行版本比较。
QString fileName = updateDir + "./" + versionFileName;
QFile file(fileName);
if(file.size() == 0)
QMessageBox::Information(this, "升级", "升级文件检测失败,请检查网络是否正常");
else
{
QSettings updateIniRead(fileName, QSettings::IniFormat);
QString updateZone = updateIniRead.value("/update/updateZone").toString();
double updateVersion = updateIniRead.value("/update/updateVersion").toString();
QString strUpdateVersion = updateIniRead.value("/update/updateVersion").toString();
if(!curUserZone.isEmpty()
&& (updateZone.contains(curUserZone) || updateZone.contains("ALL"))
&& (updateVersion > curVersion))
{
isThereNewUpdate = true;
QString tmpZone = updateZone.contains("ALL")?"ALL":curUserZone;
exeFileName = tmpZone + exeFileName + strUpdateVersion + ".exe";
}
file.remove();
}
3.3.4 开始下载
如果获取指定文件的相关信息,就允许用户单击“升级”按钮实现升级,其他按钮变灰;如果没有,则“升级”按钮变灰。
void Dialog::on_pushButton_update_clicked()
{
ui->progressBar->setValue(0);
ui->progressBar->show();
ui->label_Update->setText("正在下载升级程序,请稍候...");
UpDateByNetwork *pUpdateExeFile = new UpDateByNetwork();
pUpdateExeFile->setBaseAddress(baseUrl);
pUpdateExeFile->setDownloadFileName(exeFileName);
pUpdateExeFile->startDownload();
connect(pUpdateExeFile->pReply, &QNetworkReply::downloadProgress, this, &Dialog::myDownloadProgram);
while(!pUpdateExeFile->isDownload())
QCoreApplication::processEvents();
QString exe = updateDir + "/" + exeFileName;
QFile exeFile(exe);
if(!exeFile.open(QIODevice::ReadOnly))
{
QMessageBox::information(this, "升级", "下载升级程序失败,请检查网络是否连通");
exeFile.close();
delete pUpdateExeFile;
return;
}
else if(exeFile.size() < 4096)
{
if(exeFile.size() == 0)
QMessageBox::information(this, "升级", "下载升级程序失败,请检查网络是否连通。");
else
QMessageBox::information(this, "升级", "下载升级程序失败,下载源不存在。请联系客服。");
exeFile.close();
delete pUpdateExeFile;
return;
}
}
当自定义类,且这个类要执行的是一个占用CPU较长的工作时,Qt主程序和这个类的子程序之间需要一个同步过程,即主程序等待子程序执行完后再继续执行。QCoreApplication::processEvents()函数一般情况下可以实现事件驱动和保持同步功能。但在外面加一个循环更能保持程序的有效性。
3.3.5 下载完成
下载完成后,首先启动升级的EXE程序;然后是关闭当前运行的程序。
qDebug() << "EXE已下载";
delete pUpdateExeFile;
ui->label_Update->setText("升级程序下载完毕");
ui->progressBar->hide();
ui->pushButton_update->setEnabled(false);
QMessageBox::information(this, "升级", "下载升级程序成功,单击确定按钮开始升级。\n\n单击确定按钮后,将关闭当前程序并启动升级程序。");
QProcess::startDetached(updateDir + "/" + exeFileName);
qApp->quit();
QProcess::startDetached可以执行进程外的程序。
4.跨语言通信层
4.1 进程间通信
1)无名管道
一种半双工的通信方式。且只能具有亲缘关系的进程间使用(通常指父子进程)。
2)高级管道
即使不是父子进程,也可以通过这种管道通信。并且,可以把另一个程序当做新进程与当前进程通信。
3)有名管道
半双工通信。允许无亲缘关系进程间通信。
4)信号量
是一个计数器,控制多个进程对共享资源的访问。通常作为锁机制。
5)信号
用于通知接受进程某个事件已经发生
6)消息队列
由消息的链表存放在内核中并由消息队列标识符标识的,要通信的进程将消息放到消息队列中,操作系统从消息队列中提取消息。消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
7)共享内存
最快的IPC方式,通常与信号量一起使用。但降低应用程序的安全性,黑客可以利用共享内存找到攻击引用程序的入口。
8)套接字
用于不同机器间的进程通信。
4.2 跨语言通信
客观的说,无论哪种语言,最后都生成通信程序。两个程序经过调试就可以完全通过主机“地址::端口”号相互通信,实现跨语言的网络通信。因为通信协议层的结构与程序设计语言无关。
跨语言通信目前主要有两种方式:链接库层跨语言通信和代码层跨语言通信。
4.2.1 链接库层跨语言通信
为解决可执行代码层通信不够灵活的问题,产生了动态链接库技术。使用一种语言编译生成的动态链接库,完全可由其他语言编写的应用程序调用。
1.Qt制作动态库
//dll.h
#ifndef DLL_H
#define DLL_H
#include "dll_global.h" //系统自动生成
class DLLSHARED_EXPORT Dll
{
public:
Dll();
};
extern "C" DLLSHARED_EXPORT int myadd(int a, int b); //使myadd函数一传统C语言格式出现
#endif DLL_H
//dll.cpp
#include "dll.h"
DLL::Dll()
{
}
int myadd(int x, int y)
{
return x+y;
}
2.VC2010使用动态库
#include <stdafx.h> //包含VC预编译头文件,比如stdio等
#include <iostream>
#include <windows.h>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
HINSTANCE hdll = LoadLibrary(L"dll.dll"); //动态加载DLL
typedef int(_stdcall* lpFN)(int, int); // 定义函数指针
lpFN FN;
FN = (lpFN)::GetProcAddress(hdll, "myadd"); //找到myadd函数的指针地址
int nResult = FN(15, 2);
FreeLibrary(hdll);
cout << nResult << endl;
system("pause");
return 0;
}
4.2.2 代码层跨语言通信
指程序开发时,像调用自定义函数一样调用另一种语言编写的函数代码。在调用过程中,不需要先把两种语言编译成EXE的二进制格式,也不用通过动态链接库实现中转调用。
在代码层跨语言通信方面,有一些成熟技术,包括Java的远程过程调用(RMI)、通用的web service技术,轻量级协议hessian。
1)Java RMI
允许某个Java虚拟机对象可以像调用本机Java方法或对象一样,调用网络中任何位置的Java虚拟机中的方法或对象。但客户端和服务器端程序都必须为Java。
2)web service
无论使用何种语言,只要支持标准XML格式信息接口,就可以交换数据信息。
3)hessian
主要用于面向对象的消息通信,支持紧凑的动态数据类型。支持的语言:Java、python、C++、PHP。
与web service相比,hessian采用二进制RPC协议,适合发送二进制数据。web service只能发送符合XML格式的文本数据。如果需要发送二进制数据,则需要进行base64码转换;与Java RMI相比,Java RMI支持任意复杂格式数据,但不支持HTTP通信,所以无法穿透防火墙。而hessian支持数据格式有限,但使用HTTP协议,可以穿透防火墙。
4.3 hessian协议的不同版本
4.3.1 C++官方版本
代码参见http://hessian.caucho.com/。在这可以下载hessian协议的C++版本,程序包被称为hessiancpp。
4.3.2 第三方C语言版本
hessianord项目是一个使用比较广泛的版本。代码参见https://code.google.com/p/hessianorb/。
搭建hessian环境时需要以下配置:
java -jdk 1.6+
apache -ant 在服务器端部署Java web程序必需的软件包
cmake 2.8+ 编译客户端C语言程序的编译器
curl -devel heesianord必需的工具包,提供HTPP通信支持。
4.3.3 Qt实现
代码参见:https://github.com/caiiiycuk/qhessian。
5. 硬件模块
5.1 加载动态库
5.1.1 静态加载动态链接库
静态加载需要修改.pro文件并包含完整的头文件。
1)Linux平台
LIBS += -L/usr/local/lib \
-lmath
2)Windows平台
//引入第三方动态库
LIBS += mydll.dll //只能引入.dll格式,.lib和.a格式不可用
//引入Windows系统动态库
LIBS += -lshell32 \
-lsetupapi //不带dll扩展名
若用Qt调试程序,则将DLL放在“build-xxx-Debug/Relese”目录下;若是直接打开EXE,则将DLL放在EXE同级目录下。
5.1.2 动态加载动态链接库
动态加载不需要修改.pro,也不用加载头文件。
#include "ui_dialog.h"
#include <QDebug>
#include <QLibrary>
typedef int(*ConnectFun)(int a, int b);
Dialog::Dialog(QWidget *parent):
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
}
void Dialog::on_pushButton_clicked()
{
qDebug() << "here";
QLibrary m_lib;
m_lib.setFileName("dll.dll");
bool bLoaded = m_lib.load();
if(bLoaded)
{
ConnectFun myConnectFun = (ConnectFun)m_lib.resolve("myadd");
int r = myConnectFun(15, 20);
qDebug() << QString::number(r);
}
else
{
qDebug() << "未能动态加载DLL";
}
}
6. Qt关键模块
6.1 消息机制
Windows的消息机制是紧耦合状态(所有功能处于一个函数中),消息处理函数在自定义函数中完成。如果有新消息,需要程序员手动更改这个函数。而Qt的信号与槽机制是松耦合机制,信号与槽函数可以分开处理。
6.2 事件机制
6.2.1 事件响应
#include "dialog.h"
#include "ui_dialog.h"
#include <QDebug>
#include <QKeyEvent> //键盘事件
#include <QMouseEvent> //鼠标事件
#include <QWheelEvent> //滚轮事件
#include <QTime>
Dialog::Dialog(QWidget *parent):
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
idTimer1 = startTimer(1000);
idTimer2 = startTimer(2000);
}
void Dialog::keyPressEvent(QKeyEvent *event)
{
QString s;
if(event->modifiers() == Qt::ControlModifier)
if(event->key() == (Qt::Key_A))
s = "ctrl + A";
qDebug() << "按下了键盘" << event->text() << s;
QWidget::keyPressEvent(event);
//event->ignore();
}
void Dialog::mousePressEvent(QMousePressEvent *s)
{
if(e->button() == Qt::LeftButton)
{
qDebug() << "按下了鼠标左键";
QCursor c;
c.setShape(Qt::ClosedHandCursor);
QApplication::setOverrideCursor(c);
}
}
void Dialog::mouseMoveEvent(QMouseEvent *e)
{
qDebug() << "鼠标移动";
if(e->buttons() & Qt::LeftButton) //按着左键移动
{
QPoint t;
t = e->globalPos() - offset;
move(t);
}
}
void Dialog::mouseReleaseEvent(QMouseEvent *e)
{
QApplication::restoreOverrideCursor();
e->ignore(); //其他不处理
}
void Dialog::mouseDoubleClickEvent(QMouseEvent *e)
{
if(e->button() == Qt::LeftButton)
{
if(windowState() != Qt::WindowFullScreen)
setWindowState(Qt::WindowFullScreen);
else
setWindowState(Qt::WindowNoState); //恢复原状
}
}
void Dialog::wheelEvent(QWheelEvent *e)
{
if(e->delta() > 0)
qDebug() << "向上";
else
qDebug() << "向下";
}
void Dialog::timerEvent(QTimerEvent *e)
{
if(e->timerId() == idTimer1)
qDebug() << "timer1";
else if(e->timerId() == idTimer2)
qDebug() << "timer2";
}
6.2.2 自主推动事件循环
当Qt程序处于远程网络通信、线程间通信等异步通信状态时,事件需要应用程序自行推动,否则程序会出现异常。函数为QCoreApplication::processEvents(),一般放在一个循环里。如果异步事件已经同步,则可以退出循环。
6.2.3 与Windows程序消息通信
Qt可以处理Windows标准消息。
6.3 执行外部程序
//执行外部程序
#include <QProcess>
QProcess myProcess;
void Dialog::on_pushButton_clicked()
{
myProcess.start(ui->lineEdit->text());
}
//停止外部程序
process.start("taskkill /IM iexplore.exe /T /F"); //借用Windows工具taskkill来停止进程
process.waitForFinished();
//后台启动程序
process.setStandardOutputFile(QProcess::nullDevice()); //输出界面为空,即不显示运行界面
Process.start(ui->lineEdit->text());
//以指定方式输出
process.setStandardOutputFile("aaa.txt"); //进程结果输出到aaa.txt中
6.4 服务模块
6.4.1 INI文件操作
之前已经详细描述。
6.4.2 JSON文件操作
JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。它基于 ECMAScript (欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率
1)简单格式,如{"name":"use1", "id": "3577"}。由纯字符串组成。
//读取JSON串
#include <QJsonParseError>
#include <QJsonDocument>
#include <QVariantMap>
#include <QDebug>
QString json("{"
"\"dealerNo\":\"0240000044\", "
"\"userId\":\"000000000000000000002400044\", "
"\"topDealerNo\":\"0240\""
"}");
QJsonParseError error;
QJsonDocument jsonDocument = QJsonDocument::fromJson(json.toUtf8(), &error);
if(error.error == QJsonParseError::NoError)
{
if(jsonDocument.isObject())
{
QVariantMap result = jsonDocument.toVariant().toMap();
QString curUserName = result["dealerNo"].toString();
QString curUserID = result["userId"].toString();
QString curUserZone = result["topDealerNo"].toString();
}
}
else
{
qDebug() << "json error";
}
//写入JSON串
#include <QJsonDocument>
QString json("{"
"\"dealerNo\":\"0240000044\", "
"\"userId\":\"000000000000000000002400044\", "
"\"topDealerNo\":\"0240\""
"}");
QString Name = "cnk";
QString sex = "male";
QString nation = "china";
json.insert("Name", QString::fromLocal8Bit(Name).trimmed());
json.insert("sex", QString::fromLocal8Bit(sex).trimmed());
json.insert("nation", QString::fromLocal8Bit(nation).trimmed());
QJsonDocument jsondocument;
jsondocument.setObject(json);
QByteArray byte = jsondocument.toJson(QJsonDocument::Compact);
QString json_str(byte);
2)复杂格式,如{"name":"use1", "id": 1,"array":[23,"asdf",true]}。其中,第一个值是字符串,第二个值是整形,第三个值是由整数、字符串、布尔值组成的数组。
QJsonParseError error;
QJsonDocument jsonDocument = QJsonDocument::fromJson(QString(json).toUtf8(), &error);
if(error.error == QJsonParseError::NoError)
if(jsonDocument.isObject())
{
QJsonObject result = jsonDocument.object();
QJsonObject::Iterator it = result.begin();
while(it != result.end())
{
switch (it.value().type()) {
case QJsonValue::String:
qDebug() << it.key() << "=" << it.value().toString();
break;
case QJsonValue::Array:
qDebug() << it.key() << "=" << it.value().toArray();
QJsonArray subArray = it.value().toArray();
qDebug() << "subArray count=" <<subArray.count();
qDebug() << "value 1 =" <<subArray.at(0).toInt();
qDebug() << "value 2 =" <<subArray.at(1).toString();
qDebug() << "value 3 =" <<subArray.at(2).toBool();
break;
case QJsonValue::Bool:
qDebug() << it.key() << "=" << it.value().toBool();
break;
case QJsonValue::Double:
qDebug() << it.key() << "=" << it.value().toDouble();
break;
case QJsonValue::Object:
qDebug() << it.key() << "=" << it.value().toObject();
break;
case QJsonValue::Null:
break;
case QJsonValue::Undefined:
qDebug() << "type is undefined";
break;
}
it++;
}
}
6.4.3 XML文件操作
XML文件是前几年流行的数据格式,近来有被JSON格式替代的趋势。Qt中操作XML的技术有DOM、SAX等。但DOM比较慢,而SAX比较复杂,建议使用QXmlStreamReader与QXmlStreamWriter。
6.4.4 二进制数据处理
将二进制信息转换为文本ASCII码信息,是通过web方式传输数据的重要方式。
QByteArray b1 = QString("abc").toLatin1();
QByteArray b1_64 = b1.toBase64();
qDebug() << "b1:" << b1;
qDebug() << "b1_64:" << b1_64;
QByteArray b2 = QByteArray::fromBase64(b1_64);
qDebug() << "b2:" << b2;
6.4.5 Qt日志
void MyOutputMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
static QMutex mutex;
mutex.lock();
QString text;
switch (type) {
case QtDebugMsg:
text = QString("Debug:");
break;
case QtWarningMsg:
text = QString("Warning:");
break;
case QtCriticalMsg:
text = QString("Critical:");
break;
case QtFatalMsg:
text = QString("Fatal:");
break;
}
QString context_info = "";
QString current_date_time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss ddd");
QString current_date = QString("%1").arg(current_date_time);
QString message = QString("%1 %2 %3 %4").arg(current_date).arg(text).arg(context_info).arg(msg);
QString strAppDir = QCoreApplication::applicationFilePath();
strAppDir = strAppDir.left(strAppDir.lastIndexOf("/"));
QFile file(strAppDir + "/log.txt");
if(file.size() > 20000000)
file.remove(); //大于20MB就删除
file.open(QIODevice::WriteOnly | QIODevice::Append);
QTextStream text_stream(&file);
text_stream << message << "\r\n";
file.flush(); //强制刷新
file.close();
mutex.unlock();
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
qInstallMessageHandler(MyOutputMessage); //注册日志
Dialog w;
w.show();
return a.exec();
}
注册日志后,qDebug()、qWarning()语句信息都会输出到log文件中。