活动公告

系统通知
05-18 21:22
系统通知
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

深入浅出Linux平台下Qt图形界面编程从入门到精通的完整学习路径掌握核心技能打造专业级桌面应用实战案例分析解决常见开发难题

SunJu_FaceMall

3万

主题

2860

科技点

3万

积分

白金月票

碾压王

积分
32872

塔罗立华奏

<font color=白金月票" /> 发表于 2025-9-24 11:10:00 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
引言

Qt是一个跨平台的C++图形用户界面应用程序开发框架,它不仅提供了创建图形界面所需的各种组件,还包括了数据库、网络、多线程、XML、OpenGL等众多功能模块。在Linux平台上,Qt是开发桌面应用程序的首选框架之一,KDE桌面环境就是基于Qt构建的。本文将带你从零开始,系统学习Linux平台下的Qt图形界面编程,逐步掌握核心技能,最终能够开发出专业级的桌面应用程序。

一、Qt入门基础

1.1 环境搭建

在Linux系统中安装Qt开发环境非常简单。以Ubuntu为例,你可以通过以下命令安装Qt5开发工具:
  1. sudo apt update
  2. sudo apt install qt5-default qtcreator qtdeclarative5-dev qtbase5-dev-tools
复制代码

安装完成后,你可以从应用程序菜单中启动Qt Creator,这是Qt官方提供的集成开发环境(IDE),集成了代码编辑器、可视化界面设计器、调试器和构建工具。

1.2 Qt基本概念

在开始编写Qt程序之前,需要了解几个基本概念:

• QObject:所有Qt对象的基类,提供了对象树、信号槽等核心功能。
• QWidget:所有用户界面对象的基类,是Qt窗口系统的基础。
• QApplication:管理GUI应用程序的控制流和主要设置。
• 信号与槽(Signals & Slots):Qt的核心机制,用于对象之间的通信。
• 布局管理(Layout Management):自动排列和管理界面上的控件。

1.3 第一个Qt程序

让我们创建一个简单的”Hello World”程序,体验Qt的基本开发流程。

在Qt Creator中创建一个新的Qt Widgets Application项目,然后修改main.cpp文件:
  1. #include <QApplication>
  2. #include <QLabel>
  3. #include <QWidget>
  4. #include <QVBoxLayout>
  5. int main(int argc, char *argv[])
  6. {
  7.     QApplication app(argc, argv);
  8.    
  9.     // 创建主窗口
  10.     QWidget window;
  11.     window.setWindowTitle("Hello Qt");
  12.     window.resize(300, 200);
  13.    
  14.     // 创建标签控件
  15.     QLabel *label = new QLabel("Hello, World!");
  16.     label->setAlignment(Qt::AlignCenter);
  17.    
  18.     // 创建布局并添加标签
  19.     QVBoxLayout *layout = new QVBoxLayout;
  20.     layout->addWidget(label);
  21.    
  22.     // 设置窗口的布局
  23.     window.setLayout(layout);
  24.    
  25.     // 显示窗口
  26.     window.show();
  27.    
  28.     return app.exec();
  29. }
复制代码

这个程序创建了一个简单的窗口,其中包含一个居中显示的”Hello, World!“标签。编译并运行这个程序,你将看到一个基本的GUI窗口。

二、Qt核心技能

2.1 信号与槽机制

信号与槽是Qt的核心特性,它允许对象之间的通信,是实现事件驱动编程的基础。当一个对象的状态发生变化时,它会发出一个信号;其他对象可以连接到这个信号,并通过槽函数来响应这个变化。

让我们通过一个简单的例子来理解信号与槽:
  1. #include <QApplication>
  2. #include <QPushButton>
  3. #include <QMessageBox>
  4. #include <QVBoxLayout>
  5. #include <QWidget>
  6. int main(int argc, char *argv[])
  7. {
  8.     QApplication app(argc, argv);
  9.    
  10.     QWidget window;
  11.     window.setWindowTitle("信号与槽示例");
  12.     window.resize(300, 200);
  13.    
  14.     QPushButton *button = new QPushButton("点击我");
  15.    
  16.     // 连接按钮的clicked信号到QMessageBox的静态槽函数
  17.     QObject::connect(button, &QPushButton::clicked, []() {
  18.         QMessageBox::information(nullptr, "消息", "按钮被点击了!");
  19.     });
  20.    
  21.     QVBoxLayout *layout = new QVBoxLayout;
  22.     layout->addWidget(button);
  23.    
  24.     window.setLayout(layout);
  25.     window.show();
  26.    
  27.     return app.exec();
  28. }
复制代码

在这个例子中,当用户点击按钮时,按钮会发出clicked信号,我们连接到一个lambda表达式(作为槽函数),该函数会显示一个消息框。

2.2 布局管理

Qt提供了多种布局管理器,帮助开发者自动排列和管理界面上的控件。常用的布局管理器包括:

• QHBoxLayout:水平布局,将控件从左到右排列。
• QVBoxLayout:垂直布局,将控件从上到下排列。
• QGridLayout:网格布局,将控件排列在网格中。
• QFormLayout:表单布局,适用于标签和字段的排列。

下面是一个使用多种布局的示例:
  1. #include <QApplication>
  2. #include <QWidget>
  3. #include <QPushButton>
  4. #include <QLineEdit>
  5. #include <QLabel>
  6. #include <QVBoxLayout>
  7. #include <QHBoxLayout>
  8. #include <QGridLayout>
  9. #include <QGroupBox>
  10. int main(int argc, char *argv[])
  11. {
  12.     QApplication app(argc, argv);
  13.    
  14.     QWidget window;
  15.     window.setWindowTitle("布局管理示例");
  16.     window.resize(400, 300);
  17.    
  18.     // 主垂直布局
  19.     QVBoxLayout *mainLayout = new QVBoxLayout;
  20.    
  21.     // 第一组:水平布局
  22.     QGroupBox *horizontalGroup = new QGroupBox("水平布局");
  23.     QHBoxLayout *hLayout = new QHBoxLayout;
  24.    
  25.     for (int i = 1; i <= 3; ++i) {
  26.         QPushButton *button = new QPushButton(QString("按钮 %1").arg(i));
  27.         hLayout->addWidget(button);
  28.     }
  29.    
  30.     horizontalGroup->setLayout(hLayout);
  31.     mainLayout->addWidget(horizontalGroup);
  32.    
  33.     // 第二组:网格布局
  34.     QGroupBox *gridGroup = new QGroupBox("网格布局");
  35.     QGridLayout *gLayout = new QGridLayout;
  36.    
  37.     gLayout->addWidget(new QLabel("用户名:"), 0, 0);
  38.     gLayout->addWidget(new QLineEdit(), 0, 1);
  39.     gLayout->addWidget(new QLabel("密码:"), 1, 0);
  40.     gLayout->addWidget(new QLineEdit(), 1, 1);
  41.    
  42.     QPushButton *loginButton = new QPushButton("登录");
  43.     gLayout->addWidget(loginButton, 2, 0, 1, 2);
  44.    
  45.     gridGroup->setLayout(gLayout);
  46.     mainLayout->addWidget(gridGroup);
  47.    
  48.     window.setLayout(mainLayout);
  49.     window.show();
  50.    
  51.     return app.exec();
  52. }
复制代码

这个示例展示了如何在窗口中使用水平布局和网格布局来组织控件。

2.3 常用控件

Qt提供了丰富的标准控件,下面介绍一些常用的控件及其基本用法:
  1. #include <QApplication>
  2. #include <QWidget>
  3. #include <QPushButton>
  4. #include <QCheckBox>
  5. #include <QRadioButton>
  6. #include <QButtonGroup>
  7. #include <QVBoxLayout>
  8. #include <QDebug>
  9. int main(int argc, char *argv[])
  10. {
  11.     QApplication app(argc, argv);
  12.    
  13.     QWidget window;
  14.     window.setWindowTitle("按钮控件示例");
  15.    
  16.     QVBoxLayout *layout = new QVBoxLayout;
  17.    
  18.     // 普通按钮
  19.     QPushButton *pushButton = new QPushButton("普通按钮");
  20.     QObject::connect(pushButton, &QPushButton::clicked, []() {
  21.         qDebug() << "普通按钮被点击";
  22.     });
  23.     layout->addWidget(pushButton);
  24.    
  25.     // 复选框
  26.     QCheckBox *checkBox = new QCheckBox("复选框");
  27.     QObject::connect(checkBox, &QCheckBox::stateChanged, [](int state) {
  28.         qDebug() << "复选框状态改变:" << (state == Qt::Checked ? "选中" : "未选中");
  29.     });
  30.     layout->addWidget(checkBox);
  31.    
  32.     // 单选按钮
  33.     QButtonGroup *radioGroup = new QButtonGroup;
  34.     QRadioButton *radio1 = new QRadioButton("选项1");
  35.     QRadioButton *radio2 = new QRadioButton("选项2");
  36.     radioGroup->addButton(radio1);
  37.     radioGroup->addButton(radio2);
  38.    
  39.     QObject::connect(radioGroup, QOverload<QAbstractButton*>::of(&QButtonGroup::buttonClicked), [](QAbstractButton *button) {
  40.         qDebug() << "单选按钮被点击:" << button->text();
  41.     });
  42.    
  43.     layout->addWidget(radio1);
  44.     layout->addWidget(radio2);
  45.    
  46.     window.setLayout(layout);
  47.     window.show();
  48.    
  49.     return app.exec();
  50. }
复制代码
  1. #include <QApplication>
  2. #include <QWidget>
  3. #include <QLineEdit>
  4. #include <QTextEdit>
  5. #include <QSpinBox>
  6. #include <QComboBox>
  7. #include <QSlider>
  8. #include <QLabel>
  9. #include <QVBoxLayout>
  10. #include <QDebug>
  11. int main(int argc, char *argv[])
  12. {
  13.     QApplication app(argc, argv);
  14.    
  15.     QWidget window;
  16.     window.setWindowTitle("输入控件示例");
  17.    
  18.     QVBoxLayout *layout = new QVBoxLayout;
  19.    
  20.     // 单行文本输入
  21.     QLineEdit *lineEdit = new QLineEdit;
  22.     lineEdit->setPlaceholderText("请输入文本");
  23.     QObject::connect(lineEdit, &QLineEdit::textChanged, [](const QString &text) {
  24.         qDebug() << "文本改变:" << text;
  25.     });
  26.     layout->addWidget(new QLabel("单行文本输入:"));
  27.     layout->addWidget(lineEdit);
  28.    
  29.     // 多行文本输入
  30.     QTextEdit *textEdit = new QTextEdit;
  31.     textEdit->setPlaceholderText("请输入多行文本");
  32.     layout->addWidget(new QLabel("多行文本输入:"));
  33.     layout->addWidget(textEdit);
  34.    
  35.     // 数字输入
  36.     QSpinBox *spinBox = new QSpinBox;
  37.     spinBox->setRange(0, 100);
  38.     spinBox->setValue(50);
  39.     layout->addWidget(new QLabel("数字输入:"));
  40.     layout->addWidget(spinBox);
  41.    
  42.     // 下拉框
  43.     QComboBox *comboBox = new QComboBox;
  44.     comboBox->addItem("选项1");
  45.     comboBox->addItem("选项2");
  46.     comboBox->addItem("选项3");
  47.     layout->addWidget(new QLabel("下拉框:"));
  48.     layout->addWidget(comboBox);
  49.    
  50.     // 滑块
  51.     QSlider *slider = new QSlider(Qt::Horizontal);
  52.     slider->setRange(0, 100);
  53.     slider->setValue(50);
  54.     layout->addWidget(new QLabel("滑块:"));
  55.     layout->addWidget(slider);
  56.    
  57.     window.setLayout(layout);
  58.     window.show();
  59.    
  60.     return app.exec();
  61. }
复制代码
  1. #include <QApplication>
  2. #include <QWidget>
  3. #include <QLabel>
  4. #include <QProgressBar>
  5. #include <QLCDNumber>
  6. #include <QPixmap>
  7. #include <QVBoxLayout>
  8. #include <QTimer>
  9. int main(int argc, char *argv[])
  10. {
  11.     QApplication app(argc, argv);
  12.    
  13.     QWidget window;
  14.     window.setWindowTitle("显示控件示例");
  15.    
  16.     QVBoxLayout *layout = new QVBoxLayout;
  17.    
  18.     // 标签
  19.     QLabel *label = new QLabel("这是一个标签");
  20.     label->setAlignment(Qt::AlignCenter);
  21.     layout->addWidget(label);
  22.    
  23.     // 进度条
  24.     QProgressBar *progressBar = new QProgressBar;
  25.     progressBar->setRange(0, 100);
  26.     progressBar->setValue(0);
  27.     layout->addWidget(new QLabel("进度条:"));
  28.     layout->addWidget(progressBar);
  29.    
  30.     // LCD数字显示
  31.     QLCDNumber *lcdNumber = new QLCDNumber;
  32.     lcdNumber->setSegmentStyle(QLCDNumber::Flat);
  33.     lcdNumber->display(0);
  34.     layout->addWidget(new QLabel("LCD数字:"));
  35.     layout->addWidget(lcdNumber);
  36.    
  37.     // 图片显示
  38.     QLabel *imageLabel = new QLabel;
  39.     QPixmap pixmap(":/images/sample.png"); // 假设有一个资源文件
  40.     if (!pixmap.isNull()) {
  41.         imageLabel->setPixmap(pixmap.scaled(200, 200, Qt::KeepAspectRatio));
  42.         imageLabel->setAlignment(Qt::AlignCenter);
  43.     } else {
  44.         imageLabel->setText("无法加载图片");
  45.         imageLabel->setAlignment(Qt::AlignCenter);
  46.     }
  47.     layout->addWidget(new QLabel("图片显示:"));
  48.     layout->addWidget(imageLabel);
  49.    
  50.     // 设置定时器更新进度条和LCD数字
  51.     QTimer timer;
  52.     int value = 0;
  53.     QObject::connect(&timer, &QTimer::timeout, [&]() {
  54.         value = (value + 1) % 101;
  55.         progressBar->setValue(value);
  56.         lcdNumber->display(value);
  57.     });
  58.     timer.start(100);
  59.    
  60.     window.setLayout(layout);
  61.     window.show();
  62.    
  63.     return app.exec();
  64. }
复制代码

三、Qt进阶主题

3.1 自定义控件

虽然Qt提供了丰富的标准控件,但有时我们需要创建自定义控件来满足特定需求。自定义控件通常通过继承现有的Qt控件或直接继承QWidget来实现。

下面是一个自定义圆形进度条的例子:
  1. // circularprogressbar.h
  2. #ifndef CIRCULARPROGRESSBAR_H
  3. #define CIRCULARPROGRESSBAR_H
  4. #include <QWidget>
  5. #include <QPainter>
  6. class CircularProgressBar : public QWidget
  7. {
  8.     Q_OBJECT
  9.     Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)
  10.     Q_PROPERTY(int minimum READ minimum WRITE setMinimum)
  11.     Q_PROPERTY(int maximum READ maximum WRITE setMaximum)
  12. public:
  13.     explicit CircularProgressBar(QWidget *parent = nullptr);
  14.    
  15.     int value() const;
  16.     int minimum() const;
  17.     int maximum() const;
  18.    
  19. public slots:
  20.     void setValue(int value);
  21.     void setMinimum(int min);
  22.     void setMaximum(int max);
  23.    
  24. signals:
  25.     void valueChanged(int value);
  26.    
  27. protected:
  28.     void paintEvent(QPaintEvent *event) override;
  29.    
  30. private:
  31.     int m_value;
  32.     int m_minimum;
  33.     int m_maximum;
  34. };
  35. #endif // CIRCULARPROGRESSBAR_H
复制代码
  1. // circularprogressbar.cpp
  2. #include "circularprogressbar.h"
  3. #include <QPainter>
  4. #include <QTimer>
  5. CircularProgressBar::CircularProgressBar(QWidget *parent) : QWidget(parent),
  6.     m_value(0),
  7.     m_minimum(0),
  8.     m_maximum(100)
  9. {
  10.     setMinimumSize(100, 100);
  11. }
  12. int CircularProgressBar::value() const
  13. {
  14.     return m_value;
  15. }
  16. int CircularProgressBar::minimum() const
  17. {
  18.     return m_minimum;
  19. }
  20. int CircularProgressBar::maximum() const
  21. {
  22.     return m_maximum;
  23. }
  24. void CircularProgressBar::setValue(int value)
  25. {
  26.     if (m_value != value) {
  27.         m_value = qBound(m_minimum, value, m_maximum);
  28.         update(); // 触发重绘
  29.         emit valueChanged(m_value);
  30.     }
  31. }
  32. void CircularProgressBar::setMinimum(int min)
  33. {
  34.     if (m_minimum != min) {
  35.         m_minimum = min;
  36.         if (m_value < m_minimum) {
  37.             setValue(m_minimum);
  38.         }
  39.         update();
  40.     }
  41. }
  42. void CircularProgressBar::setMaximum(int max)
  43. {
  44.     if (m_maximum != max) {
  45.         m_maximum = max;
  46.         if (m_value > m_maximum) {
  47.             setValue(m_maximum);
  48.         }
  49.         update();
  50.     }
  51. }
  52. void CircularProgressBar::paintEvent(QPaintEvent *event)
  53. {
  54.     Q_UNUSED(event);
  55.    
  56.     QPainter painter(this);
  57.     painter.setRenderHint(QPainter::Antialiasing);
  58.    
  59.     // 计算绘制区域
  60.     int side = qMin(width(), height());
  61.     painter.setViewport((width() - side) / 2, (height() - side) / 2, side, side);
  62.     painter.setWindow(0, 0, 100, 100);
  63.    
  64.     // 绘制背景圆环
  65.     painter.setPen(QPen(Qt::lightGray, 5, Qt::SolidLine, Qt::RoundCap));
  66.     painter.drawArc(10, 10, 80, 80, 30 * 16, 390 * 16);
  67.    
  68.     // 计算进度角度
  69.     double percentage = static_cast<double>(m_value - m_minimum) / (m_maximum - m_minimum);
  70.     int progressAngle = static_cast<int>(percentage * 390);
  71.    
  72.     // 绘制进度圆环
  73.     painter.setPen(QPen(Qt::blue, 5, Qt::SolidLine, Qt::RoundCap));
  74.     painter.drawArc(10, 10, 80, 80, 30 * 16, progressAngle * 16);
  75.    
  76.     // 绘制进度文本
  77.     painter.setPen(Qt::black);
  78.     QFont font = painter.font();
  79.     font.setBold(true);
  80.     font.setPointSize(10);
  81.     painter.setFont(font);
  82.     painter.drawText(0, 0, 100, 100, Qt::AlignCenter,
  83.                    QString("%1%").arg(static_cast<int>(percentage * 100)));
  84. }
复制代码

使用自定义控件的示例:
  1. #include <QApplication>
  2. #include <QWidget>
  3. #include <QVBoxLayout>
  4. #include <QPushButton>
  5. #include "circularprogressbar.h"
  6. int main(int argc, char *argv[])
  7. {
  8.     QApplication app(argc, argv);
  9.    
  10.     QWidget window;
  11.     window.setWindowTitle("自定义控件示例");
  12.     window.resize(300, 400);
  13.    
  14.     QVBoxLayout *layout = new QVBoxLayout;
  15.    
  16.     // 创建自定义圆形进度条
  17.     CircularProgressBar *progressBar = new CircularProgressBar;
  18.     progressBar->setMinimum(0);
  19.     progressBar->setMaximum(100);
  20.     progressBar->setValue(30);
  21.     layout->addWidget(progressBar);
  22.    
  23.     // 添加按钮来改变进度
  24.     QPushButton *button = new QPushButton("增加进度");
  25.     layout->addWidget(button);
  26.    
  27.     // 连接按钮点击信号到进度条
  28.     QObject::connect(button, &QPushButton::clicked, [progressBar]() {
  29.         int newValue = progressBar->value() + 10;
  30.         if (newValue > progressBar->maximum()) {
  31.             newValue = progressBar->minimum();
  32.         }
  33.         progressBar->setValue(newValue);
  34.     });
  35.    
  36.     window.setLayout(layout);
  37.     window.show();
  38.    
  39.     return app.exec();
  40. }
复制代码

3.2 模型视图编程

Qt的模型/视图架构是一种用于显示和编辑数据的方式,它将数据存储(模型)和数据表示(视图)分离。这种架构使得同一份数据可以用不同的方式显示,并且不需要修改数据源代码。

下面是一个使用模型/视图架构显示表格数据的例子:
  1. #include <QApplication>
  2. #include <QTableView>
  3. #include <QStandardItemModel>
  4. #include <QHeaderView>
  5. #include <QVBoxLayout>
  6. #include <QWidget>
  7. int main(int argc, char *argv[])
  8. {
  9.     QApplication app(argc, argv);
  10.    
  11.     QWidget window;
  12.     window.setWindowTitle("模型视图编程示例");
  13.     window.resize(600, 400);
  14.    
  15.     QVBoxLayout *layout = new QVBoxLayout;
  16.    
  17.     // 创建标准项模型
  18.     QStandardItemModel *model = new QStandardItemModel(&window);
  19.     model->setColumnCount(3);
  20.     model->setRowCount(5);
  21.    
  22.     // 设置表头
  23.     model->setHorizontalHeaderLabels({"姓名", "年龄", "职业"});
  24.    
  25.     // 填充数据
  26.     QList<QString> names = {"张三", "李四", "王五", "赵六", "钱七"};
  27.     QList<int> ages = {25, 30, 35, 28, 32};
  28.     QList<QString> jobs = {"工程师", "医生", "教师", "律师", "设计师"};
  29.    
  30.     for (int row = 0; row < 5; ++row) {
  31.         QStandardItem *nameItem = new QStandardItem(names[row]);
  32.         QStandardItem *ageItem = new QStandardItem(QString::number(ages[row]));
  33.         QStandardItem *jobItem = new QStandardItem(jobs[row]);
  34.         
  35.         model->setItem(row, 0, nameItem);
  36.         model->setItem(row, 1, ageItem);
  37.         model->setItem(row, 2, jobItem);
  38.     }
  39.    
  40.     // 创建表格视图并设置模型
  41.     QTableView *tableView = new QTableView;
  42.     tableView->setModel(model);
  43.    
  44.     // 设置表头属性
  45.     tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
  46.     tableView->verticalHeader()->setVisible(false);
  47.    
  48.     // 设置选择行为
  49.     tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
  50.     tableView->setSelectionMode(QAbstractItemView::SingleSelection);
  51.    
  52.     layout->addWidget(tableView);
  53.     window.setLayout(layout);
  54.     window.show();
  55.    
  56.     return app.exec();
  57. }
复制代码

3.3 多线程编程

在GUI应用程序中,耗时的操作(如文件读写、网络请求、复杂计算等)如果在主线程中执行,会导致界面冻结,影响用户体验。Qt提供了QThread类和相关的类来支持多线程编程。

下面是一个使用多线程执行耗时任务的例子:
  1. // worker.h
  2. #ifndef WORKER_H
  3. #define WORKER_H
  4. #include <QObject>
  5. #include <QRunnable>
  6. class Worker : public QObject, public QRunnable
  7. {
  8.     Q_OBJECT
  9. public:
  10.     explicit Worker(QObject *parent = nullptr);
  11.    
  12.     void run() override;
  13.    
  14. signals:
  15.     void progressChanged(int value);
  16.     void finished(const QString &result);
  17.    
  18. public slots:
  19.     void cancel();
  20.    
  21. private:
  22.     bool m_cancelled;
  23. };
  24. #endif // WORKER_H
复制代码
  1. // worker.cpp
  2. #include "worker.h"
  3. #include <QThread>
  4. #include <QDebug>
  5. Worker::Worker(QObject *parent) : QObject(parent), m_cancelled(false)
  6. {
  7.     setAutoDelete(true); // 任务完成后自动删除
  8. }
  9. void Worker::run()
  10. {
  11.     QString result;
  12.    
  13.     for (int i = 0; i <= 100; ++i) {
  14.         if (m_cancelled) {
  15.             result = "任务被取消";
  16.             break;
  17.         }
  18.         
  19.         // 模拟耗时操作
  20.         QThread::msleep(50);
  21.         
  22.         // 发送进度信号
  23.         emit progressChanged(i);
  24.         
  25.         // 构建结果字符串
  26.         if (i == 100) {
  27.             result = "任务完成";
  28.         }
  29.     }
  30.    
  31.     // 发送完成信号
  32.     emit finished(result);
  33. }
  34. void Worker::cancel()
  35. {
  36.     m_cancelled = true;
  37. }
复制代码
  1. // main.cpp
  2. #include <QApplication>
  3. #include <QWidget>
  4. #include <QVBoxLayout>
  5. #include <QPushButton>
  6. #include <QProgressBar>
  7. #include <QLabel>
  8. #include <QThreadPool>
  9. #include "worker.h"
  10. int main(int argc, char *argv[])
  11. {
  12.     QApplication app(argc, argv);
  13.    
  14.     QWidget window;
  15.     window.setWindowTitle("多线程编程示例");
  16.     window.resize(400, 200);
  17.    
  18.     QVBoxLayout *layout = new QVBoxLayout;
  19.    
  20.     QProgressBar *progressBar = new QProgressBar;
  21.     progressBar->setRange(0, 100);
  22.     progressBar->setValue(0);
  23.    
  24.     QLabel *statusLabel = new QLabel("准备就绪");
  25.    
  26.     QPushButton *startButton = new QPushButton("开始任务");
  27.     QPushButton *cancelButton = new QPushButton("取消任务");
  28.     cancelButton->setEnabled(false);
  29.    
  30.     Worker *worker = nullptr;
  31.    
  32.     QObject::connect(startButton, &QPushButton::clicked, [&]() {
  33.         // 创建工作线程
  34.         worker = new Worker;
  35.         
  36.         // 连接信号槽
  37.         QObject::connect(worker, &Worker::progressChanged, progressBar, &QProgressBar::setValue);
  38.         QObject::connect(worker, &Worker::finished, [=](const QString &result) {
  39.             statusLabel->setText(result);
  40.             startButton->setEnabled(true);
  41.             cancelButton->setEnabled(false);
  42.         });
  43.         
  44.         // 启动线程
  45.         QThreadPool::globalInstance()->start(worker);
  46.         
  47.         // 更新UI状态
  48.         startButton->setEnabled(false);
  49.         cancelButton->setEnabled(true);
  50.         statusLabel->setText("任务进行中...");
  51.     });
  52.    
  53.     QObject::connect(cancelButton, &QPushButton::clicked, [&]() {
  54.         if (worker) {
  55.             worker->cancel();
  56.         }
  57.     });
  58.    
  59.     layout->addWidget(progressBar);
  60.     layout->addWidget(statusLabel);
  61.     layout->addWidget(startButton);
  62.     layout->addWidget(cancelButton);
  63.    
  64.     window.setLayout(layout);
  65.     window.show();
  66.    
  67.     return app.exec();
  68. }
复制代码

这个例子中,我们在工作线程中执行一个模拟的耗时任务,并通过信号槽机制与主线程的UI进行通信,避免了界面冻结的问题。

四、实战案例:文本编辑器

现在,让我们综合运用前面所学的知识,开发一个简单的文本编辑器应用程序。这个文本编辑器将具有以下功能:

1. 新建、打开、保存文件
2. 基本的文本编辑功能
3. 查找和替换文本
4. 字体设置
5. 状态栏显示信息

4.1 项目结构

首先,创建一个Qt Widgets Application项目,然后添加以下文件:

• mainwindow.h:主窗口类的头文件
• mainwindow.cpp:主窗口类的实现文件
• textedit.h:自定义文本编辑类的头文件
• textedit.cpp:自定义文本编辑类的实现文件
• finddialog.h:查找对话框的头文件
• finddialog.cpp:查找对话框的实现文件
• replacedialog.h:替换对话框的头文件
• replacedialog.cpp:替换对话框的实现文件

4.2 自定义文本编辑类
  1. // textedit.h
  2. #ifndef TEXTEDIT_H
  3. #define TEXTEDIT_H
  4. #include <QTextEdit>
  5. class TextEdit : public QTextEdit
  6. {
  7.     Q_OBJECT
  8. public:
  9.     explicit TextEdit(QWidget *parent = nullptr);
  10.    
  11.     bool loadFile(const QString &fileName);
  12.     bool saveFile(const QString &fileName);
  13.     QString currentFile() const;
  14.    
  15. protected:
  16.     void closeEvent(QCloseEvent *event) override;
  17.    
  18. private slots:
  19.     void documentWasModified();
  20.    
  21. private:
  22.     void setCurrentFile(const QString &fileName);
  23.     bool maybeSave();
  24.    
  25.     QString curFile;
  26. };
  27. #endif // TEXTEDIT_H
复制代码
  1. // textedit.cpp
  2. #include "textedit.h"
  3. #include <QCloseEvent>
  4. #include <QFile>
  5. #include <QTextStream>
  6. #include <QMessageBox>
  7. #include <QApplication>
  8. TextEdit::TextEdit(QWidget *parent) : QTextEdit(parent)
  9. {
  10.     connect(document(), &QTextDocument::contentsChanged,
  11.             this, &TextEdit::documentWasModified);
  12.     setWindowModified(false);
  13. }
  14. bool TextEdit::loadFile(const QString &fileName)
  15. {
  16.     QFile file(fileName);
  17.     if (!file.open(QFile::ReadOnly | QFile::Text)) {
  18.         QMessageBox::warning(this, tr("文本编辑器"),
  19.                              tr("无法读取文件 %1:\n%2.")
  20.                              .arg(QDir::toNativeSeparators(fileName),
  21.                                   file.errorString()));
  22.         return false;
  23.     }
  24.     QTextStream in(&file);
  25.     QApplication::setOverrideCursor(Qt::WaitCursor);
  26.     setPlainText(in.readAll());
  27.     QApplication::restoreOverrideCursor();
  28.     setCurrentFile(fileName);
  29.     return true;
  30. }
  31. bool TextEdit::saveFile(const QString &fileName)
  32. {
  33.     QFile file(fileName);
  34.     if (!file.open(QFile::WriteOnly | QFile::Text)) {
  35.         QMessageBox::warning(this, tr("文本编辑器"),
  36.                              tr("无法写入文件 %1:\n%2.")
  37.                              .arg(QDir::toNativeSeparators(fileName),
  38.                                   file.errorString()));
  39.         return false;
  40.     }
  41.     QTextStream out(&file);
  42.     QApplication::setOverrideCursor(Qt::WaitCursor);
  43.     out << toPlainText();
  44.     QApplication::restoreOverrideCursor();
  45.     setCurrentFile(fileName);
  46.     return true;
  47. }
  48. QString TextEdit::currentFile() const
  49. {
  50.     return curFile;
  51. }
  52. void TextEdit::closeEvent(QCloseEvent *event)
  53. {
  54.     if (maybeSave()) {
  55.         event->accept();
  56.     } else {
  57.         event->ignore();
  58.     }
  59. }
  60. void TextEdit::documentWasModified()
  61. {
  62.     setWindowModified(document()->isModified());
  63. }
  64. void TextEdit::setCurrentFile(const QString &fileName)
  65. {
  66.     curFile = fileName;
  67.     document()->setModified(false);
  68.     setWindowModified(false);
  69.     QString shownName;
  70.     if (curFile.isEmpty())
  71.         shownName = "untitled.txt";
  72.     else
  73.         shownName = QFileInfo(curFile).fileName();
  74.     setWindowTitle(tr("%1[*] - %2").arg(shownName, QCoreApplication::applicationName()));
  75. }
  76. bool TextEdit::maybeSave()
  77. {
  78.     if (!document()->isModified())
  79.         return true;
  80.     const QMessageBox::StandardButton ret
  81.         = QMessageBox::warning(this, tr("文本编辑器"),
  82.                              tr("文档已被修改.\n"
  83.                                 "是否保存修改?"),
  84.                              QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
  85.     switch (ret) {
  86.     case QMessageBox::Save:
  87.         return true;
  88.     case QMessageBox::Cancel:
  89.         return false;
  90.     default:
  91.         break;
  92.     }
  93.     return true;
  94. }
复制代码

4.3 查找对话框
  1. // finddialog.h
  2. #ifndef FINDDIALOG_H
  3. #define FINDDIALOG_H
  4. #include <QDialog>
  5. class QCheckBox;
  6. class QLabel;
  7. class QLineEdit;
  8. class QPushButton;
  9. class FindDialog : public QDialog
  10. {
  11.     Q_OBJECT
  12. public:
  13.     explicit FindDialog(QWidget *parent = nullptr);
  14.    
  15.     QString getFindText() const;
  16.     bool caseSensitive() const;
  17.     bool wholeWords() const;
  18.     bool backward() const;
  19.    
  20. signals:
  21.     void findNext(const QString &str, Qt::CaseSensitivity cs);
  22.     void findPrevious(const QString &str, Qt::CaseSensitivity cs);
  23. private slots:
  24.     void findClicked();
  25.     void enableFindButton(const QString &text);
  26. private:
  27.     QLineEdit *lineEdit;
  28.     QCheckBox *caseCheckBox;
  29.     QCheckBox *wholeWordsCheckBox;
  30.     QCheckBox *backwardCheckBox;
  31.     QPushButton *findButton;
  32.     QPushButton *closeButton;
  33. };
  34. #endif // FINDDIALOG_H
复制代码
  1. // finddialog.cpp
  2. #include "finddialog.h"
  3. #include <QLabel>
  4. #include <QLineEdit>
  5. #include <QCheckBox>
  6. #include <QPushButton>
  7. #include <QHBoxLayout>
  8. #include <QVBoxLayout>
  9. FindDialog::FindDialog(QWidget *parent) : QDialog(parent)
  10. {
  11.     QLabel *label = new QLabel(tr("查找内容:"));
  12.     lineEdit = new QLineEdit;
  13.     label->setBuddy(lineEdit);
  14.    
  15.     caseCheckBox = new QCheckBox(tr("区分大小写"));
  16.     wholeWordsCheckBox = new QCheckBox(tr("全词匹配"));
  17.     backwardCheckBox = new QCheckBox(tr("向上查找"));
  18.    
  19.     findButton = new QPushButton(tr("查找"));
  20.     findButton->setDefault(true);
  21.     findButton->setEnabled(false);
  22.    
  23.     closeButton = new QPushButton(tr("关闭"));
  24.    
  25.     connect(lineEdit, &QLineEdit::textChanged, this, &FindDialog::enableFindButton);
  26.     connect(findButton, &QPushButton::clicked, this, &FindDialog::findClicked);
  27.     connect(closeButton, &QPushButton::clicked, this, &FindDialog::close);
  28.    
  29.     QHBoxLayout *topLeftLayout = new QHBoxLayout;
  30.     topLeftLayout->addWidget(label);
  31.     topLeftLayout->addWidget(lineEdit);
  32.    
  33.     QVBoxLayout *leftLayout = new QVBoxLayout;
  34.     leftLayout->addLayout(topLeftLayout);
  35.     leftLayout->addWidget(caseCheckBox);
  36.     leftLayout->addWidget(wholeWordsCheckBox);
  37.     leftLayout->addWidget(backwardCheckBox);
  38.    
  39.     QVBoxLayout *rightLayout = new QVBoxLayout;
  40.     rightLayout->addWidget(findButton);
  41.     rightLayout->addWidget(closeButton);
  42.     rightLayout->addStretch();
  43.    
  44.     QHBoxLayout *mainLayout = new QHBoxLayout;
  45.     mainLayout->addLayout(leftLayout);
  46.     mainLayout->addLayout(rightLayout);
  47.     setLayout(mainLayout);
  48.    
  49.     setWindowTitle(tr("查找"));
  50.     setFixedHeight(sizeHint().height());
  51. }
  52. QString FindDialog::getFindText() const
  53. {
  54.     return lineEdit->text();
  55. }
  56. bool FindDialog::caseSensitive() const
  57. {
  58.     return caseCheckBox->isChecked();
  59. }
  60. bool FindDialog::wholeWords() const
  61. {
  62.     return wholeWordsCheckBox->isChecked();
  63. }
  64. bool FindDialog::backward() const
  65. {
  66.     return backwardCheckBox->isChecked();
  67. }
  68. void FindDialog::findClicked()
  69. {
  70.     Qt::CaseSensitivity cs = caseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive;
  71.    
  72.     if (backward()) {
  73.         emit findPrevious(getFindText(), cs);
  74.     } else {
  75.         emit findNext(getFindText(), cs);
  76.     }
  77. }
  78. void FindDialog::enableFindButton(const QString &text)
  79. {
  80.     findButton->setEnabled(!text.isEmpty());
  81. }
复制代码

4.4 替换对话框
  1. // replacedialog.h
  2. #ifndef REPLACEDIALOG_H
  3. #define REPLACEDIALOG_H
  4. #include <QDialog>
  5. class QCheckBox;
  6. class QLabel;
  7. class QLineEdit;
  8. class QPushButton;
  9. class ReplaceDialog : public QDialog
  10. {
  11.     Q_OBJECT
  12. public:
  13.     explicit ReplaceDialog(QWidget *parent = nullptr);
  14.    
  15.     QString getFindText() const;
  16.     QString getReplaceText() const;
  17.     bool caseSensitive() const;
  18.     bool wholeWords() const;
  19.    
  20. signals:
  21.     void findNext(const QString &str, Qt::CaseSensitivity cs);
  22.     void replace(const QString &findText, const QString &replaceText, Qt::CaseSensitivity cs);
  23.     void replaceAll(const QString &findText, const QString &replaceText, Qt::CaseSensitivity cs);
  24. private slots:
  25.     void findClicked();
  26.     void replaceClicked();
  27.     void replaceAllClicked();
  28.     void enableFindButton(const QString &text);
  29. private:
  30.     QLineEdit *findLineEdit;
  31.     QLineEdit *replaceLineEdit;
  32.     QCheckBox *caseCheckBox;
  33.     QCheckBox *wholeWordsCheckBox;
  34.     QPushButton *findButton;
  35.     QPushButton *replaceButton;
  36.     QPushButton *replaceAllButton;
  37.     QPushButton *closeButton;
  38. };
  39. #endif // REPLACEDIALOG_H
复制代码
  1. // replacedialog.cpp
  2. #include "replacedialog.h"
  3. #include <QLabel>
  4. #include <QLineEdit>
  5. #include <QCheckBox>
  6. #include <QPushButton>
  7. #include <QHBoxLayout>
  8. #include <QVBoxLayout>
  9. ReplaceDialog::ReplaceDialog(QWidget *parent) : QDialog(parent)
  10. {
  11.     QLabel *findLabel = new QLabel(tr("查找内容:"));
  12.     findLineEdit = new QLineEdit;
  13.     findLabel->setBuddy(findLineEdit);
  14.    
  15.     QLabel *replaceLabel = new QLabel(tr("替换为:"));
  16.     replaceLineEdit = new QLineEdit;
  17.     replaceLabel->setBuddy(replaceLineEdit);
  18.    
  19.     caseCheckBox = new QCheckBox(tr("区分大小写"));
  20.     wholeWordsCheckBox = new QCheckBox(tr("全词匹配"));
  21.    
  22.     findButton = new QPushButton(tr("查找"));
  23.     findButton->setDefault(true);
  24.     findButton->setEnabled(false);
  25.    
  26.     replaceButton = new QPushButton(tr("替换"));
  27.     replaceButton->setEnabled(false);
  28.    
  29.     replaceAllButton = new QPushButton(tr("全部替换"));
  30.     replaceAllButton->setEnabled(false);
  31.    
  32.     closeButton = new QPushButton(tr("关闭"));
  33.    
  34.     connect(findLineEdit, &QLineEdit::textChanged, this, &ReplaceDialog::enableFindButton);
  35.     connect(findButton, &QPushButton::clicked, this, &ReplaceDialog::findClicked);
  36.     connect(replaceButton, &QPushButton::clicked, this, &ReplaceDialog::replaceClicked);
  37.     connect(replaceAllButton, &QPushButton::clicked, this, &ReplaceDialog::replaceAllClicked);
  38.     connect(closeButton, &QPushButton::clicked, this, &ReplaceDialog::close);
  39.    
  40.     QHBoxLayout *findLayout = new QHBoxLayout;
  41.     findLayout->addWidget(findLabel);
  42.     findLayout->addWidget(findLineEdit);
  43.    
  44.     QHBoxLayout *replaceLayout = new QHBoxLayout;
  45.     replaceLayout->addWidget(replaceLabel);
  46.     replaceLayout->addWidget(replaceLineEdit);
  47.    
  48.     QVBoxLayout *leftLayout = new QVBoxLayout;
  49.     leftLayout->addLayout(findLayout);
  50.     leftLayout->addLayout(replaceLayout);
  51.     leftLayout->addWidget(caseCheckBox);
  52.     leftLayout->addWidget(wholeWordsCheckBox);
  53.    
  54.     QVBoxLayout *rightLayout = new QVBoxLayout;
  55.     rightLayout->addWidget(findButton);
  56.     rightLayout->addWidget(replaceButton);
  57.     rightLayout->addWidget(replaceAllButton);
  58.     rightLayout->addWidget(closeButton);
  59.     rightLayout->addStretch();
  60.    
  61.     QHBoxLayout *mainLayout = new QHBoxLayout;
  62.     mainLayout->addLayout(leftLayout);
  63.     mainLayout->addLayout(rightLayout);
  64.     setLayout(mainLayout);
  65.    
  66.     setWindowTitle(tr("替换"));
  67.     setFixedHeight(sizeHint().height());
  68. }
  69. QString ReplaceDialog::getFindText() const
  70. {
  71.     return findLineEdit->text();
  72. }
  73. QString ReplaceDialog::getReplaceText() const
  74. {
  75.     return replaceLineEdit->text();
  76. }
  77. bool ReplaceDialog::caseSensitive() const
  78. {
  79.     return caseCheckBox->isChecked();
  80. }
  81. bool ReplaceDialog::wholeWords() const
  82. {
  83.     return wholeWordsCheckBox->isChecked();
  84. }
  85. void ReplaceDialog::findClicked()
  86. {
  87.     Qt::CaseSensitivity cs = caseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive;
  88.     emit findNext(getFindText(), cs);
  89. }
  90. void ReplaceDialog::replaceClicked()
  91. {
  92.     Qt::CaseSensitivity cs = caseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive;
  93.     emit replace(getFindText(), getReplaceText(), cs);
  94. }
  95. void ReplaceDialog::replaceAllClicked()
  96. {
  97.     Qt::CaseSensitivity cs = caseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive;
  98.     emit replaceAll(getFindText(), getReplaceText(), cs);
  99. }
  100. void ReplaceDialog::enableFindButton(const QString &text)
  101. {
  102.     findButton->setEnabled(!text.isEmpty());
  103.     replaceButton->setEnabled(!text.isEmpty());
  104.     replaceAllButton->setEnabled(!text.isEmpty());
  105. }
复制代码

4.5 主窗口
  1. // mainwindow.h
  2. #ifndef MAINWINDOW_H
  3. #define MAINWINDOW_H
  4. #include <QMainWindow>
  5. #include "textedit.h"
  6. class QAction;
  7. class QMenu;
  8. class QStatusBar;
  9. class FindDialog;
  10. class ReplaceDialog;
  11. class MainWindow : public QMainWindow
  12. {
  13.     Q_OBJECT
  14. public:
  15.     MainWindow();
  16. protected:
  17.     void closeEvent(QCloseEvent *event) override;
  18. private slots:
  19.     void newFile();
  20.     void open();
  21.     bool save();
  22.     bool saveAs();
  23.     void about();
  24.     void documentWasModified();
  25.     void find();
  26.     void replace();
  27.     void findNext(const QString &str, Qt::CaseSensitivity cs);
  28.     void findPrevious(const QString &str, Qt::CaseSensitivity cs);
  29.     void replaceText(const QString &findText, const QString &replaceText, Qt::CaseSensitivity cs);
  30.     void replaceAllText(const QString &findText, const QString &replaceText, Qt::CaseSensitivity cs);
  31.     void fontDialog();
  32. private:
  33.     void createActions();
  34.     void createMenus();
  35.     void createStatusBar();
  36.     void readSettings();
  37.     void writeSettings();
  38.     bool maybeSave();
  39.     void loadFile(const QString &fileName);
  40.     bool saveFile(const QString &fileName);
  41.     void setCurrentFile(const QString &fileName);
  42.     QString strippedName(const QString &fullFileName);
  43.     TextEdit *textEdit;
  44.     FindDialog *findDialog;
  45.     ReplaceDialog *replaceDialog;
  46.    
  47.     QMenu *fileMenu;
  48.     QMenu *editMenu;
  49.     QMenu *formatMenu;
  50.     QMenu *helpMenu;
  51.    
  52.     QAction *newAct;
  53.     QAction *openAct;
  54.     QAction *saveAct;
  55.     QAction *saveAsAct;
  56.     QAction *exitAct;
  57.     QAction *undoAct;
  58.     QAction *redoAct;
  59.     QAction *cutAct;
  60.     QAction *copyAct;
  61.     QAction *pasteAct;
  62.     QAction *findAct;
  63.     QAction *replaceAct;
  64.     QAction *fontAct;
  65.     QAction *aboutAct;
  66.     QAction *aboutQtAct;
  67.    
  68.     QString curFile;
  69. };
  70. #endif // MAINWINDOW_H
复制代码
  1. // mainwindow.cpp
  2. #include "mainwindow.h"
  3. #include "finddialog.h"
  4. #include "replacedialog.h"
  5. #include <QApplication>
  6. #include <QFileDialog>
  7. #include <QMessageBox>
  8. #include <QTextStream>
  9. #include <QFontDialog>
  10. #include <QStatusBar>
  11. #include <QCloseEvent>
  12. MainWindow::MainWindow()
  13. {
  14.     textEdit = new TextEdit;
  15.     setCentralWidget(textEdit);
  16.    
  17.     findDialog = nullptr;
  18.     replaceDialog = nullptr;
  19.    
  20.     createActions();
  21.     createMenus();
  22.     createStatusBar();
  23.    
  24.     readSettings();
  25.    
  26.     setUnifiedTitleAndToolBarOnMac(true);
  27. }
  28. void MainWindow::closeEvent(QCloseEvent *event)
  29. {
  30.     if (textEdit->maybeSave()) {
  31.         writeSettings();
  32.         event->accept();
  33.     } else {
  34.         event->ignore();
  35.     }
  36. }
  37. void MainWindow::newFile()
  38. {
  39.     if (textEdit->maybeSave()) {
  40.         textEdit->clear();
  41.         setCurrentFile(QString());
  42.     }
  43. }
  44. void MainWindow::open()
  45. {
  46.     if (textEdit->maybeSave()) {
  47.         QString fileName = QFileDialog::getOpenFileName(this);
  48.         if (!fileName.isEmpty())
  49.             loadFile(fileName);
  50.     }
  51. }
  52. bool MainWindow::save()
  53. {
  54.     if (curFile.isEmpty()) {
  55.         return saveAs();
  56.     } else {
  57.         return saveFile(curFile);
  58.     }
  59. }
  60. bool MainWindow::saveAs()
  61. {
  62.     QString fileName = QFileDialog::getSaveFileName(this);
  63.     if (fileName.isEmpty())
  64.         return false;
  65.     return saveFile(fileName);
  66. }
  67. void MainWindow::about()
  68. {
  69.    QMessageBox::about(this, tr("关于文本编辑器"),
  70.             tr("<b>文本编辑器</b> 是一个使用Qt开发的基本文本编辑应用程序。"
  71.                "它可以用来编辑和保存纯文本文件。"));
  72. }
  73. void MainWindow::documentWasModified()
  74. {
  75.     setWindowModified(textEdit->document()->isModified());
  76. }
  77. void MainWindow::find()
  78. {
  79.     if (!findDialog) {
  80.         findDialog = new FindDialog(this);
  81.         connect(findDialog, &FindDialog::findNext, this, &MainWindow::findNext);
  82.         connect(findDialog, &FindDialog::findPrevious, this, &MainWindow::findPrevious);
  83.     }
  84.    
  85.     findDialog->show();
  86.     findDialog->raise();
  87.     findDialog->activateWindow();
  88. }
  89. void MainWindow::replace()
  90. {
  91.     if (!replaceDialog) {
  92.         replaceDialog = new ReplaceDialog(this);
  93.         connect(replaceDialog, &ReplaceDialog::findNext, this, &MainWindow::findNext);
  94.         connect(replaceDialog, &ReplaceDialog::replace, this, &MainWindow::replaceText);
  95.         connect(replaceDialog, &ReplaceDialog::replaceAll, this, &MainWindow::replaceAllText);
  96.     }
  97.    
  98.     replaceDialog->show();
  99.     replaceDialog->raise();
  100.     replaceDialog->activateWindow();
  101. }
  102. void MainWindow::findNext(const QString &str, Qt::CaseSensitivity cs)
  103. {
  104.     if (!textEdit->find(str, QTextDocument::FindFlag(0) | (cs == Qt::CaseSensitive ? QTextDocument::FindCaseSensitively : QTextDocument::FindFlag(0)))) {
  105.         QMessageBox::information(this, tr("查找"), tr("找不到 "%1"").arg(str));
  106.     }
  107. }
  108. void MainWindow::findPrevious(const QString &str, Qt::CaseSensitivity cs)
  109. {
  110.     if (!textEdit->find(str, QTextDocument::FindBackward | (cs == Qt::CaseSensitive ? QTextDocument::FindCaseSensitively : QTextDocument::FindFlag(0)))) {
  111.         QMessageBox::information(this, tr("查找"), tr("找不到 "%1"").arg(str));
  112.     }
  113. }
  114. void MainWindow::replaceText(const QString &findText, const QString &replaceText, Qt::CaseSensitivity cs)
  115. {
  116.     QTextCursor cursor = textEdit->textCursor();
  117.     if (!cursor.hasSelection() || cursor.selectedText() != findText) {
  118.         if (!textEdit->find(findText, QTextDocument::FindFlag(0) | (cs == Qt::CaseSensitive ? QTextDocument::FindCaseSensitively : QTextDocument::FindFlag(0)))) {
  119.             QMessageBox::information(this, tr("替换"), tr("找不到 "%1"").arg(findText));
  120.             return;
  121.         }
  122.     }
  123.    
  124.     textEdit->textCursor().insertText(replaceText);
  125. }
  126. void MainWindow::replaceAllText(const QString &findText, const QString &replaceText, Qt::CaseSensitivity cs)
  127. {
  128.     QTextDocument::FindFlags flags;
  129.     if (cs == Qt::CaseSensitive)
  130.         flags |= QTextDocument::FindCaseSensitively;
  131.    
  132.     int count = 0;
  133.     QTextCursor cursor = textEdit->textCursor();
  134.     cursor.movePosition(QTextCursor::Start);
  135.     textEdit->setTextCursor(cursor);
  136.    
  137.     while (textEdit->find(findText, flags)) {
  138.         textEdit->textCursor().insertText(replaceText);
  139.         count++;
  140.     }
  141.    
  142.     QMessageBox::information(this, tr("替换"), tr("已替换 %1 处").arg(count));
  143. }
  144. void MainWindow::fontDialog()
  145. {
  146.     bool ok;
  147.     QFont font = QFontDialog::getFont(&ok, textEdit->font(), this);
  148.     if (ok) {
  149.         textEdit->setFont(font);
  150.     }
  151. }
  152. void MainWindow::createActions()
  153. {
  154.     newAct = new QAction(tr("新建"), this);
  155.     newAct->setShortcuts(QKeySequence::New);
  156.     newAct->setStatusTip(tr("创建新文件"));
  157.     connect(newAct, &QAction::triggered, this, &MainWindow::newFile);
  158.     openAct = new QAction(tr("打开..."), this);
  159.     openAct->setShortcuts(QKeySequence::Open);
  160.     openAct->setStatusTip(tr("打开已有文件"));
  161.     connect(openAct, &QAction::triggered, this, &MainWindow::open);
  162.     saveAct = new QAction(tr("保存"), this);
  163.     saveAct->setShortcuts(QKeySequence::Save);
  164.     saveAct->setStatusTip(tr("保存当前文档"));
  165.     connect(saveAct, &QAction::triggered, this, &MainWindow::save);
  166.     saveAsAct = new QAction(tr("另存为..."), this);
  167.     saveAsAct->setShortcuts(QKeySequence::SaveAs);
  168.     saveAsAct->setStatusTip(tr("以新名称保存文档"));
  169.     connect(saveAsAct, &QAction::triggered, this, &MainWindow::saveAs);
  170.     exitAct = new QAction(tr("退出"), this);
  171.     exitAct->setShortcuts(QKeySequence::Quit);
  172.     exitAct->setStatusTip(tr("退出应用程序"));
  173.     connect(exitAct, &QAction::triggered, this, &QWidget::close);
  174.     undoAct = new QAction(tr("撤销"), this);
  175.     undoAct->setShortcuts(QKeySequence::Undo);
  176.     connect(undoAct, &QAction::triggered, textEdit, &TextEdit::undo);
  177.     redoAct = new QAction(tr("重做"), this);
  178.     redoAct->setShortcuts(QKeySequence::Redo);
  179.     connect(redoAct, &QAction::triggered, textEdit, &TextEdit::redo);
  180.     cutAct = new QAction(tr("剪切"), this);
  181.     cutAct->setShortcuts(QKeySequence::Cut);
  182.     connect(cutAct, &QAction::triggered, textEdit, &TextEdit::cut);
  183.     copyAct = new QAction(tr("复制"), this);
  184.     copyAct->setShortcuts(QKeySequence::Copy);
  185.     connect(copyAct, &QAction::triggered, textEdit, &TextEdit::copy);
  186.     pasteAct = new QAction(tr("粘贴"), this);
  187.     pasteAct->setShortcuts(QKeySequence::Paste);
  188.     connect(pasteAct, &QAction::triggered, textEdit, &TextEdit::paste);
  189.     findAct = new QAction(tr("查找..."), this);
  190.     findAct->setShortcuts(QKeySequence::Find);
  191.     connect(findAct, &QAction::triggered, this, &MainWindow::find);
  192.     replaceAct = new QAction(tr("替换..."), this);
  193.     replaceAct->setShortcuts(QKeySequence::Replace);
  194.     connect(replaceAct, &QAction::triggered, this, &MainWindow::replace);
  195.     fontAct = new QAction(tr("字体..."), this);
  196.     connect(fontAct, &QAction::triggered, this, &MainWindow::fontDialog);
  197.     aboutAct = new QAction(tr("关于"), this);
  198.     aboutAct->setStatusTip(tr("显示应用程序的关于框"));
  199.     connect(aboutAct, &QAction::triggered, this, &MainWindow::about);
  200.     aboutQtAct = new QAction(tr("关于 Qt"), this);
  201.     aboutQtAct->setStatusTip(tr("显示Qt库的关于框"));
  202.     connect(aboutQtAct, &QAction::triggered, qApp, &QApplication::aboutQt);
  203. }
  204. void MainWindow::createMenus()
  205. {
  206.     fileMenu = menuBar()->addMenu(tr("文件"));
  207.     fileMenu->addAction(newAct);
  208.     fileMenu->addAction(openAct);
  209.     fileMenu->addAction(saveAct);
  210.     fileMenu->addAction(saveAsAct);
  211.     fileMenu->addSeparator();
  212.     fileMenu->addAction(exitAct);
  213.     editMenu = menuBar()->addMenu(tr("编辑"));
  214.     editMenu->addAction(undoAct);
  215.     editMenu->addAction(redoAct);
  216.     editMenu->addSeparator();
  217.     editMenu->addAction(cutAct);
  218.     editMenu->addAction(copyAct);
  219.     editMenu->addAction(pasteAct);
  220.     editMenu->addSeparator();
  221.     editMenu->addAction(findAct);
  222.     editMenu->addAction(replaceAct);
  223.     formatMenu = menuBar()->addMenu(tr("格式"));
  224.     formatMenu->addAction(fontAct);
  225.     helpMenu = menuBar()->addMenu(tr("帮助"));
  226.     helpMenu->addAction(aboutAct);
  227.     helpMenu->addAction(aboutQtAct);
  228. }
  229. void MainWindow::createStatusBar()
  230. {
  231.     statusBar()->showMessage(tr("就绪"));
  232. }
  233. void MainWindow::readSettings()
  234. {
  235.     QSettings settings("MyCompany", "TextEditor");
  236.     QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
  237.     QSize size = settings.value("size", QSize(400, 400)).toSize();
  238.     resize(size);
  239.     move(pos);
  240. }
  241. void MainWindow::writeSettings()
  242. {
  243.     QSettings settings("MyCompany", "TextEditor");
  244.     settings.setValue("pos", pos());
  245.     settings.setValue("size", size());
  246. }
  247. bool MainWindow::maybeSave()
  248. {
  249.     if (!textEdit->document()->isModified())
  250.         return true;
  251.     const QMessageBox::StandardButton ret
  252.         = QMessageBox::warning(this, tr("文本编辑器"),
  253.                              tr("文档已被修改.\n"
  254.                                 "是否保存修改?"),
  255.                              QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
  256.     switch (ret) {
  257.     case QMessageBox::Save:
  258.         return save();
  259.     case QMessageBox::Cancel:
  260.         return false;
  261.     default:
  262.         break;
  263.     }
  264.     return true;
  265. }
  266. void MainWindow::loadFile(const QString &fileName)
  267. {
  268.     if (!textEdit->loadFile(fileName)) {
  269.         statusBar()->showMessage(tr("加载失败"), 2000);
  270.         return;
  271.     }
  272.     setCurrentFile(fileName);
  273.     statusBar()->showMessage(tr("文件已加载"), 2000);
  274. }
  275. bool MainWindow::saveFile(const QString &fileName)
  276. {
  277.     if (!textEdit->saveFile(fileName)) {
  278.         statusBar()->showMessage(tr("保存失败"), 2000);
  279.         return false;
  280.     }
  281.     setCurrentFile(fileName);
  282.     statusBar()->showMessage(tr("文件已保存"), 2000);
  283.     return true;
  284. }
  285. void MainWindow::setCurrentFile(const QString &fileName)
  286. {
  287.     curFile = fileName;
  288.     textEdit->document()->setModified(false);
  289.     setWindowModified(false);
  290.     QString shownName;
  291.     if (curFile.isEmpty())
  292.         shownName = "untitled.txt";
  293.     else
  294.         shownName = QFileInfo(curFile).fileName();
  295.     setWindowTitle(tr("%1[*] - %2").arg(shownName, QCoreApplication::applicationName()));
  296. }
  297. QString MainWindow::strippedName(const QString &fullFileName)
  298. {
  299.     return QFileInfo(fullFileName).fileName();
  300. }
复制代码

4.6 主函数
  1. // main.cpp
  2. #include "mainwindow.h"
  3. #include <QApplication>
  4. int main(int argc, char *argv[])
  5. {
  6.     QApplication app(argc, argv);
  7.     app.setApplicationName("文本编辑器");
  8.     app.setOrganizationName("MyCompany");
  9.    
  10.     MainWindow window;
  11.     window.show();
  12.    
  13.     return app.exec();
  14. }
复制代码

4.7 项目文件
  1. # TextEditor.pro
  2. QT       += core gui
  3. greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
  4. CONFIG += c++17
  5. # You can make your code fail to compile if it uses deprecated APIs.
  6. # In order to do so, uncomment the following line.
  7. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
  8. SOURCES += \
  9.     main.cpp \
  10.     mainwindow.cpp \
  11.     textedit.cpp \
  12.     finddialog.cpp \
  13.     replacedialog.cpp
  14. HEADERS += \
  15.     mainwindow.h \
  16.     textedit.h \
  17.     finddialog.h \
  18.     replacedialog.h
  19. # Default rules for deployment.
  20. qnx: target.path = /tmp/$${TARGET}/bin
  21. else: unix:!android: target.path = /opt/$${TARGET}/bin
  22. !isEmpty(target.path): INSTALLS += target
复制代码

这个文本编辑器应用程序具有完整的文件操作功能(新建、打开、保存、另存为)、基本的文本编辑功能(撤销、重做、剪切、复制、粘贴)、查找和替换功能以及字体设置功能。通过这个实战案例,我们综合运用了前面所学的Qt核心技能,包括信号槽机制、布局管理、常用控件、对话框、文件操作等。

五、常见问题与解决方案

5.1 中文显示乱码问题

在Linux平台下,Qt应用程序中显示中文时可能会出现乱码问题。这通常是由于编码不一致导致的。解决方法如下:
  1. #include <QApplication>
  2. #include <QTextCodec>
  3. #include <QLabel>
  4. int main(int argc, char *argv[])
  5. {
  6.     QApplication app(argc, argv);
  7.    
  8.     // 设置编码为UTF-8
  9.     QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));
  10.    
  11.     QLabel label("你好,世界!");
  12.     label.show();
  13.    
  14.     return app.exec();
  15. }
复制代码

另外,在Qt Creator中,确保源代码文件以UTF-8编码保存。可以在”工具” -> “选项” -> “文本编辑器” -> “行为”中设置默认编码为UTF-8。

5.2 程序打包与发布

在Linux平台下,Qt应用程序的打包与发布通常有以下几种方式:

静态编译将Qt库直接链接到可执行文件中,使得程序可以在没有安装Qt库的系统上运行。但是,静态编译需要自己编译Qt库,并且会增加可执行文件的大小。
  1. # 在.pro文件中添加以下配置
  2. CONFIG += static
复制代码

linuxdeployqt是一个用于打包Qt应用程序的工具,它会收集所有依赖的库并创建一个AppDir或AppImage。
  1. # 安装linuxdeployqt
  2. wget -c "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage"
  3. chmod a+x linuxdeployqt-continuous-x86_64.AppImage
  4. # 打包应用程序
  5. ./linuxdeployqt-continuous-x86_64.AppImage /path/to/your/app -appimage
复制代码

如果你的目标系统是基于Debian的(如Ubuntu),可以创建Debian包来分发应用程序。
  1. # 安装打包工具
  2. sudo apt install dh-make devscripts
  3. # 创建Debian包结构
  4. dh_make --createorig --single --yes
  5. # 构建包
  6. debuild -us -uc
复制代码

5.3 性能优化

Qt应用程序的性能优化是一个重要的话题,以下是一些常见的优化技巧:
  1. // 在自定义控件中,只有当内容真正改变时才调用update()
  2. void MyWidget::setData(const Data &data)
  3. {
  4.     if (m_data != data) {
  5.         m_data = data;
  6.         update(); // 触发重绘
  7.     }
  8. }
复制代码

当需要显示大量数据时,使用模型/视图架构比直接使用控件更高效:
  1. // 使用QTableView和自定义模型而不是QTableWidget
  2. QTableView *tableView = new QTableView;
  3. MyCustomModel *model = new MyCustomModel(this);
  4. tableView->setModel(model);
复制代码
  1. // 将耗时操作放在工作线程中
  2. class Worker : public QObject
  3. {
  4.     Q_OBJECT
  5. public:
  6.     explicit Worker(QObject *parent = nullptr) : QObject(parent) {}
  7.    
  8. public slots:
  9.     void doWork()
  10.     {
  11.         // 执行耗时操作
  12.         emit resultReady(result);
  13.     }
  14.    
  15. signals:
  16.     void resultReady(const Result &result);
  17. };
  18. // 在主线程中
  19. QThread *thread = new QThread;
  20. Worker *worker = new Worker;
  21. worker->moveToThread(thread);
  22. connect(thread, &QThread::started, worker, &Worker::doWork);
  23. connect(worker, &Worker::resultReady, this, &MyClass::handleResult);
  24. connect(worker, &Worker::resultReady, thread, &QThread::quit);
  25. connect(worker, &Worker::resultReady, worker, &Worker::deleteLater);
  26. connect(thread, &QThread::finished, thread, &QThread::deleteLater);
  27. thread->start();
复制代码

5.4 内存管理

Qt的父子对象机制简化了内存管理,但在某些情况下仍需特别注意:
  1. // 错误示例:没有设置父对象,可能导致内存泄漏
  2. void MyClass::createWidgets()
  3. {
  4.     QPushButton *button = new QPushButton("Click me");
  5.     // 没有设置父对象,也没有手动删除
  6. }
  7. // 正确示例:设置父对象
  8. void MyClass::createWidgets()
  9. {
  10.     QPushButton *button = new QPushButton("Click me", this); // 设置父对象
  11.     // 父对象被删除时,会自动删除子对象
  12. }
复制代码
  1. #include <QSharedPointer>
  2. #include <QWeakPointer>
  3. // 使用QSharedPointer管理对象
  4. QSharedPointer<MyClass> sharedObj(new MyClass);
  5. // 使用QWeakPointer避免循环引用
  6. class ClassA : public QObject
  7. {
  8.     Q_OBJECT
  9. public:
  10.     void setClassB(QWeakPointer<ClassB> b) { m_b = b; }
  11. private:
  12.     QWeakPointer<ClassB> m_b;
  13. };
  14. class ClassB : public QObject
  15. {
  16.     Q_OBJECT
  17. public:
  18.     void setClassA(QSharedPointer<ClassA> a) { m_a = a; }
  19. private:
  20.     QSharedPointer<ClassA> m_a;
  21. };
复制代码

六、学习路径与资源推荐

6.1 学习路径

对于初学者,建议按照以下路径学习Qt编程:

1. 基础阶段:学习C++基础知识了解Qt的基本概念和架构掌握信号槽机制学习常用控件的使用理解布局管理
2. 学习C++基础知识
3. 了解Qt的基本概念和架构
4. 掌握信号槽机制
5. 学习常用控件的使用
6. 理解布局管理
7. 进阶阶段:学习自定义控件的创建掌握模型/视图编程了解Qt的多线程编程学习文件操作和数据处理掌握Qt的绘图系统
8. 学习自定义控件的创建
9. 掌握模型/视图编程
10. 了解Qt的多线程编程
11. 学习文件操作和数据处理
12. 掌握Qt的绘图系统
13. 高级阶段:学习Qt的网络编程掌握Qt的数据库操作了解Qt的国际化支持学习Qt的单元测试掌握应用程序的打包与发布
14. 学习Qt的网络编程
15. 掌握Qt的数据库操作
16. 了解Qt的国际化支持
17. 学习Qt的单元测试
18. 掌握应用程序的打包与发布

基础阶段:

• 学习C++基础知识
• 了解Qt的基本概念和架构
• 掌握信号槽机制
• 学习常用控件的使用
• 理解布局管理

进阶阶段:

• 学习自定义控件的创建
• 掌握模型/视图编程
• 了解Qt的多线程编程
• 学习文件操作和数据处理
• 掌握Qt的绘图系统

高级阶段:

• 学习Qt的网络编程
• 掌握Qt的数据库操作
• 了解Qt的国际化支持
• 学习Qt的单元测试
• 掌握应用程序的打包与发布

6.2 推荐资源

• Qt官方文档:最权威的Qt参考资料,包含详细的类说明和示例代码。
• Qt Wiki:包含各种教程、技巧和最佳实践。
• Qt示例代码:官方提供的示例代码,涵盖各种功能和使用场景。

• 《C++ GUI Programming with Qt 4》:经典的Qt编程书籍,虽然基于Qt 4,但大部分内容仍然适用。
• 《Qt 5开发实战》:详细介绍Qt 5的各种功能和编程技巧。
• 《Qt Creator快速入门》:专注于Qt Creator IDE的使用和Qt编程基础。

• Qt中文社区:国内最大的Qt开发者社区,有丰富的教程和讨论。
• Stack Overflow:全球最大的编程问答网站,有大量Qt相关的问题和解答。
• GitHub上的Qt项目:可以学习其他开发者的代码和项目。

• Qt官方YouTube频道:官方发布的各种教程和演示视频。
• Bilibili上的Qt教程:国内视频网站上有许多Qt相关的中文教程。

结语

Qt是一个功能强大、灵活且跨平台的GUI开发框架,特别适合在Linux平台上开发桌面应用程序。通过本文的学习,你已经从入门到精通掌握了Qt图形界面编程的核心技能,包括基本概念、常用控件、信号槽机制、布局管理、自定义控件、模型视图编程、多线程编程等内容。通过实战案例的开发,你了解了如何综合运用这些技能来构建一个完整的桌面应用程序。同时,我们还讨论了常见问题的解决方案和学习资源推荐,帮助你进一步提高Qt编程能力。

Qt的学习是一个持续的过程,随着Qt版本的更新和新功能的加入,你需要不断学习和探索。希望本文能够成为你Qt编程之路上的指南,帮助你开发出更加专业、高效的桌面应用程序。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

0

主题

1304

科技点

654

积分

候风辨气

积分
654
候风辨气 发表于 2025-9-24 11:27:27 | 显示全部楼层
感謝分享
温馨提示:看帖回帖是一种美德,您的每一次发帖、回帖都是对论坛最大的支持,谢谢! [这是默认签名,点我更换签名]
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则