Qt笔记总结(下)

简介: Qt笔记总结的下篇来喽!

一、绘图和绘图设备


QPainter


QPainter用来执行绘制的操作;QPaintDevice是一个二维空间的抽象,QPaintEngine提供了画笔(QPainter)在不同的设备上进行绘制的统一的接口


0.png


上面的示意图告诉我们,Qt 的绘图系统实际上是,使用QPainter在QPainterDevice上进行绘制,它们之间使用QPaintEngine进行通讯(也就是翻译QPainter的指令)。


下面我们通过一个实例来介绍QPainter的使用:


class PaintedWidget : public QWidget
 {
  Q_OBJECT
public:
  PaintedWidget(QWidget *parent = 0);
protected:
  void paintEvent(QPaintEvent *);
 }


注意我们重写了QWidget的paintEvent()函数。接下来就是PaintedWidget的源代码:


PaintedWidget::PaintedWidget(QWidget *parent) :
  QWidget(parent)
 {
  resize(800, 600);
  setWindowTitle(tr("Paint Demo"));
 }
 void PaintedWidget::paintEvent(QPaintEvent *)
 {
  QPainter painter(this);
  painter.drawLine(80, 100, 650, 500);
  painter.setPen(Qt::red);
  painter.drawRect(10, 10, 100, 400);
  painter.setPen(QPen(Qt::green, 5));
  painter.setBrush(Qt::blue);
  painter.drawEllipse(50, 150, 400, 200);
 }


构造函数中,我们仅仅设置了窗口的大小和标题。而paintEvent()函数则是绘制的代码。


绘图设备


绘图设备是指继承QPainterDevice的子类。


QBitmap是QPixmap的一个子类,它的色深限定为1,可以使用 QPixmap的isQBitmap()函数来确定这个QPixmap是不是一个QBitmap。


QPixmap:和平台无关,针对屏幕进行了优化了,不能对图片进行修改


QImage:和平台无关,在线程中绘图,可以对图片进行优化


QPicture:保存绘图的状态(二进制文件)


1、QPixmap -> QImage
QPixmap a;
a.toImage();
2、QImage -> QPixmap
QImage b;
QPixmap::fromImage(b);
3、QPainter
QPainter p;
QPicture pic;
p.begin(&pic);
//绘图动作
p.end();
pic.save("路径");
4、加载图片
QPicture temp;
temp.load("路径");


不规则窗口


1、给窗口画一张背景图

2、去表框

3、设定属性(背景透明)

4、移动坐标是相对于屏幕而言


二、文件系统


I/O 设备的类图(Qt5):


01.png


  1. QIODevice:所有 I/O 设备类的父类,提供了字节块读写的通用操作以及基本接口;
  2. QFileDevice:Qt5新增加的类,提供了有关文件操作的通用实现。
  3. QFlie:访问本地文件或者嵌入资源;
  4. QTemporaryFile:创建和访问本地文件系统的临时文件;
  5. QBuffer:读写QbyteArray, 内存文件;
  6. QProcess:运行外部程序,处理进程间通讯;
  7. QAbstractSocket:所有套接字类的父类;
  8. QTcpSocket:TCP协议网络数据传输;
  9. QUdpSocket:传输 UDP 报文;
  10. QSslSocket:使用 SSL/TLS 传输数据;


QFile提供了从文件中读取和写入数据的能力。


我们通常会将文件路径作为参数传给QFile的构造函数。不过也可以在创建好对象最后,使用setFileName()来修改


我们可以使用QDataStream或QTextStream类来读写文件,也可以使用QIODevice类提供的read()、readLine()、readAll()以及write()这样的函数。值得注意的是,有关文件本身的信息,比如文件名、文件所在目录的名字等,则是通过QFileInfo获取


QFileInfo有很多类型的函数,举一些常用例子。比如:


  1. isDir()检查该文件是否是目录;
  2. isExecutable() 检查该文件是否是可执行文件等。
  3. baseName() 可以直接获得文件名;
  4. completeBaseName() 获取完整的文件名
  5. suffix() 则直接获取文件后缀名。
  6. completeSuffix() 获取完整的文件后缀


文件


1、QFile file;
file.setFileName();
file.open();
file.write();
file.read();
file.close();
2、QFileInfo info;
info.size();
info.fileName();
 QDataStream:二进制方式
 QTextStream:文本方式(指定编码)
 QBuffer:内存文件(内容放在内存)


三、Socket通信


Qt中提供的所有的Socket类都是非阻塞的。


Qt中常用的用于socket通信的套接字类:


  1. QTcpServer 用于TCP通信, 作为服务器端套接字使用
  2. QTcpSocket 用于TCP通信,作为客户端套接字使用。
  3. QUdpSocket 用于UDP通信,服务器,客户端均使用此套接字。


TCP


在Qt中实现TCP服务器端通信的流程:


  1. 创建套接字
  2. 将套接字设置为监听模式
  3. 等待并接受客户端请求


可以通过QTcpServer提供的void newConnection()_信号*来检测是否有连接请求,如果有可以在对应的槽函数中调用nextPendingConnection函数获取到客户端的Socket信息(返回值为QTcpSocket_类型指针),通过此套接字与客户端之间进行通信。


  1. 接收或者向客户端发送数据


接收数据:使用read()或者readAll()函数

发送数据:使用write()函数


在Qt中实现TCP/IP客户端通信的流程:


  1. 创建套接字


  1. 连接服务器


可以使用QTcpSocket类的connectToHost()函数来连接服务器。


  1. 向服务器发送或者接受数据


下面例子为简单的TCP/IP通信的实现例子:


TCP服务端


//---------- tcpserver.h ------------
class TCPServer : public QMainWindow
 {
  Q_OBJECT
public:
  explicit TCPServer(QWidget *parent = 0);
  ~TCPServer();
public slots:
  void slotNewConnection();
  void slotReadyRead();
private:
  Ui::TCPServer *ui;
  // 负责监听的套接字
  QTcpServer* m_server;
  // 负责通信的套接字
  QTcpSocket* m_client;
 };
//---------- tcpserver.cpp ------------
 TCPServer::TCPServer(QWidget *parent) :
  QMainWindow(parent),
  ui(new Ui::TCPServer),
  m_server(NULL),
  m_client(NULL)
 {
  ui->setupUi(this);
  //创建套接字对象
  m_server = new QTcpServer(this);
  //将套接字设置为监听模式
  m_server->listen(QHostAddress::Any, 9999);
  //通过信号接收客户端请求
  connect(m_server, &QTcpServer::newConnection, 
this, &TCPServer::slotNewConnection);
 }
 TCPServer::~TCPServer()
 {
  delete ui;
 }
void TCPServer::slotNewConnection()
 {
  if(m_client == NULL)
  {
  //处理客户端的连接请求
  m_client = m_server->nextPendingConnection();
  //发送数据
  m_client->write("服务器连接成功!!!");
  //连接信号, 接收客户端数据
  connect(m_client, &QTcpSocket::readyRead, 
this, &TCPServer::slotReadyRead);
  }
 }
void TCPServer::slotReadyRead()
 {
  //接收数据
  QByteArray array = m_client->readAll();
  QMessageBox::information(this, "Client Message", array);
 }


TCP客户端


//------------- tcpclient.h ------------
class TCPClient : public QMainWindow
 {
  Q_OBJECT
public:
  explicit TCPClient(QWidget *parent = 0);
  ~TCPClient();
public slots:
  void slotReadyRead();
  void slotSendMsg();
private:
  Ui::TCPClient *ui;
  QTcpSocket* m_client;
 };
//------------- tcpclient.cpp --------------
 TCPClient::TCPClient(QWidget *parent) :
  QMainWindow(parent),
  ui(new Ui::TCPClient)
 {
  ui->setupUi(this);
  //创建套接字
  m_client = new QTcpSocket(this);
  //连接服务器
  m_client->connectToHost(QHostAddress("127.0.0.1"), 9999);
  //通过信号接收服务器数据
  connect(m_client, &QTcpSocket::readyRead, 
this, &TCPClient::slotReadyRead);
  //发送按钮
  connect(ui->btnSend, &QPushButton::clicked, 
this, &TCPClient::slotSendMsg);
 }
 TCPClient::~TCPClient()
 {
  delete ui;
 }
void TCPClient::slotReadyRead()
 {
  //接收数据
  QByteArray array = m_client->readAll();
  QMessageBox::information(this, "Server Message", array);
 }
void TCPClient::slotSendMsg()
 {
  QString text = ui->textEdit->toPlainText();
  //发送数据
  m_client->write(text.toUtf8());
  ui->textEdit->clear();
 }


UDP


在UDP方式下,客户端并不与服务器建立连接,它只负责调用发送函数向服务器发送数据。类似的服务器也不从客户端接收连接,只负责调用接收函数,等待来自客户端的数据的到达


在UDP通信中,服务器端和客户端的概念已经显得有些淡化,两部分做的工作都大致相同:、


  1. 创建套接字
  2. 绑定套接字


在UDP中如果需要接收数据需要对套接字进行绑定,只发送数据则不需要对套接字进行绑定。


通过调用bind()函数将套接字绑定到指定端口上。


  1. 接收或者发送数据
  2. 接收数据:使用readDatagram()接收数据,函数声明如下:


qint64 readDatagram(char * data, qint64 maxSize,
QHostAddress address = 0, quint16 port = 0)


参数:


data: 接收数据的缓存地址

maxSize: 缓存接收的最大字节数

address: 数据发送方的地址(一般使用提供的默认值)

port: 数据发送方的端口号(一般使用提供的默认值)


使用**pendingDatagramSize()可以获取到将要接收的数据的大小**,根据该函数返回值来准备对应大小的内存空间存放将要接收的数据。


*   发送数据: 使用writeDatagram()函数发送数据,函数声明如下:


qint64 writeDatagram(const QByteArray & datagram,
const QHostAddress & host, quint16 port)


参数:


datagram:要发送的字符串

host:数据接收方的地址

port:数据接收方的端口号


广播


在使用QUdpSocket类的writeDatagram()函数发送数据的时候,其中第二个参数host应该指定为广播地址:QHostAddress::Broadcast此设置相当于QHostAddress("255.255.255.255")


使用UDP广播的的特点:


*   使用UDP进行广播,局域网内的其他的UDP用户全部可以收到广播的消息

   

*   UDP广播只能在局域网范围内使用

   

组播


在使用QUdpSocket类的writeDatagram()函数发送数据的时候,其中第二个参数host应该指定为组播地址,关于组播地址的分类:


*   224.0.0.0~224.0.0.255为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用;

   

*   224.0.1.0~224.0.1.255是公用组播地址,可以用于Internet;

   

*   224.0.2.0~238.255.255.255为用户可用的组播地址(临时组地址),全网范围内有效;

   

*   239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效。

   

注册加入到组播地址需要使用QUdpSocket类的成员函数:


bool joinMulticastGroup(const QHostAddress & groupAddress)


四、多线程的使用


在次线程中处理的业务放在独立的模块(类)中,由主线程创建完该对象后,将其移交给指定的线程,且可以将多个类似的对象移交给同一个线程。


例子中,信号由主线程的QTimer对象发出,之后Qt会将关联的事件放到worker所属线程的事件队列。由于队列连接的作用,在不同线程间连接信号和槽是很安全的。


示例代码如下:


class Worker : public QObject
 {
  Q_OBJECT
 private slots:
  void onTimeout()
  {
  qDebug()<<"Worker::onTimeout get called from?: "
 <
  }
 };
 int main(int argc, char *argv[])
 {
  QApplication a(argc, argv);
  qDebug()<<"From main thread: "<
  QThread t;
  QTimer timer;
  Worker worker;
  QObject::connect(&timer, SIGNAL(timeout()),
 &worker, SLOT(onTimeout()));
  // 启动定时器
  timer.start(1000);
  // 将类对象移交个线程
  worker.moveToThread(&t);
  // 启动线程
  t.start();
  return a.exec();
 }


关于Qobject类的connect函数最后一个参数,连接类型:


*   自动连接(AutoConnection),默认的连接方式。

   

如果信号与槽,也就是发送者与接受者在同一线程,等同于直接连接;


如果发送者与接受者处在不同线程,等同于队列连接。


*   直接连接(DirectConnection)

   

当信号发射时,槽函数立即直接调用。无论槽函数所属对象在哪个线程,槽函数总在发送者所在线程执行。


*   队列连接(QueuedConnection)

   

当控制权回到接受者所在线程的事件循环时,槽函数被调用。槽函数在接受者所在线程执行。


总结


* 队列连接:槽函数在接受者所在线程执行。


* 直接连接:槽函数在发送者所在线程执行。


*自动连接:二者不在同一线程时,等同于队列连接


多线程使用过程中注意事项:


*   线程不能操作UI对象(从Qwidget直接或间接派生的窗口对象)

   

*   需要移动到子线程中处理的模块类,创建的对象的时候不能指定父对象。

   


五、Qt数据库操作


操作数据库


Qt 提供了 QtSql 模块来提供平台独立的基于 SQL 的数据库操作。


Qt 使用QSqlDatabase表示一个数据库连接。


可以通过:


//打印Qt支持的数据库驱动
 qDebug() << QSqlDatabase::drivers();


找到系统中所有可用的数据库驱动的名字列表。


封装一个连接数据库的函数:


bool connect(const QString &dbName)
 {
  QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
 //   db.setHostName("host");
 //   db.setDatabaseName("dbname");
 //   db.setUserName("username");
 //   db.setPassword("password");
  db.setDatabaseName(dbName);
  if (!db.open()) {
  QMessageBox::critical(0, QObject::tr("Database Error"),
  db.lastError().text());
  return false;
  }
  return true;
 }


创建数据库表student后,插入数据,然后将其独取出来:


if (connect("demo.db"))
 {
  QSqlQuery query;
  query.prepare("INSERT INTO student (name, age) VALUES (?, ?)");
  QVariantList names;
  names << "Tom" << "Jack" << "Jane" << "Jerry";
  query.addBindValue(names);
  QVariantList ages;
  ages << 20 << 23 << 22 << 25;
  query.addBindValue(ages);
  if (!query.execBatch()) {
  QMessageBox::critical(0, QObject::tr("Database Error"),
  query.lastError().text());
  }
  query.exec("SELECT name, age FROM student");
  while (query.next()) {
  QString name = query.value(0).toString();
  int age = query.value(1).toInt();
  qDebug() << name << ": " << age;
  }
 }
 else
 {
  return 1;
 }


插入多条数据,此时可以使用QSqlQuery::exec()函数一条一条插入数据,但是这里我们选择了另外一种方法:批量执行。首先,我们使用**QSqlQuery::prepare()函数对这条 SQL 语句进行预处理,问号 ? 相当于占位符,预示着以后我们可以使用实际数据替换这些位置。


在上面的代码中,我们使用一个字符串列表 names 替换掉第一个问号的位置,一个整型列表 ages 替换掉第二个问号的位置,利用**QSqlQuery::addBindValue()我们将实际数据绑定到这个预处理的 SQL 语句上**。需要注意的是,names 和 ages 这两个列表里面的数据需要一一对应。然后我们调用**QSqlQuery::execBatch()批量执行 SQL,之后结束该对象。这样,插入操作便完成了。


使用模型操作数据库


基于QSqlTableModel 的模型处理更为高级,如果对 SQL 语句不熟悉,并且不需要很多复杂的查询,这种QSqlTableModel模型基本可以满足一般的需求。


1、查询操作


if (connect("demo.db"))
 {
  QSqlTableModel model;
  model.setTable("student");
  model.setFilter("age > 20 and age < 25");
  if (model.select()) {
  for (int i = 0; i < model.rowCount(); ++i) {
  QSqlRecord record = model.record(i);
  QString name = record.value("name").toString();
  int age = record.value("age").toInt();
  qDebug() << name << ": " << age;
  }
  }
 }
 else
 {
  return 1;
 }


*   setTable()函数设置所需要操作的表格;

 

*   setFilter()函数则是添加过滤器,也就是 WHERE 语句所需要的部分。

 

   例如上面代码中的操作实际相当于 SQL 语句:


SELECT * FROM student WHERE age > 20 and age < 25
```


注意:我们使用QSqlTableModel只能进行 SELECT * 的查询,不能只查询其中某些列的数据。


2、插入操作


QSqlTableModel model;
 model.setTable("student");
int row = 0;
 model.insertRows(row, 1);
 model.setData(model.index(row, 1), "Cheng");
 model.setData(model.index(row, 2), 24);
 model.submitAll();


model.insertRows(row, 1);说明我们想在索引 0 的位置插入 1 行新的数据。使用setData()函数则开始准备实际需要插入的数据。最后,调用submitAll()函数提交所有修改


例如上面代码中的操作实际相当于 SQL 语句:


INSERT INTO student (name, age) VALUES ('Cheng', 24)


3、更新操作


QSqlTableModel model;
model.setTable("student");
model.setFilter("age = 25");
if (model.select()) {
  if (model.rowCount() == 1) {
  QSqlRecord record = model.record(0);
  record.setValue("age", 26);
  model.setRecord(0, record);
  model.submitAll();
  }
 }


找到 age = 25 的记录,然后将 age 重新设置为 26,存入相同的位置(在这里都是索引 0 的位置),提交之后完成一次更新


例如上面代码中的操作实际相当于 SQL 语句:


UPDATE student SET age = 26 WHERE age = 25


4、删除操作


QSqlTableModel model;
model.setTable("student");
model.setFilter("age = 25");
if (model.select()) {
  if (model.rowCount() == 1) {
  model.removeRows(0, 1);
  model.submitAll();
  }
 }


removeRows()函数可以一次删除多行。


例如上面代码中的操作实际相当于 SQL 语句:


DELETE FROM student WHERE age = 25


如果你觉得文章还不错,记得"点赞关注"


关注我的微信公众号【 加班猿 】可以获取更多内容

目录
相关文章
|
6月前
|
Linux C语言
[学习][笔记] qt5 从入门到入坟:<一>hello Qt
[学习][笔记] qt5 从入门到入坟:<一>hello Qt
|
6月前
|
C++
[学习][笔记] qt5 从入门到入坟:《零》vs开发qt项目
[学习][笔记] qt5 从入门到入坟:《零》vs开发qt项目
|
6月前
|
定位技术 图形学
[学习][笔记] qt5 从入门到入坟:<13>基于GraphicsViewFrame的贪吃蛇实现
[学习][笔记] qt5 从入门到入坟:<13>基于GraphicsViewFrame的贪吃蛇实现
|
6月前
|
图形学
[学习][笔记] qt5 从入门到入坟:<12>Graphics View Framework
[学习][笔记] qt5 从入门到入坟:<12>Graphics View Framework
|
6月前
|
编解码 图形学
[学习][笔记] qt5 从入门到入坟:<11>绘制设备
[学习][笔记] qt5 从入门到入坟:<11>绘制设备
|
6月前
[学习][笔记] qt5 从入门到入坟:<10>坐标系统
[学习][笔记] qt5 从入门到入坟:<10>坐标系统
|
6月前
|
图形学
[学习][笔记] qt5 从入门到入坟:<九>反走样,渐变
[学习][笔记] qt5 从入门到入坟:<九>反走样,渐变
|
6月前
|
API
[学习][笔记] qt5 从入门到入坟:<八>Qt 绘制系统
[学习][笔记] qt5 从入门到入坟:<八>Qt 绘制系统
|
6月前
|
安全 API Android开发
[学习][笔记] qt5 从入门到入坟:<七>事件
[学习][笔记] qt5 从入门到入坟:<七>事件
|
6月前
|
Linux iOS开发 MacOS
[学习][笔记] qt5 从入门到入坟:<六>对话框
[学习][笔记] qt5 从入门到入坟:<六>对话框

热门文章

最新文章

推荐镜像

更多