Qt客户端编程相关核心内容解析

简介

QT 91年由奇趣公司开发的跨平台C++图形界面应用程序开发框架,96年发布第一版

既可以开发GUI程序,也可以开发非GUI程序,比如控制台和服务器

2008年奇趣科技被诺基亚收购,qt成为了诺基亚旗下的编程语言工具

QT,MFC都计算机图形框架的C++的解决方案

Qt应用范围

视频电话/家庭影院/航空航天/汽车信息娱乐/医疗/视觉效果

Qt类图

Widget->(Windows-get缩写)

1
2
3
4
QColorDialog  
QFrontDialog
QTcpSocket
QUdpSocket

这么多类,Qt的20%的类会被80%的使用到
帕累托改进,基于2/8原则建立在帕累托最优理论基础上

2/8原则

1
2
3
4
5
世界上的财富80%掌握在20%人的手中
氢弹的能力80%源于中子的动能,20%源于其他粒子的势能
女孩子第一次和男友见面80%第一次印象决定是否下一次见面甚至嫁给他
处理器80%的指令会被20%几率使用到
打盹的学生20%的时候接受了80%的内容

Qt支持的平台

  • Unix: AIX, HP-UX
  • BSD: FreeBSD, NetBSD
  • Mac: OSX10.5.6以上版本
  • Windows: Windows2000以上版本
  • Linux: redHat, suse, ubuntu, fedora

Qt开发的软件

  • KDE核心库
  • Maya动画渲染模块
  • Google Earth
  • Opera浏览器
  • SkyPe
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Qt部件Widget  

信号与槽

对象树关系

布局管理

标准对话框

自定义对话框

文件与目录

数据库编程

网络

桌面服务与系统操作环境

进程/线程

逆向工程

音频/多媒体

Qt基础

1
2
3
4
5
6
7
8
9
10
#include <QApplication>
#include <QLable>

int main(int argc, char ** argv){
QApplication app(argc, argv);
QLable *lable = new QLable;
lable->setText("Hello World");
lable->show();
return app.exec();
}
  • qmake -project:生成main.pro
  • qmake:生成MakeFile
  • make

设置固定大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
this->setMaximumSize(290,170);
this->setMinimumSize(290, 170);
}

MainWindow::~MainWindow()
{
delete ui;
}


系统信号与槽

  • 能携带任意数量和任意类型的参数,取代原始的回调和消息映射机制

  • 面对对象,独立与标准C/C++,必须借助Qt工具moc(Meta Object Compiler)
    C++预处理程序,为高层次的事件处理自动生成所需要的附加代码

  • 必须把事件和相关的代码联系起来,才能对事件做出响应,才能使不同类型的对象之间能够通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
xxx.h

...
private slots:
void chageTitleSlots();
...


xxx.cpp

...
void MainWindows::changeTitleSlot(){
this->setWindowTitle("Hi");
}


//最后在构造函数声明连接
QObject::connect(ui->changeTitleButton, SIGNAL(clicked()),this, SLOT(changeTitleSlot()));

声明一个槽,后实现,再连接

也可以在ui组件上直接go to slot

自定义信号与槽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
xxx.h

private signals:
void custSignals();


MainWindows::MainWindows(QWidget *parent):QMainWindow(parent),
ui:(new Ui::MainWindow){
QObject::connect(this, SIGNAL(custSignal()), qApp, SLOT(quit()));
}


void MainWindows::on_custButton_clicked(){
//发射这样的信号
emit this->custSignals();
}
  • 1) 支持多对多的信号与槽函数的关联关系,QObject的connect静态函数实现信号与槽的关联(connect有多个重载函数);

  • 2) 当某个信号关联到不同的槽函数时,不同槽执行的顺序是按照建立某信号时的顺序;

  • 3) 信号只能声明且无返回值,此外由关键字signals指定,也不可有其他修饰限定符如public、static等;

  • 4) 发射信号用emit关键字以及信号函数或可能的参数值,注意信号的参数应与槽的参数对应,并且信号的参数个数可以比槽的参数个数少,反之不可;

  • 5) 槽函数slots关键字作为修饰限定符,可以有其他的修饰限定符如public、static等,也可以为虚函数;

  • 6) connect函数最后一个参数为关联关系,默认为AutoConnection即槽执行完成后才返回执行emit后的代码;
    若为其他的如QueuedConnection则会继续执行emit后的代码而无论槽是否执行或执行完成;
    具体效果还需要信号与槽所在线程是否为同一线程与否有不同的表现,disconnect可断开关联关系;

  • 7) 支持信号与槽机制时,需要继承自QObject类或其子类并且在类声明开始时添加Q_OBJECT宏;

  • 8) 自动关联信号与槽,一般声明槽格式如:on_pushButton_clicked()
    其以on、部件对象名、信号加下划线组成;不再需要connect建立关联;
    另外一定要在setupUI调用之前设置部件的对象名(因setupUI内部调用了connectSlotsByName,若setupUI之后设置组件对象名自动关联可能会失效)
    此外自动关联只能支持已预先定义好的信号,不支持自定义信号;

  • 9) 信号与槽机制使得发送者和接收者为松耦合的,参数类型、参数个数任意比较灵活,但是性能较差于回调函数机制的方式。

线程

Qt中包含的线程类:

  • QThread 所有线程的基类,提供了创建一个线程的方法

  • QThreadStorge 提供一逐线程数据存储

  • QMutex 提供相互排斥的锁,或互斥量

  • QMutexLocker 可以自动对QMutex加锁与解锁

  • QReadWirterLock 提供了一个可以同时读操作的锁

  • QreadLocker与QwriteLocker可以自动对QReadWriteLock加锁与解锁

  • QSempHore提供了一个整形信号量,是互斥的泛化

  • QWaitCondition提供了一种方法,使线程可以在被另外线程唤醒之前一直休眠

线程的基础

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>

class MyThread : public QThread
{
Q_OBJECT
public:
bool stop ;
explicit MyThread(QObject *parent = 0);
void run();
signals:

public slots:

};
#endif // MYTHREAD_H



//=======================================================

#include "mythread.h"
#include<QDebug>
MyThread::MyThread(QObject *parent) :
QThread(parent)
{
stop = false;
}
void MyThread::run()
{
for(int i=0;i<1000;i++)
{
if(stop)break;
qDebug()<<i;
QThread::sleep(1);
}
}

//=======================================================

#include <QCoreApplication>
#include "myobject.h"
#include <QThread>
#include<QDebug>
#include "mythread.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyThread myThread;
myThread.start();
QThread::sleep(10);
myThread.stop=true;
return a.exec();
}

线程的同步

QMutex 提供相互排斥的锁,或互斥量

QMetex提供了lock和Unlock函数,如果 一个已经锁定 这个互斥量,只有这个线程unlock后其它线程才可以访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>

class QMutex;
class MyThread : public QThread
{
Q_OBJECT
private:
QMutex qm;
bool stop;
public:
explicit MyThread(QObject *parent = 0);
void run();
void SetFlg(bool flg);
signals:
public slots:

};
#endif // MYTHREAD_H


//=======================================================
#include "mythread.h"
#include<QDebug>
#include<QMutex>
MyThread::MyThread(QObject *parent) :
QThread(parent)
{
stop = false;
}
void MyThread::SetFlg(bool flg)
{
qm.lock();
stop=flg;
qm.unlock();
}

void MyThread::run()
{
for(int i=0;i<1000;i++)
{
qm.lock();
if(stop)
{
qm.unlock();
break;
}
qDebug()<<i;
QThread::sleep(1);
qm.unlock();
}
}


//=======================================================
#include <QCoreApplication>
#include "myobject.h"
#include <QThread>
#include<QDebug>
#include "mythread.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyThread myThread;
myThread.start();
QThread::sleep(10);
myThread.SetFlg(true);
return a.exec();
}

读写锁QReadWirterLock

用mutext进行线程同步有一个问题某个时刻只许一个线程访问资源如果同时有多个线程对共享资源进行访问,

同时有写操作线程那么这种情况下采用mutex就会成为程序的瓶颈。使用QReadWriteLock来实现多线程

读操作,一个线程写操作,写线程执行时会阻塞所有的读线程,而读线程运行不需要进行同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
QReadWriteLock lock;

void ReaderThread::run()
{
...
lock.lockForRead();
read_file();
lock.unlock();
...
}

void WriterThread::run()
{
...
lock.lockForWrite();
write_file();
lock.unlock();
...
}

QreadLocker和QWriteLocker类是对QReadWirterLock 的简化

它们会自动unluck();

1
2
3
4
5
6
7
8
QReadWriteLock lock;

QByteArray readData()
{
QReadLocker locker(&lock);
...
return data;
}

相当于QReadWirterLock 的写法如下

1
2
3
4
5
6
7
8
9
QReadWriteLock lock;

QByteArray readData()
{
lock.lockForRead();
...
lock.unlock();
return data;
}


文件读写操作

读文件Read

  • 加载文件对象QFile file("文件地址");
  • 打开加载的文件file.open(打开方式);
  • 操作文件
  • 关闭打开的文件file.colse();
1
2
3
4
5
6
7
8
void Widget::on_pushButton_clicked()
{
QFile file("L:/qtpro/_qtApp/text/t.txt");
file.open(QIODevice::ReadOnly | QIODevice::Text);
QByteArray t = file.readAll();
ui->text_r->setText(QString(t));
file.close();
}

写文件Wirte

  • 以纯文本的形式读取要保存文件到QString对象ui->text_e->toPlainText();
  • 创建QFile 对象保存文件
  • 打开QFile对象
  • 写入文件操作
  • 关闭打开的文件;
1
2
3
4
5
6
7
8
void Widget::on_pushButton_2_clicked()
{
QString e = ui->text_e->toPlainText();
QFile file("/var/text/e.txt");
file.open(QIODevice::WriteOnly | QIODevice::Text);
file.write(e.toUtf8());
file.close();
}

细节优化处理

  • read文件添加读取文件选择项QFileDialog::getOpenFileName();
  • 打开文件是否成功的判断;
  • 按行读取文件,可控制读取行数与每行字符数;
  • write文件创建保存路径QFileDialog::getSaveFileName();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void Widget::on_pushButton_clicked()
{
QFile file;
QString f = QFileDialog::getOpenFileName(this, QString("选择文件"), QString("/"),QString("TEXT(*.txt)"));
file.setFileName(f);
if(file.open(QIODevice::ReadOnly | QIODevice::Text))
{
QByteArray t ;
while(!file.atEnd())
{
t += file.readLine();
}
ui->text_r->setText(QString(t));
file.close();
}
}

void Widget::on_pushButton_2_clicked()
{
QString e = ui->text_e->toPlainText();
QFile file;
file.setFileName(QFileDialog::getSaveFileName(this, QString("保存路径"), QString("/"),QString("TEXT(*.txt)")));
file.open(QIODevice::WriteOnly | QIODevice::Text);
file.write(e.toUtf8());
file.close();
}

各编码转换

1
2
3
4
5
QString -> QByteArray      QString.toUtf8();

QByteArray -> std::string QByteArray.toStdString();

std::string -> char * string.date();

常用静态函数

1
2
3
QFileDialog::getOpenFileName()    //获取指定文件路径名返回QString
QFileDialog::getExistingDirectory() //获取指定路径返回QString
QFileDialog::getSaveFileName() //获取指定保存路径名返回QString

文本流与数据流

QT中将文件分为文本文件和数据文件,文本文件内容是可读的文本字符,数据文件的内容是二进制数据。

QFile直接支持文本文件和数据文件的操作,主要函数接口如下:

1
2
3
4
5
6
qint64 read( char * data, qint64 maxSize) //数据流读取
QByteArray read( qint64 maxSize) //文本流方式读取
QByteArray readAll() //文本流方式读取
QByteArray readLine()//文本流方式读取
qint64 write(const char * data, qint64 maxSize)
qint64 write(const QByteArray & byteArray)

为了简化文本文件和数据文件的读写操作,QT提供了QTextStream和QDataStream辅助类。

QTextStream可将写入的数据全部转换为可读文本,QDataStream可将写入的数据根据类型转换为二进制数据。

QTemporaryFile是QT中的临时文件操作类,用来安全创建全局唯一的临时文件,QTemporaryFile对象销毁时对应的临时文件将被删除,临时文件的打开方式为QIODevice::ReadWrite,临时文件常用于大数据传递或者进程间通信场合。

1
2
3
4
5
6
QTemporaryFile tempFile;
if( tempFile.open() )
{
tempFile.write("D.T.Software");
tempFile.close();
}

辅助配合使用的类

QFileInfo class

获取文件信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
QFileInfo类用于读取文件的属性信息

QFile file(f);
QFileInfo info(file);

qDebug() << info.exists();
qDebug() << info.isFile();
qDebug() << info.isReadable();
qDebug() << info.isWritable();
qDebug() << info.created();
qDebug() << info.lastRead();
qDebug() << info.lastModified();
qDebug() << info.path();
qDebug() << info.fileName();
qDebug() << info.suffix();
qDebug() << info.size();

QDataStream Class

数据流操作文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
创建流对象 QDataStream date;

int a= xxxx;

string b = "xxxxxxxxx" ;

将数据存在流中 date >> a >> b;

int aa;

string bb;

从流中取出数据 date << aa << bb;

QDataStream在不同的QT版本中数据流文件格式可能是不同的,如果数据流文件需要在不同版本的QT程序间传递时需要考虑版本问题。

1
2
3
void setVersion(int v)

int version() const

QTextStream Class

文本方式操作文件:

1
2
3
创建流对象 QTextStream date;

date.setCodec();

支持对文件读取编码设置(有效解决乱码问题)

QBuffer

QBuffer类为QByteArray提供QIODevice接口。

目前先理解为一个创建一个缓存文件;

QT中预定义了缓冲区的类QBuffer,可以将缓冲区看成一种特殊的IO设备,文件流辅助类可以直接用于操作缓冲区。QBuffer缓冲区写入和读取的数据必须是同一种数据类型,不能混合多种数据类型。

QBuffer的使用场合:

A、线程间不同类型的数据传递

B、缓存外部设备中的数据返回

C、数据读取速度小于写入速度

总结:

读写操作主要方法有

  • read();
  • readAll();
  • readline();
  • write();

数据库

注意要在项目中加入数据库的应用要先在.pro中加入sql保存。

1
QT       += core gui sql

连接数据库

在构造函数中加入这段代码;

1
2
3
4
5
6
7
//使用mySQL写入”QMYSQL”,如果是使用sqlite数据库写入”QSQLITE”,使用Oracle数据库写入”QOICQ” 
QSqlDatabase dataBase=QSqlDatabase::addDatabase("QMYSQL");
dataBase.setHostName("localhost");
dataBase.setUserName("root");
dataBase.setPassword("777777");
dataBase.setDatabaseName("picturedata");
dataBase.open();

如果没有问题连接成功以后open会返回一个bool型的变量true,我们可以接收一下这个变量qDebug显示成功或输出错误信息。

1
2
3
4
5
6
7
8
9
bool ok=dataBase.open();
if(ok)
{
qDebug()<<"open database success";
}
else
{
qDebug()<<"error open database because"<<this->dataBase.lastError().text();
}

插入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 QSqlQuery query(dataBase);
QString datestr = ui->dateEdit->dateTime().toString("dd-MM-yyyy");
QString timestr = ui->dateEdit->time().toString("hh:mm:ss");
QString sql=QString("select *from information");
query.exec(sql);
if(query.numRowsAffected() == 0){
QString savesql = QString("INSERT INTO information(userName,IP,storagePath,productName,date)");
savesql += QString(" VALUES('%1','%2','%3','%4','%5')").arg(ui->userNameEdit->text())
.arg(ui->ipAddressEdit->text())
.arg(ui->storagePathEdit->text())
.arg(ui->productNameEdit->text())
.arg(datestr+' '+timestr);
bool ok=query.exec(savesql);
if(ok){
QMessageBox::about(NULL, "Save", "save new database success");
}
else{
QMessageBox::about(NULL, "Save", "error save new database");
}
}

当然sql语句也可以这样写QString("INSERT INTO information(userName,IP,storagePath,productName,date) VALUES('%1','%2','%3','%4','%5')");

修改数据

1
2
3
4
5
6
7
8
9
10
11
12
QString updatesql = QString("UPDATE information SET userName='%1',IP='%2',storagePath='%3',productName='%4',date='%5' WHERE IP=%6")
.arg(ui->userNameEdit->text())
.arg(ui->ipAddressEdit->text())
.arg(ui->storagePathEdit->text())
.arg(ui->productNameEdit->text())
.arg(datestr+' '+timestr)
.arg(oldIP);
bool ok=query.exec(updatesql);
if(ok){
QMessageBox::about(NULL, "Save", "save database success");}
else{
QMessageBox::about(NULL, "Save", "error save database");}

删除数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
QSqlQuery deletequery(dataBase);
QString deletesql = QString("DELETE FROM information WHERE IP='%1'").arg(oldIP);
bool ok=deletequery.exec(deletesql);
if(ok)
{
QMessageBox::about(NULL, "Reset", "Reset database success");
ui->userNameEdit->clear();
ui->ipAddressEdit->clear();
ui->storagePathEdit->clear();
ui->productNameEdit->clear();
ui->dateEdit->clear();
}
else
{
QMessageBox::about(NULL, "Reset", "error reset database");
}

查询数据

1
2
3
4
5
6
7
8
9
10
11
12
QSqlQuery showquery(dataBase);
QString showsql=QString("select *from information");
showquery.exec(showsql);
if(showquery.numRowsAffected() != 0)
{
showquery.next();
ui->userNameEdit->setText(showquery.value(0).toString());
ui->ipAddressEdit->setText(showquery.value(1).toString());
ui->storagePathEdit->setText(showquery.value(2).toString());
ui->productNameEdit->setText(showquery.value(3).toString());
ui->dateEdit->setDateTime(QDateTime::fromString(showquery.value(4).toString(),"dd-MM-yyyy hh:mm:ss"));
}