QT快速入门

本文档将介绍QT工程的创建、UI界面布局,并以计数器为例了解QT中多线程的用法,最终完成一个基础的QT项目。

1 创建QT工程文件

在安装好QT之后,能够在其安装组件中找到Qt Creator,点击

设置项目名称及路径等,设置支持32位与64位,其他都直接下一步;

创建完成,项目中包含以下几个文件:

QT项目文件QTTEST.pro,主窗口头文件mainwindow.h,主窗口程序mainwindow.cpp,主函数main.cpp以及窗口UI文件mainwindow.ui

我们当然可以直接在QT creator中编完这个工程,但推荐使用更加成熟、稳健的VS完成后续的编程与设计。

2 UI界面设计

首先,使用VS打开新建的.pro文件,同样能看到这几个文件。在一切正常的情况下,此时点击运行程序会出现一个空白窗口(图④)。

随后根据需求设计窗体界面与布局,双击打开UI文件,默认UI操作界面如下:

其中控件区域的所有组件为:

然后回到主界面,UI设计的主要思路为:将控件从工具栏拖拽到主界面中,使界面能够简洁明了地反映工作流并反馈执行状态;通过修改显示名称、对象属性、信号与槽函数使界面与背后的主程序链接。通常一个界面包括输入、输出、中间过程、计算、退出等操作,具体例子如下:

3 信号与槽函数、连接

(1) 内置的连接方法

以上述界面为例,重点演示Exit功能以及Input data的浏览和单行输入框。

对于最简单的Exit单击(click)时只需执行关闭(close)界面即可,如图:

流程概括如下:

flowchart LR;
Z(选中控件)-->A;
A(修改属性名)-->B(添加指定的信号与槽);
B-->C(保存);

在QT设计师中添加的信号与槽函数,只需通过简单的点击即可建立连接。其实质是:

mainwindow.cpp文件中,能够看到#include "ui_mainwindow.h"引用了这样一个头文件,打开之后,可以找到:

QObject::connect(ExitPushBotton, SIGNAL(clicked()), MainWindow, SLOT(close()));

其底层原理是通过connect单击Exit按钮这一信号与关闭界面这个槽函数相关联,信号由按钮PushBotton发射,主窗口MainWindow接收。

(2) 自定义槽函数

期望达到的效果如图,在点击Browse之后,我们能够浏览文件目录并将文件名、路径填入到前面的文本框中。而在之后的操作中,可以直接从界面上获取文件信息。

分析这个步骤,即是在单击(click)按钮Browse后,弹出选择文件/路径的对话框,并将值传给文本编辑框中显示。

step1: 修改属性名

命名的规则为:控件功能+控件名(如Inputdata+lineEdit),这是为了在后台调用控件时能够快速、准确定位。

step2: 编写槽函数

在主窗口头文件中声明槽函数:

class MainWindow : public QMainWindow
{
Q_OBJECT
...
private slots:
//3个Browse对应的槽函数
void inputdataSelect(); //输入文件选择格式
void outputdataPathSelect(); //输出文件路径选择
void waveletFileSelect(); //子波文件选择 };

在mainwindow.cpp文件中定义槽函数:

// 以输入和输出两个为例,其他的浏览可类推
#include <QFileDialog> //需引入QFileDialog头文件才能使用对话窗口选择文件 void MainWindow::inputdataSelect() { //输入数据路径及文件名选择
// 文件名将存为QString字符串格式
QString fileNameInput = QFileDialog::getOpenFileName(this, //getOpenFileName获取文件名
tr("Input File"),
"F:", // 默认启动位置为F盘
tr("Seismic(*sgy *segy *SEGY);;")); //创建文件名及路径选择对话窗口、支持的格式为segy if (fileNameInput.isEmpty() == false) {
ui->InputdatalineEdit->setText(fileNameInput); //将选择输入数据的文件名路径填入文本框
}
} void MainWindow::outputdataPathSelect(){ //输出数据的路径选择 QString fileNameInputPath = QFileDialog::getExistingDirectory(this, //getExistingDirectory获取路径
tr("Select Output File Folder"),
tr("C:")); //读取输出文件保存路径,只有路径因此无需预设文件格式 if (fileNameInputPath.isEmpty() == false) {
ui->OutputlineEdit->setText(fileNameInputPath); //显示选择保存路径
}
}

使用ui->xxx可以调取界面中的控件,如ui->InputdatalineEdit则会指定到主窗口中的第一个文本框,这里的命名为step1中修改后的属性名,可从.ui界面中复制粘贴。

step3:建立连接

在UI界面中建立clicked()与我们自定义的槽函数的连接,保存后,重新运行程序,即可实现上述功能。

(3)自定义信号与槽函数

算法计算(Compute)是这个QT工程的核心,按照同样的方法将界面上的Compute按钮与compute()槽函数连接;

而在计算过程中,我们希望能够显示进度并打印日志,这部分通常没有固定的连接,因此需要自定义发射信号与接收槽函数。在展开介绍Compute的实现过程之前,再次强调以下Qt中使用多线程的注意事项:

  • 默认的线程在Qt中称之为窗口线程,也就叫主线程,负责窗口事件处理或者窗口控件数据的更新;
  • 子线程负责后台的业务逻辑处理,子线程中不能对窗口对象做任何操作,这些事情需要交给窗口线程处理;
  • 主线程和子线程之间如果要进行数据的传递,需要使用Qt中的信号槽机制
  • 子线程一般不允许越级进行对窗口参数进行操作

简单来说,当我们需要执行计算处理,并同步更新结果到窗口时,如果只使用一个线程,会出现窗口卡顿的情况。于是,我们将计算、处理放在子线程中,计算的中间过程与结果通过信号槽机制传递到主线程进行显示。

①mainwindow.h

  • 创建一个子线程类,它继承自QThread,通过在protected成员方法中重新实现run()
  • 在主线程(Mainwindow)中声明:子线程成员,以及接收信号的主窗口上的槽函数。
#include <QThread>
#include <QProgressDialog> /****************** 子线程--发射端 *****************/
class MyThread :public QThread { //MyThread子类继承自QThread
Q_OBJECT
public:
MyThread() { }
~MyThread() { } protected: // 受保护的成员
void run() { //重写run()方法,此处的方法为一个间隔0.1s从0~100的计数器
for (int i = 0; i < 101; i++) {
emit SendNumber(i); //使用emit发射子线程中的信号SendNumber。它将传递出当前的实参:一个1~100之间的整数
//emit是一个宏定义,本质上会在moc_*.cpp文件中生成一个SendNumber()信号
//QT内部进行调用时,会找到底层的相应代码并进行信号与槽函数的连接 msleep(100); //停滞0.1s
}
} signals:
void SendNumber(const int nNum); //在类中声明信号函数
}; /****************** 主线程--主窗口--接收端 *****************/
class MainWindow : public QMainWindow
{
Q_OBJECT public:
MainWindow(QWidget *parent = nullptr);
~MainWindow(); MyThread *_mthread; //在主窗口中声明一个子线程成员 private:
Ui::MainWindow *ui; private slots: //声明私有的槽函数 void compute(); //compute功能函数
void updateProgress(int iter); //刷新进度条
void printlog(int num); //打印日志
};

②mainwindow.cpp

首先在compute信号中启动子线程;

void MainWindow::compute(){

	_mthread->start();		//使用start()启动子线程

}

然后定义主线程中的槽函数方法;

void MainWindow::updateProgress(int iter) {  //Qt设置进度条的槽函数

	ui->computeprogressBar->setValue(iter);
} void MainWindow::printlog(int num) { //Qt打印计算日志 QString qs = "Process:";
QString q1; q1 = q1.sprintf("%d", num);
qs = qs + q1; ui->computeLogTextBrowser->append(tr("Reading Data Suceesed!"));
ui->computeLogTextBrowser->append(qs);
}

最后在主窗口中连接子线程信号与槽函数。

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this); this->_mthread = new MyThread(); //实例化一个子线程对象
// 从子线程_mthread发送一个信号SendNumber
// 由主线程(this)的槽函数updateProgress与printlog接收
connect(_mthread, &MyThread::SendNumber, this, &MainWindow::updateProgress);
connect(_mthread, &MyThread::SendNumber, this, &MainWindow::printlog); }

关于connect官方给出的文档中包含5个参数,具体如下:

QObject::connect(const QObject *sender, 	// 发送者
const char *signal, // 信号
const QObject *receiver, // 接收者
const char *method, // 接收的槽函数
Qt::ConnectionType type) /* 连接方式
Qt::ConnectionType type = Qt::AutoConnection (1)默认连接
Qt:: DirectConnection (2)立即调用
Qt::QueuedConnection (3)异步调用
Qt::BlockingQueuedConnection (4)同步调用
Qt:: UniqueConnection (5)单一连接 */

完善Compute,在读取文件名称、路径中加入判空:

if(filenameInput.isEmpty()==true){				//如果输入文件名为空

    QMessageBox msgbox;
msgbox.setText("no select input data");
msgbox.exec();
return;
} if(filenameOutputPath.isEmpty() == true) { //如果输出路径为空 QMessageBox msgbox;
msgbox.setText("no select output data path");
msgbox.exec();
return;
} if (filenameOutput.isEmpty() == true){ //如果输出文件名为空 QMessageBox msgbox;
msgbox.setText("no input output data name");
msgbox.exec();
return;
}

最终效果:

完整代码

Ⅰ mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H #include <QMainWindow>
#include <QFileDialog>
#include <QProgressDialog>
#include <QDebug>
#include <QMessageBox>
#include <QThread> class MyThread :public QThread { Q_OBJECT public: MyThread() { }
~MyThread() { } protected:
void run() {
for (int i = 0; i < 101; i++) { emit SendNumber(i);
msleep(100);
}
} signals: void SendNumber(const int nNum); }; QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE class MainWindow : public QMainWindow
{
Q_OBJECT public:
MainWindow(QWidget *parent = nullptr);
~MainWindow(); MyThread *_mthread; private:
Ui::MainWindow *ui; private slots: void inputdataSelect(); //输入文件选择格式
void outputdataPathSelect(); //输出文件路径选择
void waveletFileSelect(); //子波文件选择 void compute(); //计算函数
void updateProgress(int iter); //刷新进度条
void printlog(int num); //打印日志 };
#endif // MAINWINDOW_H

Ⅱ mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this); this->_mthread = new MyThread(); connect(_mthread, &MyThread::SendNumber, this, &MainWindow::updateProgress);
connect(_mthread, &MyThread::SendNumber, this, &MainWindow::printlog); } MainWindow::~MainWindow()
{
delete ui;
} void MainWindow::inputdataSelect(){ //输入数据路径及文件名选择 QString fileNameInput = QFileDialog::getOpenFileName(this,
tr("Input File"),
"F:",
tr("Seismic(*sgy *segy *SEGY);;")); //创建文件名及路径选择对话窗口 if (fileNameInput.isEmpty() == false) { ui->InputdatalineEdit->setText(fileNameInput); //将选择输入数据的文件名路径
qDebug() << "filename : " << fileNameInput;
}
else { }//end if(fileNameInput.isEmpty()==false) } void MainWindow::outputdataPathSelect(){ //输出数据的路径选择 QString fileNameInputPath = QFileDialog::getExistingDirectory(this,
tr("Select Output File Folder"),
tr("C:")); //读取输出文件保存路径 if (fileNameInputPath.isEmpty() == false) { ui->OutputlineEdit->setText(fileNameInputPath); //显示选择保存路径
}
else { }//end if(fileNameInputPath.isEmpty()==false) } void MainWindow::waveletFileSelect(){ QString fileNameInput = QFileDialog::getOpenFileName(this,
tr("Input File"),
"F:",
tr("wavelet file(*dat *txt);;")); //创建文件名及路径选择对话窗口 if (fileNameInput.isEmpty() == false){ ui->waveletFileNamelineEdit->setText(fileNameInput); //将选择输入数据的文件名路径
qDebug() << "filename : " << fileNameInput;
} } void MainWindow::compute(){ QString filenameInput = ui->InputdatalineEdit->text(); //从界面获取输入模型的SEGY文件
QString filenameOutputPath = ui->OutputlineEdit->text(); //从界面获取输出模型的SEGY文件文件路径
QString filenameOutput = ui->outputDataFilenamelineEdit->text(); //从界面获取输出数据的SEGY的文件名 if(filenameInput.isEmpty()==true){ QMessageBox msgbox;
msgbox.setText("no select input data");
msgbox.exec();
return;
} if(filenameOutputPath.isEmpty() == true) { QMessageBox msgbox;
msgbox.setText("no select output data path");
msgbox.exec();
return;
} if (filenameOutput.isEmpty() == true){ QMessageBox msgbox;
msgbox.setText("no input output data name");
msgbox.exec();
return;
} _mthread->start();
} void MainWindow::updateProgress(int iter) { //Qt设置进度条的槽函数 ui->computeprogressBar->setValue(iter);
} void MainWindow::printlog(int num) { //Qt打印计算日志 QString qs = "Process:";
QString q1; q1 = q1.sprintf("%d", num);
qs = qs + q1; ui->computeLogTextBrowser->append(tr("Reading Data Suceesed!"));
ui->computeLogTextBrowser->append(qs); }

QT快速入门的更多相关文章

  1. Qt快速入门系列教程目录

    Qt快速入门系列教程目录

  2. Qt快速入门学习笔记(基础篇)

    本文基于Qter开源社区论坛版主yafeilinux编写的<Qt快速入门系列教程目录>,网址:http://bbs.qter.org/forum.php?mod=viewthread&am ...

  3. Qt快速入门学习笔记(画图篇)

    1.Qt中提供了强大的2D绘图系统,可以使用相同的API在屏幕和绘图设备上进行绘制,它主要基于QPainter.QPaintDevice和QPaintEngine这三个类.其中QPainter用来执行 ...

  4. [Qt Creator 快速入门] 第5章 应用程序主窗口

    对于日常见到的应用程序而言,许多都是基于主窗口的,主窗口中包含了菜单栏.工具栏.状态栏和中心区域等.这一章会详细介绍主窗口的每一个部分,还会涉及资源管理.富文本处理.拖放操作和文档打印等相关内容.重点 ...

  5. [Qt Creator 快速入门] 第2章 Qt程序编译和源码详解

    一.编写 Hello World Gui程序 Hello World程序就是让应用程序显示"Hello World"字符串.这是最简单的应用,但却包含了一个应用程序的基本要素,所以 ...

  6. [Qt Creator 快速入门] 第1章 Qt Creator简介

    Qt Creator 是一个跨平台的.完整的 Qt 集成开发环境,其中包括了高级C++代码编辑器.项目和生成管理工具.集成的上下文相关的帮助系统.图形化调试器.代码管理和浏览工具等.这一章先对 Qt ...

  7. Qt 动画快速入门(一)

    Qt-4.6动画Animation快速入门三字决 Qt-4.6新增了Animation Framework(动画框架),让我们能够方便的写一些生动的程序.不必像以前的版本一样,所有的控件都枯燥的呆在伟 ...

  8. Java转Ruby【快速入门】

    最近参加实习了,公司的技术栈中需要用到 Ruby 以及 Rails 框架,所以算是开始了踩坑之旅吧.. Ruby 简介 网上的简介要搜都能搜到,具体涉及的包括历史啦之类这里不再赘述,提几个关键词吧: ...

  9. Jupyter 快速入门——写python项目博客非常有用!!!

    from:https://blog.csdn.net/m0_37338590/article/details/78862488 一.简介: Jupyter Notebook(此前被称为 IPython ...

随机推荐

  1. web自动化测试用例编写的规范

    1.一个脚本是一个完整的场景,从用户登陆操作到用户退出系统关闭浏览器. 2.一个脚本脚本只验证一个功能点,不要试图用户登陆系统后把所有的功能都进行验证再退出系统 3.尽量只做功能中正向逻辑的验证,不要 ...

  2. vue项目给less传递参数,出现 Invalid options object. Less Loader has been initialized using an options object that does not match the API schema.

    vue-cli创建的项目,想使用less-loader的全局变量配置,但是配置(vue.config.js文件)项出现 error in ./src/components/BookFooter.vue ...

  3. 学习Nginx(一)

    实验目的 通过nginx实现反向代理的功能,类似apache反向代理和haproxy反向代理 工作中用nginx做反向代理和负载均衡的也越来越多了 有些公司从web服务器到反向代理,都使用nginx. ...

  4. 《剑指offer》面试题4:替换空格

    面试题4:替换空格 题目:请实现一个函数,把字符串中的每个空格替换成"%20",例如输入"We are happy.",则输出"we%20are%20 ...

  5. Vue2.0一个login跳转实例

    需要解决的问题:store存储登录状态Vue-Router导航钩子拦截路由Vue-Resource获取后台的数据需要判断登录返回的user源码参考原文地址 主要技术栈:Vuex + Vue-Resou ...

  6. Java/C++实现解释器模式---机器人控制程序

    某机器人控制程序包含一些简单的英文指令,其文法规则如下: expression ::= direction action distance | composite composite ::= expr ...

  7. 子线程中如何修改ui界面

    1.Android进程 一个应用程序被启动时,系统默认创建执行一个叫做"main"的线程.这个线程也是你的应用与界面工具包(android.widget和android.view包 ...

  8. SQL语句总结---表操作

    https://blog.csdn.net/hallomrzhang/article/details/85010014 表的操作 1.查看表结构 desc 表名 2.创建表结构的语法 create t ...

  9. javascript,如何实现数据绑定

    在三大框架 抢占前端开发技术领域的时代,基本每个框架都是实现了数据绑定, 今天我们来解密一下数据绑定的原理, 原理关乎一个很关键的东西,Object.prototype.__defineSetter和 ...

  10. vscode快速生成html的基本代码

    转载自:https://blog.csdn.net/suwyer/article/details/81237880 在vscode里新建html文件, 总是要一行一行的写标准的html代码: 而DW新 ...