当前位置: 首页 > news >正文

QT的C++接口基础用法

1. QT是什么

QT是一个跨平台的C++开发库,主要用于开发图形界面程序。简而言之,就是用来做UI界面的。下边笔者将介绍QT的基础用法,包括信号与槽机制、常用控件以及多线程。建议大家在学习QT时,先掌握QT的用法即可,不用深究原理。

2. QT的基础用法

2.1 信号与槽

信号(signal)指的是在特定情况下被发射的事件。比如说你在QT窗口里创建了一个按钮,点击这个按钮就会发送一个信号,然后界面窗口会对你所发送的信号进行响应。

槽(slot)就是对信号响应的函数,成为槽函数。它可以具有参数,也可以直接被调用。当信号发射时,所关联的槽函数会自动执行。

2.1.1 信号的创建

信号仅需要声明,不需要定义,一般在mainwindow.h里声明信号即可,如下所示:

#ifndef MAINWINDOW_H #define MAINWINDOW_H /*主窗口基类*/ #include<QMainWindow> /** * MainWindow - 主窗口 */ class MainWindow : public QMainWindow { Q_OBJECT /*在使用信号与槽的类中,必须在类的定义中加入此宏!*/ public: /* 构造函数声明*/ MainWindow(QWidget *parent = nullptr); ~MainWindow(); /* 析构函数声明 */ signals: /* 声明一个信号,带参数,仅需声明,无需定义*/ void start_inspection(const recipe_config &recipe, const cv::Mat &gray); }; #endif

note:创建信号时最好贴合信号本身的含义,此处笔者的项目中代表的意思是点击按钮后,发送开始检测信号。

2.1.2 槽的创建

创建槽需要现在mainwindow.h里边进行声明,然后在main.cpp里实现槽的定义。与信号不同,声明槽必须写槽的定义,否则会编译报错。它有以下特点:1、槽可以时任何成员函数;2、槽函数和信号的参数、返回值要保持一致。槽的创建如下所示:

#ifndef MAINWINDOW_H #define MAINWINDOW_H /*主窗口基类*/ #include<QMainWindow> /** * MainWindow - 主窗口 */ class MainWindow : public QMainWindow { Q_OBJECT /*在使用信号与槽的类中,必须在类的定义中加入此宏!*/ public: /* 构造函数声明*/ MainWindow(QWidget *parent = nullptr); ~MainWindow(); /* 析构函数声明 */ signals: /* 声明一个信号,带参数,仅需声明,无需定义*/ void start_inspection(const recipe_config &recipe, const cv::Mat &gray); public slots: /* 声明一个槽函数,带参数,需要声明+定义*/ void request_inspection(const recipe_config &recipe, const cv::Mat &gray); }; #endif /*声明完成后需要在对应cpp文件里进行调用*/ void MainWindow::request_inspection(const recipe_config &recipe, const cv::Mat &gray) { /* 准备结果缓冲并调用核心检测引擎 */ inspection_result result{}; int code = run_inspection(recipe, gray, result); /* 无论成功失败都发射信号,让 UI 侧统一处理 */ emit inspection_finished(code, result); }

2.1.3 信号与槽的关联

信号与槽的关联通过connect函数来实现。其基本格式为:

connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));

sender是发射对象的名称,signal() 是信号名称。信号可以看做是特殊的函 数,需要带括号,有参数时还需要指明参数。receiver 是接收信号的对象名称,slot() 是槽函数 的名称,需要带括号,有参数时还需要指明参数。

而SIGNAL 和 SLOT 是 Qt 的宏,用于指明信号和槽,并将它们的参数转换为相应的字符 串。note:当信号和槽函数带有参数时,在 connect()函数里,要写明参数的类型,但可以不写参数名称。用法如下所示:

/*当信号和槽函数带有参数时,在连接函数里边要写明参数的类型,但可以不写参数名称*/ connect(worker_, SIGNAL(inspection_finished(int, inspection_result)), this, SLOT(on_inspection_finished(int, inspection_result)));

如上图的意思是:假设worker_是按钮,代表的意思就是按钮点击后,开始发送inspection_finished信号,然后QT界面窗口会执行槽函数on_inspection_finished。其中,this代表的是主窗口MainWindow(C++中的this指针指向实例化的对象本身)。

2.2 QT常用控件

2.2.1 按钮QPushButton

这是一个按钮控件,通常用于创建一个按钮,点击后,主界面做出相应的响应,用法如下。

1、先在MainWindow.h里引入按钮库并声明按钮对象,代码如下:

#ifndef MAINWINDOW_H #define MAINWINDOW_H /*主窗口基类*/ #include<QMainWindow> /*引入按钮控件库*/ #include <QPushButton> /** * MainWindow - 主窗口 */ class MainWindow : public QMainWindow { Q_OBJECT /*在使用信号与槽的类中,必须在类的定义中加入此宏!*/ public: /* 构造函数声明*/ MainWindow(QWidget *parent = nullptr); ~MainWindow(); /* 析构函数声明 */ private: /*声明一个QPushButton对象*/ QPushButton *load_image_button_; /* 加载图片按钮 */ private slots: /* 加载图片按钮点击槽 */ void on_load_image_clicked(); }; #endif

2、在MainWindow.cpp的MainWindow构造函数中实例化按钮对象并连接信号与槽,如下所示:

/*实例化按钮对象*/ load_image_button_ = new QPushButton(QString::fromUtf8("加载图片"), central); /*连接信号与槽*/ connect(load_image_button_, SIGNAL(clicked()), this, SLOT(on_load_image_clicked())); // 连接加载图片按钮点击信号和槽函数

2.2.2 文本编辑框QLineEdit

文本编辑框的意思就是提供一个可以输入信息的文本框,用法如下:

#ifndef MAINWINDOW_H #define MAINWINDOW_H /*主窗口基类*/ #include<QMainWindow> /*引入文本编辑框*/ #include <QLineEdit> /** * MainWindow - 主窗口 */ class MainWindow : public QMainWindow { Q_OBJECT /*在使用信号与槽的类中,必须在类的定义中加入此宏!*/ public: /* 构造函数声明*/ MainWindow(QWidget *parent = nullptr); ~MainWindow(); /* 析构函数声明 */ private: /* 操作员输入框 */ QLineEdit *operator_edit_; /* 初始化菜单栏(操作员输入) */ void setup_menu_bar(); }; #endif

如上图,也是需要在头文件里进行声明;接下来在对应的cpp文件里进行实例化对象,以及构建文本框,如下所示:

/** * setup_menu_bar - 构建菜单栏(操作员输入) */ void MainWindow::setup_menu_bar() { /*QMenu是QT的菜单类,此处先通过menuBar()获取菜单栏,再添加一个"操作员"菜单 */ QMenu *operator_menu = menuBar()->addMenu(QString::fromUtf8("操作员")); /* 把 QLineEdit 嵌入菜单栏里作为操作员录入入口 */ /*QWidgetAction是QT的控件动作类,只有通过它才可以将控件嵌入菜单栏 *一般模板为先调用setDefaultWidget()方法,将控件设置为默认小部件 *然后将QWidgetAction对象添加到菜单栏中 */ QWidgetAction *action = new QWidgetAction(this); operator_edit_ = new QLineEdit(this); operator_edit_->setPlaceholderText(QString::fromUtf8("输入操作员名称"));//设置占位文本为“输入操作员名称” operator_edit_->setText(QString::fromUtf8("unknown"));//设置默认文本为“unknown” operator_edit_->setFixedWidth(200);//设置固定宽度为200 action->setDefaultWidget(operator_edit_);//将操作员编辑框设置为默认小部件 operator_menu->addAction(action); /* 将操作员编辑框添加到操作员菜单 */ }

2.2.3 QLable

QLable提供了一种用于文本或图像显示的小部件,其用法如下,也是需要先在头文件里进行声明:

#ifndef MAINWINDOW_H #define MAINWINDOW_H /*主窗口基类*/ #include<QMainWindow> /*引入QLable*/ #include <QLabel> /** * MainWindow - 主窗口 */ class MainWindow : public QMainWindow { Q_OBJECT /*在使用信号与槽的类中,必须在类的定义中加入此宏!*/ public: /* 构造函数声明*/ MainWindow(QWidget *parent = nullptr); ~MainWindow(); /* 析构函数声明 */ private: /* 左侧图像显示区 */ QLabel *image_label_; /* 初始化中央控件与布局 */ void setup_ui(); }; #endif

下边需要在对应CPP文件里进行实例化对象以及设置控件,如下所示:

void MainWindow::setup_ui() { /* 创建中央 QWidget 和主水平布局 */ QWidget *central = new QWidget(this); QHBoxLayout *main_layout = new QHBoxLayout(central); /* 左侧图像区:至少 640x480,带边框 */ image_label_ = new QLabel(central); image_label_->setMinimumSize(640, 480); // 设置图像标签的最小尺寸为 640x480 image_label_->setAlignment(Qt::AlignCenter); // 设置图像标签的对齐方式为居中 image_label_->setStyleSheet(QString::fromUtf8("border: 1px solid gray;")); // 设置图像标签的样式表为带边框 image_label_->setText(QString::fromUtf8("未加载图片")); // 设置图像标签的文本为“未加载图片” main_layout->addWidget(image_label_, 3); // 将图像标签加入主布局,并指定伸缩因子为3,表示该区域占总宽度的3/4 /* 右侧按钮 + 结果区 */ QVBoxLayout *right_layout = new QVBoxLayout(); // 创建右侧垂直布局,用于容纳功能按钮和结果显示区 /*其他代码省略....*/ }

2.2.4 布局管理

如上图所示,QHBoxLayout(水平布局)、QVBoxLayout(垂直布局),简单来说,水平布局就是将控件平着放;垂直布局就是将控件垂着着放。

/* 右侧按钮 + 结果区 */ QVBoxLayout *right_layout = new QVBoxLayout(); // 创建右侧垂直布局,用于容纳功能按钮和结果显示区 /* 四个功能按钮 */ load_image_button_ = new QPushButton(QString::fromUtf8("加载图片"), central); run_inspection_button_ = new QPushButton(QString::fromUtf8("执行检测"), central); view_history_button_ = new QPushButton(QString::fromUtf8("查看历史"), central); switch_recipe_button_ = new QPushButton(QString::fromUtf8("切换配方"), central); /* 结果显示区:支持换行、左上对齐、浅灰边框 */ result_label_ = new QLabel(central); result_label_->setWordWrap(true); // 设置结果标签支持换行 result_label_->setAlignment(Qt::AlignTop | Qt::AlignLeft); // 设置结果标签的对齐方式为左上对齐 result_label_->setText(QString::fromUtf8("等待检测...")); // 设置结果标签的文本为“等待检测...” result_label_->setStyleSheet(QString::fromUtf8("border: 1px solid lightgray; padding: 6px;")); // 设置结果标签的样式表为浅灰边框 result_label_->setMinimumHeight(200); // 设置结果标签的最小高度为 200 result_label_->setMinimumHeight(200); // 设置结果标签的最小高度为 200 /* 按顺序加入右侧垂直布局 */ right_layout->addWidget(load_image_button_); right_layout->addWidget(run_inspection_button_); right_layout->addWidget(view_history_button_); right_layout->addWidget(switch_recipe_button_); right_layout->addWidget(result_label_, 1);

2.2.5 QTableWidget

代表表格控件,用法如下:

#ifndef HISTORY_DIALOG_HPP #define HISTORY_DIALOG_HPP #include <QDialog> /* QDialog 基类 */ #include <QTableWidget> /* 表格控件 */ #include <string> /* std::string */ /** * HistoryDialog - 历史记录对话框 */ class HistoryDialog : public QDialog { Q_OBJECT public: /* 构造函数:db_path 是 SQLite 数据库路径 */ HistoryDialog(const std::string &db_path, QWidget *parent = nullptr); ~HistoryDialog(); private: /* 从数据库加载最多 limit 条记录填入表格 */ void load_records(int limit); std::string db_path_; /* 数据库路径 */ QTableWidget *table_; /* 记录表格 */ }; #endif /* HISTORY_DIALOG_HPP */

可以看到也是需要先引入相应的表格控件头文件,并且在头文件里声明表格控件对象。然后在对应CPP文件里构造表格控件,用法示例如下:

HistoryDialog::HistoryDialog(const std::string &db_path, QWidget *parent) : QDialog(parent), // 调用父类构造函数 db_path_(db_path), // 初始化数据库路径成员变量 table_(nullptr) // 初始化表格成员变量为 nullptr { /* 设置窗口标题与几何信息 */ this->setWindowTitle(QString::fromUtf8("检测历史记录")); this->setGeometry(200, 200, 900, 500); /* 构造表格控件并设置 7 列表头 */ table_ = new QTableWidget(this); table_->setColumnCount(7); /* 准备表头文本 */ /* QStringList是QT中专门用于存储多个字符串的容器,使用 * <<操作符向容器中追加字符串元素,如第0列为时间 */ QStringList headers; headers << QString::fromUtf8("时间") << QString::fromUtf8("产品") << QString::fromUtf8("结果") << QString::fromUtf8("失败原因") << QString::fromUtf8("操作员") << QString::fromUtf8("配方版本") << QString::fromUtf8("图片路径"); table_->setHorizontalHeaderLabels(headers); // 调用表格控件方法来设置表头文本 /* 最后一列自适应填充剩余宽度,避免图片路径被截断 */ table_->horizontalHeader()->setStretchLastSection(true); /* 只读表格,禁止任何编辑 */ table_->setEditTriggers(QAbstractItemView::NoEditTriggers); /* 构造对话框布局并装入表格 */ QVBoxLayout *layout = new QVBoxLayout(this); // 构造对话框布局 layout->addWidget(table_); // 装入表格 }

2.2.6 QPixmap和QImage

这是QT提供的图像处理类,QPixmap更侧重于显示(类似于pdf),而QImage更侧重于图像处理(类似于word,方便编辑)。一般将QImage用于转换图像到QT显示,而QPixmap用于显示图像。用法如下:

/* 转 QImage 并按 Label 尺寸等比缩放 */ QImage image = mat_to_qimage(display); QPixmap pixmap = QPixmap::fromImage(image).scaled( image_label_->size(), //参数1:目标尺寸 Qt::KeepAspectRatio, //参数2:缩放模式,保持宽高比 Qt::SmoothTransformation); //参数3:缩放质量,平滑转换 image_label_->setPixmap(pixmap); // 设置图像标签的像素图,显示图像

2.2.7 QFileDialog和QMessageBox

QFileDialog和QMessageBox分别代表文件对话框和消息框,QFileDialog用于打开文件或保存文件,打开文件时用getOpenFileName(参数为:父窗口、对话框标题、默认路径、过滤器),保存文件时用getSaveFileName(参数为:父窗口、对话框标题、默认路径、过滤器);

QMessageBox用于显示消息框,常用函数有QMessageBox::information(表示信息提示蓝色图标)、QMessageBox::warning(表示警告提示黄色图标)、QMessageBox::critical(表示错误提示红色图标)。用法如下:

void MainWindow::on_load_image_clicked() { /* 弹出选文件对话框,限制常见图片格式 */ QString file = QFileDialog::getOpenFileName(this, QString::fromUtf8("选择待检测图片"), QString(), QString::fromUtf8("图片文件 (*.png *.jpg *.bmp)")); /* 用户取消选择则直接返回 */ if (file.isEmpty()) { return; } /* 读图,失败则弹警告 */ cv::Mat bgr = cv::imread(file.toStdString(), cv::IMREAD_COLOR);/*将Qstring类型转换为std string*/ if (bgr.empty()) { QMessageBox::warning(this, QString::fromUtf8("读取图片失败"), QString::fromUtf8("无法读取该文件,请确认格式正确。")); return; } }

2.2.8 QstatusBar

代表状态栏,一般存在于QT主窗口的底部。用法如下:

/* 初始状态栏提示 */ statusBar()->showMessage(QString::fromUtf8("就绪"));

2.3 QT多线程

为什么要使用多线程呢?因为如在单线程中,操作都是按顺序执行的,如果UI界面内某个点击按钮的操作是比较耗时的,就会发现点击后没有响应,界面卡住了,必须等到耗时结束后才能恢复。

为了解决这种问题,就要使用多线程方法,而QT中实现多线程的核心是存在QTHread线程类,其有两种多线程方法,一种是继承QThread的run函数,另一种是将继承QObject的类转移到一个线程里,官方主要推荐第二种用法,因此笔者将以第二种用法为准进行介绍。

先来看用法示例,在做讲解:

void MainWindow::setup_worker_thread() { /* 工作线程归 MainWindow 所有,但 Worker 对象不能挂父级,否则 moveToThread 会失败 */ worker_thread_ = new QThread(this); worker_ = new InspectionWorker(); worker_->moveToThread(worker_thread_);//将 worker_ 移动到 worker_thread_ 线程,worker_的槽函数将在该线程中执行 /* 线程结束后自动删除 Worker 对象 */ connect(worker_thread_, SIGNAL(finished()), worker_, SLOT(deleteLater())); /* 主窗口信号 -> Worker 槽:按 recipe 和图像发起检测请求 */ /*当主窗口点击执行检测时会发送检测信号,worker_(在工作线程中)接收到信号后会调用request_inspection槽函数进行检测*/ connect(this, SIGNAL(start_inspection(recipe_config, cv::Mat)), worker_, SLOT(request_inspection(recipe_config, cv::Mat))); /* Worker 信号 -> 主窗口槽:检测完成回传结果 */ /*当信号和槽函数带有参数时,在连接函数里边要写明参数的类型,但可以不写参数名称*/ connect(worker_, SIGNAL(inspection_finished(int, inspection_result)), this, SLOT(on_inspection_finished(int, inspection_result))); /* 启动工作线程 */ /*调用start方法创建线程worker_thread_,并执行该线程,该线程会调用run方法, *run方法中会调用exec()函数,该函数会启动事件循环,即检测到信号时才会唤醒,否则处于阻塞态。 */ worker_thread_->start(); }

如上图的代码是运行在UI线程中的, worker_thread_是实例化的线程对象,worker_是实例化的InspectionWorker对象,此处的InspectionWorker就是继承自QObject的派生类。然后接下来调用moveToThread方法将worker_对象移动到所创建的工作线程(worker_thread_)中,代表之后worker_对象的槽函数将在新的工作线程中执行。

比如上图中的第二个connect函数:当UI线程对象发送开始检测信号后,处在工作线程的对象worker_会在工作线程里执行槽函数on_inspection_finished。最后调用工作线程的start方法来创建工作线程并执行。具体来说,start方法内部会调用pthread_create()创建线程,然后执行所创建的线程,最后在该线程中启动事件循环

2.3.1 跨越信号和槽

像上述所说的,发送信号的对象this和接收对象worker_不在同一线程中时,这种情况就称为跨越信号和槽。其工作机制是:主线程发送信号后,QT会将信号的参数拷贝,并封装成事件,然后将事件发送到工作线程的事件队列里边;等待工作线程执行事件循环取出该队列,才会去调用接收对象的槽函数进行执行

其中,事件循环指的是底层维护一个事件队列,无事件时线程调用 epoll_wait 进入阻塞,内核将其移出就绪表、加入等待表,此时放弃CPU控制权;当其他线程投递事件时会触发内部唤醒管道,内核立即把线程移回就绪表,恢复 TASK_RUNNING 状态,随后事件循环取出队列中的事件并执行。

了解工作原理之后,下边说一个多线程调度的示例帮助理解,如:

1、假设主线程有一个按钮,点击后发射信号让工作线程开始计算: 2、程序启动,主线程进入 app.exec()(代表事件循环),等待事件。 3、工作线程启动后进入 exec(),等待事件。 4、两者都阻塞,CPU 空闲。 5、用户点击按钮 → 主线程被唤醒,执行按钮对应的槽函数,该槽函数发射信号给工作线程。 6、工作线程的信号被包装为事件,投递到工作线程的事件队列,工作线程的 exec() 被唤醒,处理该事件,执行 Worker 的槽(如 doWork())。 7、工作线程耗时计算期间,完全不需要事件循环,直接占用 CPU 执行 doWork()。 8、计算完成后,Worker 发射 resultReady 信号,该信号被投递到主线程的事件队列,工作线程继续回到事件循环 exec() 等待新事件(如果没有新事件,再次阻塞)。 9、主线程的事件循环被 resultReady 事件唤醒,调用主线程槽函数更新界面。 10、更新完成后,主线程继续回到事件循环,若再无事件,则再次阻塞。

2.3.2 声明和注册元类型

上小节中跨越信号和槽的工作机制中讲到,主线程发送信号后,QT会将信号的参数拷贝,并封装成事件。但是,当QT在拷贝参数时如果碰到你自定义的数据结构类型,它是没办法认识的,因此需要采用Q_DECLARE_METATYPE 和 qRegisterMetaType 进行注册,否则QT会报错。用法如下:

// 定义一个结构体 struct SensorData { int id; double temperature; QString name; }; // 声明元类型 Q_DECLARE_METATYPE(SensorData) // 注册元类型 qRegisterMetaType<SensorData>("SensorData");

2.4 QTimer

它是QT的一个定时器类,但不是硬件定时器。作用是定时一段时间后,定时器到期会唤醒事件循环,事件循环发现该定时器到期,便直接触发 QTimer 的事件处理,发射 timeout() 信号,并同步调用连接的槽函数。用法如下:

#include <QTimer> 8 9 10 class MainWindow : public QMainWindow 11 { 12 Q_OBJECT 13 14 public: 15 MainWindow(QWidget *parent = nullptr); 16 ~MainWindow(); 17 18 private: 19 /* 声明QLCDNumber对象 */ 20 QLCDNumber *lcdNumber; 21 22 /* 声明QTimer对象 */ 23 QTimer *timer; /*省略......*/ /*在对应的构造函数中*/ timer = new QTimer(this); 25 /* 设置定时器1000毫秒发送一个timeout()信号 */ 26 timer->start(1000); 27 28 /* 信号槽连接 */ 29 connect(timer, SIGNAL(timeout()), this, 30 SLOT(timerTimeOut()));
http://www.jsqmd.com/news/849163/

相关文章:

  • 告别格式大战!用VSCode的Prettier插件拯救你的代码洁癖(含保存即格式化、快捷键技巧)
  • 完全开源的语言模型学习记录--Dispersion Loss 降低小模型坍缩
  • 三维动画心得:从入门到认知
  • ARMv8-A架构AArch64异常处理机制详解
  • 如何实现TVA与RV的协同进化?
  • 源头电主轴厂家推荐!顺源精密专注进口电主轴维修,自研高速精密电主轴,告诉你电主轴哪家好,行业口碑优选 - 栗子测评
  • 别再让一条宽带拖慢整个公司!手把手教你用H3C防火墙配置双WAN口负载均衡(附HCL模拟器配置)
  • Java并发编程高频面试题附深度扩展
  • 禅论算法引擎:通达信K线结构智能解析系统深度剖析
  • 影像技术实战18:视频静音检测不准?FFmpeg silencedetect + 非静音片段生成完整方案
  • 想省时间、提效率?SOLIDWORKS 库特征值得每一位工程师试试
  • ETime:高效推动你的时间
  • 国内诚信工业厂房搭建源头厂家优选|顶天钢结构一站式施工解决方案,工业厂房搭建/搭建工业厂房,工业厂房搭建团队推荐 - 品牌推荐师
  • TXID详解
  • Langchain的学习(一)
  • C++(模拟法下练习题)
  • 杭州即刻飞行体育文化传播有限公司2026上海滑翔伞培训机构优选:江浙沪滑翔伞培训机构含考证费用与考证攻略推荐杭州即刻飞行 - 栗子测评
  • RabbitMQ 集群网络分区如何配置分区处理策略
  • 别再只会用阻塞式了!STM32CubeMX串口非阻塞收发实战(附LED灯控制案例)
  • 从沙子到车辙(1.1):什么是“计算”?
  • 手机店还会存在吗
  • 快速将现有基于OpenAIAPI的项目迁移至Taotoken平台指南
  • Zemax序列模式模拟双折射:手把手教你用多重组态同时追迹o光和e光
  • 2026杭州弱电工程哪家专业?智能照明/监控安防系统/机房施工公司实力盘点 - 栗子测评
  • 2026年优质PA管路胎具生产厂家推荐:领拓工业领衔,口碑好的TPV管路胎具制作厂家/管路胎具厂家汇总 - 栗子测评
  • 2026深圳防伪标签源头工厂推荐:一物一码防伪标签厂家对比 - 栗子测评
  • 从手机待机到芯片发热:深入聊聊CMOS反相器那点‘电费’是怎么算出来的
  • 2026杭州专业汽车4S店弱电智能化服务公司推荐:车牌识别系统/门禁道闸定制厂家实力解析 - 栗子测评
  • Linux内核消息观测生产排障流程
  • 影像技术实战19:图片上传安全校验:伪装后缀、损坏图片、超大分辨率与后端防护方案