以项目的方式学QT开发(二)——超详细讲解(120000多字详细讲解,涵盖qt大量知识)逐步更新!
API 描述
函数原型
参数说明
push_back()
在 list 尾部
添加一个元素
void push_back(const T& value);
value :要添
加到尾部的元
素
这个示例演示了如何创建 std::list 容器,并对其进行插入、删除和迭代操作。在实际应用中,
std::list 还有许多其他的功能和方法可以使用,比如 splice() 、 merge() 、 sort() 等,用于更复
杂的操作。
以下是 std::list 常用的 API 方法,包括参数说明,整理成表格形式:
#include <iostream>
#include <list>
int main() {
// 创建一个存储整数的 list 容器
std::list<int> myList;
// 在 list 尾部插入元素
myList.push_back(10);
myList.push_back(20);
myList.push_back(30);
// 在 list 头部插入元素
myList.push_front(5);
myList.push_front(15);
// 使用迭代器遍历 list 并输出元素
std::cout << "List elements: ";
for (auto it = myList.begin(); it != myList.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 删除 list 中特定的元素值
myList.remove(20);
// 使用范围-based for 循环遍历 list 并输出元素
std::cout << "List elements after removal: ";
for (int num : myList) {
std::cout << num << " ";
}
std::cout << std::endl;
// 检查 list 是否为空
if (myList.empty()) {
std::cout << "List is empty." << std::endl;
} else {
std::cout << "List is not empty." << std::endl;
}
return 0;
}API
描述
函数原型
参数说明
push_front()
在 list 头部
添加一个元素
void push_front(const T&
value);
value :要添
加到头部的元
素
pop_back()
删除 list 尾
部的一个元素
void pop_back();
无参数
pop_front()
删除 list 头
部的一个元素
void pop_front();
无参数
size()
返回 list 中
元素的数量
size_type size() const
noexcept;
无参数
empty()
检查 list 是
否为空
bool empty() const noexcept;
无参数
clear()
清空 list 中
的所有元素
void clear() noexcept;
无参数
begin()
返回指向
list 第一个
元素的迭代器
iterator begin() noexcept;
const_iterator begin() const
noexcept;
无参数
end()
返回指向
list 末尾
(最后一个元
素的后面)的
迭代器
iterator end() noexcept;
const_iterator end() const
noexcept;
无参数
rbegin()
返回指向
list 最后一
个元素的逆向
迭代器(逆向
开始迭代)
reverse_iterator rbegin()
noexcept;
const_reverse_iterator rbegin()
const noexcept;
无参数
rend()
返回指向
list 第一个
元素之前的逆
向迭代器(逆
向结束迭代)
reverse_iterator rend()
noexcept;
const_reverse_iterator rend()
const noexcept;
无参数
insert()
在指定位置插
入一个或多个
元素
iterator insert(const_iterator
pos, const T& value);
void insert(const_iterator pos,
size_type count, const T&
value);
pos :插入位
置
value :要插
入的元素
count :要插
入的元素个数API
描述
函数原型
参数说明
erase()
删除指定位置
或指定范围内
的一个或多个
元素
iterator erase(const_iterator
pos);
iterator erase(const_iterator
first, const_iterator last);
pos :要删除
的元素位置或
范围的起始位
置
first 、
last :要删
除的范围
splice()
在指定位置插
入另一个
list 中的元
素
void splice(const_iterator pos,
list& other);
void splice(const_iterator pos,
list& other, const_iterator
it);
void splice(const_iterator pos,
list& other, const_iterator
first, const_iterator last);
pos :插入位
置
other :要插
入的另一个
list
it :要插入的
元素
first 、
last :要插
入的范围
merge()
合并两个已排
序的 list
void merge(list& other);
void merge(list&& other);
other :要合
并的另一个
list
unique()
移除 list 中
重复的元素
void unique();
void unique(BinaryPredicate p);
p :可选的谓
词函数,用于
比较元素是否
相等
sort()
对 list 进行
排序
void sort();
void sort(Compare comp);
comp :可选
的比较函数,
用于元素排序
这些方法使得在 std::list 上进行插入、删除、迭代和操作变得方便。每个方法都有不同的参数和作
用,可根据需要选择合适的方法来操作 std::list 容器。
2.14.4 set
std::set 是 C++ 标准模板库中的关联容器,用于存储唯一值的集合。它基于红黑树实现,保持了元素
的有序性,且不允许重复的元素存在。
以下是关于 std::set 的一些特点和说明:
唯一性: std::set 中的元素是唯一的,不允许有重复的元素存在。当尝试向 set 中插入重复的
元素时,新元素将不会被插入。
有序性: std::set 中的元素是根据元素值进行排序的,这使得元素按照一定顺序存储,并且支持
对元素的快速搜索。
红黑树实现: std::set 的底层实现通常是基于红黑树的,这保证了插入、删除和查找操作的时间
复杂度为对数时间(O(log n))。动态操作: 可以对 std::set 进行动态操作,如插入、删除和查找元素。插入和删除操作的性能较
好,不会影响其他元素的位置。
以下是 std::set 常用的一些方法:
insert() : 向 set 中插入一个元素。
erase() : 删除 set 中指定值的元素。
find() : 查找指定值在 set 中的位置。
size() : 返回 set 中元素的数量。
empty() : 检查 set 是否为空。
clear() : 清空 set 中的所有元素。
下面是一个简单的示例,演示了如何使用 std::set :
#include <iostream>
#include <set>
int main() {
// 创建一个存储 int 类型值的 set 容器
std::set<int> mySet;
// 向 set 中插入元素
mySet.insert(10);
mySet.insert(20);
mySet.insert(30);
// 尝试插入重复元素
mySet.insert(20); // 不会插入重复的元素
// 使用迭代器遍历 set 并输出元素
std::cout << "Set elements: ";
for (auto it = mySet.begin(); it != mySet.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 查找特定值在 set 中的位置
int searchValue = 20;
auto found = mySet.find(searchValue);
if (found != mySet.end()) {
std::cout << "Found " << searchValue << " in the set." << std::endl;
} else {
std::cout << searchValue << " not found in the set." << std::endl;
}
// 删除特定值的元素
mySet.erase(30);
// 检查 set 是否为空
if (mySet.empty()) {
std::cout << "Set is empty." << std::endl;
} else {
std::cout << "Set is not empty." << std::endl;这个示例演示了如何创建 std::set 容器,并对其进行插入、删除、查找等操作。 std::set 是一个非
常有用的容器,适用于需要存储唯一值的场景。
2.14.5 map
std::map 是 C++ 标准模板库中的关联容器,用于存储键值对。它基于红黑树实现,保持了元素的有序
性,其中每个元素都是一个键值对,键和值之间存在映射关系。
以下是关于 std::map 的一些特点和说明:
有序性: std::map 中的元素是根据键值排序的,这使得元素按照一定顺序存储,并且支持对元素
的快速搜索。
唯一键: std::map 中的键是唯一的,每个键对应一个值。如果尝试使用相同的键向 map 中插入
值,则会更新键对应的值。
红黑树实现: std::map 的底层实现通常是基于红黑树的,这保证了插入、删除和查找操作的时间
复杂度为对数时间(O(log n))。
动态操作: 可以对 std::map 进行动态操作,如插入、删除和查找键值对。插入和删除操作的性能
较好,不会影响其他元素的位置。
以下是 std::map 常用的一些方法:
insert() : 向 map 中插入一个键值对。
erase() : 删除 map 中指定键的键值对。
find() : 查找指定键在 map 中的位置。
operator[] : 通过键访问对应的值。
size() : 返回 map 中键值对的数量。
empty() : 检查 map 是否为空。
clear() : 清空 map 中的所有键值对。
下面是一个简单的示例,演示了如何使用 std::map :
}
return 0;
}
#include <iostream>
#include <map>
int main() {
// 创建一个存储 string 类型键和 int 类型值的 map 容器
std::map<std::string, int> myMap;
// 向 map 中插入键值对
myMap["Alice"] = 25;
myMap["Bob"] = 30;
myMap["Charlie"] = 20;
// 使用迭代器遍历 map 并输出键值对这个示例演示了如何创建 std::map 容器,并对其进行插入、删除、查找等操作。 std::map 是一个非
常有用的容器,适用于需要键值对存储和检索的场景。
2.15 异常
2.15.1 异常基本
在 C++ 中,异常处理是一种机制,用于处理程序在运行时发生的异常情况。异常是指程序执行期间发生
的意外事件,比如除以零、访问无效的内存地址等。通过使用异常处理机制,可以使程序更健壮,并能
够处理这些意外情况,避免程序崩溃或产生不可预测的结果。
在 C++ 中,异常处理通常包括以下关键词和概念:
try-catch 块: try 块用于标识可能会引发异常的代码块,而 catch 块用于捕获和处理异常。
catch 块可以针对不同类型的异常进行处理。
throw 关键词: throw 用于在程序中显式抛出异常。当发生异常情况时,可以使用 throw 来抛出
一个特定的异常类型。
异常类型:异常可以是任何类型的数据,但通常是标准库中的异常类或自定义的异常类。标准库提
供了一些常见的异常类,如 std::exception 及其派生类,用于表示不同类型的异常情况。
下面是一个简单的示例,演示了异常处理的基本用法:
std::cout << "Map elements: " << std::endl;
for (auto it = myMap.begin(); it != myMap.end(); ++it) {
std::cout << it->first << ": " << it->second << std::endl;
}
// 查找特定键在 map 中的位置
std::string searchKey = "Bob";
auto found = myMap.find(searchKey);
if (found != myMap.end()) {
std::cout << "Found " << searchKey << " with value: " << found->second <<
std::endl;
} else {
std::cout << searchKey << " not found in the map." << std::endl;
}
// 删除特定键的键值对
myMap.erase("Charlie");
// 检查 map 是否为空
if (myMap.empty()) {
std::cout << "Map is empty." << std::endl;
} else {
std::cout << "Map is not empty." << std::endl;
}
return 0;
}
#include <iostream>
void divide(int numerator, int denominator) {
try {在这个示例中, divide() 函数尝试对 numerator 除以 denominator 进行除法运算。如果
denominator 为零,就会抛出一个字符串类型的异常。在 main() 函数中调用 divide() 函数时,由
于 b 的值为零,因此会抛出异常,然后在 catch 块中捕获并处理异常,输出错误消息。
在实际的程序开发中,可以根据具体情况设计和抛出自定义的异常类,以及使用多个 catch 块来处理不
同类型的异常,使程序能够更好地处理各种异常情况。
2.15.2 自定义异常
在 C++ 中,你可以通过继承标准库的 std::exception 类或其派生类来自定义异常类。自定义异常类通
常用于表示特定类型的异常情况,并允许你提供有关异常的额外信息。
以下是一个示例,演示了如何创建自定义的异常类:
if (denominator == 0) {
throw "Division by zero is not allowed!";
}
int result = numerator / denominator;
std::cout << "Result of division: " << result << std::endl;
} catch (const char* errorMessage) {
std::cout << "Exception caught: " << errorMessage << std::endl;
}
}
int main() {
int a = 10;
int b = 0;
divide(a, b);
return 0;
}
#include <iostream>
#include <exception>
// 自定义异常类,继承自 std::exception
class MyException : public std::exception {
private:
const char* message; // 异常消息
public:
// 构造函数,接受异常消息作为参数
MyException(const char* msg) : message(msg) {}
// 覆盖基类的 what() 方法,返回异常消息
virtual const char* what() const throw() {
return message;
}
};
// 一个函数,演示如何抛出自定义异常
void myFunction() {
// 在这个示例中,函数总是抛出自定义异常
throw MyException("This is a custom exception!");在这个示例中, MyException 类继承自 std::exception 类,并重写了基类的 what() 方法,以返回
异常消息。在 myFunction() 中,我们抛出了一个 MyException 类型的异常,并在 main() 函数中的
try-catch 块中捕获并处理该异常。
通过自定义异常类,你可以根据需要添加其他成员变量、方法或构造函数,以便更好地描述和处理特定
类型的异常情况。这种方式可以提高程序的可读性和可维护性,并允许你更精确地控制异常处理。
P3 记事本项目
3.1 项目概述
3.1.1 功能介绍
支持文本创建,打开,保存,关闭的功能
UI样式美化
添加打开快捷键,添加保存快捷
底部显示行列号及文本字符编码
Ctrl加鼠标滚轮支持字体放大缩小
}
int main() {
try {
myFunction();
} catch (const MyException& e) {
std::cout << "Caught MyException: " << e.what() << std::endl;
}
return 0;
}3.1.2 界面预览
3.2.3 工程概述
MainWindows还是Widget
在Qt中,创建 "MainWindow" 与 "Widget" 项目的主要区别在于他们的用途和功能范围:
1. MainWindow:这是一个包含完整菜单栏、工具栏和状态栏的主窗口应用程序框架。它适合于更复
杂的应用程序,需要这些额外的用户界面元素来提供丰富的功能和交互。
2. Widget:这通常是一个简单的窗口,没有内置的菜单栏、工具栏或状态栏。它适合于更简单或专用
的应用程序,不需要复杂的用户界面组件。
简而言之,选择"MainWindow"或"Widget"取决于你的应用程序需要多少内置的用户界面元素和复杂
性。 MainWindow提供了更全面的框架,而Widget则更适合简单、专注的界面。
QApplication
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}功能
说明
事件循环
维护事件循环,负责接收和分发各种事件,如鼠标点击、键盘输入等。
全局设置
处理应用程序的全局设置,包括字体、颜色和其他用户界面元素。
GUI的初始化
在没有创建 QApplication 的情况下,无法使用Qt的任何GUI组件,因此它负责
初始化GUI环境。
命令行参数
处理
可以处理命令行参数,这对于需要命令行交互的应用程序来说是必要的。
在Qt应用程序中, QApplication a(argc, argv); 这行代码的作用是创建一个 QApplication 类的
实例。这是几乎每个Qt应用程序必须做的第一步,因为它负责管理应用程序的许多核心功能。
下表总结了 QApplication 类在Qt框架中的主要功能和职责:
QApplication 是Qt应用程序的核心,它为应用程序提供了必要的环境和框架,确保GUI组件能够正常
工作并响应用户的操作。
简而言之, QApplication a(argc, argv); 用于初始化Qt应用程序的环境,设置事件循环,并准备应
用程序处理GUI事件。
GUI代表图形用户界面(Graphical User Interface)。它是一种用户界面,允许用户通过图形图标
和视觉指示器(如按钮、标签、窗口等)与电子设备交互,而不是仅仅使用文本命令。GUI使得软
件应用程序更加直观和易于使用,因为它提供了视觉导向的操作方式,用户可以通过点击、拖拽和
输入来操作界面元素,而不需要记忆和输入复杂的命令。GUI是现代计算机和移动应用程序的标准
用户界面类型。
return a.exec()
在Qt应用程序中, QApplication::exec() 函数是用来启动应用程序的事件循环的。当你调用这个函数
时,它会开始处理和分发事件,如用户的点击、键盘输入等。这个函数会一直运行,直到事件循环结
束,通常是因为调用了 QApplication::quit() 函数或者关闭了应用程序的主窗口。简而言之,
exec() 是Qt程序中的主循环,负责监听和响应事件,保持应用程序运行直到用户决定退出。
namespace Ui { class Widget; }
在Qt框架中, namespace Ui { class Widget; } 是一种常见的用法,通常出现在使用Qt Designer设
计GUI时自动生成的代码中。这里的 Ui 是一个命名空间,而 class Widget 是一个前向声明,它声明
了一个名为 Widget 的类。这种做法允许你在 .cpp 源文件中引用由Qt Designer创建的UI界面,而不需
要在头文件中包含完整的UI类定义。这种分离的方法有助于减少编译依赖性并保持代码的清晰和组织。
在你的源文件中,你会创建一个 Ui::Widget 类型的对象来访问和操作UI组件。
QT_BEGIN_NAMESPACE
QT_BEGIN_NAMESPACE 是Qt框架中用于支持命名空间的宏定义。Qt使用这些宏来确保其库中的类和函数
不会与其他库中的同名类和函数冲突。 QT_BEGIN_NAMESPACE 宏在定义Qt类和函数之前使用,用来指定
接下来的代码位于Qt的命名空间中。它通常与 QT_END_NAMESPACE 配对使用,后者标志着命名空间的结
束。这种机制对于在大型项目中维护代码的清晰度和防止命名冲突非常重要。
Q_OBJECT
Q_OBJECT 宏是Qt框架中一个非常重要的宏,用于启用Qt对象的元对象系统。当你在Qt中定义一个类
时,如果这个类继承自 QObject 或其子类,并且你想使用Qt的信号和槽机制、国际化、属性系统或其他
Qt元对象系统提供的功能,就必须在类定义中包含 Q_OBJECT 宏。这个宏允许Qt的元对象编译器(moc)识别并处理这个类,生成额外的代码,这些代码是实现信号和槽
机制以及其他元对象功能所必需的。简单地说, Q_OBJECT 宏为Qt类提供了额外的元数据,使得类能够
完全利用Qt框架的功能。
Widget::Widget(QWidget *parent) : QWidget(parent),ui(new Ui::Widget)
代码 : QWidget(parent) 是初始化列表,用于调用基类 QWidget 的构造函数,并将 parent 传递给
它。 ui(new Ui::Widget) 是初始化类内部的 ui 成员变量,这是通过 new 关键字动态分配的。
Ui::Widget 是由Qt Designer工具生成的,用于处理用户界面。这种方式允许将用户界面的设计与后端
逻辑代码分离,有助于提高代码的可维护性和可读性。
3.2 UI设计师工具
3.2.1 按键 QPushButton
涉及操作居多,在视频中演示
新建一个QPushButton
属性页面基本使用
stylesheet初步接触,按键美化操作
资源文件
stylesheet使用资源文件
3.2.2 水平布局 QHBoxLayout
新建水平布局
常用属性和功能特征
建立布局大小
布局和父控件的关联支持窗口变化
弹簧控件
3.2.3 文本编辑器 TextEdit
新建文本编辑器
属性设置
TextEdit的常用C++接口
如何读取TextEdit上的内容
如何往TextEdit上写如内容
3.2.4 垂直布局 QVBoxLayout
同水平布局
3.2.5 主窗体元素设计
图标设计
应用程序名称设计连接方式
描述
示例
使用
QObject::connect
最常用的方式,直接通过
QObject::connect 函数连接信号和
槽。
QObject::connect(sender,
SIGNAL(signal()),
receiver, SLOT(slot()));
使用C++11
Lambda表达式
利用C++11引入的Lambda表达式进行
信号与槽的连接。这种方式可以直接
在连接点使用匿名函数,使代码更加
简洁。
QObject::connect(sender,
&Sender::signal, [=]() {
/* lambda body */ });
使用函数指针
Qt 5中引入,允许使用函数指针直接
连接信号和槽,这种方式类型安全,
且可以利用IDE的代码补全和错误检
查。
QObject::connect(sender,
&Sender::signal, receiver,
&Receiver::slot);
自动连接(使用UI
文件)
在使用Qt Designer时,可以通过命名
约定自动连接信号和槽。当UI文件加
载时,以
on_<objectName>_<signalName> 命
名的槽会自动连接到相应的信号。
在Qt Designer中命名按钮为
pushButton ,然后在代码中定
义
on_pushButton_clicked() 。
3.3 按键响应-初识信号与槽
3.3.1 信号与槽基本介绍
提出疑问,界面上已经有按键了,怎么操作才能让用户按下按键后有操作上的反应呢?
在 Qt 中,信号和槽机制是一种非常强大的事件通信机制。这是一个重要的概念,特别是对于初学者来
说,理解它对于编写 Qt 程序至关重要。
概要
1. 信号 (Signals):是由对象在特定事件发生时发出的消息。例如, QPushButton 有一个
clicked() 信号,当用户点击按钮时发出。
2. 槽 (Slots):是用来响应信号的方法。一个槽可以是任何函数,当其关联的信号被发出时,该槽函数
将被调用。
3. 连接信号和槽:使用 QObject::connect() 方法将信号连接到槽。当信号发出时,关联的槽函数
会自动执行。
3.3.2 按键QPushButton设置信号与槽
在 Qt 中,有几种不同的方式来设置按键信号与槽的连接,主要包括:
Qt的信号和槽机制是其事件处理系统的核心。这种机制允许对象之间的通信,而不需要它们知道对方的
具体实现。以下是Qt信号和槽的几种常见连接方式的简要概述,我将它们整理成表格形式以便于理解:
这些方式各有优劣,选择哪种方式取决于具体的应用场景、代码风格以及个人偏好。例如,直接使用
QObject::connect 是最通用的方式,而使用Lambda表达式可以在同一位置编写信号处理逻辑,提高
代码的可读性。使用函数指针的方式则在编译时提供更好的类型检查。自动连接通常在使用Qt Designer
设计UI时比较方便。
#include "widget.h"3.3.3 自定义信号与槽
在Qt中,自定义信号与槽是实现对象间通信的一种机制。信号和槽是Qt对象通信的核心特性,使得一个
对象能够在发生某种事件时通知其他对象。自定义信号与槽的实现步骤如下:
1. 定义信号:在Qt中,信号是由 signals 关键字声明的类成员函数。它们不需要实现,只需声明。例
如:
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//在构造函数中进行信号与槽的绑定
//第二种方式:QObject::connect(sender, SIGNAL(signal()), receiver,
SLOT(slot()));
QObject::connect(ui->btnCon, SIGNAL(clicked()), this,
SLOT(on_btnCon_clickedMyself()));
//第三方式:lambda表达式:QObject::connect(sender, &Sender::signal, [=]() { /*
lambda body */ });
QObject::connect(ui->btnLambda, &QPushButton::clicked,[=](){
std::cout << "btnLambdaClicked" << std::endl;
});
//第四种方式:QObject::connect(sender, &Sender::signal, receiver,
&Receiver::slot);
QObject::connect(ui-
>btnFortch,&QPushButton::clicked,this,&Widget::on_fortch_clicked);
}
Widget::~Widget()
{
delete ui;
}
//第一种方式:通过uiDesigner
void Widget::on_btnui_clicked()
{
std::cout << "UIBtnClicked" << std::endl;
}
void Widget::on_btnCon_clickedMyself()
{
std::cout << "btnConClicked" << std::endl;
}
void Widget::on_fortch_clicked()
{
std::cout << "btnForthClicked" << std::endl;
}在上面的例子中, MyClass 有一个名为 mySignal 的信号,它带有一个整型参数。
定义槽:槽可以是任何普通的成员函数,但通常在类定义中用 slots 关键字标识。槽可以有返回类型,
也可以接受参数,但它们的参数类型需要与发出信号的参数类型匹配。例如:
在这个例子中,我们定义了一个名为 mySlot 的槽,它接收一个整型参数。
连接信号与槽:使用 QObject::connect 函数将信号与槽连接起来。当信号被发射时,连接到这个信号
的槽将被调用。
这行代码连接了 myObject 的 mySignal 信号到同一个对象的 mySlot 槽。
发射信号:使用 emit 关键字发射信号。当信号被发射时,所有连接到这个信号的槽都会被调用。
这将触发所有连接到 mySignal 的槽。
自定义信号和槽是Qt编程中非常强大的特性,它们使得组件之间的通信变得灵活而松耦合。通过信和
槽,可以方便地实现各种复杂的事件驱动逻//辑。
class MyClass : public QObject {
Q_OBJECT
public:
MyClass();
signals:
void mySignal(int value);
};
class MyClass : public QObject {
Q_OBJECT
public slots:
void mySlot(int value);
};
MyClass *myObject = new MyClass();
connect(myObject, SIGNAL(mySignal(int)), myObject, SLOT(mySlot(int)));
emit mySignal(123);
//widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <iostream>
#include <QDebug>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
signals:
void mysignal();
void mysignalparams(int value);
private slots:
void myslot();
void myslotparams(int value);
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
//widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(this,SIGNAL(mysignal()),this,SLOT(myslot()));
connect(this,SIGNAL(mysignalparams(int)),this,SLOT(myslotparams(int)));
emit mysignal();
emit mysignalparams(100);
}
Widget::~Widget()
{
delete ui;
}
void Widget::myslot()
{
std::cout << "myslot" << std::endl;
}
void Widget::myslotparams(int value)
{
qDebug() << "myslotparams";
qDebug() << value ;
}QDebug()
QDebug 是 Qt 框架中用于输出调试信息的一个类。它提供了一种方便的方式来输出文本到标准输出(通
常是控制台),这对于调试 Qt 应用程序非常有用。 QDebug 类可以与 Qt 的信号和槽机制一起使用,使
得在响应各种事件时能够输出有用的调试信息。
使用 QDebug 的一个典型方式是通过 qDebug() 函数,它返回一个 QDebug 对象。然后,可以使用流操
作符 << 来输出各种数据类型。例如:
当执行这些代码时,它们会在应用程序的控制台输出相应的文本。这对于检查程序的运行状态、变量的
值或者跟踪程序的执行流程非常有帮助。
还可以使用 qDebug() 来输出自定义类型,只要为这些类型提供了适当的输出操作符重载。此外,Qt 还
提供了 qInfo() , qWarning() , qCritical() 和 qFatal() 函数,用于输出不同级别的信息,分别用
于普通信息、警告、关键错误和致命错误。这有助于对日志信息进行级别划分,从而更好地控制输出内
容。
3.3 文件操作类 QFile
QFile 是 Qt 框架中用于文件处理的一个类。它提供了读取和写入文件的功能,支持文本和二进制文
件。
QFile 继承自 QIODevice ,因此它可以像其他IO设备一样使用。
主要功能
1. 文件读写: QFile 支持打开文件进行读取或写入操作
2. 文件信息:可以检索有关文件的信息,如大小、修改日期等。
3. 文件操作:提供了对文件进行重命名、移动、删除等操作的能力。
4. 错误处理: QFile 在操作文件时提供了错误处理机制,可以通过相应的函数检查和获取错误信息。
常用方法
open() :打开一个文件。需要指定模式(如只读、只写、读写等)。
close() :关闭文件。
read() 和 write() :用于读取和写入数据。
exists() :检查文件是否存在。
remove() :删除文件。
copy() :复制文件。
示例代码
以下是使用 QFile 的一个简单例子:
qDebug() << "This is a debug message";
int value = 10;
qDebug() << "The value is" << value;
#include "widget.h"
#include "ui_widget.h"
#include <QFile>
#include <QDebug>特性类别
说明
字符编码
支持 Unicode,可以处理如 UTF-8、UTF-16 等不同编码。通过 setCodec() 方法设
置特定编码。
读写文本
用于读写文件、字符串或任何继承自 QIODevice 的对象。
格式化
提供文本格式化功能,如数字精度、基数(十进制、十六进制等)调整。
流操作符
支持使用 << 和 >> 操作符,类似于 C++ 中的 iostream。
3.3.3 QTextStream
QTextStream 的主要特性成一个表格。请看下表:
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_btnRead_clicked()
{
//1. 打开文件
//QFile file("D:/QT/test.txt");
QFile file;
file.setFileName("D:/QT/test.txt");
if(!file.open(QIODevice::ReadOnly | QIODevice::Text)){
qDebug() << "file open error";
}
//2. 读取文件
char context[100] = {'\0'};
if( file.read(context,100) == -1) return;
//3. 输出文件内容
qDebug() << context;
file.close();
}
void Widget::on_btnWrite_clicked()
{
// 1.打开
QFile file("D:/QT/test2.txt");
file.open(QIODevice::WriteOnly | QIODevice::Text);
// 2. 写入
file.write("Program 45-QFile001 write something to This File 我是老陈");
// 3. 关闭
file.close();
}特性类别
说明
换行处理
自动处理不同操作系统间的换行符差异(如 Unix 的 \n 和 Windows 的 \r\n )。
错误处理
能够检测和报告在读写过程中出现的错误。
缓冲机制
提供缓冲机制,提高读写效率。
字符串操
作
可以方便地处理和解析字符串数据。
QTextStream 是一个功能强大的类,用于处理文本数据,特别是在需要考虑字符编码和文本格式化的情
况下。通过这些特性,它提供了一种灵活而强大的方式来读写和操作文本。
使用示例
以下是一个更详细的示例,展示了如何使用 QTextStream 来读写文件:
void Widget::on_btnstrRead_clicked()
{
QFile file;
file.setFileName("D:/QT/test.txt");
if(!file.open(QIODevice::ReadOnly | QIODevice::Text)){
qDebug() << "file open error";
}
QTextStream in(&file);
in.setCodec("UTF-8");
// QString context = in.read(file.size());
while(!in.atEnd()){
QString context = in.readLine();
qDebug() << context;
}
file.close();
}
void Widget::on_btnstreamWrite_clicked()
{
QFile file;
file.setFileName("D:/QT/test3.txt");
if(!file.open(QIODevice::WriteOnly | QIODevice::Text)){
qDebug() << "file open error";
}
QTextStream out(&file);
out.setCodec("UTF-8");
out << "I write stream char to File";
file.close();
}3.4 文件选择对话框 QFileDialog
3.4.1 QFileDialog开发流程
使用 QFileDialog 的基本步骤通常如下:
实例化:首先,创建一个 QFileDialog 对象的实例。
设置模式:根据需要设置对话框的模式,如打开文件、保存文件等。
设置过滤器:如果需要,可以设置文件类型过滤器,以限制用户可以选择的文件类型。
显示对话框:通过调用 exec() 方法显示对话框,并在用户作出选择后执行相应的操作。
通过 selectedFiles 方法获取用户选择的文件路径列表,然后对这些文件进行相应的处理。
这是使用 QFileDialog 的基本模式。Qt 也允许使用静态方法直接创建和显示对话框,例如
QFileDialog::getOpenFileName() ,这些方法更简单,但提供的自定义选项较少。
3.4.2 QFileDialog 打开开发案例
QFileDialog dialog;
dialog.setFileMode(QFileDialog::AnyFile);
dialog.setNameFilter(tr("Images (*.png *.xpm *.jpg);;Text files (*.txt);;XML
files (*.xml)"));
if (dialog.exec()) {
QStringList files = dialog.selectedFiles();
// 对用户选定的文件进行操作
}
#include <QApplication>
#include <QFileDialog>
#include <QStringList>
#include <QString>
#include <QMessageBox>
/*
fileName = QFileDialog::getOpenFileName(this, tr("Open File"), "",
tr("Text Files (*.txt);;All Files
(*)"));
*/
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
// 创建一个 QFileDialog 实例
QFileDialog dialog;
// 设置对话框为打开文件模式
dialog.setFileMode(QFileDialog::ExistingFiles);
// 设置文件过滤器3.4.3 QFileDialog 保存开发案例
3.6 实现文件打开功能
3.6.1 开发流程
为QPushButton对应Open的控件设置槽函数
槽函数代码开发
打开文件
读取文件
把文件数据显示在TextEdit控件上
dialog.setNameFilter("Text files (*.txt);;Images (*.png *.jpg);;All files
(*)");
// 显示对话框
if (dialog.exec()) {
// 获取用户选中的文件列表
QStringList fileNames = dialog.selectedFiles();
// 遍历列表并处理每个文件
for (const QString &fileName : fileNames) {
// 此处可以添加对 fileName 的处理代码
QMessageBox::information(nullptr, "File Selected", fileName);
}
}
return app.exec();
}
void Widget::on_btnSave_clicked()
{
QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"),
"D:/QT/untitled.txt",
tr("Text (*.txt *.doc)"));
qDebug()<<fileName;
QFile file;
file.setFileName(fileName);
if(!file.open(QIODevice::WriteOnly | QIODevice::Text)){
qDebug() << "file open error";
}
QTextStream out(&file);
out.setCodec("UTF-8");
out << "Qdialog Write Data to the Txt File";
file.close();
}编码名称
描述
UTF-8
用于表示 Unicode 文本的变长字符编码,广泛用于网络和多语言文本
UTF-16
用于表示 Unicode 文本的定长字符编码
ISO 8859-1
也称为 Latin1,用于表示西欧语言字符
GBK
用于表示简体中文字符,是 GB2312 的扩展
Big5
用于表示繁体中文字符,常用于台湾和香港地区
Windows-
1252
用于表示西欧语言字符,是 ISO 8859-1 的超集
ANSI
在 Qt 中,"ANSI" 编码并不是一个明确指定的编码标准,因为 ANSI 编码可以指代
不同的编码标准,这取决于操作系统的语言和区域设置。例如,在中文 Windows
系统中,ANSI 编码通常指的是 GBK 编码;而在西欧语言的 Windows 系统中,
ANSI 编码可能指的是 ISO 8859-1 或 Windows-1252。
3.6.2 代码实现
3.6.3 打开功能优化
字符编码相关问题解决
在 Qt 中, QTextStream 常用的字符编码主要包括以下几种:
这些编码覆盖了大部分常用的语言字符集,可以通过 QTextCodec::codecForName() 方法在
QTextStream 中进行设置。
void Widget::on_btnFileOpen_clicked()
{
QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"),
"D:/QT/",
tr("Text (*.txt)"));
ui->textEdit->clear();
file.setFileName(fileName);
if(!file.open(QIODevice::ReadOnly | QIODevice::Text)){
qDebug() << "file open error";
}
QTextStream in(&file);
in.setCodec("UTF-8");
while(!in.atEnd()){
QString context = in.readLine();
// qDebug() << qPrintable(context);
// ui->textEdit->setText(context);
ui->textEdit->append(context);
}
}功能
描述
API 方法
添加选项
向下拉列表添加单个或多个选项
addItem() , addItems()
获取选项
获取当前选中的文本或索引
currentText() , currentIndex()
设置选项
设置当前选中的项
setCurrentIndex(int)
移除选项
从下拉列表中移除项
removeItem(int)
信号
当选项改变时触发的事件
currentIndexChanged(int)
可编辑性
设置下拉列表是否可编辑
setEditable(bool)
自定义数据
向下拉列表项关联额外的数据
setItemData(int, const QVariant&)
清空列表
移除所有选项
clear()
检测光标位置,并在右下角显示光标位置
在程序左上方显示当前打开的文件名称
3.6.4 QComboBox
QComboBox 是 Qt 框架中用于创建下拉列表的一个控件。
它允许用户从一组选项中选择一个选项,并可以配置为可编辑,使用户能够在其中输入文本。
QComboBox 提供了一系列方法来添加、删除和修改列表中的项,支持通过索引或文本检索项,并可以通
过信号和槽机制来响应用户的选择变化。该
控件广泛应用于需要从多个选项中进行选择的用户界面场景,例如表单和设置界面。
示例代码
#include <QComboBox>
#include <QVBoxLayout>
#include <QWidget>
class ComboBoxDemo : public QWidget {
Q_OBJECT
public:
ComboBoxDemo() {
QComboBox *comboBox = new QComboBox(this);
comboBox->addItems({"选项1", "选项2", "选项3"});
comboBox->setEditable(true);
connect(comboBox, SIGNAL(currentIndexChanged(int)), this,
SLOT(onSelectionChanged(int)));
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(comboBox);
}
private slots:
void onSelectionChanged(int index) {
// 当选中的项改变时的处理逻辑这个示例展示了 QComboBox 的基本用法,包括添加选项、设置为可编辑以及连接信号和槽。您可以根据
需要调整和扩展这个示例。
3.6.5 记事本支持字符编码
获取用户在QComboBox上选择的字符编码,用特定编码打开文件,这里注意QComboBox返回QString
类型,
setCodec参数要求const char*型
QString先转成C++的String,再转换成const char *
支持打开文件后进行字符编码的重新选择和显示加载
}
};
void Widget::on_btnFileOpen_clicked()
{
// 使用文件对话框获取要打开的文件的路径
QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"),
"D:/QT/",
tr("Text (*.txt)"));
// 清空文本编辑器的内容
ui->textEdit->clear();
// 设置 QFile 对象的文件名
file.setFileName(fileName);
// 尝试以只读和文本模式打开文件
if(!file.open(QIODevice::ReadOnly | QIODevice::Text)){
// 如果文件打开失败,输出错误信息
qDebug() << "file open error";
}
// 创建 QTextStream 用于读取文件内容
QTextStream in(&file);
// 从下拉框获取当前选中的字符编码
QString str = ui->comboBox->currentText();
// 将 QString 转化为 char* 类型
const char* c_str = str.toStdString().c_str();
// 设置 QTextStream 的字符编码
in.setCodec(c_str);
// 循环读取文件直到结束
while(!in.atEnd()){
// 读取文件的一行
QString context = in.readLine();
// 将读取的内容追加到文本编辑器
ui->textEdit->append(context);
}
}
//1. 在Widget的构造函数中关联信号与槽,检测用户选择条目的信号。
connect(ui-
>comboBox,SIGNAL(currentIndexChanged(int)),this,SLOT(onCurrentIndexChanged(int)))
;3.6.6 添加行列显示
使用QTextEdit的cursorPositionChanged信号,当光标发生移动时候刷新显示
//2. 添加槽函数,当用户选择信号后被调用,判断是否当前有打开的文件,如果有,则重新用新的编码读取文
件并重新显示。
// onCurrentIndexChanged 方法:当 QComboBox 的选中项变化时执行
void Widget::onCurrentIndexChanged(int index)
{
// 输出调试信息,表示此槽函数被触发
qDebug() << "currentIndexChanged Signal";
// 清空文本编辑器的内容
ui->textEdit->clear();
// 检查文件是否已经打开
if(file.isOpen()){
// 输出调试信息,表示文件是打开状态
qDebug() << "file is Open";
// 创建 QTextStream 用于读取文件内容
QTextStream in(&file);
// 设置 QTextStream 的字符编码为 QComboBox 当前选中的编码
in.setCodec(ui->comboBox->currentText().toStdString().c_str());
// 将文件指针移动到文件开始位置
file.seek(0);
// 循环读取文件直到文件结束
while(!in.atEnd()){
// 读取文件的一行
QString context = in.readLine();
// 将读取的内容追加到文本编辑器
ui->textEdit->append(context);
}
}
}
//1. 在构造函数中添加信号与槽
connect(ui-
>textEdit,SIGNAL(cursorPositionChanged()),this,SLOT(onCursorPositionChanged()));
//2. 槽函数获取textEdit的行列并显示到QLabel上
void Widget::onCursorPositionChanged()
{
QTextCursor cursor = ui->textEdit->textCursor();
//qDebug() << cursor.blockNumber()+1 <<","<< cursor.columnNumber() + 1;
QString blockNum = QString::number(cursor.blockNumber()+1);
QString columnNum = QString::number(cursor.columnNumber()+1);
const QString labelMes = "L:"+blockNum+",C:"+columnNum+" ";
//const QString labelMes = "行:"+blockNum+",列:"+columnNum+" ";
ui->labelPosition->setText(labelMes);
}3.6.7 添加文件打开提示
3.6.8 设置当前行高亮
实现策略:
获取当前行的光标位置,使用的信号和获取行列值是一样的
通过ExtraSelection来配置相关属性
在当前行设置该属性
实现该功能,需要用到一个API,
3.6.8.1 QList
在 Qt 框架中, QList 是一个容器类,它在内部实现上类似于一个数组,但也提供了一些链表的特性。
QList 的设计旨在提供一个在多数情况下既高效又方便的通用列表容器。用于存储元素列表。它提供了
丰富的功能,包括添加、移除、访问元素等。
QList 的内部工作原理:
1. 数组式存储: QList 在大多数情况下使用连续内存存储其元素,类似于数组。这意味着它提供了快
速的索引访问(通过下标操作符 [] ),以及相对高效的迭代性能。
2. 动态调整大小:与静态数组不同, QList 可以动态增长和缩减,自动管理内存分配。
3. 链表特性:虽然 QList 主要基于数组,但它也提供了一些链表的操作,比如在列表的开始或结束
处添加和移除元素。这些操作通常比在数组中间插入或删除元素更高效。
4. 复制时共享内存: QList 使用一种称为“隐式共享”(implicit sharing)或“写时复制”(copy-on
write)的技术。这意味着当你复制一个 QList 时,它不会立即复制所有元素,而是共享相同的数
据,直到你尝试修改其中一个列表,此时才进行实际的复制。这使得复制 QList 变得非常高效。
使用场景:
当你需要快速的随机访问(如通过索引访问元素)时, QList 是一个不错的选择。
如果你的主要操作是在列表的两端添加或移除元素, QList 也表现得很好。
基本用法
包含头文件:首先,你需要包含 QList 的头文件。
创建 QList 实例:创建一个 QList 对象,并指定存储的元素类型。
添加元素:使用 append 或 push_back 方法添加元素。
//功能简单,在视频课程代码中体现
this->setWindowTitle(fileName + "- MyNoteBook");
QList<QTextEdit::ExtraSelection> extraSelections;
void setExtraSelections(const QList<QTextEdit::ExtraSelection> &extraSelections)
#include <QList>
QList<int> list;访问元素:可以使用下标操作符或 at() 方法访问元素。
遍历列表:使用迭代器或范围基的 for 循环遍历列表。
移除元素:使用 removeAt 、 removeOne 或 clear 方法移除元素。
3.8.2 ExtraSelection 简介
QTextEdit::ExtraSelection 是一个在 QTextEdit 中用来表示额外的文本选择和高亮的结构。
如何工作
1. ExtraSelection 结构体: QTextEdit::ExtraSelection 是一个结构体,包含了两个主要成员:
QTextCursor 和 QTextCharFormat 。 QTextCursor 表示在文本中的一个位置或者区间,而
QTextCharFormat 用于定义这个区间的格式,比如背景颜色、字体等。
2. 设置 ExtraSelection:你可以创建一个或多个 ExtraSelection 对象,为它们设置相应的光标位
置和格式,然后通过 QTextEdit 的 setExtraSelections 方法将这些对象应用到文本编辑器中。
这样,你可以对文本的特定部分应用特定的格式,而不影响其他文本。
3. 高亮当前行:要高亮显示当前行,你需要在 cursorPositionChanged() 信号的槽函数中创建一个
ExtraSelection 对象。使用当前的 QTextCursor 对象(通过 textCursor() 方法获取)来确
定当前行的位置,并设置背景颜色为你选择的高亮颜色。
QTextCharFormat 类是 Qt 框架中的一部分,用于描述文本字符的格式。这个类提供了丰富的接口来设
置和获取文本字符的各种属性,如字体、颜色、背景色等。 QTextCharFormat 通常用于富文本处理,可
以在像 QTextEdit 和 QTextDocument 这样的类中使用
下面列出了 QTextCharFormat 的一些常用功能和方法:
1. 设置和获取字体样式:
使用 setFont() 方法设置字体。
通过 font() 方法获取当前字体。
list.append(1);
list.append(2);
list.append(3);
int firstElement = list[0];
int secondElement = list.at(1);
for(int i = 0; i < list.size(); ++i) { // size = sizeof(arr)/sizeof(arr[0])
qDebug() << list[i];
}
// 或者使用范围基的 for 循环
for(int item : list) {
qDebug() << item;
}
list.removeAt(1); // 移除索引为 1 的元素
list.removeOne(3); // 移除一个值为 3 的元素
list.clear(); // 清空整个列表2. 设置字体属性:
setFontWeight() : 设置字体的粗细。
setFontItalic() : 设置字体是否倾斜。
setFontUnderline() : 设置是否有下划线。
3. 设置文本颜色和背景色:
setForeground() : 设置文本的前景色(即字体颜色)。
setBackground() : 设置文本的背景色。
4. 其他文本属性:
setToolTip() : 设置文本的工具提示。
setAnchor() : 设置文本是否为超链接。
setAnchorHref() : 设置超链接的目标 URL。
示例代码
下面是一个简单的示例,展示如何在 QTextEdit 中使用 QTextCharFormat 来设置特定文本的格式:
3.7 文件保存功能优化
3.7.1 开发流程
判断当下是否有已经打开的文件,如果有打开的文件
读取TextEdit的内容
写入新文件
#include <QApplication>
#include <QTextEdit>
#include <QTextCharFormat>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
QTextEdit editor;
// 创建一个 QTextCharFormat 对象
QTextCharFormat format;
format.setFontWeight(QFont::Bold);
format.setForeground(Qt::blue);
format.setBackground(Qt::yellow);
// 将格式应用到编辑器中的特定文本
QTextCursor cursor = editor.textCursor();
cursor.movePosition(QTextCursor::Start);
cursor.insertText("Hello, World!", format);
editor.show();
return a.exec();
void Widget::on_btnSave_clicked()
{3.8 关闭优化
在上节课中关闭部分稍微优化了以下,但是还是不够, 我们应该弹出窗口多一个询问!
3.8.1 消息对话框 QMessageBox
QMessageBox 是 Qt 框架中用于显示消息框的一个类,它常用于向用户显示信息、询问问题或者报告错
误。以下是 QMessageBox 的一些主要用途:
1. 显示信息:向用户显示一些信息性的消息。
2. 询问用户决策:询问用户一个问题,并根据其回答做出相应的操作。
3. 报告错误:向用户报告程序运行中的错误。
代码示例
以下是一个简单的 QMessageBox 使用示例,展示了如何创建一个基本的消息框:
//如果当前没有文件打开,就弹窗让用户选择新文件,创建新文件,而不是原来那样,都弹出新的
文件保存窗口
if(!file.isOpen()){
QString fileName = QFileDialog::getSaveFileName(this, tr("Save
File"),
"D:/QT/untitled.txt",
tr("Text (*.txt
*.doc)"));
file.setFileName(fileName);
if(!file.open(QIODevice::WriteOnly | QIODevice::Text)){
qDebug() << "file open error";
}
this->setWindowTitle(fileName + "- MyNoteBook");
}
//当保存被按下,不管是已有打开的文件还是上面if满足后用户选择新文件,都要读取TextEdit
内容并写入文件中
QTextStream out(&file);
out.setCodec(ui->comboBox->currentText().toStdString().c_str());
QString context = ui->textEdit->toPlainText();
out << context;
}
void Widget::on_btnClose_clicked()
{
ui->textEdit->clear();
if(file.isOpen()){
file.close();
this->setWindowTitle("MyNoteBook");
}
}
#include <QApplication>在这个例子中,我们创建了一个 QMessageBox 对象,并设置了窗口标题、主要文本、附加信息文本和
图标。还添加了两个按钮(OK 和 Cancel),并设置了默认按钮。通过 exec() 方法显示消息框,并根
据用户的选择执行不同的操作。
由于 QMessageBox 是为标准对话框设计的,其定制能力有限,但你可以通过添加自定义按钮来实现一
定程度的定制。例如,如果你想要添加一个自定义的按钮,可以这样做:
#include <QMessageBox>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QMessageBox msgBox;
msgBox.setWindowTitle("Message Title");
msgBox.setText("This is the main message text.");
msgBox.setInformativeText("This is additional informative text.");
msgBox.setIcon(QMessageBox::Information);
msgBox.addButton(QMessageBox::Ok);
msgBox.addButton(QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Ok);
int ret = msgBox.exec();
if (ret == QMessageBox::Ok) {
// 用户点击了 OK
} else if (ret == QMessageBox::Cancel) {
// 用户点击了 Cancel
}
return app.exec();
}
#include <QApplication>
#include <QMessageBox>
#include <QPushButton>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QMessageBox msgBox;
msgBox.setText("Custom message box with a custom button");
QPushButton *customButton = msgBox.addButton("自定义的名字",
QMessageBox::ActionRole);
msgBox.exec();
if (msgBox.clickedButton() == customButton) {
// 用户点击了自定义按钮
}
return app.exec();
}在这个例子中,通过 addButton() 方法添加了一个自定义按钮。按钮的角色被设置为
QMessageBox::ActionRole ,这意味着它将被放置在对话框的底部,与其他标准按钮一起。通过检查
用户点击的按钮来确定是否点击了自定义按钮。
3.7.3 代码实现
3.9 实现快捷键功能
3.9.1 快捷键开发基础
在 Qt 中实现快捷键功能通常涉及到 QShortcut 类的使用。下面是一个简单的代码示例,展示了如何在
Qt 应用程序中为特定功能设置快捷键:
void Widget::on_btnClose_clicked()
{
QMessageBox msgBox;
int ret = QMessageBox::warning(this, tr("MyNoteBook Notice:"),
tr("The document has been modified.\n"
"Do you want to save your changes?"),
QMessageBox::Save | QMessageBox::Discard
| QMessageBox::Cancel,
QMessageBox::Save);
switch (ret) {
case QMessageBox::Save:
// Save was clicked
on_btnSave_clicked();
qDebug() << "QMessageBox::Save:";
break;
case QMessageBox::Discard:
// Don't Save was clicked
ui->textEdit->clear();
if(file.isOpen()){
file.close();
this->setWindowTitle("MyNoteBook");
}
qDebug() << "QMessageBox::Discard:";
break;
case QMessageBox::Cancel:
// Cancel was clicked
qDebug() << "QMessageBox::Cancel:";
break;
default:
// should never be reached
break;
}
}在这个示例中,当用户按下 Ctrl + N 时,程序将弹出一个消息框。这是通过创建一个 QShortcut 对象,
并将其快捷键序列设置为 "Ctrl+N" 来实现的。然后,将 activated 信号连接到一个 Lambda 函数,
该函数在快捷键被激活时执行。这种方法非常适用于为特定操作提供快速访问路径。
3.9.2 上官记事本添加快捷键
3.10 实现字体放大缩小功能
3.10.1 滚动调节字体大小的流程
为TextEdit添加事件过滤器
重写窗口的eventFilter函数
eventFilter设置滚轮事件和目标对象
实现字体放大缩小的功能函数
3.10.2 本节笔记失误
啥也没有,嘿嘿嘿
3.10.3 检测Ctrl键被按下
QGuiApplication::keyboardModifiers() 是 Qt 中一个静态函数,用于返回当前按下的键盘修饰符
(如 Ctrl、Shift、Alt 等)。当与 Qt::ControlModifier 结合使用时,这个函数可以用来检测是否按下
了 Control 键。
例如,以下代码片段检查 Control 键是否被按下:
// 创建一个快捷键 (Ctrl + N) 并关联到窗口
QShortcut *shortcut = new QShortcut(QKeySequence("Ctrl+N"), &window);
// 当快捷键被按下时,显示一个消息框
QObject::connect(shortcut, &QShortcut::activated, [&]() {
QMessageBox::information(&window, "Shortcut Activated", "Ctrl+N was
pressed");
});
// 制作两个快捷键
QShortcut *shortcutOpen = new QShortcut(QKeySequence("Ctrl+O"), this);
QShortcut *shortcutSave = new QShortcut(QKeySequence("Ctrl+S"), this);
// 把Ctrl+O的信号添加槽,调用打开按键的槽函数
QObject::connect(shortcutOpen, &QShortcut::activated, [&]() {
Widget::on_pushButtonOpen_clicked();
});
// 把Ctrl+S的信号添加槽,调用保存按键的槽函数
QObject::connect(shortcutSave, &QShortcut::activated, [&]() {
Widget::on_pushButtonSave_clicked();
});这里, QGuiApplication::keyboardModifiers() 返回当前按下的修饰符,而
Qt::ControlModifier 是一个枚举值,表示 Control 键。使用位与运算符 & 检查 Control 键是否在当
前按下的修饰符中。
3.10.4 记事本添加字体放大缩小
if (QGuiApplication::keyboardModifiers() & Qt::ControlModifier) {
// Control 键当前被按下
}
QShortcut *shortcutZoomIn = new QShortcut(QKeySequence(tr("Ctrl+Shift+=",
"File|Save")),this);
QShortcut *shortcutZoomOut = new QShortcut(QKeySequence(tr("Ctrl+Shift+-",
"File|Save")),this);
connect(shortcutZoomIn,&QShortcut::activated,[=](){
zoomIn();
});
connect(shortcutZoomOut,&QShortcut::activated,[=](){
zoomOut();
});
void Widget::zoomIn()
{
//获得TextEdit的当前字体信息
QFont font = ui->textEdit->font();
//获得当前字体的大小
int fontSize = font.pointSize();
if(fontSize == -1) return;
//改变大小,并设置字体大小
int newFontSize = fontSize+1;
font.setPointSize(newFontSize);
ui->textEdit->setFont(font);
}
void Widget::zoomOut()
{
//获得TextEdit的当前字体信息
QFont font = ui->textEdit->font();
//获得当前字体的大小
int fontSize = font.pointSize();
if(fontSize == -1) return;
//改变大小,并设置字体大小
int newFontSize = fontSize-1;
font.setPointSize(newFontSize);
ui->textEdit->setFont(font);
}3.10.5 事件
事件处理过程
众所周知Qt是一个基于C++的框架,主要用来开发带窗口的应用程序(不带窗口的也行,但不是主流)。
我们使用的基于窗口的应用程序都是基于事件,其目的主要是用来实现回调(因为只有这样程序的效率
才是最高的)。所以在Qt框架内部为我们提供了一些列的事件处理机制,当窗口事件产生之后,事件会
经过: 事件派发 -> 事件过滤->事件分发->事件处理 几个阶段。Qt窗口中对于产生的一系列事件都有默认
的处理动作,如果我们有特殊需求就需要在合适的阶段重写事件的处理动作,比如信号与槽就是一种
事件(event)是由系统或者 Qt 本身在不同的场景下发出的。当用户按下/移动鼠标、敲下键盘,或者是
窗口关闭/大小发生变化/隐藏或显示都会发出一个相应的事件。一些事件在对用户操作做出响应时发出,
如鼠标/键盘事件等;另一些事件则是由系统自动发出,如计时器事件。
每一个Qt应用程序都对应一个唯一的 QApplication 应用程序对象,然后调用这个对象的 exec() 函
数,这样Qt框架内部的事件检测就开始了( 程序将进入事件循环来监听应用程序的事件 )。
事件在Qt中产生之后,的分发过程是这样的:
1. 当事件产生之后,Qt使用用应用程序对象调用 notify() 函数将事件发送到指定的窗口:
2. 事件在发送过程中可以通过事件过滤器进行过滤,默认不对任何产生的事件进行过滤。
3. 当事件发送到指定窗口之后,窗口的事件分发器会对收到的事件进行分类:
4. 事件分发器会将分类之后的事件(鼠标事件、键盘事件、绘图事件。。。)分发给对应的事件处理
器函数进行处理,每个事件处理器函数都有默认的处理动作(我们也可以重写这些事件处理器函
数),比如:鼠标事件:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow* w = new MainWindow;
w.show();
return a.exec();
}
[override virtual] bool QApplication::notify(QObject *receiver, QEvent *e);
// 需要先给窗口安装过滤器, 该事件才会触发
[virtual] bool QObject::eventFilter(QObject *watched, QEvent *event)
[override virtual protected] bool QWidget::event(QEvent *event);
// 鼠标按下
[virtual protected] void QWidget::mousePressEvent(QMouseEvent *event);
// 鼠标释放
[virtual protected] void QWidget::mouseReleaseEvent(QMouseEvent *event);
// 鼠标移动
[virtual protected] void QWidget::mouseMoveEvent(QMouseEvent *event);重写事件案例
程序关闭之前的询问,鼠标进入,鼠标离开,窗口大小改变
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QMessageBox>
#include <QWheelEvent>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::enterEvent(QEvent *event)
{
qDebug() << "mouse enter";
}
void Widget::leaveEvent(QEvent *event)
{
qDebug() << "mouse leave";
}
void Widget::wheelEvent(QWheelEvent *event)
{
qDebug() << event->angleDelta();
}
void Widget::closeEvent(QCloseEvent *event)
{
int ret = QMessageBox::warning(this, tr("My Application"),
tr("close the window\n"
"Do you want to close the window?"),
QMessageBox::Ok | QMessageBox::No
);
switch(ret){
case QMessageBox::Ok:
event->accept();
break;
case QMessageBox::No:
event->ignore();
break;
}
}
void Widget::resizeEvent(QResizeEvent *event)
{
qDebug() << "oldSize:" << event->oldSize()
<< "newSize:" << event->size();自定义按键
mybutton.h
mybutton.cpp
}
void Widget::on_pushButton_clicked()
{
}
#ifndef MYBUTTON_H
#define MYBUTTON_H
#include <QWidget>
class MyButton : public QWidget
{
Q_OBJECT
private:
QPixmap pic;
public:
explicit MyButton(QWidget *parent = nullptr);
protected:
void mousePressEvent(QMouseEvent *event) override;
void leaveEvent(QEvent *event) override;
void enterEvent(QEvent *event) override;
void paintEvent(QPaintEvent *event) override;
signals:
void clicked();
};
#endif // MYBUTTON_H
#include "mybutton.h"
#include <QPainter>
MyButton::MyButton(QWidget *parent) : QWidget(parent)
{
pic.load(":/o1.png");
setFixedSize(pic.size());
update();
}
void MyButton::mousePressEvent(QMouseEvent *event)
{
pic.load(":/o3.png");
update();widget.cpp
事件方式实现字体放大缩小
自定义控件MyTextEdit
头文件
emit clicked();
}
void MyButton::leaveEvent(QEvent *event)
{
pic.load(":/o1.png");
update();
}
void MyButton::enterEvent(QEvent *event)
{
pic.load(":/o2.png");
update();
}
void MyButton::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.drawPixmap(rect(),pic);
}
#include "widget.h"
#include "ui_widget.h"
#include "mybutton.h"
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->mybtn,&MyButton::clicked,[=](){
qDebug() << "myButton is clicked !";
});
}
Widget::~Widget()
{
delete ui;
}
#ifndef MYTEXTEDIT_H
#define MYTEXTEDIT_H
#include <QTextEdit>实现文件
class MyTextEdit : public QTextEdit
{
public:
MyTextEdit(QWidget *parent);
protected:
void wheelEvent(QWheelEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
void keyReleaseEvent(QKeyEvent *e) override;
private:
bool ctrlKeyPressed = 0;
};
#endif // MYTEXTEDIT_H
#include "mytextedit.h"
#include <QWheelEvent>
#include <QDebug>
MyTextEdit::MyTextEdit(QWidget *parent) : QTextEdit(parent)
{
}
void MyTextEdit::wheelEvent(QWheelEvent *e)
{
//qDebug() << e->angleDelta().y();
if(ctrlKeyPressed == 1){
if(e->angleDelta().y() > 0){
zoomIn();
}else if(e->angleDelta().y() < 0){
zoomOut();
}
e->accept();
}else{
QTextEdit::wheelEvent(e);
}
}
void MyTextEdit::keyPressEvent(QKeyEvent *e)
{
if(e->key() == Qt::Key_Control){
// qDebug() << "ctrl Pressed";
ctrlKeyPressed = 1;
}
QTextEdit::keyPressEvent(e);
}
void MyTextEdit::keyReleaseEvent(QKeyEvent *e)
{事件过滤器
我们通过继承QTextEdit来重写事件实现Ctrl加滚轮的检测,还有一种处理方式,叫做事件过滤器
在Qt的事件处理过程中,引入事件过滤器(Event Filter)可以让你在事件达到目标对象之前进行拦截和
处理。这是一种强大的机制,允许你在不同对象间共享事件处理逻辑或在父对象中集中处理特定事件。
下面是加入事件过滤器的步骤:
1. 定义事件过滤器: 事件过滤器通常是一个重写了 QObject::eventFilter() 方法的对象。这个方法
会在事件传递给目标对象之前被调用。
2. 安装事件过滤器: 使用 QObject::installEventFilter() 方法安装事件过滤器。这个方法告诉Qt
在将事件发送给特定对象之前先通过过滤器对象。例如,如果你想在父窗口中过滤子窗口的事件,
你需要在父窗口的对象上调用 installEventFilter() ,并将子窗口作为参数传递。
3. 事件过滤器逻辑: 在 eventFilter() 方法内部,你可以编写自定义逻辑来决定如何处理或忽略事
件。如果此方法返回 true ,则表示事件已被处理,不应该继续传递;如果返回 false ,则事件将
正常传递给目标对象。
4. 事件分发: 当事件发生时,Qt首先将事件发送到安装了事件过滤器的对象。在这一步,
eventFilter() 方法被调用。
5. 决定是否传递事件: 根据 eventFilter() 方法的返回值,Qt决定是否继续向目标对象传递事件。如
果过滤器返回 true ,事件处理到此结束;如果返回 false ,事件继续传递到原始目标对象。
6. 目标对象处理事件: 如果事件过滤器允许事件继续传递,目标对象将像没有事件过滤器存在时那样处
理事件。
事件过滤器特别适用于以下情况:
当你想在不修改子类代码的情况下改变事件的行为。
当多个对象需要共享相同的事件处理逻辑。
当你需要在更高的层级上监控或修改应用程序的事件流。
通过使用事件过滤器,Qt应用程序可以获得更大的灵活性和更细粒度的事件处理控制。
3.10.6 鼠标滚轮和字体大小
if(e->key() == Qt::Key_Control){
// qDebug() << "ctrl Release";
ctrlKeyPressed = 0;
}
QTextEdit::keyPressEvent(e);
}
ui->textEdit->installEventFilter(this); // 给textEdit安装了事件过滤器,为滚轮字体做准备
bool Widget::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::Wheel) {
QWheelEvent *wheelEvent = dynamic_cast<QWheelEvent *>(event);
//检查键盘的CTRL是否被按下
if (QGuiApplication::keyboardModifiers() & Qt::ControlModifier){
if (wheelEvent->angleDelta().y() > 0) {
zoomInText(); // 滚轮向上滚动在 C++ 中,强制类型转换(或类型转换)是一种将变量从一种类型转换为另一种类型的方法。C++ 提供
了四种强制转换运算符,每种都有其特定的用途和适用场景:
1. static_cast
static_cast 是最常用的类型转换运算符,用于无风险的转换,如整数到浮点数,字符到整
数等。
它在编译时执行,不执行运行时类型检查(RTTI)。
示例: int x = static_cast<int>(y); 其中 y 可能是 float 类型。
2. dynamic_cast
专门用于处理对象的多态性,只能用于指针和引用,且涉及对象类必须有虚函数。
它在运行时检查类型的安全性,如果转换失败,对于指针类型返回 nullptr ,对于引用类型
抛出异常。
示例: Derived *dp = dynamic_cast<Derived *>(bp); 其中 bp 是基类指针, Derived
是派生类。
3. const_cast
用于修改类型的 const 或 volatile 属性。
通常用于去除对象的 const 性质,允许修改原本被声明为 const 的变量。
示例: const int a = 10; int* b = const_cast<int*>(&a);
4. reinterpret_cast
用于进行低级别的重新解释转换,几乎无限制,但也是最危险的。
它可以将一种完全不相关的类型转换为另一种类型,比如将指针类型转换为整数类型。
示例: long p = reinterpret_cast<long>(&object); 其中 object 是某个类的对象。
} else {
zoomOutText(); // 滚轮向下滚动
}
}
return true;//表示事件已被处理
}
return QWidget::eventFilter(watched, event);
}
void Widget::zoomInText()
{
// 放大字体
QFont font = ui->textEdit->font();
font.setPointSize(font.pointSize() + 1);
ui->textEdit->setFont(font);
}
void Widget::zoomOutText()
{
// 缩小字体
QFont font = ui->textEdit->font();
font.setPointSize(font.pointSize() - 1);
ui->textEdit->setFont(font);
}类别
功能
描述
UI设计师基本控件操作
Widget
基础的用户界面单元,用于构建复杂的用户界
面。
QPushButton
用于创建按钮。
QHBoxLayout
水平布局管理器,用于水平排列控件。
QVBoxLayout
垂直布局管理器,用于垂直排列控件。
TextEdit
多行文本编辑器控件。
Stylesheet
使用样式表来定制控件的外观。
文件操作类
QFile
用于读取和写入文件。
文件选择对话框类
QFileDialog
提供了一个对话框,允许用户选择文件或目录。
QT的信号与槽
-
用于对象之间的通信机制。
消息对话框
QMessageBox
用于显示信息、警告、错误等对话框。
快捷键捕获和处理
-
用于捕获和处理键盘快捷键。
Ctrl按键信号捕获和处理
-
专门处理Ctrl按键的信号。
鼠标滚轮信号捕获和处
理
-
用于捕获和处理鼠标滚轮动作。
事件处理
event
用于处理不同的事件。
文本字符编码检测
-
用于检测和处理文本的字符编码。
3.12 记事本项目总结
类别
功能
描述
字体放大缩小
-
用于调整字体大小。
QT程序开发流程
-
涉及从设计到部署的整个开发流程。
P4 串口调试助手项目
4.1 项目概述
项目功能描述
见下方界面,所见即所得!
4.2 串口通信核心代码开发
代码会放在网盘上全程高能力输出-代码开发和调试都在视频里P5 网络调试助手
5.1 TCP网络调试助手
5.1.1 项目概述
网络相关的一些基础概念-面试用
学习QTcpServer
学习QTcpClient
学习TextEdit特定位置输入文字颜色
学习网络通信相关知识点
复习巩固之前UI控件
程序运行如下图所示5.1.2 开发流程
5.1.3 QTtcp服务器的关键流程
工程建立,需要在.pro加入网络权限
创建一个基于 QTcpServer 的服务端涉及以下关键步骤:
1. 创建并初始化 QTcpServer 实例:
实例化 QTcpServer 。
调用 listen 方法在特定端口监听传入的连接。
2. 处理新连接:
为 newConnection 信号连接一个槽函数。
在槽函数中,使用 nextPendingConnection 获取 QTcpSocket 以与客户端通信。
3. 读取和发送数据:
通过连接 QTcpSocket 的 readyRead 信号来读取来自客户端的数据。
使用 write 方法发送数据回客户端。
4. 关闭连接:在适当的时候关闭 QTcpSocket 。
示例代码可能如下:
确保在使用 QTcpServer 和 QTcpSocket 时妥善处理网络错误和异常情况。
5.1.4 QTtcp客户端的关键流程
工程建立,需要在.pro加入网络权限
创建一个基于 QTcpSocket 的Qt客户端涉及以下步骤:
1. 创建 QTcpSocket 实例:
实例化 QTcpSocket 。
2. 连接到服务器:
使用 connectToHost 方法连接到服务器的IP地址和端口。
3. 发送数据到服务器:
使用 write 方法发送数据。
4. 接收来自服务器的数据:
为 readyRead 信号连接一个槽函数来接收数据。
5. 关闭连接:
class MyServer : public QObject {
Q_OBJECT
public:
MyServer() {
QTcpServer *server = new QTcpServer(this);
connect(server, &QTcpServer::newConnection, this,
&MyServer::onNewConnection);
server->listen(QHostAddress::Any, 1234);
}
private slots:
void onNewConnection() {
QTcpSocket *clientSocket = server->nextPendingConnection();
connect(clientSocket, &QTcpSocket::readyRead, this,
&MyServer::onReadyRead);
// ...
}
void onReadyRead() {
QTcpSocket *clientSocket = qobject_cast<QTcpSocket *>(sender());
// 读取数据
QByteArray data = clientSocket->readAll();
// 处理数据
// ...
}
};关闭 QTcpSocket 连接。
示例代码如下:
这个客户端尝试连接到指定的服务器地址和端口,然后等待和处理来自服务器的数据。记得根据需要管
理和处理网络错误和异常情况。
5.1.2 TCP协议
以下内容自省阅读和消化,主要在面试之前类似八股文问答,实际编程我们不需要关系这么多,
QTcpSocket类底下的API已经做好所有的封装。
TCP(传输控制协议)是一种广泛使用的网络通信协议,设计用于在网络中的计算机之间可靠地传输数
据。它是互联网协议套件的核心部分,通常与IP(互联网协议)一起使用,合称为TCP/IP。以下是TCP协
议的一些基本特点:
1. 面向连接:在数据传输之前,TCP 需要在发送方和接收方之间建立一个连接。这包括三次握手过
程,确保两端都准备好进行数据传输。
2. 可靠传输:TCP 提供可靠的数据传输服务,这意味着它保证数据包准确无误地到达目的地。如果发
生数据丢失或错误,TCP 会重新发送数据包。
3. 顺序控制:TCP 保证数据包的传输顺序。即使数据包在网络中的传输顺序被打乱,接收方也能按照
正确的顺序重组这些数据。
4. 流量控制:TCP 使用窗口机制来控制发送方的数据传输速率,以防止网络过载。这有助于防止接收
方被发送方发送的数据所淹没。
5. 拥塞控制:TCP 还包括拥塞控制机制,用来检测并防止网络拥塞。当网络拥塞发生时,TCP 会减少
其数据传输速率。
6. 数据分段:大块的数据在发送前会被分割成更小的段,以便于传输。这些段会被独立发送并在接收
端重新组装。
7. 确认和重传:接收方对成功接收的数据包发送确认(ACK)信号。如果发送方没有收到确认,它会
重传丢失的数据包。
8. 终止连接:数据传输完成后,TCP 连接需要被正常关闭,这通常涉及到四次挥手过程。
class MyClient : public QObject {
Q_OBJECT
public:
MyClient() {
QTcpSocket *socket = new QTcpSocket(this);
connect(socket, &QTcpSocket::readyRead, this, &MyClient::onReadyRead);
socket->connectToHost("server_address", 1234);
}
private slots:
void onReadyRead() {
QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
QByteArray data = socket->readAll();
// 处理接收到的数据
// ...
}
};TCP 适用于需要高可靠性的应用,如网页浏览、文件传输、电子邮件等。然而,由于它的这些特性,TCP
在处理速度上可能不如其他协议(如UDP)那么快速。
TCP协议中的三次握手和四次挥手是建立和终止连接的重要过程。下面是它们的简要描述:
三次握手(建立连接)
三次握手的主要目的是在两台设备之间建立一个可靠的连接。它包括以下步骤:
1. SYN:客户端向服务器发送一个SYN(同步序列编号)报文来开始一个新的连接。此时,客户端进
入SYN-SENT状态。
2. SYN-ACK:服务器接收到SYN报文后,回复一个SYN-ACK(同步和确认)报文。此时服务器进入
SYN-RECEIVED状态。
3. ACK:客户端接收到SYN-ACK后,发送一个ACK(确认)报文作为回应,并进入ESTABLISHED(已
建立)状态。服务器在收到这个ACK报文后,也进入ESTABLISHED状态。这标志着连接已经建立。
四次挥手(断开连接)
四次挥手的目的是终止已经建立的连接。这个过程包括以下步骤:
1. FIN:当通信的一方完成数据发送任务后,它会发送一个FIN(结束)报文来关闭连接。发送完FIN
报文后,该方进入FIN-WAIT-1状态。
2. ACK:另一方接收到FIN报文后,发送一个ACK报文作为回应,并进入CLOSE-WAIT状态。发送FIN
报文的一方在收到ACK后,进入FIN-WAIT-2状态。
3. FIN:在等待一段时间并完成所有数据的发送后,CLOSE-WAIT状态的一方也发送一个FIN报文来请
求关闭连接。
4. ACK:最初发送FIN报文的一方在收到这个FIN报文后,发送一个ACK报文作为最后的确认,并进入
TIME-WAIT状态。经过一段时间后,确保对方接收到了最后的ACK报文,该方最终关闭连接。在这两个过程中,三次握手主要确保双方都准备好进行通信,而四次挥手则确保双方都已经完成通信并
同意关闭连接。
5.1.4 Socket
Socket 不是一个协议,而是一种编程接口(API)或机制,用于在网络中实现通信。Socket 通常在应用
层和传输层之间提供一个端点,使得应用程序可以通过网络发送和接收数据。它支持多种协议,主要是
TCP 和 UDP。
以下是 Socket 的一些基本特点:
类型:有两种主要类型的 Sockets —— TCP Socket(面向连接,可靠)和 UDP Socket(无连接,
不可靠)。
应用:在各种网络应用中广泛使用,如网页服务器、聊天应用、在线游戏等。
编程语言支持:大多数现代编程语言如 Python, Java, C++, 等都提供 Socket 编程的支持。
功能:提供了创建网络连接、监听传入的连接、发送和接收数据等功能。
QT: 在QT组件中,QTcpSocket用来管理和实现TCP Socket通信,QUdpSocket用来管理和实现
UDP Socket通信
总之,Socket 是实现网络通信的基础工具之一,它抽象化了网络层的复杂性,为开发者提供了一种相对
简单的方式来建立和管理网络连接。
5.2 UI设计
UI设计过程,教学视频展示,都是大家熟悉的内容了5.3 网络通信核心代码
QTcpServer 是 Qt 网络模块的一部分,用于构建 TCP 服务器。它提供了一种机制来异步监听来自客户
端的连接。一旦接受了一个连接,服务器就可以与客户端进行数据交换。
5.3.1 创建TCP服务端的核心代码
主要步骤如下:
1. 创建 QTcpServer 实例:启动服务器并开始监听指定端口。
2. 监听连接请求:调用 listen() 方法使服务器监听特定的 IP 地址和端口。
3. 接受连接:当客户端尝试连接时, QTcpServer 产生一个信号。你需要实现一个槽(slot)来响应
这个信号,并接受连接。
4. 处理客户端连接:每个连接的客户端都关联一个 QTcpSocket 对象,用于数据交换。
示例代码
代码解释
#include <QTcpServer>
#include <QTcpSocket>
#include <QCoreApplication>
#include <QDebug>
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
QTcpServer server;
// 监听端口
if (!server.listen(QHostAddress::Any, 12345)) {
qDebug() << "Server could not start";
return -1;
}
qDebug() << "Server started!";
// 当有新连接时,执行相应的操作
QObject::connect(&server, &QTcpServer::newConnection, [&]() {
QTcpSocket *client = server.nextPendingConnection();
QObject::connect(client, &QTcpSocket::readyRead, [client]() {
QByteArray data = client->readAll();
qDebug() << "Received data:" << data;
client->write("Hello, client!");
});
QObject::connect(client, &QTcpSocket::disconnected, client,
&QTcpSocket::deleteLater);
});
return a.exec();
}1. 创建 QTcpServer 对象:在主函数中,直接创建了一个 QTcpServer 对象。
2. 监听端口:使用 listen() 方法监听所有接口上的 12345 端口。
3. 处理新连接:通过连接 newConnection 信号,当有新客户端连接时,会调用相应的槽函数。
4. 读取数据:为每个连接的客户端创建 QTcpSocket 对象,并连接 readyRead 信号以接收数据。
5. 发送数据:向客户端发送响应消息。
6. 客户端断开连接时的处理:使用 disconnected 信号确保客户端在断开连接时被适当地清理。
这个代码示例展示了如何使用 QTcpServer 创建一个基本的 TCP 服务器,而无需通过继承来扩展类。这
种方式通常更简单,适用于不需要复杂处理的基本应用场景。
5.3.2 创建TCP客户端的核心代码
为了使客户端代码更具模块化和响应性,可以使用 Qt 的信号与槽机制。这种方法允许客户端以事件驱动
的方式响应网络事件,如连接建立、数据接收等。下面是一个使用信号与槽的 TCP 客户端示例。
示例代码
#include <QTcpSocket>
#include <QCoreApplication>
#include <QDebug>
class TcpClient : public QObject {
Q_OBJECT
public:
TcpClient(const QString &host, quint16 port) {
connect(&socket, &QTcpSocket::connected, this, &TcpClient::onConnected);
connect(&socket, &QTcpSocket::readyRead, this, &TcpClient::onReadyRead);
socket.connectToHost(host, port);
}
private slots:
void onConnected() {
qDebug() << "Connected to server!";
socket.write("Hello, server!");
}
void onReadyRead() {
QByteArray data = socket.readAll();
qDebug() << "Server said:" << data;
socket.disconnectFromHost();
}
private:
QTcpSocket socket;
};
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
TcpClient client("localhost", 12345);代码解释
1. 创建 TcpClient 类:这个类继承自 QObject ,允许使用信号与槽机制。
2. 连接信号和槽:在构造函数中,将 QTcpSocket 的 connected 和 readyRead 信号分别连接到
onConnected 和 onReadyRead 槽。
3. 连接到服务器:使用 connectToHost() 方法开始连接过程。
4. 处理连接建立:一旦连接建立, onConnected 槽被触发,客户端向服务器发送一条消息。
5. 接收数据:当数据可读时, onReadyRead 槽被触发,客户端读取并打印来自服务器的数据。
6. 断开连接:在接收数据后,客户端断开与服务器的连接。
这个客户端示例展示了如何使用 Qt 的信号与槽机制来处理 TCP 连接。这种方式使得代码更加清晰,易
于维护,并且能更好地处理异步事件。
5.4 TCP服务端项目开发
核心代码
return a.exec();
}
#include "main.moc"
// 包含主窗口和用户界面定义
#include "mainwindow.h"
#include "ui_mainwindow.h"
// 主窗口构造函数
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
// 初始化用户界面
ui->setupUi(this);
// 设置主窗口中心部件的布局
ui->centralwidget->setLayout(ui->verticalLayout_2);
// 设置主窗口标题
this->setWindowTitle("网络调试助手服务端-上官QT案例");
cursor = ui->textBrowserRev->textCursor(); // 获取文本浏览器的文本光标
// 初始时禁用“停止监听”按钮
ui->pushButtonListenStop->setEnabled(false);
// 创建新的 TCP 服务器实例
tcpServer = new QTcpServer(this);
// 将新连接信号连接到处理新连接的槽函数
connect(tcpServer, SIGNAL(newConnection()), this,
SLOT(mnewConnectionHandler()));
// 获取系统上所有网络接口,并将 IPv4 地址添加到下拉列表中
QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();for (const QNetworkInterface &interface : interfaces) {
for (const QNetworkAddressEntry &entry : interface.addressEntries()) {
if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol) {
ui->comboBoxIpAddr->addItem(entry.ip().toString());
}
}
}
}
// 主窗口析构函数
MainWindow::~MainWindow()
{
// 释放用户界面资源
delete ui;
}
// “开始监听”按钮的点击事件处理函数
void MainWindow::on_pushButtonListen_clicked()
{
// 侦听指定 IP 地址和端口
tcpServer->listen(QHostAddress(ui->comboBoxIpAddr->currentText()),
ui->lineEditPort->text().toInt());
// 更新按钮状态
ui->pushButtonListen->setEnabled(false);
ui->pushButtonListenStop->setEnabled(true);
}
// 新 TCP 连接的处理函数
void MainWindow::mnewConnectionHandler()
{
// 获取下一个待处理的连接
QTcpSocket *tmpSocket = tcpServer->nextPendingConnection();
// 向文本浏览器中添加客户端信息
ui->textBrowserRev->append("服务器: 客户端IP地址是:"+ tmpSocket-
>peerAddress().toString()
+" 客户端端口号是: "+QString::number(tmpSocket-
>peerPort())+"\n");
// 连接套接字的状态变化和数据接收信号到相应槽函数
connect(tmpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
this, SLOT(mstateChanged(QAbstractSocket::SocketState)));
connect(tmpSocket, SIGNAL(readyRead()), this, SLOT(mreadData()));
}
// 套接字状态改变时的槽函数
void MainWindow::mstateChanged(QAbstractSocket::SocketState state)
{
// 获取发送信号的套接字对象
QTcpSocket *tmp = (QTcpSocket *)sender();
// 根据套接字的不同状态进行不同处理
switch(state){
case QAbstractSocket::UnconnectedState:
// 客户端断开连接ui->textBrowserRev->append("服务器:有客户端断开连接!");
tmp->deleteLater();
break;
case QAbstractSocket::ConnectedState:
// 客户端连接
ui->textBrowserRev->append("服务器:有新客户端接入!");
break;
default:
break;
}
}
// “停止监听”按钮的点击事件处理函数
void MainWindow::on_pushButtonListenStop_clicked()
{
// 更新按钮状态
ui->pushButtonListen->setEnabled(true);
ui->pushButtonListenStop->setEnabled(true);
// 停止监听端口
tcpServer->close();
}
// 接收到数据时的槽函数
void MainWindow::mreadData()
{
// 获取发送信号的套接字对象
QTcpSocket *tmp = (QTcpSocket *)sender();
setTextColor(0,0,0); // 设置文本颜色为红色
cursor.insertText("客户端:"+ tmp->readAll()+"\n");
}
// “发送”按钮的点击事件处理函数
void MainWindow::on_pushButtonSend_clicked()
{
// 查找所有的子 QTcpSocket 对象
QList<QTcpSocket*> socketList = tcpServer->findChildren<QTcpSocket*>();
// 向每个连接的客户端发送数据
foreach(QTcpSocket *tmp, socketList){
tmp->write(ui->textEditSnd->toPlainText().toUtf8());
setTextColor(255,0,0); // 设置文本颜色为红色
cursor.insertText("服务端:"+ui->textEditSnd-
>toPlainText().toUtf8()+"\n");
};
}
// 设置文本颜色的函数
void MainWindow::setTextColor(int r, int g, int b)
{
QTextCharFormat textFormat;
textFormat.setForeground(QBrush(QColor(r, g, b))); // 根据提供的 RGB 值设置颜色
// 应用格式到光标
cursor.setCharFormat(textFormat); }