QTCreator学习

1.新建程序

在这里插入图片描述
2. 设置项目名称
在这里插入图片描述3. Build System选择qmake,若选择cmake则只会产生CmakeLists文件,不会产生pro文件。这里选择qmake
在这里插入图片描述
4.Base class选择QDialog,表示该类继承于QDialog类,选择QWindow也可
在这里插入图片描述
5.套件选择MinGW 32bit,记得取消掉其他的。
在这里插入图片描述
6. ,如果不需要ui,纯程序那就取消勾选Generate Form,双击ui文件,拖动可添加工具。
7.点击左下方绿色运行按钮可运行。
在这里插入图片描述

7.QT控件

7.3 显示窗口部件

7.1.3 添加资源文件

  1. 右键项目->add new->QT::QT Resource File
    在这里插入图片描述
  2. 填上资源名称
    在这里插入图片描述
  3. 新建完成了资源文件后,默认会进入 res.qrc 文件编辑模式(如果关闭了,可以右键这个文
    件点击选择“Open in Editor”),点击 Add Prefix 添加前缀,添加前缀的目的是方便分类管理文
    件,比如我们现在第⑪处添加了前缀/。“/”一定需要写,否则会找不到路径,这有点像 Linux
    的根节点一样。
    在这里插入图片描述
  4. 添加了前缀后,我们添加资源图片,放在/images 前缀的下面。准备的图片在本项目路径 images 文件夹(images 文件夹先手动创建)下。如下图步骤,添加完成需要按“Ctrl + S”保存 res.qrc 才会看到左边的结果。添加完成如下图。
  5. 在这里插入图片描述

7.3.1QLabel

QLabel 提供了一种用于文本或图像显示的小部件,在前 7.2.11、7.2.12 等小节已经出现过Label 控件,只用了它显示文本,其实它还可以用于显示图像
使用:

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QLabel>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* 用一个 QLabel 对象用于显示字符串 */
        QLabel *labelString;

    /* 用一个 QLabel 对象用于显示图像 */
        QLabel *labelImage;
};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    this->setGeometry(0,0,800,480);
    QPixmap pixmap(":/images/img1.jpg");

    labelImage = new QLabel(this);
    labelImage->setGeometry(180,150,452,132);
    labelImage->setPixmap(pixmap);
    labelImage->setScaledContents(true);

    labelString = new QLabel(this);
    labelString->setText("标签演示文本");
    labelString->setGeometry(300,300,100,20);
}

MainWindow::~MainWindow()
{
}

  1. main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}
  1. 运行效果
    在这里插入图片描述

7.3.4 QProgressBar

QProgressBar 继承 QWidget。QProgressBar 小部件提供了一个水平或垂直的进度条。进度条用于向用户显示操作的进度,并向他们确认应用程序仍在运行。在新建例程中不要勾选“Generate form”,默认继承 QMainWindow 类即可。项目新建完成
如下图(已经添加了资源img1.jpg,添加资源参考7.1.3)
在这里插入图片描述
使用:

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QProgressBar>
#include <QTimer>


class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    /* 声明对象 */
    QProgressBar *progressBar;
    QTimer *timer;

    /* 用于设置当前 QProgressBar 的值 */
    int value;

    private slots:
    /* 槽函数 */
    void timerTimeOut();
};
#endif // MAINWINDOW_H

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QFrame>
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* 声明对象 */
    QFrame *hline;
    QFrame *vline;
};
#endif // MAINWINDOW_H
  1. mainwindow.cpp
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置主窗体位置与大小 */
    this->setGeometry(0, 0, 800, 480);

    progressBar = new QProgressBar(this);
    progressBar->setGeometry(300, 200, 200, 60);

    /*样式表设置,常用使用 setStyleSheet 来设置样式(实现界面美化的功能),* 具体可参考 styleSheet */
    progressBar->setStyleSheet (
        "QProgressBar{border:8px solid #FFFFFF;"
        "height:30;"
        "border-image:url(:/img1.jpg);" //背景图片
        "text-align:center;" // 文字居中
        "color:rgb(255,0,255);"
        "font:20px;" // 字体大小为 20px
        "border-radius:10px;}"
        "QProgressBar::chunk{"
        "border-radius:5px;" // 斑马线圆角
        "border:1px solid black;" // 黑边,默认无边
        "background-color:skyblue;"
        "width:10px;margin:1px;}" // 宽度和间距
        );

    /* 设置 progressBar 的范围值 */
    progressBar->setRange(0, 100);
    /* 初始化 value 为 0 */
    value = 0;
    /* 给 progressBar 设置当前值 */
    progressBar->setValue(value);
    /* 设置当前文本字符串的显示格式 */
    progressBar->setFormat("充电中%p%");

    /* 定时器实例化设置每 100ms 发送一个 timeout 信号 */
    timer = new QTimer(this);
    timer->start(100);

    /* 信号槽连接 */
    connect(timer, SIGNAL(timeout()),
            this, SLOT(timerTimeOut()));
}

MainWindow::~MainWindow()
{
}

void MainWindow::timerTimeOut()
{
    /* 定显示器时间到,value 值自加一 */
    value ++;
    progressBar->setValue(value);
    /* 若 value 值大于 100,令 value 再回到 0 */
    if(value>100)
    value = 0;
}

  1. main.cpp保持默认
  2. 运行:程序运行后,可以看到在定时器的作用下,电池一直在充电,充到 100%,又重新回到 0%重新充电。QProgressBar 一般用于表示进度,常用于如复制进度,打开、加载进度等
    在这里插入图片描述

7.3.5 QFrame

QFrame 继承 QWidget。QFrame 类是有框架的窗口部件的基类,它绘制框架并且调用一个虚函数 drawContents()来填充这个框架。这个函数是被子类重新实现的。这里至少还有两个有用的函数:drawFrame()和 frameChanged()。
QPopupMenu 使用这个来把菜单“升高”,高于周围屏幕。QProgressBar 有“凹陷”的外观。QLabel 有平坦的外观。这些有框架的窗口部件可以被改变。

QFrame::Shape 这个枚举类型定义了 QFrame 的框架所使用的外形。当前定义的效果有:
 NoFrame - QFrame 不画任何东西
 Box - QFrame 在它的内容周围画一个框
 Panel - QFrame 画一个平板使内容看起来凸起或者凹陷
 WinPanel - 像 Panel,但 QFrame 绘制三维效果的方式和 Microsoft Windows 95(及其它)的一样
 ToolBarPanel - QFrame 调用 QStyle::drawToolBarPanel()
 MenuBarPanel - QFrame 调用 QStyle::drawMenuBarPanel()
 HLine - QFrame 绘制一个水平线,但没有框任何东西(作为分隔是有用的)
 VLine - QFrame 绘制一个竖直线,但没有框任何东西(作为分隔是有用的)
 StyledPanel - QFrame 调用 QStyle::drawPanel()
 PopupPanel - QFrame 调用 QStyle::drawPopupPanel()
阴影风格有:
 Plain 使用调色板的前景颜色绘制(没有任何三维效果)。
 Raised 使用当前颜色组的亮和暗颜色绘制三维的凸起线。
 Sunken 使用当前颜色组的亮和暗颜色绘制三维的凹陷线。

  1. 使用:
    定义两个 QFrame 对象,实例化后设置成一条
    水平样式,一条垂直样式。实际上 Display Widgets 里的 Horizontal Line/Vertical Line 是 QFrame
    已经封装好的控件,也可以通过下面的例子来实现不同的效果
  2. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QFrame>
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* 声明对象 */
    QFrame *hline;
    QFrame *vline;
};
#endif // MAINWINDOW_H
  1. mainwindows.cpp
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置主窗体的显示位置与大小 */
    this->setGeometry(0, 0, 800, 480);

    /* 实例化 */
    hline = new QFrame(this);
    /* 确定起始点,设置长和宽,绘制距形 */
    hline->setGeometry(QRect(200, 100, 400, 40));

    /* 设置框架样式为 Hline,水平,可设置为其他样式例如 Box, 由于是样式选择 HLine,所以只显示一条水平直线 */
    hline->setFrameShape(QFrame::HLine);
    /* 绘制阴影 */
    hline->setFrameShadow(QFrame::Sunken);

    /* 实例化 */
    vline = new QFrame(this);
    /* 确定起始点,设置长和宽,绘制距形 */
    vline->setGeometry(QRect(300, 100, 2, 200));

    /* 设置框架样式为 Vline,垂直,可设置为其他样式例如 Box,由于是样式选择 Vline,所以只显示一条垂直直线 */
    vline->setFrameShape(QFrame::VLine);
    /* 绘制阴影 */
    vline->setFrameShadow(QFrame::Sunken);
}
MainWindow::~MainWindow()
{
}
  1. main.cpp保持默认,程序编译运行的结果如下。在窗口中央出现一条垂直的直线和一条水平的直线。可以用
    QFrame 来绘制一些比较好看的边框等,用作美化界面。QFrame 类是所有框架的窗口部件的基
    类,所以像 QLabel 这种见的控件也可以设置成独特的边框样式。
    在这里插入图片描述

7.4 显示窗口部件之浏览器

7.4.1QTextBrower

QTextBrowser 继承 QTextEdit,QTextBrowser 类提供了一个具有超文本导航的文本浏览器。
该类扩展了 QTextEdit(在只读模式下),添加了一些导航功能,以便用户可以跟踪超文本文档中
的链接。
本例设计一个文本浏览器程序,可以打开并显示 txt、html 等文件。本小节还用到 QAction,菜单栏,学习文件的打开以及处理等。本例设计一个文本浏览器程序,可以打开并显示 txt、html 等文件。本小节还用到 QAction,菜单栏,学习文件的打开以及处理等。生成项目时勾选 mainwindow.ui,因为我们要在工具栏里添加按钮来打开文件等操作。

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTextBrowser>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    Ui::MainWindow *ui;
    /* 声明对象 */
    QTextBrowser *textBrowser;
    QAction *openAction;
private slots:
    /* 槽函数 */
    void openActionTriggered();
};
#endif // MAINWINDOW_H
  1. mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"

/* 窗口对话框与文本流 */
#include <QFileDialog>
#include <QTextStream>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    /* 设置主窗体位置与大小 */
    this->setGeometry(0, 0, 800, 480);

    /* 将窗口标题设置为文本浏览器 */
    this->setWindowTitle("文本浏览器");

    /* 实例化 */
    textBrowser = new QTextBrowser(this);
    /* 将文本浏览器窗口居中 */
    this->setCentralWidget(textBrowser);

    /* 实例化 */
    openAction = new QAction("打开",this);
    /* ui 窗口自带有 menubar(菜单栏)、mainToolbar(工具栏)与
    * statusbar(状态栏)
    * menuBar 是 ui 生成工程就有的,所以可以在 menubar 里添加
    * 我们的 QActiont 等,如果不需要 menubar,可以在 ui 设计
    * 窗口里,在右则对象里把 menubar 删除,再自己重新定义自己的
    * 菜单栏
    */
    /* 将动作添加到菜单栏 */
    ui->menubar->addAction(openAction);

    /* 信号槽连接 */
    connect(openAction, SIGNAL(triggered()),
        this, SLOT(openActionTriggered()));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::openActionTriggered()
{
    /* 调用系统打开文件窗口,过滤文件名 */
    QString fileName = QFileDialog::getOpenFileName(
    this,tr("打开文件"),"",
    tr("Files(*.txt *.cpp *.h *.html *.htm)")
    );
    QFile myFile(fileName);
    /* 以只读、文本方式打开,若打开失败,则返回 */
    if(!myFile.open(QIODevice::ReadOnly | QIODevice::Text))
    return;

    /* 用 QTextStream 对象接收 */
    QTextStream in (&myFile);

    /* 读取全部数据 */
    QString myText = in.readAll();

    /* 判断打开文件的后缀,如果是 html 格式的则设置文本浏览器为 html 格式 */
    if(fileName.endsWith("html") || fileName.endsWith("htm")){
    textBrowser->setHtml(myText);
    } else {
    textBrowser->setPlainText(myText);
    }

    /* ui 窗口自带有 statusbar(状态栏),设置打开的文件名 */
    ui->statusbar->showMessage("文件名:" + fileName);
}

  1. main.cpp保持默认,运行,在菜单栏点击打开后,选择任意一个可打开的文件,本次打开的是该工程中的 mainwindow.h 文件,结果如下图。根据上面的例子可自行拓展打造自己的文本浏览器,例如在菜单栏上加上关闭动作等,在工具栏还可以添加字体颜色,与及背景颜色,以及字体的放大与缩小等,可自行拓展。

在这里插入图片描述

7.4.2 QGraphicsView

QGraphicsView 继承 QAbstractScrollArea。QGraphicsView 是图形视图框架的一部分,它提
供了基于图元的模型/视图编程。QGraphicsView 在可滚动视图中可视化 QGraphicsScene 的内容。
要可视化场景,首先构造一个 QGraphicsView 对象,将要可视化的场景的地址传递给 QGraphicsView 的构造函数。或者,可以调用 setScene()在稍后设置场景。
本例设计一个图像浏览器程序,在上一节一的基础上,将它改变为图像浏览器。在新建例程默认继承 QMainWindow 类即可。勾选 mainwindow.ui,因为我们要在工具栏里添加按钮来打开文件等操作。

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QGraphicsView>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
    /* 声明对象 */
    QGraphicsView *graphicsView;
    QGraphicsScene *graphicsScene;
    QAction *openAction;
    private slots:
    /* 槽函数 */
    void openActionTriggered();
};
#endif // MAINWINDOW_H
  1. mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QFileDialog>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    this->setGeometry(0, 0, 800, 480);
    /* 将窗口标题设置为图像浏览器 */
    this->setWindowTitle("图像浏览器");

    /* 实例化图形视图对象 */
    graphicsView = new QGraphicsView(this);
    /* 将图像浏览器窗口居中 */
    this->setCentralWidget(graphicsView);

    /* 实例化场景对象 */
    graphicsScene = new QGraphicsScene(this);

    /* 在 QGraphicsView 设置场景 */
    graphicsView->setScene(graphicsScene);

    /* 将动作添加到菜单栏 */
    openAction = new QAction("打开",this);
    ui->menubar->addAction(openAction);

    /* 信号槽连接 */
    connect(openAction, SIGNAL(triggered()),
        this, SLOT(openActionTriggered()));
}

MainWindow::~MainWindow()
{
    delete ui;
}
void MainWindow::openActionTriggered()
{
    /*调用系统打开文件窗口,设置窗口标题为“打开文件”,过滤文件名*/
    QString fileName = QFileDialog::getOpenFileName(
        this,tr("打开文件"), "",
        tr("Files(*.png *.jpg *.bmp)")
        );
    /* 定义 QPixmap 对象,指向 fileName */
    QPixmap image(fileName);
    /* 将 image 用 scaled 来重新设置长宽为 graphicsView 的长宽,
     * 保持纵横比等
    */

    /* 假若用户没选择文件,则返回 */
    if(image.isNull())
        return;
    image = image.scaled(graphicsView->width(),
        graphicsView->height(),

        Qt::KeepAspectRatio,
        Qt::FastTransformation
        );
    /* 在添加场景内容前,先清除之前的场景内容 */
    graphicsScene->clear();
    /* 添加场景内容 image */
    graphicsScene->addPixmap(image);
    /* ui 窗口自带有 statusBar(状态栏),设置打开的文件名 */
    ui->statusbar->showMessage("文件名:" + fileName);
}

  1. 运行:运行的结果如下。菜单栏点击打开后,系统默认是打开的最近打开的位置,选择
    任意一个可打开的图片
    在这里插入图片描述

7.5 布局管理

Qt 提供了非常丰富的布局类,基本布局管理类包括:QBoxLayout、QGridLayout、QFormLayout 和 QStackedLayout。这些类都从 QLayout 继承而来,它们都来源于 QObject(而不是 QWidget)。创建更加复杂的布局,可以让它们彼此嵌套完成。
其中 QBoxLayout 提供了水平和垂直的布局管理;QFormLayout 提供了将输入部件和标签成组排列的布局管理;QGridLayout 提供了网格形式的布局管理;QStackedLayout 提供了一组布局后的部件,可以对它们进行分布显示。它们的继承关系如下图:
在这里插入图片描述
下面将学习 Layouts 组里面的 4 种布局,如下图。
在这里插入图片描述各个控件的名称依次解释如下。
(1) Vertiacl Layout:垂直布局
(2) Horizontal Layout:水平布局
(3) Grid Layout:网格布局
(4) Form Layout:表单布局
QBoxLayout 继承 QLayout。QBoxLayout 类提供水平或垂直地排列子部件。QBoxLayout 获取从它的父布局或从 parentWidget()中所获得的空间,将其分成一列框,并使每个托管小部件填充一个框。
QGridLayout继承QLayout。QGridLayout获取可用的空间(通过其父布局或parentWidget())),将其分为行和列,并将其管理的每个小部件放入正确的单元格中。由于网格布局管理器中的组件也是会随着窗口拉伸而发生变化的,所以也是需要设置组件之间的比例系数的,与 QBoxLayout 不同的是网格布局管理器还需要分别设置行和列的比例系数。
QFormLayout 继承 QLayout。QFormLayout 类管理输入小部件及其关联标签的表单。QFormLayout 是一个方便的布局类,它以两列的形式布局其子类。左列由标签组成,右列由“字段”小部件(QLineEdit(行编辑器)、QSpinBox(旋转框等))组成。通常使用 setRowWrapPolicy(RowWrapPolicy policy)接口函数设置布局的换行策略进行布局等。

7.5.1 QBoxLayout

QBoxLayout 继承 QLayout。QBoxLayout 类提供水平或垂直地排列子部件。QBoxLayout 获
取从它的父布局或从 parentWidget()中所获得的空间,将其分成一列框,并使每个托管小部件填
充一个框。
使用几个按钮,将他们设置为垂直排布和水平排布,以及设置它们的一些属性。在新建例程中不要勾选“Generate form”.

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QPushButton>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* 声明按钮对象数组 */
    QPushButton *pushButton[6];

    /* 定义两个 widget,用于容纳排布按钮 */
    QWidget *hWidget;
    QWidget *vWidget;

    /* QHBoxLayout 与 QVBoxLayout 对象 */
    QHBoxLayout *hLayout;
    QVBoxLayout *vLayout;

};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
#include "mainwindow.h"
#include <QList>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置主窗口的位置与大小 */
    this->setGeometry(0, 0, 800, 480);

    /* 实例化与设置位置大小 */
    hWidget = new QWidget(this);
    hWidget->setGeometry(0, 0, 800, 240);

    vWidget = new QWidget(this);
    vWidget->setGeometry(0, 240, 800, 240);

    hLayout = new QHBoxLayout();
    vLayout = new QVBoxLayout();

    /* QList<T>是 Qt 的一种泛型容器类。* 它以链表方式存储一组值,* 并能对这组数据进行快速索引*/
     QList <QString>list;
    /* 将字符串值插入 list */
    list<<"one"<<"two"<<"three"<<"four"<<"five"<<"six";

    /* 用一个循环实例化 6 个按钮 */
    for(int i = 0; i < 6; i++){
        pushButton[i] = new QPushButton();
        pushButton[i]->setText(list[i]);
        if(i < 3) {
            /* 将按钮添加至 hLayout 中 */
                hLayout->addWidget(pushButton[i]);
        }
        else {
            /* 将按钮添加至 vLayout 中 */

             vLayout->addWidget(pushButton[i]);
        }
    }
        /* 设置间隔为 50 */
    hLayout->setSpacing(50);

    /* hWidget 与 vWidget 的布局设置为 hLayout/vLayout */
    hWidget->setLayout(hLayout);
    vWidget->setLayout(vLayout);
}

MainWindow::~MainWindow()
{
}


  1. 运行,可以看到在 hWidget 中添加了 3 个水平排布的按钮,在 vWidget
    中添加了 3 个垂直排布的按钮。
    在这里插入图片描述

7.5.2 QGridLayout

QGridLayout 类提供了布局管理器里的一种以网格(二维)的方式管理界面组件,以按钮组件为例,它们所对应网格的坐标下表,与二维数组类似。
在这里插入图片描述
QGridLayout继承QLayout。QGridLayout获取可用的空间(通过其父布局或parentWidget())),将其分为行和列,并将其管理的每个小部件放入正确的单元格中。由于网格布局管理器中的组件也是会随着窗口拉伸而发生变化的,所以也是需要设置组件之间的比例系数的,与QBoxLayout 不同的是网格布局管理器还需要分别设置行和列的比例系数。
使用几个按钮,将他们设置为网格布局,同时设置它们的行、列比例系数(拉伸因子),以及设置它们的一些属性。

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QGridLayout>
#include <QPushButton>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* 声明 widget 窗口部件,用于容纳下面 4 个 pushButton 按钮 */
    QWidget *gWidget;

    /* 声明 QGridLayout 对象 */
    QGridLayout *gridLayout;

    /* 声明 pushButton 按钮数组 */
    QPushButton *pushButton[4];
};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置位置与大小 */
    this->setGeometry(0, 0, 800, 480);

    /* 实例化 */
    gWidget = new QWidget(this);
    /* 设置 gWidget 居中央 */
    this->setCentralWidget(gWidget);

    gridLayout = new QGridLayout();
    /* QList 链表,字符串类型 */
    QList <QString> list;
    list<<"按钮 1"<<"按钮 2"<<"按钮 3"<<"按钮 4";
    for (int i = 0; i < 4; i++){
        pushButton[i] = new QPushButton();
        pushButton[i]->setText(list[i]);
        /* 设置最小宽度与高度 */
            pushButton[i]->setMinimumSize(100, 30);
        /* 自动调整按钮的大小 */
            pushButton[i]->setSizePolicy(
                    QSizePolicy::Expanding,
                    QSizePolicy::Expanding
                );
        switch (i) {
        case 0:
            /* 将 pushButton[0]添加至网格的坐标(0,0),下同 */
            gridLayout->addWidget(pushButton[i], 0, 0);
            break;
        case 1:
            gridLayout->addWidget(pushButton[i], 0, 1);
            break;
        case 2:
            gridLayout->addWidget(pushButton[i], 1, 0);
            break;
        case 3:
            gridLayout->addWidget(pushButton[i], 1, 1);
            break;
        default:
                    break;
        }
        }
    /* 设置第 0 行与第 1 行的行比例系数 */
    gridLayout->setRowStretch(0, 2);
    gridLayout->setRowStretch(1, 3);
    /* 设置第 0 列与第 1 列的列比例系数 */
    gridLayout->setColumnStretch(0, 1);
    gridLayout->setColumnStretch(1, 3);

    /* 将 gridLayout 设置到 gWidget */
    gWidget->setLayout(gridLayout);
}

MainWindow::~MainWindow()
{
}

  1. 运行,程序编译运行的结果如下。可以看到在 gWidget 中添加了 4 个按钮,因为设置了行、列的
    系数比(拉伸因子),所以看到的按钮是按系数比的比例显示:
    在这里插入图片描述

7.5.3 QFormLayout

QFormLayout 继承 QLayout。QFormLayout 类管理输入小部件及其关联标签的表单。QFormLayout 是一个方便的布局类,它以两列的形式布局其子类。左列由标签组成,右列由“字段”小部件(QLineEdit(行编辑器)、QSpinBox(旋转框等))组成。通常使用setRowWrapPolicy(RowWrapPolicy policy)接口函数设置布局的换行策略进行布局等。

  1. mianwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QFormLayout>
#include <QLineEdit>
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    /* widget 对象 */
    QWidget *fWidget;
    /* 用于输入用户名 */
    QLineEdit *userLineEdit;
    /* 用于输入密码 */
    QLineEdit *passwordLineEdit;
    /* 声明 QFormLayout 对象 */
    QFormLayout *formLayout;
};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置位置与大小 */
    this->setGeometry(0, 0, 800, 480);
    /* 实例化及设置位置与大小,下同 */
    fWidget = new QWidget(this);
    fWidget->setGeometry(250, 100, 300, 200);
    userLineEdit = new QLineEdit();
    passwordLineEdit = new QLineEdit();
    formLayout = new QFormLayout();
    /* 添加行 */
    formLayout->addRow("用户名:", userLineEdit);
    formLayout->addRow("密码 :", passwordLineEdit);
    /* 设置水平垂直间距 */
    formLayout->setSpacing(10);
    /* 设置布局外框的宽度 */
    formLayout->setMargin(20);
    /* 将 formLayout 布局到 fWidget */
    fWidget->setLayout(formLayout);
}
MainWindow::~MainWindow()
{
}
  1. 可以看到在 fWidget 中添加了两行,同时设置了它们的间隔,与距边框的宽度。与 QGirdLayout 布局比较,QFomLayout 布局比较适用于行与列比较少的布局格局。如果是多行多列的布局,应该使用 QGirdLayout 布局
    在这里插入图片描述

7.6 空间间隔

空间间隔组(Spacers),如下图所示
在这里插入图片描述
(1) Horizontal Spacer:水平间隔
(2) Vertical Spacer:垂直间隔
QSpacerItem 继承 QLayoutItem。QSpacerItem 类在布局中提供空白(空间间隔)。所以QSpacerItem 是在布局中使用的。它包含 Horizontal Spacer(水平间隔)与 Vertical Spacer(垂直间隔)。

7.6.1 QSpacerItem

QSpacerItem 继承 QLayoutItem。QSpacerItem 类在布局中提供空白(空间间隔)。所以 QSpacerItem 是在布局中使用的。使用 4 个按钮,在垂直布局添加垂直间隔与按钮 1,在水平布局添加按钮 2~4 与水平间隔。简单的展示空间间隔布局的使用方法。在程序运行结果分析了空间间隔部分

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QPushButton>
#include <QSpacerItem>
#include <QBoxLayout>

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* 按钮对象数组 */
    QPushButton *bt[4];
    /* 垂直间隔 */
    QSpacerItem *vSpacer;
    /* 水平间隔 */
    QSpacerItem *hSpacer;
    /* 声明一个 widget 用来存放布局的内容 */
    QWidget *widget;
    /* 主布局对象 */
    QHBoxLayout *mainLayout;
    /* 垂直布局对象 */
    QVBoxLayout *vBoxLayout;
    /* 水平布局对象 */
    QHBoxLayout *hBoxLayout;
};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置主窗体显示位置与大小 */
    this->setGeometry(0, 0, 800, 480);

    widget = new QWidget(this);
    /* 居中 widget */

    this->setCentralWidget(widget);

    /* 实例化对象 */
    vSpacer = new QSpacerItem(10, 10,
                QSizePolicy::Minimum,
                QSizePolicy::Expanding
                );
    hSpacer = new QSpacerItem(10, 10,
                QSizePolicy::Expanding,
                QSizePolicy::Minimum
                );

    vBoxLayout = new QVBoxLayout();
    hBoxLayout = new QHBoxLayout();
    mainLayout = new QHBoxLayout();

    /* 在 vBoxLayout 添加垂直间隔 */
    vBoxLayout->addSpacerItem(vSpacer);

    QList <QString>list;
    /* 将字符串值插入 list */
    list<<"按钮 1"<<"按钮 2"<<"按钮 3"<<"按钮 4";
    /* 用一个循环实例化 4 个按钮 */
    for(int i = 0; i < 4 ; i++){
        bt[i] = new QPushButton();
        bt[i]->setText(list[i]);
        if (i == 0){
            /* 按钮 1,设置为 100*100 */
            bt[i]->setFixedSize(100, 100);
            /* 在 vBoxLayout 添加按钮 1 */
            vBoxLayout->addWidget(bt[i]);
        }
        else {
            /* 按钮 2~4,设置为 60*60 */
            bt[i]->setFixedSize(60, 60);
            /* 在 hBoxLayout 添加按钮 2~4 */
            hBoxLayout->addWidget(bt[i]);
        }
    }
    /* 在 hBoxLayout 添加水平间隔 */
    hBoxLayout->addSpacerItem(hSpacer);
    /* 在主布局里添加垂直布局 */
    mainLayout->addLayout(vBoxLayout);
    /* 在主布局里添加水平布局 */
    mainLayout->addLayout(hBoxLayout);
    /* 设置部件间距 */
    mainLayout->setSpacing(50);
    /* 将主布局设置为 widget 的布局 */
    widget->setLayout(mainLayout);
}
MainWindow::~MainWindow()
{
}

  1. 在垂直布局里添加了垂直空间间隔与按钮 1,在水平布局里添加了按钮 2~4 与水平空间间隔。
    在这里插入图片描述

7.7 容器

在这里插入图片描述
各个控件的名称依次解释如下。
(1) Group Box:组框
(2) Scroll Area:滚动区域
(3) Tool Box:工具箱
(4) Tab Widget:标签小部件
(5) Stacked WIdget:堆叠小部件
(6) Frame:帧
(7) Widget:小部件
(8) MDI Area:MDI 区域
(9) Dock Widget:停靠窗体部件
各种容器的解释如下:

  1. QGroupBox 继承 QWidget。QGroupBox 为构建分组框提供了支持。分组框通常带有一个边框和一个标题栏,作为容器部件来使用,在其中可以布置各种窗口部件。布局时可用作一组控件的容器,但是需要注意的是,内部通常使用布局控件(如 QBoxLayout)进行布局。组框还提供键盘快捷方式,键盘快捷方式将键盘焦点移动到组框的一个子部件。
  2. QScrollArea 继承 QAbstractScrollArea。滚动区域用于在框架中显示子部件的内容。如果小部件超过框架的大小,视图就会出现滚动条,以便可以查看子小部件的整个区域。
  3. QToolBox 继承 QFrame。QToolBox 类提供了一列选项卡小部件项。工具箱是一个小部件,它显示一列选项卡在另一列的上面,当前项显示在当前选项卡的下面。每个选项卡在选项卡列中都有一个索引位置。选项卡的项是 QWidget。
  4. QTabWidget 继承 QWidget。abWidget 类提供了一组选项卡(多页面)小部件。QTabWidget主要是用来分页显示的,每一页一个界面,众多界面公用一块区域,节省了界面大小,很方便的为用户显示更多的信息。
  5. QStackedWidget 继承 QFrame。QStackedWidget 类提供了一个小部件堆栈,其中一次只能看到一个小部件,与 QQ 的设置面板类似。QStackedWidget 可用于创建类似于 QTabWidget 提供的用户界面。它是构建在 QStackedLayout 类之上的一个方便的布局小部件。常与 QListWidget搭配使用,效果如下图,左边的是 QListWidget 列表,右边的是 QStackedWidget。他们一般与信号槽连接,通过点击左边的 QListWidget 列表,使用信号槽连接后,就可以让右边的QStackedWidget 显示不同的内容,每次显示一个 widget 小部件。
  6. QWidget 类是所有用户界面对象的基类(如 QLabel 类继承于 QFrame 类,而 QFrame 类又继承于 QWidget 类)。Widget 是用户界面的基本单元:它从窗口系统接收鼠标,键盘和其他事件,并在屏幕上绘制自己。每个 Widget 都是矩形的,它们按照 Z-order 进行排序。 注:Z-order是重叠二维对象的顺序,例如堆叠窗口管理器中的窗口。典型的 GUI 的特征之一是窗口可能重叠,使得一个窗口隐藏另一个窗口的一部分或全部。当两个窗口重叠时,它们的 Z 顺序确定哪个窗口出现在另一个窗口的顶部。理解:术语"z-order"指沿着 z 轴物体的顺序。三维坐标轴中 x横轴,y 数轴,z 上下轴。可以将 gui 窗口视为平行与显示平面的一系列平面。因此,窗口沿着z 轴堆叠。所以 z-order 指定了窗口的前后顺序。就像您桌面上的一叠纸一样,每张纸是一个窗口,桌面是您的屏幕,最上面的窗口 z 值最高。QWidget 不是一个抽象类,它可以用作其他 Widget的容器,并很容易作为子类来创建定制 Widget。它经常用于创建、放置和容纳其他的 Widget窗口。由上可知,QWidget 一般用于容纳其他 Widget 窗口,其属性和方法相当的多,对于初学者,我们通常只用它来作可以容纳其他窗口的容器,还会用来接收鼠标,键盘和其他事件等。
  7. QMdiArea 继承 QAbstractScrollArea。QMdiArea 小部件提供一个显示 MDI 窗口的区域。QMdiArea的功能本质上类似于MDI窗口的窗管理器。大多数复杂的程序,都使用MDI框架,在 Qt designer 中可以直接将控件 MDI Area 拖入使用。
  8. QDockWidget 继承 QWidget。QDockWidget 类提供了一个小部件,可以停靠在 QMainWindow 内,也可以作为桌面的顶级窗口浮动。QDockWidget 提供了停靠部件的概念,也称为工具面板或实用程序窗口。停靠窗口是放置在 QMainWindow 中央窗口附近的停靠窗口部件区域中的辅助窗口。停靠窗口可以被移动到当前区域内,移动到新的区域,并由终端用户浮动(例如,不停靠)。QDockWidget API 允许程序员限制 dock widget 的移动、浮动和关闭能力,以及它们可以放置的区域。QDockWidget 的初始停靠区域有 Qt.BottomDockWidgetArea(底部停靠)、Qt.LeftDockWidgetArea(左边停靠、Qt.RightDockWidgetArea(右边停靠)、Qt.TopDockWidgetArea(顶部停靠)和 Qt.NoDockWidgetArea(不显示 Widget)。

7.7.1 QGroupBox

QGroupBox 小部件提供一个带有标题的组框框架。一般与一组或者是同类型的部件一起使用。
使用 3 个 QRadioButton 单选框按钮,与QVBoxLayout(垂直布局)来展示组框的基本使用。

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QRadioButton>
#include <QGroupBox>
#include <QVBoxLayout>
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* 声明对象 */
    QGroupBox *groupBox;
    QVBoxLayout *vBoxLayout;
    QRadioButton *radioButton[3];
};
#endif // MAINWINDOW_H
  1. mainwindow.cpp
#include "mainwindow.h"
#include <QList>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置主窗体位置与大小 */
    this->setGeometry(0, 0, 800, 480);
    /* 以标题为“QGroupBox 示例”实例化 groupBox 对象 */
    groupBox = new QGroupBox(tr("QGroupBox 示例"), this);
    groupBox->setGeometry(300, 100, 300, 200);
    vBoxLayout = new QVBoxLayout();
    /* 字符串链表 */
    QList <QString>list;
    list<<"选项一"<<"选项二"<<"选项三";
    for(int i = 0; i < 3; i++){
        radioButton[i] = new QRadioButton();
        radioButton[i]->setText(list[i]);
        /* 在 vBoxLayout 添加 radioButton */
        vBoxLayout->addWidget(radioButton[i]);
    }
    /* 添加一个伸缩量 1 */
    vBoxLayout->addStretch(1);
    /* vBoxLayout 布局设置为 groupBox 布局 */
    groupBox->setLayout(vBoxLayout);
}
MainWindow::~MainWindow()
{
}
  1. 运行,可以看到 radioButton 有规则的排布在 groupBox 组框里面
    在这里插入图片描述

7.7.2 QScrollArea

QScrollArea 类提供到另一个小部件的滚动视图。
使用一个 Label 标签,将 Label 标签设置为一张图片,并把 Label 标签放置于滚动区域内,此时图片应要大于滚动区域才会出现滚动条,在新建例程中不要勾选“Generate form”,默认继承 QMainWindow 类即可。项目新建完成,并添加了一张资源图片

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QScrollArea>
#include <QLabel>
class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    /* 定义 QScrollArea 对象 */
    QScrollArea *scrollArea;
    QLabel *label;
};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    this->setGeometry(0, 0, 800, 480);
    scrollArea = new QScrollArea(this);
    /* 设置滚动区域为 700*380 */
    scrollArea->setGeometry(50, 50, 700, 380);
    label = new QLabel();
    /* label 显示的 lantingxu.png 图片分辨率为 1076*500 */
    QImage image(":/image/img1.jpg");
    label->setPixmap(QPixmap::fromImage(image));
    scrollArea->setWidget(label);
}
MainWindow::~MainWindow()
{
}
  1. 运行,由于图片的大小大于滚动区域的大小,所以在滚动区域出现了滚动条,可以拖动滚动条来查看这张图片其余部分。
    在这里插入图片描述

7.7.3 QToolBox

QToolBox(工具盒类)提供了一种列状的层叠窗体,中文译为工具箱,类似抽屉。
QQ 好友面板之 QToolBox(难度:简单),本例将使用到前面的知识QGroupBox 组框与 QBoxLayout 布局管理。前面我们已经学过 QGroupBox 组框和学过
QBoxLayout。有了前面的基础,那么去理解本例就会快很多。本例思路:使用 6 个 QToolButton 分成 2 组,使用垂直布局将 QToolButton 的 2 组排布好,然后添加到 2 组 QGroupBox 组框,再把 2 组 QGroupBox 组框作为子项添加到 QToolBox。

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QToolBox>
#include <QGroupBox>
#include <QToolButton>
#include <QVBoxLayout>
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* 声明对象 */
    QToolBox *toolBox;
    QGroupBox *groupBox[2];
    QVBoxLayout *vBoxLayout[2];
    QToolButton *toolButton[6];
};
#endif // MAINWINDOW_H
  1. mainwindow.cpp
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    this->setGeometry(0, 0, 800, 480);

    toolBox = new QToolBox(this);
    toolBox->setGeometry(300, 50, 200, 250);
    /* 设置 toolBox 的样式,此处设置为 30%不透明度的黑色 */
    toolBox->setStyleSheet("QToolBox {background-color:rgba(0, 0, 0, 30%);}");
    for(int i = 0; i < 2; i++){
        vBoxLayout[i] = new QVBoxLayout();
        groupBox[i] = new QGroupBox(this);
    }
    /* 字符串链表 */
    QList <QString>strList;
    strList<<"李白"<<"王照君"<<"李元芳"<<"程咬金"<<"钟馗"<<"上官婉儿";
    /* 字符串图标链表 */
    QList <QString>iconsList;
    iconsList<<":/icons/libai"<<":/icons/wangzhaojun"<<":/icons/liyuanfang"<<":/icons/chengyaojin"<<":/icons/zhongkui"<<":/icons/shangguanwaner";
    for(int i = 0; i < 6; i++){
        toolButton[i] = new QToolButton();
        /* 设置 toolButton 图标 */
        toolButton[i]->setIcon(QIcon(iconsList[i]));
        /* 设置 toolButton 的文本 */
        toolButton[i]->setText(strList[i]);
        /* 设置 toolButton 的大小 */
        toolButton[i]->setFixedSize(150, 40);
        /* 设置 toolButton 的 setToolButtonStyle 的样式 */
        toolButton[i]->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
        if( i < 3 ) {
            /* 将 toolButton 添加到时垂直布局 */
            vBoxLayout[0]->addWidget(toolButton[i]);
            /* 添加一个伸缩量 1 */
            vBoxLayout[0]->addStretch(1);
        }
        else {
            vBoxLayout[1]->addWidget(toolButton[i]);
            vBoxLayout[1]->addStretch(1);
        }
    }
    /* 将垂直布局的内容添加到组框 groupBox */
    groupBox[0]->setLayout(vBoxLayout[0]);
    groupBox[1]->setLayout(vBoxLayout[1]);
    /* 将组框加入 QToolBox 里 */
    toolBox->addItem(groupBox[0],"我的好友");
    toolBox->addItem(groupBox[1],"黑名单");
}
MainWindow::~MainWindow()
{
}
  1. 运行,本次使用 QToolButool 作为 QGroupBox 的子项,也可以使用其他 QWidget 小部件,如 QWidget 等。点击“我的好友”列表,则会出现好友列表,点击“黑名单”则会出现黑名单列表。
    在这里插入图片描述

7.7.4 QTabWidget

QTabWidget 继承 QWidget,QTabWidget 类提供了一组选项卡(多页面)小部件。QTabWidget 主要是用来分页显示的,每一页一个界面,众多界面公用一块区域,节省了界面大小,很方便的为用户显示更多的信息。类似浏览器的多标签页面,所以这个控件在实际项目中也会经常用到。
本例创建 3 个页面,每个页面里有一个 Label 标签部件,点击每个页面的选项卡则会切换到不同的页面上。

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTableWidget>
#include <QHBoxLayout>
#include <QLabel>
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* 声明对象 */
    QWidget *widget;
    QTabWidget *tabWidget;
    QHBoxLayout *hBoxLayout;
    QLabel *label[3];
};
#endif // MAINWINDOW_H
  1. mainwindow.cpp
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    this->setGeometry(0, 0, 800, 480);
    widget = new QWidget(this);
    /* 居中 */
    this->setCentralWidget(widget);

    /* 多页面小部件 */
    tabWidget = new QTabWidget();

    /* 水平布局实例化 */
    hBoxLayout = new QHBoxLayout();
    QList <QString>strLabelList;
    strLabelList<<"标签一"<<"标签二"<<"标签三";

    QList <QString>strTabList;
    strTabList<<"页面一"<<"页面二"<<"页面三";

    QList <QString>iconList;
    iconList<<":/icons/icon1"<<":/icons/icon2"<<":/icons/icon3";

    for (int i = 0; i < 3; i++) {
        label[i] = new QLabel();
        /* 设置标签文本 */
        label[i]->setText(strLabelList[i]);
        /* 标签对齐方式(居中) */
        label[i]->setAlignment(Qt::AlignCenter);
        /* 添加页面 */
        tabWidget->addTab(label[i],
            QIcon(iconList[i]),
            strTabList[i]
        );
    }
    /* 是否添加关闭按钮 */
    tabWidget->setTabsClosable(true);
    /* 将 tabWidget 水平直排布 */
    hBoxLayout->addWidget(tabWidget);
    /* 将垂直布局设置到 widget */
    widget->setLayout(hBoxLayout);
}

MainWindow::~MainWindow()
{
}

  1. 运行,点击不同页面的选项卡则会切换到不同的页面上。本例还可拓展使用 void setTabsClosable(bool closeable)函数在选项卡后加一个关闭按钮,再连接信号槽实现关闭页面的操作
    在这里插入图片描述

7.7.5 QStackedWidget

QStackedWidget 继承 QFrame。QStackedWidget 类提供了一个小部件堆栈,其中一次只能看到一个小部件,与 QQ 的设置面板类似。
QStackedWidget 可 用 于 创 建 类 似 于 QTabWidget 提供的用户界面。它是构建在QStackedLayout 类之上的一个方便的布局小部件。常与 QListWidget 搭配使用,效果如下图,左边的是 QListWidget 列表,右边的是 QStackedWidget。他们一般与信号槽连接,通过点击左边的 QListWidget 列表,使用信号槽连接后,就可以让右边的 QStackedWidget 显示不同的内容,每次显示一个 widget 小部件。
例 37_qstackedwidget,列表栏多页面切换(难度:简单),本例创建 3 个堆栈页面,每个页
面里有一个 Label 标签部件,点击每个列表的不同项则会切换到不同的页面上。

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QStackedWidget>
#include <QHBoxLayout>
#include <QListWidget>
#include <QLabel>


class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* widget 小部件 */
    QWidget *widget;
    /* 水平布局 */
    QHBoxLayout *hBoxLayout;
    /* 列表视图 */
    QListWidget *listWidget;
    /* 堆栈窗口部件 */
    QStackedWidget *stackedWidget;
    /* 3 个标签 */
    QLabel *label[3];
};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    this->setGeometry(0, 0, 800, 480);
    /* widget 小部件实例化 */
    widget = new QWidget(this);
    /* 设置居中 */
    this->setCentralWidget(widget);

    /* 垂直布局实例化 */
    hBoxLayout = new QHBoxLayout();

    /* 堆栈部件实例化 */
    stackedWidget = new QStackedWidget();

    /* 列表实例化 */
    listWidget = new QListWidget();

    QList <QString>strListWidgetList;
    strListWidgetList<<"窗口一"<<"窗口二"<<"窗口三";

    for (int i = 0; i < 3; i++){
        /* listWidget 插入项 */
        listWidget->insertItem(i,strListWidgetList[i]);
    }

    QList <QString>strLabelList;
    strLabelList<<"标签一"<<"标签二"<<"标签三";

    for (int i = 0; i < 3; i++){
        label[i] = new QLabel();
        /* 设置标签文本 */
        label[i]->setText(strLabelList[i]);
        /* 标签对齐方式(居中) */
        label[i]->setAlignment(Qt::AlignCenter);
        /* 添加页面 */
        stackedWidget->addWidget(label[i]);
    }

    /* 设置列表的最大宽度 */
    listWidget->setMaximumWidth(200);
    /* 添加到水平布局 */
    hBoxLayout->addWidget(listWidget);
    hBoxLayout->addWidget(stackedWidget);

    /* 将 widget 的布局设置成 hboxLayout */
    widget->setLayout(hBoxLayout);
    /* 利用 listWidget 的信号函数 currentRowChanged()与
    * 槽函数 setCurrentIndex(),进行信号与槽连接
    */
    connect(listWidget, SIGNAL(currentRowChanged(int)),
                stackedWidget, SLOT(setCurrentIndex(int)));
}

MainWindow::~MainWindow()
{
}


  1. 运行,点击列表视图的不同的项会切换到不同的页面上。-
    在这里插入图片描述

7.7.6 QFrame

在 7.3.5 小节已经举过例子。

7.7.7 QWidget

QWidget 类是所有用户界面对象的基类(如 QLabel 类继承于 QFrame 类,而 QFrame 类又继承于 QWidget 类)。Widget 是用户界面的基本单元:它从窗口系统接收鼠标,键盘和其他事件,并在屏幕上绘制自己。每个 Widget 都是矩形的,它们按照 Z-order 进行排序。
注:Z-order 是重叠二维对象的顺序,例如堆叠窗口管理器中的窗口。典型的 GUI 的特征之一是窗口可能重叠,使得一个窗口隐藏另一个窗口的一部分或全部。 当两个窗口重叠时,它们的 Z 顺序确定哪个窗口出现在另一个窗口的顶部。理解:术语"z-order"指沿着 z 轴物体的顺序。三维坐标轴中 x 横轴,y 数轴,z 上下轴。可以将 gui 窗口视为平行与显示平面的一系列平面。因此,窗口沿着 z 轴堆叠。所以 z-order 指定了窗口的前后顺序。就像您桌面上的一叠纸一样,每张纸是一个窗口,桌面是您的屏幕,最上面的窗口 z 值最高。
QWidget 不是一个抽象类,它可以用作其他 Widget 的容器,并很容易作为子类来创建定制Widget。它经常用于创建、放置和容纳其他的Widget 窗口
上面这么多例子都有用到 QWidget

7.7.8 QMdiArea

QMdiArea 继承 QAbstractScrollArea。QMdiArea 小部件提供一个显示 MDI 窗口的区域。QMdiArea的功能本质上类似于MDI窗口的窗口管理器。大多数复杂的程序,都使用MDI框架,在 Qt designer 中可以直接将控件 MDI Area 拖入使用。
本例创建一个 MDI Area 区域,使用一个按钮,每单击按钮时,就会在 MDI Area 区域新建一个 MdiSubWindow 窗口。

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QMdiSubWindow>
#include <QMdiArea>
#include <QPushButton>
class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* Mdi Area 区域对象 */
    QMdiArea *mdiArea;
    /* MdiSubWindow 子窗口对象 */
    QMdiSubWindow *newMdiSubWindow;
    /* 用作点击创建新的窗口 */
    QPushButton *pushButton;

private slots:
    /* 按钮槽函数 */
    void creat_newMdiSubWindow();
};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置窗口的显示位置与大小 */
    this->setGeometry(0, 0, 800, 480);
    pushButton = new QPushButton("新建窗口", this);
    pushButton->setGeometry(0, 30, 100, 30);

    mdiArea = new QMdiArea(this);
    /* 设置 MDI Area 区域大小 */
    mdiArea->setGeometry(100, 30, 700, 430);
    /* 连接信号槽 */
    connect(pushButton, SIGNAL(clicked()),
        this, SLOT(creat_newMdiSubWindow()));
}

void MainWindow::creat_newMdiSubWindow()
{
    newMdiSubWindow = new QMdiSubWindow();
    newMdiSubWindow->setWindowTitle("新建窗口");


    /* 如果窗口设置了 Qt::WA_DeleteOnClose 这个属性,
    * 在窗口接受了关闭事件后,Qt 会释放这个窗口所占用的资源
    */
    newMdiSubWindow->setAttribute(Qt::WA_DeleteOnClose);

    /* 添加子窗口 */
    mdiArea->addSubWindow(newMdiSubWindow);
    /* 显示窗口,不设置时为不显示 */
    newMdiSubWindow->show();
    /* 自适应窗口 */
    newMdiSubWindow->sizePolicy();
    /* 以级联的方式排列所有窗口 */
    // mdiArea->cascadeSubWindows();
    /* 以平铺方式排列所有窗口 */
    mdiArea->tileSubWindows();
}
MainWindow::~MainWindow()
{
}

  1. 运行,当点击新建窗口按钮时,在 MDI Area 区域里新建了一个窗口标题为“新建窗口”窗口,下图为点击2次新建窗口的效果。本例使用了一个按钮,进行了新建窗口操作,其他功能例如添加删除按钮等,可以自行添加。本文在新建窗口里的内容为空,newMdiSubWindow 可以使用 setWidget(QWidget *widget)方法添加内容,如添加前面所学过的 QLineEdit 等
    在这里插入图片描述

7.7.9 QDockWidget

QDockWidget继承QWidget。QDockWidget类提供了一个小部件,可以停靠在QMainWindow内,也可以作为桌面的顶级窗口浮动。QDockWidget 提供了停靠部件的概念,也称为工具面板或实用程序窗口。停靠窗口是放置在 QMainWindow 中央窗口附近的停靠窗口部件区域中的辅助窗口。停靠窗口可以被移动到当前区域内,移动到新的区域,并由终端用户浮动(例如,不停靠)。QDockWidget API 允许程序员限制 dock widget 的移动、浮动和关闭能力,以及它们可以放置的区域。QDockWidget 的初始停靠区域有 Qt.BottomDockWidgetArea(底部停靠)、Qt.LeftDockWidgetArea(左边停靠、;Qt.RightDockWidgetArea ( 右 边 停 靠 )、 Qt.TopDockWidgetArea ( 顶 部 停 靠 ) 和Qt.NoDockWidgetArea(不显示 Widget)。
本例创建一个停靠窗口,在停靠窗口里添加文本编辑框,并且把这个停靠窗口放置到 QMainWindow 的顶部。

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QDockWidget>
#include <QTextEdit>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* 停靠窗口部件 */
    QDockWidget *dockWidget;
    /* 文本编辑框 */
    QTextEdit *textEdit;
};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置主窗体的显示的位置与大小 */
    this->setGeometry(0, 0, 800, 480);

    /* 实例化标题为停靠窗口 */
    dockWidget = new QDockWidget("停靠窗口", this);

    /* 实例化文本编辑框 */
    textEdit = new QTextEdit(dockWidget);

    textEdit->setText("这是一个测试");

    /* 停靠窗口添加文本编辑框 */
    dockWidget->setWidget(textEdit);

    /* 放在主窗体的顶部 */
    this->addDockWidget(Qt::TopDockWidgetArea, dockWidget);

}

MainWindow::~MainWindow()
{
}


  1. 运行,拖动停靠窗口可以移动到另一个位置,松手则停靠。

在这里插入图片描述

7.8 项目视图组(基于模型)

在这里插入图片描述
以上各个控件的解释如下:
(1) List View:清单视图
(2) Tree View:树形视图
(3) Table View:表视图
(4) Column View:列表视图
(5) Undo View:撤销列表视图
以下是各个控件的简介:
QListView 继承 QAbstractItemView,被 QListWidget 和 QUndoView 继承。QListView 类提供模型上的列表或图标视图。QListView 以简单的非分层列表或图标集合的形式显示存储在模型中的项。该类用于提供以前由 QListBox 和 QIconView 类提供的列表和图标视图,但是使用
了 Qt 的模型/视图体系结构提供的更灵活的方法。QListView 是基于 Model,而 QListWidget 是基于 Item,这是它们的本质区别。QListView 需要建模,所以减少了冗余的数据,程序效率高;而 QListWidget 是一个升级版本的 QListView(实质是封装好了 model 的 QListView),它已经自己为我们建立了一个数据存储模型(QListWidgetItem),操作方便,直接调用 addItem 即可添加
项目(ICON,文字)。
QTreeView 继承 QAbstractItemView,被 QTreeWidget 继承。QTreeView 类提供树视图的默认模型/视图实现。QTreeView 实现了模型项的树表示。该类用于提供以前由 QListView 类提供的标准分层列表,但是使用了 Qt 的模型/视图体系结构提供的更灵活的方法。
QTableView 继承 QAbstractItemView,被 QTableWidget 继承。QTableView 类提供了表视图的默认模型/视图实现。QTableView 实现了一个表视图,用于显示来自模型的项。该类用于提供以前由 QTable 类提供的标准表,但使用 Qt 的模型/视图体系结构提供的更灵活的方法。
QColumnView 继承 QAbstractItemView。QColumnView 在许多 QListViews 中显示一个模型,每个 QListViews 对应树中的每个层次结构。这有时被称为级联列表。QColumnView 类是模型/视图类之一,是 Qt 模型/视图框架的一部分。QColumnView 实现了由 QAbstractItemView 类定义的接口,以允许它显示由派生自 QAbstractItemModel 类的模型提供的数据。
QUndoView 继承 QlistView。QUndoView 类显示 QUndoStack 的内容。QUndoView 是一个QListView,它显示在撤销堆栈上推送的命令列表。总是选择最近执行的命令。选择不同的命令会导致调用QUndoStack::setIndex(),将文档的状态向后或向前滚动到新命令。可以使用setStack()显式地设置堆栈。或者,可以使用 setGroup()来设置 QUndoGroup 对象。当组的活动堆栈发生
变化时,视图将自动更新自身。

7.8.1 QListView

QListView 继承 QAbstractItemView,被 QListWidget 和 QUndoView 继承。QListView 类提供模型上的列表或图标视图。QListView 以简单的非分层列表或图标集合的形式显示存储在模型中的项。该类用于提供以前由 QListBox 和 QIconView 类提供的列表和图标视图,但是使用
了 Qt 的模型/视图体系结构提供的更灵活的方法。QListView 是基于 Model,而 QListWidget 是基于 Item,这是它们的本质区别。QListView 需要建模,所以减少了冗余的数据,程序效率高;而 QListWidget 是一个升级版本的 QListView(实质是封装好了 model 的 QListView),它已经自己为我们建立了一个数据存储模型(QListWidgetItem),操作方便,直接调用 addItem 即可添加
项目(ICON,文字)。
QT 提供了一些现成的 models 用于处理数据项(这些是 Qt 处理数据模型的精华,如果用到Qt 数据模型,下面这些是经常要用到的):
(1) QStringListModel 用于存储简单的 QString 列表。
(2) QStandardItemModel 管理复杂的树型结构数据项,每项都可以包含任意数据。
(3) QDirModel 提供本地文件系统中的文件与目录信息。
(4) QSqlQueryModel, QSqlTableModel,QSqlRelationTableModel 用来访问数据库。
用法示例。使用一个 ListView 控件,展示如何在列表中插入数据,其中表中的内容可编辑,可删除。

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QListView>
#include <QStringListModel>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* QListView 对象 */
    QListView *listView;
    /* 字符串模型对象 */
    QStringListModel *stringListModel;
};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置主窗口位置与大小 */
    this->setGeometry(0, 0, 800, 480);
    /* 实例化 */
    listView = new QListView(this);
    /* 将 listView 居中 */
    setCentralWidget(listView);
    QStringList strList;
    strList<<"高三(1)班"<<"高三(2)班"<<"高三(3)班";
    /* 实例化,字符串模型 */
    stringListModel = new QStringListModel(strList);
    /* 向表中插入一段数据 */
    listView->setModel(stringListModel);
    /* 设置为视图为图标模式 */
    listView->setViewMode(QListView::IconMode);
    /* 设置为不可拖动 */
    listView->setDragEnabled(false);
}
MainWindow::~MainWindow()
{
}
  1. 运行,。双击表格中的项,可修改表格的内容,同时也可以删除内容等。
    在这里插入图片描述

7.8.2 QTreeView

QTreeView 继承 QAbstractItemView,被 QTreeWidget 继承。QTreeView 类提供树视图的默认模型/视图实现。QTreeView 实现了模型项的树表示。该类用于提供以前由 QListView 类提供的标准分层列表,但是使用了 Qt 的模型/视图体系结构提供的更灵活的方法
仿 word 标题(难度:简单)。要使一个 QTreeView 能够显示数据,需要构造一个 model 并设置 QTreeView。Qt 提供了一些类型的Model,其中最常用的就是这个QStandardItemModel 类,一般可以满足大部分需求。另外,表头的内容也由这个 model 管理,setHorizontalHeaderLabels 函数可以设置共有多少列、每列文字。一级标题直接使用 appendRow方法添加到 model 上,次级标题则是添加到第一个父级标题上,依次构成父子关系树

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTreeView>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    QTreeView *treeView;
};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
#include "mainwindow.h"

#include <QStandardItemModel>
#include <QStandardItem>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置窗口的位置与大小 */
    this->setGeometry(0, 0, 800, 480);
    /* 实例化 QTreeView 对象 */
    treeView = new QTreeView(this);
    /* 居中 */
    setCentralWidget(treeView);

    /* 构建 Model */
    QStandardItemModel *sdiModel = new QStandardItemModel(treeView);
    sdiModel->setHorizontalHeaderLabels(
        QStringList()<<QStringLiteral("标题") << QStringLiteral("名称"));

    for(int i = 0; i < 5; i++) {
        /* 一级标题 */
        QList<QStandardItem*> items1;
        QStandardItem* item1 =new QStandardItem(QString::number(i));
        QStandardItem* item2 =new QStandardItem(QStringLiteral("一级标题"));
        /* 添加项一 */
        items1.append(item1);
        /* 添加项二 */
        items1.append(item2);
        /* appendRow 方法添加到 model 上 */
        sdiModel->appendRow(items1);

        for(int j = 0; j < 5; j++) {
            /* 在一级标题后面插入二级标题 */
            QList<QStandardItem*> items2;
            QStandardItem* item3 =new QStandardItem(QString::number(j));
            QStandardItem* item4 =new QStandardItem(QStringLiteral("二级标题"));
            items2.append(item3);
            items2.append(item4);
            /* 使用 appendRow 方法添加到 item1 上 */
            item1->appendRow(items2);
        }
    }
    /* 设置 Model 给 treeView */
    treeView->setModel(sdiModel);
}

MainWindow::~MainWindow()
{
}
  1. 运行,可以点击数字来展开二级标题。
    在这里插入图片描述

7.8.3 QTableView

QTableView 继承 QAbstractItemView,被 QTableWidget 继承。QTableView 类提供了表视图的默认模型/视图实现。QTableView 实现了一个表视图,用于显示来自模型的项。该类用于提供以前由 QTable 类提供的标准表,但使用 Qt 的模型/视图体系结构提供的更灵活的方法。
例 42_qtableview,表格视图(难度:简单)。要使一个 QTableView 能够显示数据,需要构造一个 model 并设置给 QTableView。Qt 提供了一些类型的 Model,其中最常用的就是这个QStandardItemModel 类,一般可以满足大部分需求。另外,表头的内容也由这个 model 管理,setHorizontalHeaderLabels 函数可以设置共有多少列、每列文字

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTableView>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    QTableView *tableView;
};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
#include "mainwindow.h"
#include <QStandardItemModel>
#include <QHeaderView>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置窗口的位置与大小 */
    this->setGeometry(0, 0, 800, 480);
    tableView = new QTableView(this);
    setCentralWidget(tableView);
    /* 显示网格线 */
    tableView->setShowGrid(true);

    QStandardItemModel* model = new QStandardItemModel();
    QStringList labels =QObject::tr("语文,数学,英语").simplified().split(",");
    /* 设置水平头标签 */
    model->setHorizontalHeaderLabels(labels);

    /* item */
    QStandardItem* item = 0;
    /* model 插入项内容 */
    for(int i = 0; i < 5; i++){
        item = new QStandardItem("80");
        model->setItem(i, 0, item);
        item = new QStandardItem("99");
        model->setItem(i, 1, item);
        item = new QStandardItem("100");
        model->setItem(i, 2, item);
    }
    /* 将 model 设置给 tableView */
    tableView->setModel(model);
    /* 平均分列 */
    tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
    /* 平均分行 */
    tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch);
}

MainWindow::~MainWindow()
{
}


  1. 运行,建立了一个成绩表格。
    在这里插入图片描述

7.8.4 QColumnView

QColumnView 继承 QAbstractItemView。QColumnView 在许多 QListViews 中显示一个模型,每个 QListViews 对应树中的每个层次结构。这有时被称为级联列表。QColumnView 类是模型/视图类之一,是 Qt 模型/视图框架的一部分。QColumnView 实现了由 QAbstractItemView 类定义的接口,以允许它显示由派生自 QAbstractItemModel 类的模型提供的数据。
使用一个 QColumnView,向其插入多级QStandardItem。这样就可以模拟成一个多级联的视图。与我们在像某宝,某东里填写的收货地
址十分类似。

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QColumnView>
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    QColumnView *columnView;
};
#endif // MAINWINDOW_H
  1. mainwindow.cpp
#include "mainwindow.h"
#include <QStandardItem>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置主窗体显示位置与大小 */
    this->setGeometry(0, 0, 800, 480);
    QStandardItemModel *model = new QStandardItemModel;
    /* 省份 */
    QStandardItem *province = new QStandardItem("广东省");
    /* 城市 */
    QStandardItem *city1 = new QStandardItem("茂名市");
    QStandardItem *city2 = new QStandardItem("中山市");
    /* 添加城市到省份下 */
    province->appendRow(city1);
    province->appendRow(city2);
    QStandardItem *town1 = new QStandardItem("电白镇");
    QStandardItem *town2 = new QStandardItem("南头镇");
    /* 添加城镇到城市下 */
    city1->appendRow(town1);
    city2->appendRow(town2);
    columnView = new QColumnView;
    /* 建立 model */
    model->appendRow(province);
    /* 设置 model */
    columnView->setModel(model);
    /* 设置居中 */
    setCentralWidget(columnView);
}
MainWindow::~MainWindow()
{
}
  1. 运行,当点击省份出现城市,点击城市出现城镇。
    在这里插入图片描述

7.8.5 QUndoView

QUndoView 继承 QlistView。QUndoView 类显示 QUndoStack 的内容。QUndoView 是一个QListView,它显示在撤销堆栈上推送的命令列表。总是选择最近执行的命令。选择不同的命令会导致调用QUndoStack::setIndex(),将文档的状态向后或向前滚动到新命令。可以使用setStack()显式地设置堆栈。或者,可以使用 setGroup()来设置 QUndoGroup 对象。当组的活动堆栈发生
变化时,视图将自动更新自身。
例 44_qundoview,仿 PS 历史记录(难度:一般)。如果大家学习过 PS,都知道 PS 里有个历史记录面板,点击就会撤回到历史记录的步骤。例子其实也可以参考Qt官方提供的例子“Undo Framework Example”。但是官方的例子过于复杂,整个理解下来需要花费大量时间。于是笔者写一个仿 PS 历史记录的例子来加深大家对 QUndoView 的理解。
在新建例程中不要勾选“Generate form”,默认继承 QMainWindow 类即可。同时添加了两个新建cpp文件, 命名 command.h 和 command.cpp 文件,用于重写 QUndoCommand。项目新建完成,如下图。
在这里插入图片描述

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QUndoView>
#include <QUndoStack>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QPushButton>
#include <QLabel>
#include <command.h>

class MainWindow : public QMainWindow
{
    Q_OBJECT
    
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* 水平布局 */
    QHBoxLayout *hLayout;
    /* 水平布局 */
    QVBoxLayout *vLayout;
    /* 用于容纳 hLayout 布局 */
    QWidget *mainWidget;
    /* 容器作用 QWidget,用于容纳标签与按钮 */
    QWidget *widget;
    /* 存放 QUndoCommand 命令的栈 */
    QUndoStack *undoStack;
    /* 历史记录面板 */
    QUndoView *undoView;
    /* 用于显示计算结果 */
    QLabel *label;
    /* 按钮 */
    QPushButton *pushButton;
    /* 计算结果 */
    int count;
    
private slots:
    void pushButtonClieked();
    void showCountValue(int);
};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
#include "mainwindow.h"
#include <QDebug>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置主窗体显示的位置与大小 */
    this->setGeometry(0, 0, 800, 480);

    /* 实例一个水平布局,用于左侧按钮区域与右侧历史记录面板 */
    hLayout = new QHBoxLayout();

    /* 实例一个水平布局,用于左侧标签与按钮 */
    vLayout = new QVBoxLayout();

    /* 主 Widget, 因为 MainWindow 自带一个布局,
    * 我们要新建一个 Widget 容纳新布局
    */
    mainWidget = new QWidget();

    /* 用于存放命令行栈 */
    undoStack = new QUndoStack(this);

    /* 用于容纳左侧标签与按钮布局 */
    widget = new QWidget();

    /* 历史记录面板实例化 */
    undoView = new QUndoView(undoStack);

    /* 实例一个按钮,用于加一操作 */
    pushButton = new QPushButton();

    /* 标签,用于显示计算结果 */
    label = new QLabel();

    /* 设置 widget 的大小 */
    widget->setMinimumSize(400, 480);


    /* 将两个 widget 添加到水平布局 */
    hLayout->addWidget(widget);
    hLayout->addWidget(undoView);
    /* 初始化 count 的值 */
    count = 0;

    /* 显示初始化计算结果 */
    label->setText("计算结果:" + QString::number(count));
    label->setAlignment(Qt::AlignCenter);

    /* 左侧布局 */
    vLayout->addWidget(label);
    vLayout->addWidget(pushButton);

    /* 左侧布局控件的高度设置 */
    label->setMaximumHeight(this->height() / 5);
    pushButton->setMaximumHeight(this->height() / 5);

    /* 按钮文件设置 */
    pushButton->setText("加 1");
    /* 设置 widget 的布局为 vLayout */
    widget->setLayout(vLayout);

    /* 将主窗体的布局设置为 hLayout */
    mainWidget->setLayout(hLayout);

    /* 设置 mainWidget 为主窗体的居中 widget */
    this->setCentralWidget(mainWidget);

    /* 信号槽连接,按钮点击,执行加一操作 */
    connect(pushButton, SIGNAL(clicked()), this,
        SLOT(pushButtonClieked()));
    /* 信号槽连接,历史记录项 index 发生变化,显示 count 大小 */
    connect(undoStack, SIGNAL(indexChanged(int) ),
        this, SLOT(showCountValue(int)));
}

/* 入栈操作会自动调用 addCommand 的 redo */
void MainWindow::pushButtonClieked()
{
    /* 变量值加一 */
    count++;

    /* value 指向 count 的地址 */
    int *value = &count;

    /* 用重写的 addCommand 类实例化 */
    QUndoCommand *add = new addCommand(value);

    /* 入栈 */
    undoStack->push(add);
}

void MainWindow::showCountValue(int)
{
    /* 标签用于显示计算结果 */
    label->setText("计算结果:" + QString::number(count));
}


MainWindow::~MainWindow()
{
}

  1. command.h
#ifndef COMMAND_H
#define COMMAND_H

#include <QUndoCommand>
#include <QObject>


class addCommand : public QUndoCommand
{
public:
    addCommand(int *value, QUndoCommand* parent = 0);
    ~addCommand();

    /* 重写重做与撤回方法 */
    void redo() override;
    void undo() override;

private:
    /* 新的 count */
    int *new_count;

    /* 旧的 count */
    int old_count;
};

#endif // COMMAND_H

  1. command.cpp
#include "command.h"
#include <QDebug>

addCommand::addCommand(int *value, QUndoCommand *parent)
{
    /* 使用 Q_UNUSED,避免未使用的数据类型 */
    Q_UNUSED(parent);

    /* undoView 显示的操作信息 */
    setText("进行了加 1 操作");

    /* value 的地址赋值给 new_count */
    new_count = value;

    /* 让构造函数传过来的*new_count 的值赋值给 old_count */
    old_count = *new_count;
}


/* 执行 stack push 时或者重做操作时会自动调用 */
void addCommand::redo()
{
    /* 重新赋值给 new_count */
    *new_count = old_count;

    /* 打印出*new_count 的值 */
    qDebug()<<"redo:"<<*new_count<<endl;
}

/* 回撤操作时执行 */
void addCommand::undo()
{
    /* 回撤操作每次应减一 */
    (*new_count)--;
    /* 打印出*new_count 的值 */
    qDebug()<<"undo:"<<*new_count<<endl;
}

addCommand::~addCommand()
{

}

  1. 运行,点击“加 1”按钮,计算结果将加 1,用方向键或者鼠标进行选择右边的历史记录面板将进行重做或者回撤操作,同时应用程序输出窗口打印出 debug 信息,计算结果也将回到该步时的计算结果。
    在这里插入图片描述

7.9 项目控件组(基于项)

在这里插入图片描述
在上一小节学习过视图组,下面学习控件组。仔细观察视图组里的某几个控件与控件组的控件名字相似。以 QListWidget 为例,QListWidget 就是继承 QListView。QListView 是基于模型的,而 QListWidget 是基于项的。两种控件在不同的场合可以酌情选择使用!一般处理大数据使用基于模型的多。视图组与控件组的控件在 Qt 里展示数据时是会经常使用的!大家要掌握它们的使用方法。
以上各个控件的解释如下:
(1) List Widget:清单控件
(2) TreeWidget:树形控件
(3) Table Widget:表控件
以下是各个控件的简介:
QListWidget 继承 QListView。QListWidget 类提供了一个基于项的列表小部件。QListWidget 是一个便捷的类,它提供了一个类似于 QListView 提供的列表视图,但是提供了一个用于添加和删除项目的基于项目的经典接口。QListWidget 使用内部模型来管理列表中的每个 QListWidgetItem。QListView 是基于 model 的,需要自己来建模(例如建立 QStringListModel、QSqlTableModel 等),保存数据,这样就大大降低了数据冗余,提高了程序的效率,但是需要我们对数据建模有一定了解,而 QListWidget 是一个升级版本的 QListView,它已经自己为我们建立了一个数据存储模型(QListWidgetItem),操作方便,直接调用 addItem 即可添加项目(ICON,
文字)。
QTreeWidget 继承 QTreeView。QTreeWidget 类提供了一个使用预定义树模型的树视图。QTreeWidget 类是一个便捷的类,它提供了一个标准的树小部件,具有一个类似于 qt3 中的 QListView 类所使用的基于项目的经典接口。该类基于 Qt 的模型/视图体系结构,并使用默认模型来保存项,每个项都是 QTreeWidgetItem。
QTableWidget 继承 QTableView。QTableWidget 类提供了一个带有默认模型的基于项的表视图。表小部件为应用程序提供标准的表显示工具。QTableWidget 中的项由 QTableWidgetItem 提供.

7.9.1 QListWidget

QListWidget 继承 QListView。QListWidget 类提供了一个基于项的列表小部件。QListWidget 是一个便捷的类,它提供了一个类似于 QListView(下一小节将讲到)提供的列表视图,但是提供了一个用于添加和删除项目的基于项目的经典接口。QListWidget 使用内部模型来管理列表中的每个 QListWidgetItem。
本例使用一个 QListWidget 以及一个按钮,当单击按钮时,就会调用系统打开文件窗口,过滤 mp3 后缀的文件(本例使用 touch 指令创建
2 个 mp3 后缀的文件,并不是真正的歌曲,在终端输入指令为 touch 0.mp3 1.mp3,本例在项目下已经创建了两个以 mp3 为后缀的文件),当打开系统文件选择框时,就会选择这两个 mp3 文件作为 QListWidget 的项添加到 QListWidget 的窗口中。(PS:我们写音乐播放器就要用到这种操作—打开歌曲。实际本例就是一个打开歌曲的代码部分。)

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QListWidget>
#include <QPushButton>

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* 声明对象 */
    QListWidget *listWidget;
    QPushButton *pushButton;
private slots:
    void pushButtonClicked();
};
#endif // MAINWINDOW_H
  1. mainwindow.cpp
#include "mainwindow.h"
#include "QFileDialog"
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置主窗口的显示位置与大小 */
    this->setGeometry(0, 0, 800, 480);
    listWidget = new QListWidget(this);
    /* 设置 listWidget 的大小 */
    listWidget->setGeometry(0, 0, 480, 480);
    listWidget->addItem("请单击右边的添加项添加内容");
    pushButton = new QPushButton(this);
    /* 设置 pushButton 的位置与大小 */
    pushButton->setGeometry(540, 200, 200, 100);
    pushButton->setText("添加项");
    /* 信号与槽连接 */
    connect(pushButton, SIGNAL(clicked()),
            this, SLOT(pushButtonClicked()));
}
void MainWindow::pushButtonClicked()
{
    /* 调用系统打开文件窗口,设置窗口标题为“打开文件”,过滤文件名 */
    QString fileName = QFileDialog::getOpenFileName(
        this,tr("添加项"),"",
        tr("Files(*.mp3)")
        );
    /* 判断是否选中打开 mp3 文件 */
    if (fileName != NULL)
    /* 添加项到列表中 */
    listWidget->addItem(fileName);
}
MainWindow::~MainWindow()
{
}
  1. 运行,当点击添加项按钮时出现系统选择文件的对话框,系统打开文件时会过滤 mp3 后缀的文件,点击后缀为 mp3 的文件,双击或者选择后再点击右上角的“Open”的打开来把这个文件添加到左边的 QListWidget 列表中。读者可以模仿这个示例,还可以添加删除项的按钮,或者删除全部按钮等,酷狗音乐播放器的的歌单与此类似。后续继续学习如何播放音乐,与此结合,就可以做出一款音乐播放器了。
    在这里插入图片描述

7.9.2 QTreeWidget

QTreeWidget 继承 QTreeView。QTreeWidget 类提供了一个使用预定义树模型的树视图。QTreeWidget 类是一个便捷的类,它提供了一个标准的树小部件,具有一个类似于 qt3 中的 QListView 类所使用的基于项目的经典接口。该类基于 Qt 的模型/视图体系结构,并使用默认模型来保存项,每个项都是 QTreeWidgetItem。
群发信息,本例使用一个 TreeWidget,模拟成一个飞信联系人分组,通过选中组内联系人来“群发”信息。实际并不是真正做一个群发信息的飞信,只是模拟飞信群发信息时选择联系人的场景,通过例子来熟悉 QTreeWidget 的使用。本例相对前面的例子稍长,出现了树节点与子节点的概念。本例的思路:当选中顶层的树形节点时,子节点全部被选中;当取消选择顶层树形节点时,子节点原来选中的状态将全部取消;当不完全选中子节点时,树节点显示为半选状态

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTreeWidget>
#include <QTreeWidgetItem>
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* QTreeWidget 对象 */
    QTreeWidget *treeWidget;
    /* 顶层树节点 */
    QTreeWidgetItem *parentItem;
    /* 声明三个子节点 */
    QTreeWidgetItem *subItem[3];
    /* 子节点处理函数 */
    void updateParentItem(QTreeWidgetItem*);
private slots:
    /* 槽函数 */
    void treeItemChanged(QTreeWidgetItem*, int);
};
#endif // MAINWINDOW_H
  1. mainwindow.cpp
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置主窗体大小 */
    this->setGeometry(0, 0, 800, 480);
    /* 实例化 */
    treeWidget = new QTreeWidget(this);
    /* 居中 */
    setCentralWidget(treeWidget);
    /* 清空列表 */
    treeWidget->clear();
    /* 实例化顶层树节点 */
    parentItem = new QTreeWidgetItem(treeWidget);
    parentItem->setText(0, "同事");

    parentItem->setFlags(
        Qt::ItemIsUserCheckable
        | Qt::ItemIsEnabled
        | Qt::ItemIsSelectable
        );
    /* 树节点设置为未选中 */
    parentItem->setCheckState(0, Qt::Unchecked);

    /* 字符串链表 */
    QList <QString> strList;
    strList<<"关羽"<<"刘备"<<"张飞";

    for (int i = 0; i < 3; i++){
        /* 实例化子节点 */
        subItem[i] = new QTreeWidgetItem(parentItem);
        /* 设置子节点的文本,参数 0 代表第 0 列 */
        subItem[i]->setText(0, strList[i]);
        /* 设置子节点的属性为用户可选、项开启、项可选 */
        subItem[i]->setFlags(
            Qt::ItemIsUserCheckable
            | Qt::ItemIsEnabled
            | Qt::ItemIsSelectable
        );
        /* 设置子节点的状态为未选中 */
        subItem[i]->setCheckState(0,Qt::Unchecked);
    }
    /* 信号槽连接 */
    connect(treeWidget,SIGNAL(itemChanged(QTreeWidgetItem* , int)),
                this, SLOT(treeItemChanged(QTreeWidgetItem* , int)));

}
/* 更新树节点函数 */
void MainWindow::updateParentItem(QTreeWidgetItem *item)
{
    /* 获取子节点的父节点(树节点) */
    QTreeWidgetItem* parent = item->parent();
    if(parent == NULL){
        return;
    }
    /* 初始化选中的数目为 0,下面根据 selectCount 来判断树节点的状态 */
    int selectCount = 0;
    /* 获取树节点的子节点总数 */
    int childCount = parent->childCount();
    /* 循环判断子节点的状态 */
    for(int i = 0; i < childCount; i ++){
        QTreeWidgetItem* childItem =parent->child(i);
        /* 判断当前子节点的状是否为选中状态,如果是,则加一 */
        if(childItem->checkState(0) == Qt::Checked) {
            selectCount ++;
        }
     }
    /* 根据 selectCount 来判断树节点的状态 */
    /* 当选中的子节点小于或等于 0 时,则为设置树节点为未选中状态 */

    if (selectCount <= 0) {
        /* 设置树节点为未选中状态 */
        parent->setCheckState(0, Qt::Unchecked);
        /* 部分选中时,树节点为半选状态 */
        } else if (selectCount > 0 && selectCount < childCount) {
        /* 设置为半选状态 */
        parent->setCheckState(0, Qt::PartiallyChecked);
        /* 子节点全选时 */
        } else if (selectCount == childCount){
        /* 设置为树节点为选中状态 */
        parent->setCheckState(0, Qt::Checked);
        }
    }

void MainWindow::treeItemChanged(QTreeWidgetItem *item, int)
{
    /* 获取子节点总数 */
    int count = item->childCount();

    /* 若顶层树节点选中 */
    if(Qt::Checked == item->checkState(0) ) {
    /* 若选中的项是树节点,count 会大于 0,否则选中的项是子节点 */
        if (count > 0) {
            for (int i = 0; i < count; i++) {
                /* 子节点全选 */
                item->child(i)->setCheckState(0, Qt::Checked);
                }
        } else {
            /* 子节点处理 */
            updateParentItem(item);
            }
        /* 若顶层树节点取消选中时 */
        } else if (Qt::Unchecked == item->checkState(0)) {
            if (count > 0){
                /* 若选中的项是树节点,count 会大于 0,否则选中的项是子节点 */
                for (int i = 0; i < count; i++) {
                    /* 子节点全不选 */
                    item->child(i)->setCheckState(0, Qt::Unchecked);
                }
            } else {
                /* 子节点处理 */
                updateParentItem(item);

                }
            }
}

MainWindow::~MainWindow()
{
}
  1. 运行
    在这里插入图片描述

7.9.3 QTableWidget

QTableWidget 继承 QTableView。QTableWidget 类提供了一个带有默认模型的基于项的表视图。表小部件为应用程序提供标准的表显示工具。QTableWidget 中的项由 QTableWidgetItem 提供。
本例使用一个 TableWidget,绘制一个表格,同时修改项的标题,在表格里可以直接通过双击进行编辑项里的内容,也可以删除项里的内容等。

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTableWidget>
class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* QTabelWidget 表格 */
    QTableWidget *tableWidget;

    /* QTabelWidgetItem 表格数据(项) */
    QTableWidgetItem *tableWidgetItem[4];
};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置主窗体的大小与位置 */
    this->setGeometry(0, 0, 800, 480);

    /* 实例化 */
    tableWidget = new QTableWidget(this);
    /* 设置 tableWidget 表居中 */
    setCentralWidget(tableWidget);
    /* 设置列数 */
    tableWidget->setColumnCount(2);
    /* 设置行数 */
    tableWidget->setRowCount(2);
    /* 使用标签设置水平标题标签 */
    tableWidget->setHorizontalHeaderLabels(
        QStringList()<<"姓名"<<"性别"
       );
    /* 字符串类型链表 */
    QList <QString> strList;
    strList<<"小明"<<"小红"<<"男"<<"女";
    for (int i = 0; i < 4; i++) {
        /* 实例化 */
        tableWidgetItem[i] = new QTableWidgetItem(strList[i]);
        /* 设置文本居中对齐 */
        tableWidgetItem[i]->setTextAlignment(Qt::AlignCenter);
        }
    /* 插入数据,表的 index 就是一个二维数组数据 */
    tableWidget->setItem(0, 0, tableWidgetItem[0]);
    tableWidget->setItem(1, 0, tableWidgetItem[1]);
    tableWidget->setItem(0, 1, tableWidgetItem[2]);
    tableWidget->setItem(1, 1, tableWidgetItem[3]);
}

MainWindow::~MainWindow()
{
}
  1. 运行。双击表格中的项,可修改表格的内容,同时也可以删除内容等。
    在这里插入图片描述

8.文本读写

8.1 QFile 读写文本

QFile 类提供了读取和写入文件的接口。在嵌入式里如果需要读写文件,最简单的方法就是用 Qfile,在第 14.2 和 14.3 小节就是利用了 QFile 来读写 Linux 下的字符设备(可把字符设备当作一个文本处理,linux 下一切皆文件),虽然只是写‘0’或‘1’,但也是对文件(文本)的读写了。
QFile 是一个读写文本、二进制文件和资源的 I/O 设备。QFile 可以自己使用,也可以更方便地与 QTextStream 或 QDataStream 一起使用。
文件名通常在构造函数中传递,但它可以在任何时候使用 setFileName()设置。不支持使用其他分隔符(例如’‘)。所以在 Windows、 Linux 或者 Mac 里文件的路径都是用’/‘。不能看到Windows 的路径是’‘,我们就可以在写入的文件路径里添加这个’‘。不管操作系统是什么,QFile
的文件分隔符都是’/'。可以使用 exists()检查文件是否存在,并使用 remove()删除文件。(更高级的文件系统相关操作由 QFileInfo 和 QDir 提供。)用 open()打开文件,用 close()关闭文件,用 flush()刷新文件。通常使用 QDataStream 或 QTextStream 读写数据,但也可以调用 QIODevice 继承的函数 read()、readLine()、readAll()、write()。QFile 还继承 getChar()、putChar()和 ungetChar(),它们一次只处
理一个字符。文件的大小由 size()返回。可以使用 pos()获取当前文件位置,也可以使用 seek()移动到新的文件位置。如果已经到达文件的末尾,则 atEnd()返回 true。
QFile::open()函数打开文件时需要传递 QIODevice::OpenModeFlag 枚举类型的参数,决定文件以什么方式打开QIODevice::OpenModeFlag 类型的主要取值如下:

  • QIODevice::ReadOnly:以只读方式打开文件,用于载入文件。
  • QIODevice::WriteOnly:以只写方式打开文件,用于保存文件。
  • QIODevice::ReadWrite:以读写方式打开。
  • QIODevice::Append:以添加模式打开,新写入文件的数据添加到文件尾部。
  • QIODevice::Truncate:以截取方式打开文件,文件原有的内容全部被删除。
  • QIODevice::Text:以文本方式打开文件,读取时“\n”被自动翻译为换行符,写入时字符串结束符会自动翻译为系统平台的编码,如 Windows 平台下是“\r\n”。这些取值可以组合,例如 QIODevice::ReadOnly | QIODevice::Text 表示以只读和文本方式
    打开文件。
    使用 QFile 对一个文本文件的操作流程是以下这样的。
    在这里插入图片描述
  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTextEdit>
#include <QFile>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    /* 用于读取文件后显示 */
    QTextEdit *textEdit;
    /* QFile 类型对象 */
    QFile file;

    /* 水平布局 */
    QHBoxLayout *hBoxLayout;

    /* 垂直布局 */
    QVBoxLayout *vBoxLayout;

    /* 水平布局 Widget */
    QWidget *hWidget;
    /* 垂直布局 Widget */
    QWidget *vWidget;
    /* 打开文件按钮 */
    QPushButton *openPushButton;
    /* 关闭文件按钮 */
    QPushButton *closePushButton;
private slots:
    /* 打开文本文件 */
    bool openFile();
    /* 关闭文本文件 */
    void closeFile();
};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
#include "mainwindow.h"
#include <QFileDialog>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置窗口的位置与大小 */

    this->setGeometry(0, 0, 800, 480);
    /* 布局设置 */
    textEdit = new QTextEdit();
    vBoxLayout = new QVBoxLayout();
    hBoxLayout = new QHBoxLayout();
    vWidget = new QWidget();
    hWidget = new QWidget();
    openPushButton = new QPushButton();
    closePushButton = new QPushButton();
    
    /* 设置两个按钮的大小 */
    openPushButton->setMinimumHeight(50);
    openPushButton->setMaximumWidth(120);
    closePushButton->setMinimumHeight(50);
    closePushButton->setMaximumWidth(120);
    /* 设置两个按钮的文本 */
    openPushButton->setText("打开");
    closePushButton->setText("关闭");
    /* 设置关闭按钮为不可用属性,需要打开文件才设置为可用属性 */
    closePushButton->setEnabled(false);
    /* 水平布局 */
    hBoxLayout->addWidget(openPushButton);
    hBoxLayout->addWidget(closePushButton);
    hWidget->setLayout(hBoxLayout);
    /* 垂直布局 */
    vBoxLayout->addWidget(textEdit);
    vBoxLayout->addWidget(hWidget);
    vWidget->setLayout(vBoxLayout);
    /* 居中 */
    setCentralWidget(vWidget);
    /* 信号槽连接 */
    connect(openPushButton, SIGNAL(clicked()),
            this, SLOT(openFile()));
    connect(closePushButton, SIGNAL(clicked()),
            this, SLOT(closeFile()));
}

MainWindow::~MainWindow()
{
}

bool MainWindow::openFile()
{
    /* 获取文件的路径 */
    QString fileName = QFileDialog::getOpenFileName(this);
    /* 指向文件 */
    file.setFileName(fileName);
    /* 判断文件是否存在 */
    if (!file.exists())
        return false;

    /* 以读写的方式打开 */
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
        return false;

    /* 读取文本到 textEdit */
    textEdit->setPlainText(file.readAll());
    /* 设置打开按钮不可用,需要关闭再打开 */
    openPushButton->setEnabled(false);
    /* 设置关闭按钮为可用属性 */
    closePushButton->setEnabled(true);
    /* 关闭文件 */
    file.close();
    return true;
}
void MainWindow::closeFile()
{
    /* 检测打开按钮是否可用,不可用时,说明已经打开了文件 */
    if (!openPushButton->isEnabled()) {
    /* 获取 textEdit 的文本内容 */
    QString str = textEdit->toPlainText();

    /* 以只读的方式打开 */
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
        return;
    /* 转换为字节数组 */
    QByteArray strBytes = str.toUtf8();
    /* 写入文件 */
    file.write(strBytes, strBytes.length());
    /* 清空 textEdit 的显示内容 */
    textEdit->clear();
    /* 关闭文件 */
    file.close();
    /* 重新设置打开和关闭按钮的属性 */
    openPushButton->setEnabled(true);
    closePushButton->setEnabled(false);
    }
}

  1. 运行,点击打开文件。
    在这里插入图片描述

9.绘图与图表

9.1 QPainter 绘图

Qt 里的所有绘图,比如一个按钮和一个 Label 的显示,都有绘图系统来执行。绘图系统基于 QPainter、和 QPaintDevice 和 QPainEngine 类。QPainter 是可以直接用来操作绘图的类,而QPaintDevice 和 QPainEngine 都比 QPainter 更底层,我们只需要了解一下 QPaintDevice 和QPainEngine 就行了。可以用下面一张图来表示它们的关系
在这里插入图片描述
一般用于显示的类,如 QWidget、QPixmap、QImage、Qlabel 等可视类控件都可以充当绘图区域的“画布”,从 QWidget 继承的类都有 virtual void paintEvent(QPaintEvent *event);属性。这个 paintEvent()是一个虚函数,它在 qwidget.h 头文件的 protected:修饰符下。
paintEvent()事件可以被重写。(解释:什么是绘图事件?可以这么理解,当界面初始化或者需要刷新时才会执行的事件,也就是说绘图事件在构造对象实例化时会执行,需要刷新界面我们可以使用 update()方法执行 paintEvent()事件)。paintEvent()事件是父类 QWidget 提供给子类的接口,在父类里定义为空,所以可以说paintEvent()事件就是专门给子类画图用的。
paintEvent()事件在子类重写的基本结构如下:

void Widget::paintEvent(QPaintEvent *)
{
 /* 指定画图的对象,this 代表是本 Widget */
 QPainter painter(this);
 // 使用 painter 在对象上绘图...
}

旋转的 CD(难度:一般)。项目路径为 Qt/2/03_qpainter。本例使用一张图片,用 QPainter 在 paintEvent()将 CD 画在窗口的中心,并每 100ms 旋转 1 度角度。所以 CD 看起来是旋转了的效果。
因为 paintEvent()是父类 QWidget 的 protected 修饰符下虚方法(虚函数),所以建议重写时也写到子类下的 protected 修饰符下。

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QPainter>
#include <QPaintEvent>
#include <QTimer>
class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
protected:
    void paintEvent(QPaintEvent *);
private:
    /* 定时器,用于定时更新界面 */
    QTimer *timer;
    /* 角度 */
    int angle;
private slots:
    /* 槽函数 */
    void timerTimeOut();
};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
#include "mainwindow.h"

#include "QDebug"
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置主窗口位置及颜色 */
    this->setGeometry(0, 0, 800, 480);
    setPalette(QPalette(Qt::gray));
    setAutoFillBackground(true);
    /* 定时器实例化 */
    timer = new QTimer(this);
    /* 默认角度为 0 */
    angle = 0;
    /* 定时 100ms */
    timer->start(100);
    /* 信号槽连接 */
    connect(timer, SIGNAL(timeout()), this, SLOT(timerTimeOut()));
}
MainWindow::~MainWindow()
{
}

void MainWindow::timerTimeOut()
{
    /* 需要更新界面,不设置不更新 */
    this->update();
}

void MainWindow::paintEvent(QPaintEvent *)
{
    /* 指定父对象,this 指本窗口 */
    QPainter painter(this);
    /* 设置抗锯齿,流畅转换 */
    painter.setRenderHints(QPainter::Antialiasing
                           | QPainter::SmoothPixmapTransform);
    /* 计算旋转角度 */
    if (angle++ == 360)
        angle = 0;
    /* QPixmap 类型对象 */
    QPixmap image;
    /* 加载 */
    image.load(":img1.jpg");
    /* QRectF 即,继承 QRect(Qt 的矩形类),F 代表精确到浮点类型 */
    QRectF rect((this->width() - image.width()) / 2,
                (this->height() - image.height()) / 2,
                image.width(),
                image.height());
    /* 默认参考点为左上角原点(0,0),因为旋转需要以图形的中心为参考点,
    * 我们使用 translate 把参考点设置为 CD 图形的中心点坐标 */
    painter.translate(0 + rect.x() + rect.width() / 2,
                      0 + rect.y() + rect.height() / 2);
    /* 旋转角度 */
    painter.rotate(angle);
    /* 现在参考点为 CD 图形的中心,我们需要把它设置回原点的位置,
        * 所以需要减去上面加上的数 */
    painter.translate(0 - (rect.x() + rect.width() / 2),
                      0 - (rect.y() + rect.height() / 2));
    /* 画图,QPainter 提供了许多 drawX 的方法 */
    painter.drawImage(rect, image.toImage(), image.rect());
    /* 再画一个矩形 */
    painter.drawRect(rect.toRect());
}

  1. 运行
    即使很大的图片依然是旋转的。

9.2 QChart 图表

自从 Qt 发布以来,给跨平台的用户带来很多便利。在 Qt5.7 之前,Qt 在开源社区版本里没有 Qt Charts(自带的绘图组件库)。这使得像 QWT、QCustomPlot 等第三方库有了巨大的生存空间,作者也在 Qt 5.7 以下版本使用过第三方的 QCustomPlot。要想使用 Qt Charts,我们的 Qt版本得使用 Qt 5.7 之后的版本。其实 Qt Charts 并不是 Qt 5.7 才有的,是在 Qt 5.7 以前只有商业版本的 Qt 才有 Qt Charts。我们能免费下载的 Qt 版本都是社区(开源)版本。
Qt Charts 很方便的绘制我们常见的曲线图、折线图、柱状图和饼状图等图表。不用自己花精力去了解第三方组件的使用了或者开发第三方组件。Qt 的帮助文档里已经有说明 Qt Charts主要部件的使用方法。需要用到时我们可以查看 Qt 文档就可以了。
下面我们主要简介一下 Qt Charts 模块,首先先看它的继承关系,(看继承关系可以了解这个类是怎么来的,它不可能是一下子崩出来的)。至于怎么查看 QChart 类的继承关系,在我们第六章里 Qt Creator 的快捷键有讲到,Ctrl + Shift + T ,点击要查询的类的继承关系。
在这里插入图片描述
要想在项目里使用 Qt Charts 模块,需要在 pro 文件下添加以下语句。
QT += charts
如果我们点击查看 Qt Charts 类,我们可以看到要想使用 Qt Charts 类,除了需要包括相应的头文件外,还需要使用命名空间。格式如下。
一般在头文件处加上这个。QT_CHARTS_USE_NAMESPACE
或者在头文件类外加上以下语句。using namespace QtCharts;
下面我们直接开始例子,了解一下 Qt Charts 的使用。
实时动态曲线(难度:一般)。项目路径为04_qtchart。本例基本流程如下:使用一个 QSplineSeries 对象(曲线),一个 QChart(图表),一个 QChartView(图表视图)。首先我们创建坐 chart 图表,然后创建两条坐标轴 axisX 与 axisY。将两条坐标轴添加到chart 图表上,再将 splineSeries 曲线与坐标轴连系起来。最后再将 chart 图表添加到 chartView图表视图中。曲线上的数据由系统产生随机数,使用定时器更新数据。
项目文件 04_qtchart.pro 文件第一行添加的代码部分如下:QT += core gui charts

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QChartView>
#include <QSplineSeries>
#include <QScatterSeries>
#include <QDebug>
#include <QValueAxis>
#include <QTimer>
#include <QMainWindow>
/* 必需添加命名空间 */

QT_CHARTS_USE_NAMESPACE
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* 接收数据接口 */
    void receivedData(int);
    /* 数据最大个数 */
    int maxSize;
    /* x 轴上的最大值 */
    int maxX;
    /* y 轴上的最大值 */
    int maxY;
    /* y 轴 */
    QValueAxis *axisY;
    /* x 轴 */
    QValueAxis *axisX;
    /* QList int 类型容器 */
    QList<int> data;
    /* QSplineSeries 对象(曲线)*/
    QSplineSeries *splineSeries;
    /* QChart 图表 */
    QChart *chart;
    /* 图表视图 */
    QChartView *chartView;
    /* 定时器 */
    QTimer *timer;
private slots:
    void timerTimeOut();
};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
#include "mainwindow.h"
#include <QDateTime>
MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent)
{
    /* 设置最显示位置与大小 */
    this->setGeometry(0, 0, 800, 480);
    /* 最大储存 maxSize - 1 个数据 */
    maxSize = 51;
    /* x 轴上的最大值 */
    maxX = 5000;
    /* y 轴最大值 */
    maxY = 40;
    /* splineSeries 曲线实例化(折线用 QLineSeries) */
    splineSeries = new QSplineSeries();
    /* 图表实例化 */
    chart = new QChart();
    /* 图表视图实例化 */
    chartView = new QChartView();
    /* 坐标轴 */
    axisY = new QValueAxis();
    axisX = new QValueAxis();
    /* 定时器 */
    timer = new QTimer(this);

    /* legend 译图例类型,以绘图的颜色区分,本例设置为隐藏 */
    chart->legend()->hide();
    /* chart 设置标题 */
    chart->setTitle("实时动态曲线示例");
    /* 添加一条曲线 splineSeries */
    chart->addSeries(splineSeries);
    /* 设置显示格式 */
    axisY->setLabelFormat("%i");
    /* y 轴标题 */
    axisY->setTitleText("温度/℃");
    /* y 轴标题位置(设置坐标轴的方向) */
    chart->addAxis(axisY, Qt::AlignLeft);
    /* 设置 y 轴范围 */
    axisY->setRange(0, maxY);
    /* 将 splineSeries 附加于 y 轴上 */
    splineSeries->attachAxis(axisY);
    /* 设置显示格式 */
    axisX->setLabelFormat("%i");
    /* x 轴标题 */
    axisX->setTitleText("时间/ms");
    /* x 轴标题位置(设置坐标轴的方向) */
    chart->addAxis(axisX, Qt::AlignBottom);
    /* 设置 x 轴范围 */
    axisX->setRange(0, maxX);
    /* 将 splineSeries 附加于 x 轴上 */
    splineSeries->attachAxis(axisX);
    /* 将图表的内容设置在图表视图上 */
    chartView->setChart(chart);
    /* 设置抗锯齿 */
    chartView->setRenderHint(QPainter::Antialiasing);
    /* 设置为图表视图为中心部件 */
    setCentralWidget(chartView);
    /* 定时 200ms */
    timer->start(200);
    /* 信号槽连接 */
    connect(timer, SIGNAL(timeout()), this, SLOT(timerTimeOut()));/* 设置随机种子,随机数初始化 */
    qsrand(time(NULL));
}
MainWindow::~MainWindow()
{
}

void MainWindow::timerTimeOut()
{
    /* 产生随机 0~maxY 之间的数据 */
    receivedData(qrand() % maxY );
}
void MainWindow::receivedData(int value)
{
    /* 将数据添加到 data 中 */
    data.append(value);
    /* 当储存数据的个数大于最大值时,把第一个数据删除 */
    while (data.size() > maxSize) {
    /* 移除 data 中第一个数据 */
        data.removeFirst();
    }
    /* 先清空 */
    splineSeries->clear();
    /* 计算 x 轴上的点与点之间显示的间距 */
    int xSpace = maxX / (maxSize - 1);
    /* 添加点,xSpace * i 表示第 i 个点的 x 轴的位置 */
    for (int i = 0; i < data.size(); ++i) {
        splineSeries->append(xSpace * i, data.at(i));
    }
}

  1. 运行,我用的5.15.2版本报错找不到charts模块。可以在https://download.qt.io/archive/qt/5.15/5.15.2/submodules下载,然后windows系统进去pro文件所在文件夹,然后依次执行命令:执行qmake(与编译器版本要对应),例:按顺序执行命令"qmake config+=debug_and_release"、“mingw32-make -j(此选项开启多核编译)”和“mingw32-make install”;有可能失败。
    在这里插入图片描述

10.多线程

我们写的一个应用程序,应用程序跑起来后一般情况下只有一个线程,但是可能也有特殊情况。比如我们前面章节写的例程都跑起来后只有一个线程,就是程序的主线程。线程内的操作都是顺序执行的。恩,顺序执行?试着想一下,我们的程序顺序执行,假设我们的用户界面
点击有某个操作是比较耗时的。您会发现界面点击完了,点击界面对应的操作还没有完成,所以就会冻结界面,不能响应,直到操作完成后,才返回到正常的界面里。如果我们的界面是这么设计的话,估计用户得发毛了。这种情况我们一般是创建一个单独的线程来执行这个比较耗时的操作。比如我们使用摄像头拍照保存照片。恩,很多朋友问,这个不算耗时吧。对的在电脑上使用 Qt 拍照,处理起来非常快。根本也不需要开启一个线程来做这种事。但是我们是否考虑在嵌入式的 CPU 上做这种事情呢?嵌入式的 CPU 大多数都没有电脑里的 CPU 主频(几 GHz)那么高,处理速度也不快。此时我们就需要考虑开多一个线程来拍照了。拍完照再与主线程(主线程即程序原来的线程)处理好照片的数据,就完成了一个多线程的应用程序了。
官方文档里说,QThread 类提供了一种独立于平台的方法来管理线程。QThread 对象在程序中管理一个控制线程。QThreads 在 run()中开始执行。默认情况下,run()通过调用 exec()来启动事件循环,并在线程中运行 Qt 事件循环。您可以通过使用 QObject::moveToThread()将 worker对象移动到线程来使用它们。QThread 线程类是实现多线程的核心类。Qt 有两种多线程的方法,其中一种是继承 QThread的 run()函数,另外一种是把一个继承于 QObject 的类转移到一个 Thread 里。Qt4.8 之前都是使用继承 QThread 的 run()这种方法,但是 Qt4.8 之后,Qt 官方建议使用第二种方法。两种方法区别不大,用起来都比较方便,但继承 QObject 的方法更加灵活。所以 Qt 的帮助文档里给的参考是先给继承 QObject 的类,然后再给继承 QThread 的类。另外 Qt 提供了 QMutex、QMutexLocker、QReadLocker 和 QWriteLocker 等类用于线程之间的同步,详细可以看 Qt 的帮助文档。

10.1 继承 QThread 的线程

在第十章的章节开头说过了,继承 QThread 是创建线程的一个普通方法。其中创建的线程只有 run()方法在线程里的。其他类内定义的方法都在主线程内。恩,这样不理解?我们画个图捋一捋。

通过上面的图我们可以看到,主线程内有很多方法在主线程内,但是子线程,只有 run()方法是在子线程里的。run()方法是继承于 QThread 类的方法,用户需要重写这个方法,一般是把耗时的操作写在这个 run()方法里面。
10.1.1 应用实例
本例目的:快速了解继承 QThread 类线程的使用。例 05_qthread_example1 , 继 承 QThread 类 的 线 程 。本例通过 QThread 类继承线程,然后在 MainWindow 类里使用。通过点击一个按钮开启线程。当线程执行完成时,会发送 resultReady(const QString &s)信号给主线程。流程就这么简单。

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QThread>
#include <QDebug>
#include <QPushButton>
/* 使用下面声明的 WorkerThread 线程类 */
class WorkerThread;
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* 在 MainWindow 类里声明对象 */
    WorkerThread *workerThread;
    /* 声明一个按钮,使用此按钮点击后开启线程 */
    QPushButton *pushButton;
private slots:
    /* 槽函数,用于接收线程发送的信号 */
    void handleResults(const QString &result);
    /* 点击按钮开启线程 */
    void pushButtonClicked();
};
/* 新建一个 WorkerThread 类继承于 QThread */
class WorkerThread : public QThread
{
    /* 用到信号槽即需要此宏定义 */
    Q_OBJECT
public:
    WorkerThread(QWidget *parent = nullptr) {
        Q_UNUSED(parent);
    }
    /* 重写 run 方法,继承 QThread 的类,只有 run 方法是在新的线程里 */
    void run() override {
        QString result = "线程开启成功";
        /* 这里写上比较耗时的操作 */
        // ...
        // 延时 2s,把延时 2s 当作耗时操作
        sleep(2);
        /* 发送结果准备好的信号 */
        emit resultReady(result);
        }
signals:
    /* 声明一个信号,译结果准确好的信号 */
    void resultReady(const QString &s);
};
#endif // MAINWINDOW_H
  1. mainwindow.cpp
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置位置与大小 */
    this->setGeometry(0, 0, 800, 480);
    /* 对象实例化 */
    pushButton = new QPushButton(this);
    workerThread = new WorkerThread(this);

    /* 按钮设置大小与文本 */
    pushButton->resize(100, 40);
    pushButton->setText("开启线程");
    /* 信号槽连接 */
    connect(workerThread, SIGNAL(resultReady(QString)),
            this, SLOT(handleResults(QString)));
    connect(pushButton, SIGNAL(clicked()),
            this, SLOT(pushButtonClicked()));
}
MainWindow::~MainWindow()
{
    /* 进程退出,注意本例 run()方法没写循环,此方法需要有循环才生效 */
    workerThread->quit();
    /* 阻塞等待 2000ms 检查一次进程是否已经退出 */
    if (workerThread->wait(2000)) {
        qDebug()<<"线程已经结束!"<<endl;
        }
}
void MainWindow::handleResults(const QString &result)
{
    /* 打印出线程发送过来的结果 */
    qDebug()<<result<<endl;
}
void MainWindow::pushButtonClicked()
{
    /* 检查线程是否在运行,如果没有则开始运行 */
    if (!workerThread->isRunning())
        workerThread->start();
}
  1. 运行,点击开启线程按钮后,延时 2s 后,Qt Creator 的应用程序输出窗口打印出“线程开启成功”。在 2s 内多次点击按钮则不会重复开启线程,因为线程在这 2s 内还在运行。同时我们可以看到点击按钮没卡顿现象。因为这个延时操作是在我们创建的线程里运行的,而 pushButton 是在主线程里的,通过点击按钮控制子线程的运行。
    当关闭程序后,子线程将在主线程的析构函数里退出。注意线程使用 wait()方法,这里等待 2s,因为我们开启的线和是延时 2s 就完成了,如果是实际的操作,请根据 CPU 的处理能力,给一个适合的延时,阻塞等待线程完成后,就会自动退出并打印“线程已经结束”。
    在这里插入图片描述

10.2 继承 QObject 的线程

在第 10 章章节开头已经说过,继承 QThread 类是创建线程的一种方法,另一种就是继承QObject 类。继承 QObject 类更加灵活。它通过 QObject::moveToThread()方法,将一个 QObeject的类转移到一个线程里执行。恩,不理解的话,我们下面也画个图捋一下。
在这里插入图片描述
通过上面的图不难理解,首先我们写一个类继承 QObject,通过 QObject::moveToThread()方法将它移到一个 QThread 线程里执行。那么可以通过主线程发送信号去调用 QThread 线程的方法如上图的 fun4(),fun5()等等。这些方法都是在 QThread 线程里执行的。

例 06_qthread_example2 , 继 承 QObject 类 的 线 程。本例通过 QObject 类继承线程,然后在 MainWindow 类里使用。通过点击一个按钮开启线程。另一个按钮点击关闭线程。另外通过加锁的操作来安全的终止一个线程。(我们可以通过 QMutexLocker 可以安全的使用 QMutex 以免忘记解锁。)在我们谈谈为什么需要加锁来终止一个线程?因为 quit()和 exit()方法都不会中途终止线程。要马上终止一个线程可以用 terminate()方法。但是这个函数存在非常不安全的因素,我们可以添加一个 bool 变量,通过主线程修改这个 bool 变量来终止,但是有可能引起访问冲突,所以需要加锁,例程里可能体现不是那么明确,当我们有 doWork1(),doWork2…就能
体现到 bool 变量加锁的作用了。但是加锁会消耗一定的性能,增加耗时。

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QThread>
#include <QDebug>
#include <QPushButton>
#include <QMutexLocker>
#include <QMutex>
/* 工人类 */
class Worker;
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* 开始线程按钮 */
    QPushButton *pushButton1;
    /* 打断线程按钮 */
    QPushButton *pushButton2;
    /* 全局线程 */
    QThread workerThread;
    /* 工人类 */
    Worker *worker;
private slots:
    /* 按钮 1 点击开启线程 */
    void pushButton1Clicked();
    /* 按钮 2 点击打断线程 */
    void pushButton2Clicked();
    /* 用于接收工人是否在工作的信号 */
    void handleResults(const QString &);
signals:
    /* 工人开始工作(做些耗时的操作 ) */
    void startWork(const QString &);
};
/* Worker 类,这个类声明了 doWork1 函数,将整个 Worker 类移至线程 workerThread */
class Worker : public QObject
{
    Q_OBJECT
private:
    /* 互斥锁 */
    QMutex lock;
    /* 标志位 */
    bool isCanRun;
public slots:
    /* 耗时的工作都放在槽函数下,工人可以有多份不同的工作,但是每次只能去做一份 */
    void doWork1(const QString &parameter) {
        /* 标志位为真 */
        isCanRun = true;
        /* 死循环 */
        while (1) {
        /* 此{}作用是 QMutexLocker 与 lock 的作用范围,获取锁后,运行完成后即解锁 */
            {
                QMutexLocker locker(&lock);
                /* 如果标志位不为真 */
                if (!isCanRun) {
                    /* 跳出循环 */
                    break;
                }
            }
            /* 使用 QThread 里的延时函数,当作一个普通延时 */
            QThread::sleep(2);
            emit resultReady(parameter + "doWork1 函数");
            }
        /* doWork1 运行完成,发送信号 */
        emit resultReady("打断 doWork1 函数");
    }   
            // void doWork2();...
public:
        /* 打断线程(注意此方法不能放在槽函数下) */
        void stopWork() {
            qDebug()<<"打断线程"<<endl;
            /* 获取锁后,运行完成后即解锁 */
            QMutexLocker locker(&lock);
            isCanRun = false;
        }

signals:
    /* 工人工作函数状态的信号 */
    void resultReady(const QString &result);
};
#endif
  1. mainwindow.cpp
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置显示位置与大小 */
    this->setGeometry(0, 0, 800, 480);
    pushButton1 = new QPushButton(this);
    pushButton2 = new QPushButton(this);

    /* 设置按钮的位置大小 */
    pushButton1->setGeometry(300, 200, 80, 40);
    pushButton2->setGeometry(400, 200, 80, 40);
    /* 设置两个按钮的文本 */
    pushButton1->setText("开启线程");
    pushButton2->setText("打断线程");
    /* 工人类实例化 */
    worker = new Worker;
    /* 将 worker 类移至线程 workerThread */
    worker->moveToThread(&workerThread);
    /* 信号槽连接 */
    /* 线程完成销毁对象 */
    connect(&workerThread, SIGNAL(finished()),
            worker, SLOT(deleteLater()));
    connect(&workerThread, SIGNAL(finished()),
            &workerThread, SLOT(deleteLater()));
    /* 发送开始工作的信号,开始工作 */
    connect(this, SIGNAL(startWork(QString)),
            worker, SLOT(doWork1(QString)));

    /* 接收到 worker 发送过来的信号 */
    connect(worker, SIGNAL(resultReady(QString)),
            this, SLOT(handleResults(QString)));
    /* 点击按钮开始线程 */
    connect(pushButton1, SIGNAL(clicked()),
            this, SLOT(pushButton1Clicked()));
    /* 点击按钮打断线程 */
    connect(pushButton2, SIGNAL(clicked()),
            this, SLOT(pushButton2Clicked()));
    }
MainWindow::~MainWindow()
{
    /* 打断线程再退出 */
    worker->stopWork();
    workerThread.quit();
    /* 阻塞线程 2000ms,判断线程是否结束 */
    if (workerThread.wait(2000)) {
        qDebug()<<"线程结束"<<endl;

    }
}
void MainWindow::pushButton1Clicked()
{
    /* 字符串常量 */
    const QString str = "正在运行";
    /* 判断线程是否在运行 */
    if(!workerThread.isRunning()) {
        /* 开启线程 */
        workerThread.start();
    }

    /* 发送正在运行的信号,线程收到信号后执行后返回线程耗时函数 + 此字符串 */
    emit this->startWork(str);
    }
void MainWindow::pushButton2Clicked()
{
    /* 如果线程在运行 */
    if(workerThread.isRunning()) {
    /* 停止耗时工作,跳出耗时工作的循环 */
        worker->stopWork();
    }
}
void MainWindow::handleResults(const QString & results)
{
    /* 打印线程的状态 */
    qDebug()<<"线程的状态:"<<results<<endl;
}
  1. 运行,点击开启线程按钮后,应用程序输出窗口每隔 2 秒打印“正在运行 doWork1 函数”,当我们点击打断线程按钮后,窗口打印出“打断 doWork1 函数”。点击打断线程,会打断 doWork1函数的循环,doWork1 函数就运行结束了。再点击开启线程,可以再次运行 doWork1 函数。本例界面简单,仅用了两个按钮和打印语句作为显示部分,但是对初学线程的朋友们友好,因为程序不长。我们可以结合程序的注释,一步步去理解这种线程的写法。重要的是掌握写法,最后才应用到花里胡哨的界面去吧!
    在这里插入图片描述

11 网络编程

Qt 网络模块为我们提供了编写 TCP / IP 客户端和服务器的类。它提供了较低级别的类,例如代表低级网络概念的 QTcpSocket,QTcpServer 和 QUdpSocket,以及诸如 QNetworkRequest,QNetworkReply 和 QNetworkAccessManager 之类的高级类来执行使用通用协议的网络操作。它还提供了诸如QNetworkConfiguration,QNetworkConfigurationManager和QNetworkSession等类,实现承载管理。想要在程序中使用 Qt 网络模块,我们需要在 pro 项目配置文件里增加下面的一条语句。QT += netwoRK

11.1 获取本机的网络信息

了解如何通过 QHostInfo 和 QNetworkInterface 类获取本地网络所有接口的信息。例 07_networkhostinfo , 获 取 本 机 网 络 接 口 信 息 。本例获取本机的网络接口信息,打印在文本浏览框上,点击按钮可直接获取,为了清楚看见是重新获取的过程,本例点击获取本机信息按钮后延时 1s 去刷新获取的信息。点击另一个清空文本信息按钮可以清空文本浏览框上的文本内容。

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QPushButton>
#include <QTextBrowser>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QTimer>
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* 点击获取和清空文本按钮 */
    QPushButton *pushButton[2];
    /* 文本浏览框用于显示本机的信息 */
    QTextBrowser *textBrowser;
    /* 水平 Widget 容器和垂直 Widget 容器*/
    QWidget *hWidget;
    QWidget *vWidget;
    /* 水平布局和垂直布局 */
    QHBoxLayout *hBoxLayout;
    QVBoxLayout *vBoxLayout;
    /* 定时器 */
    QTimer *timer;
    /* 获取本机的网络的信息,返回类型是 QString */
    QString getHostInfo();
private slots:
    /* 定时器槽函数,点击按钮后定时触发 */
    void timerTimeOut();
    /* 显示本机信息 */
    void showHostInfo();
    /* 启动定时器 */
    void timerStart();
    /* 清空 textBrowser 的信息 */
    void clearHostInfo();
};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
#include "mainwindow.h"
#include <QNetworkInterface>
#include <QHostInfo>
#include <QThread>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置位置与大小 */
    this->setGeometry(0, 0, 800, 480);
    /* 点击获取本地信息按钮和清空文本按钮 */
    pushButton[0] = new QPushButton();
    pushButton[1] = new QPushButton();
    pushButton[0]->setText("获取本机信息");
    pushButton[1]->setText("清空文本信息");
    /* 按钮的大小根据文本自适应,注意 setSizePolicy 需要在布局中使用 */
    pushButton[0]->setSizePolicy(QSizePolicy::Fixed,
                                 QSizePolicy::Fixed);
    pushButton[1]->setSizePolicy(QSizePolicy::Fixed,
                                 QSizePolicy::Fixed);
    /* 水平 Widget 和垂直 Widget 用于添加布局 */
    hWidget = new QWidget();
    vWidget = new QWidget();
    /* 水平布局和垂直布局 */
    hBoxLayout = new QHBoxLayout();
    vBoxLayout = new QVBoxLayout();
    /* 文本浏览框 */
    textBrowser = new QTextBrowser();
    
    /* 添加到水平布局 */
    hBoxLayout->addWidget(pushButton[0]);
    hBoxLayout->addWidget(pushButton[1]);
    /* 将水平布局设置为 hWidget 的布局 */
    hWidget->setLayout(hBoxLayout);
    
    /* 将文本浏览框和 hWidget 添加到垂直布局 */
    vBoxLayout->addWidget(textBrowser);
    vBoxLayout->addWidget(hWidget);
    /* 将垂直布局设置为 vWidget 的布局 */
    vWidget->setLayout(vBoxLayout);
    
    /* 设置 vWidget 为中心部件 */
    setCentralWidget(vWidget);
    /* 定时器初始化 */
    timer = new QTimer();
    /* 信号槽连接 */
    connect(pushButton[0], SIGNAL(clicked()),
            this, SLOT(timerStart()));
    connect(pushButton[1], SIGNAL(clicked()),
            this, SLOT(clearHostInfo()));
    connect(timer, SIGNAL(timeout()),
            this, SLOT(timerTimeOut()));
}
MainWindow::~MainWindow()
{
}
void MainWindow::timerStart()
{
    /* 清空文本 */
    textBrowser->clear();
    /* 定时 1s */
    timer->start(1000);
}
void MainWindow::timerTimeOut()
{
    /* 显示本机信息 */
    showHostInfo();
    /* 停止定时器 */
    timer->stop();
}
QString MainWindow::getHostInfo()
{
    /* 通过 QHostInfo 的 localHostName 函数获取主机名称 */
    QString str = "主机名称:" + QHostInfo::localHostName() + "\n";

    /* 获取所有的网络接口,QNetworkInterface 类提供主机的 IP 地址和网络接口的列表 */
    QList<QNetworkInterface> list= QNetworkInterface::allInterfaces();
    /* 遍历 list */
    foreach (QNetworkInterface interface, list) {
        str+= "网卡设备:" + interface.name() + "\n";
        str+= "MAC 地址:" + interface.hardwareAddress() + "\n";
        /* QNetworkAddressEntry 类存储 IP 地址子网掩码和广播地址 */
        QList<QNetworkAddressEntry> entryList= interface.addressEntries();
        /* 遍历 entryList */
        foreach (QNetworkAddressEntry entry, entryList) {
            /* 过滤 IPv6 地址,只留下 IPv4 */
            if (entry.ip().protocol() ==QAbstractSocket::IPv4Protocol) {
                str+= "IP 地址:" + entry.ip().toString() + "\n";
                str+= "子网掩码:" + entry.netmask().toString() + "\n";
                str+= "广播地址:" + entry.broadcast().toString() + "\n\n";
            }
        }
    }

    /* 返回网络信息 */
    return str;
}
void MainWindow::showHostInfo()
{
    /* 获取本机信息后显示到 textBrowser */
    textBrowser->insertPlainText(getHostInfo());

}
void MainWindow::clearHostInfo()
{
    /* 判断 textBrowser 是否为空,如果不为空则清空文本 */
    if (!textBrowser->toPlainText().isEmpty())
        /* 清空文本 */
        textBrowser->clear();
}
  1. 运行,
    在这里插入图片描述

11.2 TCP 通信

在 Qt 中,Qt 把 socket 当成输入输出流来对待的,数据的收发是通过 read()和 write()来进行的,需要与我们常见的 send()与 recv()进行区分。TCP 客户端与服务端通信示意图如下。
在这里插入图片描述

11.2.1 TCP 服务端

08_tcpserver。本例大体流程首先获取本地 IP 地址。创建一个 tcpSocket 套接字,一个 tcpServer 服务端。点击监听即监听本地的主机 IP 地址和端口,同时等待服务端的连接。此程序需要结合客户端一起使用。
项目文件 08_tcpserver.pro 文件第一行添加的代码部分如下。QT += core gui network

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTcpServer>
#include <QTcpSocket>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QTextBrowser>
#include <QLabel>
#include <QComboBox>
#include <QSpinBox>
#include <QHostInfo>
#include <QLineEdit>
#include <QNetworkInterface>
#include <QDebug>

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* tcp 服务器 */
    QTcpServer *tcpServer;
    /* 通信套接字 */
    QTcpSocket *tcpSocket;
    /* 按钮 */
    QPushButton *pushButton[4];
    /* 标签文本 */
    QLabel *label[2];
    /* 水平容器 */
    QWidget *hWidget[3];
    /* 水平布局 */
    QHBoxLayout *hBoxLayout[3];
    /* 垂直容器 */
    QWidget *vWidget;
    /* 垂直布局 */
    QVBoxLayout *vBoxLayout;
    /* 文本浏览框 */
    QTextBrowser *textBrowser;
    /* 用于显示本地 ip */
    QComboBox *comboBox;
    /* 用于选择端口 */
    QSpinBox *spinBox;
    /* 文本输入框 */
    QLineEdit *lineEdit;
    /* 存储本地的 ip 列表地址 */
    QList<QHostAddress> IPlist;
    /* 获取本地的所有 ip */
    void getLocalHostIP();
private slots:
    /* 客户端连接处理槽函数 */
    void clientConnected();
    /* 开始监听槽函数 */
    void startListen();
    /* 停止监听槽函数 */
    void stopListen();
    /* 清除文本框时的内容 */
    void clearTextBrowser();·
    /* 接收到消息 */
    void receiveMessages();
    /* 发送消息 */
    void sendMessages();
    /* 连接状态改变槽函数 */
    void socketStateChange(QAbstractSocket::SocketState);
};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置主窗体的位置与大小 */
    this->setGeometry(0, 0, 800, 480);

    /* 实例化 tcp 服务器与 tcp 套接字 */
    tcpServer = new QTcpServer(this);
    tcpSocket = new QTcpSocket(this);

   /* 开始监听按钮 */
   pushButton[0] = new QPushButton();
   /* 停止监听按钮 */
   pushButton[1] = new QPushButton();
   /* 清空聊天文本按钮 */
   pushButton[2] = new QPushButton();
   /* 发送消息按钮 */
   pushButton[3] = new QPushButton();


   /* 水平布局一 */
   hBoxLayout[0] = new QHBoxLayout();
   /* 水平布局二 */
   hBoxLayout[1] = new QHBoxLayout();
   /* 水平布局三 */
   hBoxLayout[2] = new QHBoxLayout();
   /* 水平布局四 */
   hBoxLayout[3] = new QHBoxLayout();

   /* 水平容器一 */
   hWidget[0] = new QWidget();
   /* 水平容器二 */
   hWidget[1] = new QWidget();
   /* 水平容器三 */
   hWidget[2] = new QWidget();

   vWidget = new QWidget();
   vBoxLayout = new QVBoxLayout();

   /* 标签实例化 */
   label[0] = new QLabel();
   label[1] = new QLabel();

   lineEdit = new QLineEdit();
   comboBox = new QComboBox();
   spinBox = new QSpinBox();
   textBrowser = new QTextBrowser();

   label[0]->setText("监听 IP 地址:");
   label[1]->setText("监听端口:");

   /* 设置标签根据文本文字大小自适应大小 */
   label[0]->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed);
   label[1]->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed);

   /* 设置端口号的范围,注意不要与主机的已使用的端口号冲突 */
   spinBox->setRange(10000, 99999);

   pushButton[0]->setText("开始监听");
   pushButton[1]->setText("停止监听");
   pushButton[2]->setText("清空文本");
   pushButton[3]->setText("发送消息");

   /* 设置停止监听状态不可用 */
   pushButton[1]->setEnabled(false);

   /* 设置输入框默认的文本 */
   lineEdit->setText("www.openedv.com 正点原子论坛");

   /* 水平布局一添加内容 */
   hBoxLayout[0]->addWidget(pushButton[0]);
   hBoxLayout[0]->addWidget(pushButton[1]);
   hBoxLayout[0]->addWidget(pushButton[2]);

   /* 设置水平容器一的布局为水平布局一 */
   hWidget[0]->setLayout(hBoxLayout[0]);

   /* 水平布局二添加内容 */
   hBoxLayout[1]->addWidget(label[0]);
   hBoxLayout[1]->addWidget(comboBox);
   hBoxLayout[1]->addWidget(label[1]);
   hBoxLayout[1]->addWidget(spinBox);

   /* 设置水平容器二的布局为水平布局二 */
   hWidget[1]->setLayout(hBoxLayout[1]);

   /* 水平布局三添加内容 */
   hBoxLayout[2]->addWidget(lineEdit);
   hBoxLayout[2]->addWidget(pushButton[3]);

   /* 设置水平容器三的布局为水平布局一 */
   hWidget[2]->setLayout(hBoxLayout[2]);

   /* 垂直布局添加内容 */
   vBoxLayout->addWidget(textBrowser);
   vBoxLayout->addWidget(hWidget[1]);
   vBoxLayout->addWidget(hWidget[0]);
   vBoxLayout->addWidget(hWidget[2]);

   /* 设置垂直容器的布局为垂直布局 */
   vWidget->setLayout(vBoxLayout);


   /* 居中显示 */
   setCentralWidget(vWidget);

   /* 获取本地 ip */
   getLocalHostIP();

   /* 信号槽连接 */
   connect(pushButton[0], SIGNAL(clicked()),
           this, SLOT(startListen()));
   connect(pushButton[1], SIGNAL(clicked()),
           this, SLOT(stopListen()));
   connect(pushButton[2], SIGNAL(clicked()),
           this, SLOT(clearTextBrowser()));
   connect(pushButton[3], SIGNAL(clicked()),
           this, SLOT(sendMessages()));
   connect(tcpServer, SIGNAL(newConnection()),
           this, SLOT(clientConnected()));
   }

MainWindow::~MainWindow()
{
}

/* 新的客户端连接 */
void MainWindow::clientConnected()
{
    /* 获取客户端的套接字 */
    tcpSocket = tcpServer->nextPendingConnection();
    /* 客户端的 ip 信息 */
    QString ip = tcpSocket->peerAddress().toString();
    /* 客户端的端口信息 */
    quint16 port = tcpSocket->peerPort();
    /* 在文本浏览框里显示出客户端的连接信息 */
    textBrowser->append("客户端已连接");
    textBrowser->append("客户端 ip 地址:" + ip);
    textBrowser->append("客户端端口:"+ QString::number(port));

    connect(tcpSocket, SIGNAL(readyRead()),
            this, SLOT(receiveMessages()));
    connect(tcpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),
            this,SLOT(socketStateChange(QAbstractSocket::SocketState)));
}

/* 获取本地 IP */
void MainWindow::getLocalHostIP()
{
    // /* 获取主机的名称 */
    // QString hostName = QHostInfo::localHostName();

    // /* 主机的信息 */
    // QHostInfo hostInfo = QHostInfo::fromName(hostName);

    // /* ip 列表,addresses 返回 ip 地址列表,注意主机应能从路由器获取到
    // * IP,否则可能返回空的列表(ubuntu 用此方法只能获取到环回 IP) */
    // IPlist = hostInfo.addresses();
    // qDebug()<<IPlist<<endl;

    // /* 遍历 IPlist */
    // foreach (QHostAddress ip, IPlist) {
    // if (ip.protocol() == QAbstractSocket::IPv4Protocol)
    // comboBox->addItem(ip.toString());
    // }

    /* 获取所有的网络接口,
    * QNetworkInterface 类提供主机的 IP 地址和网络接口的列表 */
    QList<QNetworkInterface> list= QNetworkInterface::allInterfaces();

    /* 遍历 list */
    foreach (QNetworkInterface interface, list) {

        /* QNetworkAddressEntry 类存储 IP 地址子网掩码和广播地址 */
        QList<QNetworkAddressEntry> entryList = interface.addressEntries();

        /* 遍历 entryList */
        foreach (QNetworkAddressEntry entry, entryList) {
            /* 过滤 IPv6 地址,只留下 IPv4 */
            if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol) {
                comboBox->addItem(entry.ip().toString());
                /* 添加到 IP 列表中 */
                IPlist<<entry.ip();
            }
        }
    }
}

/* 开始监听 */
void MainWindow::startListen()
{
    /* 需要判断当前主机是否有 IP 项 */
    if (comboBox->currentIndex() != -1) {
        qDebug()<<"start listen"<<endl;
        tcpServer->listen(IPlist[comboBox->currentIndex()],spinBox->value());

        /* 设置按钮与下拉列表框的状态 */
        pushButton[0]->setEnabled(false);
        pushButton[1]->setEnabled(true);
        comboBox->setEnabled(false);
        spinBox->setEnabled(false);

        /* 在文本浏览框里显示出服务端 */
        textBrowser->append("服务器 IP 地址:"+ comboBox->currentText());
        textBrowser->append("正在监听端口:" + spinBox->text());
    }
}

/* 停止监听 */
void MainWindow::stopListen()
{
    qDebug()<<"stop listen"<<endl;
    /* 停止监听 */
    tcpServer->close();

    /* 如果是连接上了也应该断开,如果不断开客户端还能继续发送信息,
    * 因为 socket 未断开,还在监听上一次端口 */
    if (tcpSocket->state() == tcpSocket->ConnectedState)
        tcpSocket->disconnectFromHost();


    /* 设置按钮与下拉列表框的状态 */
    pushButton[1]->setEnabled(false);
    pushButton[0]->setEnabled(true);
    comboBox->setEnabled(true);
    spinBox->setEnabled(true);

    /* 将停止监听的信息添加到文本浏览框中 */
    textBrowser->append("已停止监听端口:"+ spinBox->text());
}

/* 清除文本浏览框里的内容 */
void MainWindow::clearTextBrowser()
{
    /* 清除文本浏览器的内容 */
    textBrowser->clear();
}

/* 服务端接收消息 */
void MainWindow::receiveMessages()
{
    /* 读取接收到的消息 */
    QString messages = "客户端:" + tcpSocket->readAll();
    textBrowser->append(messages);
}

/* 服务端发送消息 */
void MainWindow::sendMessages()
{
    if(NULL == tcpSocket)
        return;

    /* 如果已经连接 */
    if(tcpSocket->state() == tcpSocket->ConnectedState) {
        /* 发送消息 */
        tcpSocket->write(lineEdit->text().toUtf8().data());

        /* 在服务端插入发送的消息 */
        textBrowser->append("服务端:" + lineEdit->text());
    }
}


/* 服务端状态改变 */
void MainWindow::socketStateChange(QAbstractSocket::SocketState state)
{
    switch (state) {
        case QAbstractSocket::UnconnectedState:
            textBrowser->append("scoket 状态:UnconnectedState");
            break;
        case QAbstractSocket::ConnectedState:
            textBrowser->append("scoket 状态:ConnectedState");
            break;
        case QAbstractSocket::ConnectingState:
            textBrowser->append("scoket 状态:ConnectingState");
            break;
        case QAbstractSocket::HostLookupState:
            textBrowser->append("scoket 状态:HostLookupState");
            break;
        case QAbstractSocket::ClosingState:
            textBrowser->append("scoket 状态:ClosingState");
            break;
        case QAbstractSocket::ListeningState:
            textBrowser->append("scoket 状态:ListeningState");
            break;
        case QAbstractSocket::BoundState:
            textBrowser->append("scoket 状态:BoundState");
            break;
        default:
            break;
    }
}

  1. 运行,上面的代码主要是服务端开启监听,如果有客户端连到服务端,就会发射 newConnection()信号,同时也连接到接收消息的信号与槽函数。点击发送消息按钮就可以使用 tcpSocket 发送消息。注意发送消息和接收消息都是通过 tcpSocket 的 read()和 write()进行。
    在这里插入图片描述

11.2.2 TCP 客户端

09_tcpclient,本例大体流程:首先获取本地 IP 地址。创建一个 tcpSocket 套接字,然后用 tcpSocket 套接字使用 connectToHost
函数连接服务端的主机 IP 地址和端口,即可相互通信。项目文件 08_tcpserver.pro 文件第一行添加的代码部分如下。 QT += core gui network

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTcpServer>
#include <QTcpSocket>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QTextBrowser>
#include <QLabel>
#include <QComboBox>
#include <QSpinBox>
#include <QHostInfo>
#include <QLineEdit>
#include <QNetworkInterface>
#include <QDebug>
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* 通信套接字 */
    QTcpSocket *tcpSocket;
    /* 按钮 */
    QPushButton *pushButton[4];
    /* 标签文本 */
    QLabel *label[2];
    /* 水平容器 */
    QWidget *hWidget[3];
    /* 水平布局 */
    QHBoxLayout *hBoxLayout[3];
    /* 垂直容器 */
    QWidget *vWidget;
    /* 垂直布局 */

    QVBoxLayout *vBoxLayout;
    /* 文本浏览框 */
    QTextBrowser *textBrowser;
    /* 用于显示本地 ip */
    QComboBox *comboBox;
    /* 用于选择端口 */
    QSpinBox *spinBox;
    /* 文本输入框 */
    QLineEdit *lineEdit;
    /* 存储本地的 ip 列表地址 */
    QList<QHostAddress> IPlist;
    /* 获取本地的所有 ip */
    void getLocalHostIP();
private slots:
    /* 连接 */
    void toConnect();
    /* 断开连接 */
    void toDisConnect();
    /* 已连接 */
    void connected();
    /* 已断开连接 */
    void disconnected();
    /* 清除文本框时的内容 */
    void clearTextBrowser();
    /* 接收到消息 */
    void receiveMessages();
    /* 发送消息 */
    void sendMessages();

    /* 连接状态改变槽函数 */
    void socketStateChange(QAbstractSocket::SocketState);
};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置主窗体的位置与大小 */
     this->setGeometry(0, 0, 800, 480);

    /* tcp 套接字 */
     tcpSocket = new QTcpSocket(this);

    /* 开始监听按钮 */
    pushButton[0] = new QPushButton();
    /* 停止监听按钮 */
    pushButton[1] = new QPushButton();
    /* 清空聊天文本按钮 */
    pushButton[2] = new QPushButton();
    /* 发送消息按钮 */
    pushButton[3] = new QPushButton();

    /* 水平布局一 */
    hBoxLayout[0] = new QHBoxLayout();
    /* 水平布局二 */
    hBoxLayout[1] = new QHBoxLayout();
    /* 水平布局三 */
    hBoxLayout[2] = new QHBoxLayout();

    /* 水平布局四 */
    hBoxLayout[3] = new QHBoxLayout();

    /* 水平容器一 */
    hWidget[0] = new QWidget();
    /* 水平容器二 */
    hWidget[1] = new QWidget();
    /* 水平容器三 */
    hWidget[2] = new QWidget();


    vWidget = new QWidget();
    vBoxLayout = new QVBoxLayout();

    /* 标签实例化 */
    label[0] = new QLabel();
    label[1] = new QLabel();

    lineEdit = new QLineEdit();
    comboBox = new QComboBox();
    spinBox = new QSpinBox();
    textBrowser = new QTextBrowser();

    label[0]->setText("服务器地址:");
    label[1]->setText("服务器端口:");

    /* 设置标签根据文本文字大小自适应大小 */
    label[0]->setSizePolicy(QSizePolicy::Fixed,
    QSizePolicy::Fixed);
    label[1]->setSizePolicy(QSizePolicy::Fixed,
    QSizePolicy::Fixed);

    /* 设置端口号的范围,注意不要与主机的已使用的端口号冲突 */
     spinBox->setRange(10000, 99999);

     pushButton[0]->setText("连接服务器");
     pushButton[1]->setText("断开连接");
     pushButton[2]->setText("清空文本");
     pushButton[3]->setText("发送消息");

    /* 设置停止监听状态不可用 */
     pushButton[1]->setEnabled(false);

    /* 设置输入框默认的文本 */
     lineEdit->setText("广州星翼电子科技有限公司");

    /* 水平布局一添加内容 */
     hBoxLayout[0]->addWidget(pushButton[0]);
     hBoxLayout[0]->addWidget(pushButton[1]);
     hBoxLayout[0]->addWidget(pushButton[2]);

    /* 设置水平容器的布局为水平布局一 */
     hWidget[0]->setLayout(hBoxLayout[0]);

     hBoxLayout[1]->addWidget(label[0]);
     hBoxLayout[1]->addWidget(comboBox);
     hBoxLayout[1]->addWidget(label[1]);
     hBoxLayout[1]->addWidget(spinBox);

    /* 设置水平容器的布局为水平布局二 */
     hWidget[1]->setLayout(hBoxLayout[1]);

    /* 水平布局三添加内容 */
     hBoxLayout[2]->addWidget(lineEdit);
     hBoxLayout[2]->addWidget(pushButton[3]);

    /* 设置水平容器三的布局为水平布局一 */
     hWidget[2]->setLayout(hBoxLayout[2]);

    /* 垂直布局添加内容 */
     vBoxLayout->addWidget(textBrowser);
     vBoxLayout->addWidget(hWidget[1]);
     vBoxLayout->addWidget(hWidget[0]);
     vBoxLayout->addWidget(hWidget[2]);

    /* 设置垂直容器的布局为垂直布局 */
     vWidget->setLayout(vBoxLayout);

    /* 居中显示 */
     setCentralWidget(vWidget);

    /* 获取本地 ip */
     getLocalHostIP();


    /* 信号槽连接 */
     connect(pushButton[0], SIGNAL(clicked()),this, SLOT(toConnect()));
     connect(pushButton[1], SIGNAL(clicked()),this, SLOT(toDisConnect()));
     connect(pushButton[2], SIGNAL(clicked()), this, SLOT(clearTextBrowser()));
     connect(pushButton[3], SIGNAL(clicked()), this, SLOT(sendMessages()));
     connect(tcpSocket, SIGNAL(connected()), this, SLOT(connected()));
     connect(tcpSocket, SIGNAL(disconnected()),this, SLOT(disconnected()));
     connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(receiveMessages()));
     connect(tcpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),this,SLOT(socketStateChange(QAbstractSocket::SocketState)));
}
 MainWindow::~MainWindow()
{
}
 void MainWindow::toConnect()
{
    /* 如果连接状态还没有连接 */
     if (tcpSocket->state() != tcpSocket->ConnectedState) {
        /* 指定 IP 地址和端口连接 */
         tcpSocket->connectToHost(IPlist[comboBox->currentIndex()],
         spinBox->value());
    }
}

 void MainWindow::toDisConnect()
{
    /* 断开连接 */
     tcpSocket->disconnectFromHost();

    /* 关闭 socket*/
     tcpSocket->close();
}


 void MainWindow::connected()
{
    /* 显示已经连接 */
     textBrowser->append("已经连上服务端");

    /* 设置按钮与下拉列表框的状态 */
     pushButton[0]->setEnabled(false);
     pushButton[1]->setEnabled(true);
     comboBox->setEnabled(false);
     spinBox->setEnabled(false);
}

 void MainWindow::disconnected()
{
    /* 显示已经断开连接 */
    textBrowser->append("已经断开服务端");

    /* 设置按钮与下拉列表框的状态 */
     pushButton[1]->setEnabled(false);
     pushButton[0]->setEnabled(true);
     comboBox->setEnabled(true);
     spinBox->setEnabled(true);
}

/* 获取本地 IP */
 void MainWindow::getLocalHostIP()
{
    // /* 获取主机的名称 */
    // QString hostName = QHostInfo::localHostName();

    // /* 主机的信息 */
    // QHostInfo hostInfo = QHostInfo::fromName(hostName);

    // /* ip 列表,addresses 返回 ip 地址列表,注意主机应能从路由器获取到
    // * IP,否则可能返回空的列表(ubuntu 用此方法只能获取到环回 IP) */
    // IPlist = hostInfo.addresses();
    // qDebug()<<IPlist<<endl;

    // /* 遍历 IPlist */
    // foreach (QHostAddress ip, IPlist) {
    // if (ip.protocol() == QAbstractSocket::IPv4Protocol)

    // comboBox->addItem(ip.toString());
    // }

    /* 获取所有的网络接口,
    * QNetworkInterface 类提供主机的 IP 地址和网络接口的列表 */
    QList<QNetworkInterface> list= QNetworkInterface::allInterfaces();

    /* 遍历 list */
    foreach (QNetworkInterface interface, list) {

        /* QNetworkAddressEntry 类存储 IP 地址子网掩码和广播地址 */
        QList<QNetworkAddressEntry> entryList = interface.addressEntries();

        /* 遍历 entryList */
        foreach (QNetworkAddressEntry entry, entryList) {
            /* 过滤 IPv6 地址,只留下 IPv4 */
            if (entry.ip().protocol() ==QAbstractSocket::IPv4Protocol) {
                comboBox->addItem(entry.ip().toString());
                /* 添加到 IP 列表中 */
                IPlist<<entry.ip();
            }
        }
    }
}

/* 清除文本浏览框里的内容 */
 void MainWindow::clearTextBrowser()
{
    /* 清除文本浏览器的内容 */
     textBrowser->clear();
}

/* 客户端接收消息 */
 void MainWindow::receiveMessages()
{
    /* 读取接收到的消息 */
     QString messages = tcpSocket->readAll();
     textBrowser->append("服务端:" + messages);
}

/* 客户端发送消息 */
 void MainWindow::sendMessages()
{
     if(NULL == tcpSocket)
        return;

     if(tcpSocket->state() == tcpSocket->ConnectedState) {
    /* 客户端显示发送的消息 */
         textBrowser->append("客户端:" + lineEdit->text());

        /* 发送消息 */
         tcpSocket->write(lineEdit->text().toUtf8().data());
        }
}

/* 客户端状态改变 */
 void MainWindow::socketStateChange(QAbstractSocket::SocketState state)
{
     switch (state) {
         case QAbstractSocket::UnconnectedState:
             textBrowser->append("scoket 状态:UnconnectedState");
             break;
         case QAbstractSocket::ConnectedState:
             textBrowser->append("scoket 状态:ConnectedState");
             break;
         case QAbstractSocket::ConnectingState:
             textBrowser->append("scoket 状态:ConnectingState");
             break;
         case QAbstractSocket::HostLookupState:
             textBrowser->append("scoket 状态:HostLookupState");
             break;
         case QAbstractSocket::ClosingState:
             textBrowser->append("scoket 状态:ClosingState");
             break;
         case QAbstractSocket::ListeningState:
             textBrowser->append("scoket 状态:ListeningState");
             break;
         case QAbstractSocket::BoundState:
             textBrowser->append("scoket 状态:BoundState");
             break;

         default:break;
    }
}

  1. 运行,上面的代码主要是客户端开使用 connectToHost 通过 IP 地址和端口与服务端连接,如果连接成功,就会发射 connected ()信号,同时也连接到接收消息的信号与槽函数。点击发送消息按钮就可以使用tcpSocket发送消息。注意发送消息和接收消息都是通过tcpSocket的read()和write()进行。

开启服务端后,需要选择本地监听的 IP 地址和监听的端口(特别需要注意,不要选择监听的端口与本地主机的已经使用的端口,所以笔者把端口号设置的特别大,查看本地已经使用的端口号可以使用 netstat 指令。)启动客户端后,选择需要连接的服务器 IP 地址和服务器监听的端口。点击连接后就可以相互发送消息了。注意服务端和客户端都本例都是选择了本地环回 IP 127.0.0.1 测试。也可以选择本地的其他
IP 地址进行测试
在这里插入图片描述
11.3 UDP 通信

QUdpSocket 类提供了一个 UDP 套接字。QUdpSocket 是 QAbstractSocket 的子类,允许发送和接收 UDP 数据报。使用该类最常见的方法是使用 bind()绑定到一个地址和端口,然后调用writeDatagram()和 readDatagram() / receiveDatagram()来传输数据。注意发送数据一般少于 512字节。如果发送多于 512 字节的数据,即使我们发送成功了,也会在 IP 层被分片(分成小片段)。
如果您想使用标准的 QIODevice 函数 read()、readLine()、write()等,您必须首先通过调用connectToHost()将套接字直接连接到对等体。每次将数据报写入网络时,套接字都会发出bytesWritten()信号。
如果您只是想发送数据报,您不需要调用 bind()。readyRead()信号在数据报到达时发出。在这种情况下,hasPendingDatagrams()返回 true。调用 pendingDatagramSize()来获取第一个待处理数据报的大小,并调用 readDatagram()或 receiveDatagram()来读取它。注意:当您接收到readyRead()信号时,一个传入的数据报应该被读取,否则这个信号将不会被发送到下一个数据
报。
UDP 通信示意图如下。重点是 QUdpSocket 类,已经为我们提供了 UDP 通信的基础
在这里插入图片描述

11.3 UDP 单播与广播

例 10_udp_unicast_broadcast, UDP 单播 与广播应用 ,本例大体流程首先获取本地 IP 地址。创建一个 udpSocket 套接字,然后绑定本地主机的端口(也就是监听端口)。我们可以使用 QUdpSocket 类提供的读写函数 readDatagram 和 writeDatagram,知道目标 IP 地址和端口,即可完成消息的接收与发送。
项目文件 10_udp_unicast_broadcast.pro 文件第一行添加的代码部分如下。 QT += core gui network

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QUdpSocket>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QTextBrowser>
#include <QLabel>
#include <QComboBox>
#include <QSpinBox>
#include <QHostInfo>
#include <QLineEdit>
#include <QNetworkInterface>
#include <QDebug>

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    /* Udp 通信套接字 */
    QUdpSocket *udpSocket;

    /* 按钮 */
    QPushButton *pushButton[5];


    /* 标签文本 */
    QLabel *label[3];

    /* 水平容器 */
    QWidget *hWidget[3];

    /* 水平布局 */
    QHBoxLayout *hBoxLayout[3];

    /* 垂直容器 */
    QWidget *vWidget;

    /* 垂直布局 */
    QVBoxLayout *vBoxLayout;

    /* 文本浏览框 */
    QTextBrowser *textBrowser;

    /* 用于显示本地 ip */
    QComboBox *comboBox;

    /* 用于选择端口 */
    QSpinBox *spinBox[2];

    /* 文本输入框 */
    QLineEdit *lineEdit;

    /* 存储本地的 ip 列表地址 */
    QList<QHostAddress> IPlist;

    /* 获取本地的所有 ip */
    void getLocalHostIP();

    private slots:
    /* 绑定端口 */
    void bindPort();

    /* 解绑端口 */
    void unbindPort();

    /* 清除文本框时的内容 */
    void clearTextBrowser();

    /* 接收到消息 */
     void receiveMessages();

    /* 发送消息 */
    void sendMessages();

    /* 广播消息 */
    void sendBroadcastMessages();

    /* 连接状态改变槽函数 */
    void socketStateChange(QAbstractSocket::SocketState);
};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置主窗体的位置与大小 */
     this->setGeometry(0, 0, 800, 480);

    /* udp 套接字 */
     udpSocket = new QUdpSocket(this);

    /* 绑定端口按钮 */
     pushButton[0]= new QPushButton();
    /* 解绑端口按钮 */
     pushButton[1]= new QPushButton();
    /* 清空聊天文本按钮 */
     pushButton[2]= new QPushButton();
    /* 发送消息按钮 */
     pushButton[3]= new QPushButton();
    /* 广播消息按钮 */
     pushButton[4]= new QPushButton();

    /* 水平布局一 */
     hBoxLayout[0]= new QHBoxLayout();
    /* 水平布局二 */
     hBoxLayout[1]= new QHBoxLayout();
    /* 水平布局三 */
     hBoxLayout[2]= new QHBoxLayout();
    /* 水平布局四 */
     hBoxLayout[3]= new QHBoxLayout();

    /* 水平容器一 */
     hWidget[0]= new QWidget();
    /* 水平容器二 */
     hWidget[1]= new QWidget();
    /* 水平容器三 */
     hWidget[2]= new QWidget();


     vWidget = new QWidget();
     vBoxLayout = new QVBoxLayout();

    /* 标签实例化 */
     label[0]= new QLabel();
     label[1]= new QLabel();
     label[2]= new QLabel();

     lineEdit = new QLineEdit();
     comboBox = new QComboBox();
     spinBox[0]= new QSpinBox();
     spinBox[1]= new QSpinBox();
     textBrowser = new QTextBrowser();

     label[0]->setText("目标 IP 地址:");
     label[1]->setText("绑定端口:");
     label[2]->setText("目标端口:");

    /* 设置标签根据文本文字大小自适应大小 */
     label[0]->setSizePolicy(QSizePolicy::Fixed,
     QSizePolicy::Fixed);
     label[1]->setSizePolicy(QSizePolicy::Fixed,
     QSizePolicy::Fixed);
     label[2]->setSizePolicy(QSizePolicy::Fixed,
     QSizePolicy::Fixed);

    /* 设置端口号的范围,注意不要与主机的已使用的端口号冲突 */
     spinBox[0]->setRange(10000, 99999);
     spinBox[1]->setRange(10000, 99999);

     pushButton[0]->setText("绑定端口");
     pushButton[1]->setText("解除绑定");
     pushButton[2]->setText("清空文本");
     pushButton[3]->setText("发送消息");
     pushButton[4]->setText("广播消息");

    /* 设置停止监听状态不可用 */
     pushButton[1]->setEnabled(false);

    /* 设置输入框默认的文本 */
     lineEdit->setText("您好!");

    /* 水平布局一添加内容 */
     hBoxLayout[0]->addWidget(pushButton[0]);
     hBoxLayout[0]->addWidget(pushButton[1]);
     hBoxLayout[0]->addWidget(pushButton[2]);

    /* 设置水平容器的布局为水平布局一 */
     hWidget[0]->setLayout(hBoxLayout[0]);

     hBoxLayout[1]->addWidget(label[0]);
     hBoxLayout[1]->addWidget(comboBox);
     hBoxLayout[1]->addWidget(label[1]);
     hBoxLayout[1]->addWidget(spinBox[0]);
     hBoxLayout[1]->addWidget(label[2]);
     hBoxLayout[1]->addWidget(spinBox[1]);

    /* 设置水平容器的布局为水平布局二 */
     hWidget[1]->setLayout(hBoxLayout[1]);

    /* 水平布局三添加内容 */

     hBoxLayout[2]->addWidget(lineEdit);
     hBoxLayout[2]->addWidget(pushButton[3]);
     hBoxLayout[2]->addWidget(pushButton[4]);

    /* 设置水平容器三的布局为水平布局一 */
     hWidget[2]->setLayout(hBoxLayout[2]);

    /* 垂直布局添加内容 */
     vBoxLayout->addWidget(textBrowser);
     vBoxLayout->addWidget(hWidget[1]);
     vBoxLayout->addWidget(hWidget[0]);
     vBoxLayout->addWidget(hWidget[2]);

    /* 设置垂直容器的布局为垂直布局 */
     vWidget->setLayout(vBoxLayout);

    /* 居中显示 */
     setCentralWidget(vWidget);

    /* 获取本地 ip */
     getLocalHostIP();

    /* 信号槽连接 */
     connect(pushButton[0], SIGNAL(clicked()),this, SLOT(bindPort()));
     connect(pushButton[1], SIGNAL(clicked()),this, SLOT(unbindPort()));
     connect(pushButton[2], SIGNAL(clicked()),this, SLOT(clearTextBrowser()));
     connect(pushButton[3], SIGNAL(clicked()),this, SLOT(sendMessages()));
     connect(pushButton[4], SIGNAL(clicked()),this, SLOT(sendBroadcastMessages()));
     connect(udpSocket, SIGNAL(readyRead()),this, SLOT(receiveMessages()));
     connect(udpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),this,SLOT(socketStateChange(QAbstractSocket::SocketState)));
}

 MainWindow::~MainWindow()
{

}

 void MainWindow::bindPort()
{
     quint16 port = spinBox[0]->value();

    /* 绑定端口需要在 socket 的状态为 UnconnectedState */
     if (udpSocket->state()!= QAbstractSocket::UnconnectedState)
        udpSocket->close();

     if (udpSocket->bind(port)){
        textBrowser->append("已经成功绑定端口:"+ QString::number(port));

        /* 设置界面中的元素的可用状态 */
         pushButton[0]->setEnabled(false);
         pushButton[1]->setEnabled(true);
         spinBox[1]->setEnabled(false);
    }
}

 void MainWindow::unbindPort()
{
    /* 解绑,不再监听 */
     udpSocket->abort();

    /* 设置界面中的元素的可用状态 */
     pushButton[0]->setEnabled(true);
     pushButton[1]->setEnabled(false);
     spinBox[1]->setEnabled(true);
}

/* 获取本地 IP */
 void MainWindow::getLocalHostIP()
{
    ///* 获取主机的名称 */
    // QString hostName = QHostInfo::localHostName();

    ///* 主机的信息 */
    // QHostInfo hostInfo = QHostInfo::fromName(hostName);

    ///* ip 列表,addresses 返回 ip 地址列表,注意主机应能从路由器获取到
    //* IP,否则可能返回空的列表(ubuntu 用此方法只能获取到环回 IP)*/

    // IPlist = hostInfo.addresses();
    // qDebug()<<IPlist<<endl;

    ///* 遍历 IPlist */
    // foreach (QHostAddress ip, IPlist){
    // if (ip.protocol()== QAbstractSocket::IPv4Protocol)
    // comboBox->addItem(ip.toString());
    //}

    /* 获取所有的网络接口,
    * QNetworkInterface 类提供主机的 IP 地址和网络接口的列表 */
     QList<QNetworkInterface> list = QNetworkInterface::allInterfaces();

    /* 遍历 list */
     foreach (QNetworkInterface interface, list){

        /* QNetworkAddressEntry 类存储 IP 地址子网掩码和广播地址 */
         QList<QNetworkAddressEntry> entryList= interface.addressEntries();

        /* 遍历 entryList */
         foreach (QNetworkAddressEntry entry, entryList){
            /* 过滤 IPv6 地址,只留下 IPv4 */
             if (entry.ip().protocol()== QAbstractSocket::IPv4Protocol){
                 comboBox->addItem(entry.ip().toString());
                /* 添加到 IP 列表中 */
                 IPlist<<entry.ip();
            }
        }
    }
}

/* 清除文本浏览框里的内容 */
 void MainWindow::clearTextBrowser()
{
    /* 清除文本浏览器的内容 */
     textBrowser->clear();
}

/* 客户端接收消息 */
 void MainWindow::receiveMessages()

{
    /* 局部变量,用于获取发送者的 IP 和端口 */
     QHostAddress peerAddr;
     quint16 peerPort;

    /* 如果有数据已经准备好 */
     while (udpSocket->hasPendingDatagrams()){
        /* udpSocket 发送的数据报是 QByteArray 类型的字节数组 */
         QByteArray datagram;

        /* 重新定义数组的大小 */
         datagram.resize(udpSocket->pendingDatagramSize());

        /* 读取数据,并获取发送方的 IP 地址和端口 */
         udpSocket->readDatagram(datagram.data(),datagram.size(),&peerAddr,&peerPort);
        /* 转为字符串 */
         QString str = datagram.data();

        /* 显示信息到文本浏览框窗口 */
         textBrowser->append("接收来自"+ peerAddr.toString() +":"+ QString::number(peerPort)+ str);
    }
}

/* 客户端发送消息 */
 void MainWindow::sendMessages()
{
    /* 文本浏览框显示发送的信息 */
     textBrowser->append("发送:"+ lineEdit->text());

    /* 要发送的信息,转为 QByteArray 类型字节数组,数据一般少于 512 个字节 */
     QByteArray data = lineEdit->text().toUtf8();

    /* 要发送的目标 Ip 地址 */
     QHostAddress peerAddr = IPlist[comboBox->currentIndex()];


    /* 要发送的目标端口号 */
     quint16 peerPort = spinBox[1]->value();

    /* 发送消息 */
     udpSocket->writeDatagram(data, peerAddr, peerPort);
}

 void MainWindow::sendBroadcastMessages()
{
    /* 文本浏览框显示发送的信息 */
     textBrowser->append("发送:"+ lineEdit->text());

    /* 要发送的信息,转为 QByteArray 类型字节数组,数据一般少于 512 个字节 */
     QByteArray data = lineEdit->text().toUtf8();

    /* 广播地址,一般为 255.255.255.255,
    * 同一网段内监听目标端口的程序都会接收到消息 */
     QHostAddress peerAddr = QHostAddress::Broadcast;

    /* 要发送的目标端口号 */
     quint16 peerPort = spinBox[1]->text().toInt();

    /* 发送消息 */
     udpSocket->writeDatagram(data, peerAddr, peerPort);
}
/* socket 状态改变 */
 void MainWindow::socketStateChange(QAbstractSocket::SocketState state)
{
     switch (state){
         case QAbstractSocket::UnconnectedState:
             textBrowser->append("scoket 状态:UnconnectedState");
             break;
         case QAbstractSocket::ConnectedState:
             textBrowser->append("scoket 状态:ConnectedState");
             break;
         case QAbstractSocket::ConnectingState:
             textBrowser->append("scoket 状态:ConnectingState");
             break;
         case QAbstractSocket::HostLookupState:
             textBrowser->append("scoket 状态:HostLookupState");
             break;

         case QAbstractSocket::ClosingState:
             textBrowser->append("scoket 状态:ClosingState");
             break;
         case QAbstractSocket::ListeningState:
             textBrowser->append("scoket 状态:ListeningState");
             break;
         case QAbstractSocket::BoundState:
             textBrowser->append("scoket 状态:BoundState");
             break;
         default:break;
    }
}


  1. 运行,本例设置目标 IP 地址为 127.0.0.1,此 IP 地址是环回 IP 地址,可以用于无网络时测试。绑定端口号与目标端口号相同,也就是说,此程序正在监听端口号为 10000的数据,此程序也向目标 IP 地址 127.0.0.1 的 10000 端口号发送数据,实际上此程序就完成了
    自发自收。当我们点击发送消息按钮时,文本消息窗口显示发送的数据“您好!”,同时接收到由本地IP 127.0.0.1 发出的数据“您好!”。其中 ffff:是通信套接字的标识。呵呵!您可能会问为什么不是本主机的其它地址如(192.168.1.x)发出的呢?因为我们选择了目标的 IP 地址为 127.0.0.1,那么要与此目标地址通信,必须使用相同网段的 IP 设备与之通信。注意不能用本地环回发送消息到其他主机上。因为本地环回 IP 只适用于本地主机上的 IP 通信。当我们点击广播消息按钮时,广播发送的目标 IP 地址变成了广播地址 255.255.255.255。那么我们将收到从本地 IP 地址 192.168.x.x 的数据。如下图,收到了从 192.168.1.129 发送过来的数据。因为环回 IP 127.0.0.1 的广播地址为 255.0.0.0,所以要与 255.255.255.255 的网段里的 IP通信数据必须是由 192.168.x.x 上发出的。如果其他同一网段上的其他主机正在监听目标端口,那么它们将同时收到消息。这也验证了上一小节为什么会从 127.0.0.1 发送数据。

在这里插入图片描述

11.4 网络下载实例

Qt 网 络 模 块 还 提 供 了 直 接 访 问 如 HTTP , FTP 等 网 络 协 议 的 类 , 这 些 类 是QNetworkAccessManager、QNetworkRequest 和 QNetworkReply。通常需要这三个类协作才能完成一个网络操作。可以用于从网络获取时间,天气和图片等等数据。比如本例需要下载一张图片,大概流程如下。由 QNetworkRequest 类设置一个 URL 地址发起网络协议请求,QNetworkRequest 类保存要用QNetworkAccessManager 发送的请求。QNetworkRequest 是网络访问 API 的一部分,是一个持有通过网络发送请求所需信息的类。它包含一个 URL 和一些可用于修改请求的辅助信息。QNetworkAccessManager 类允许应用程序发送网络请求并接收响应。在QNetworkRequest发起网络请求后,QNetworkAccessManager 负责发送网络请求,创建网络响应。QNetworkReply类就用于 QNetworkAccessManager 创建的网络响应。最终由QNetworkReply处理网络响应。它提供了 finished()、readyRead()和downloadProgress()等信号,可以监测网络响应的执行情况。并且 QNetworkReply 继承于 QIODevice,所以 QNetworkReply 支持流读写,可以直接用 read()和 write 等功能。pro文件中QT += core gui network

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QFile>
#include <QLabel>
#include <QPushButton>
#include <QProgressBar>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLineEdit>

 class MainWindow : public QMainWindow
{
Q_OBJECT

public:
     MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /* 网络管理 */
     QNetworkAccessManager *networkAccessManager;

    /* 标签 */
     QLabel *label[3];

    /* 按钮 */
     QPushButton *pushButton;

    /* 下载进度条 */
     QProgressBar *progressBar;

    /* 水平布局 */
     QHBoxLayout *hBoxLayout[2];

    /* 垂直布局 */
     QVBoxLayout *vBoxLayout;

    /* 水平容器 */
     QWidget *hWidget[2];

    /* 垂直容器 */
     QWidget *vWidget;

    /* 链接输入框 */
     QLineEdit *lineEdit;

     private slots:
    /* 读取数据 */
     void readyReadData();

    /* 响应完成处理 */
     void replyFinished();

    /* 下载进度管理 */
     void imageDownloadProgress(qint64, qint64);

    /* 点击开始下载 */
     void startDownload();

    /* 响应错误处理函数 */
     void networkReplyError(QNetworkReply::NetworkError);
};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置主窗体的位置与大小 */
     this->setGeometry(0, 0, 800, 480);

    /* udp 套接字 */
     udpSocket = new QUdpSocket(this);

    /* 绑定端口按钮 */
     pushButton[0]= new QPushButton();
    /* 解绑端口按钮 */
     pushButton[1]= new QPushButton();
    /* 清空聊天文本按钮 */
     pushButton[2]= new QPushButton();
    /* 发送消息按钮 */
     pushButton[3]= new QPushButton();
    /* 广播消息按钮 */
     pushButton[4]= new QPushButton();

    /* 水平布局一 */
     hBoxLayout[0]= new QHBoxLayout();
    /* 水平布局二 */
     hBoxLayout[1]= new QHBoxLayout();
    /* 水平布局三 */
     hBoxLayout[2]= new QHBoxLayout();
    /* 水平布局四 */
     hBoxLayout[3]= new QHBoxLayout();

    /* 水平容器一 */
     hWidget[0]= new QWidget();
    /* 水平容器二 */
     hWidget[1]= new QWidget();
    /* 水平容器三 */
     hWidget[2]= new QWidget();


     vWidget = new QWidget();
     vBoxLayout = new QVBoxLayout();

    /* 标签实例化 */
     label[0]= new QLabel();
     label[1]= new QLabel();
     label[2]= new QLabel();

     lineEdit = new QLineEdit();
     comboBox = new QComboBox();
     spinBox[0]= new QSpinBox();
     spinBox[1]= new QSpinBox();
     textBrowser = new QTextBrowser();

     label[0]->setText("目标 IP 地址:");
     label[1]->setText("绑定端口:");
     label[2]->setText("目标端口:");

    /* 设置标签根据文本文字大小自适应大小 */
     label[0]->setSizePolicy(QSizePolicy::Fixed,
     QSizePolicy::Fixed);
     label[1]->setSizePolicy(QSizePolicy::Fixed,
     QSizePolicy::Fixed);
     label[2]->setSizePolicy(QSizePolicy::Fixed,
     QSizePolicy::Fixed);

    /* 设置端口号的范围,注意不要与主机的已使用的端口号冲突 */
     spinBox[0]->setRange(10000, 99999);
     spinBox[1]->setRange(10000, 99999);

     pushButton[0]->setText("绑定端口");
     pushButton[1]->setText("解除绑定");
     pushButton[2]->setText("清空文本");
     pushButton[3]->setText("发送消息");
     pushButton[4]->setText("广播消息");

    /* 设置停止监听状态不可用 */
     pushButton[1]->setEnabled(false);

    /* 设置输入框默认的文本 */
     lineEdit->setText("您好!");

    /* 水平布局一添加内容 */
     hBoxLayout[0]->addWidget(pushButton[0]);
     hBoxLayout[0]->addWidget(pushButton[1]);
     hBoxLayout[0]->addWidget(pushButton[2]);

    /* 设置水平容器的布局为水平布局一 */
     hWidget[0]->setLayout(hBoxLayout[0]);

     hBoxLayout[1]->addWidget(label[0]);
     hBoxLayout[1]->addWidget(comboBox);
     hBoxLayout[1]->addWidget(label[1]);
     hBoxLayout[1]->addWidget(spinBox[0]);
     hBoxLayout[1]->addWidget(label[2]);
     hBoxLayout[1]->addWidget(spinBox[1]);

    /* 设置水平容器的布局为水平布局二 */
     hWidget[1]->setLayout(hBoxLayout[1]);

    /* 水平布局三添加内容 */

     hBoxLayout[2]->addWidget(lineEdit);
     hBoxLayout[2]->addWidget(pushButton[3]);
     hBoxLayout[2]->addWidget(pushButton[4]);

    /* 设置水平容器三的布局为水平布局一 */
     hWidget[2]->setLayout(hBoxLayout[2]);

    /* 垂直布局添加内容 */
     vBoxLayout->addWidget(textBrowser);
     vBoxLayout->addWidget(hWidget[1]);
     vBoxLayout->addWidget(hWidget[0]);
     vBoxLayout->addWidget(hWidget[2]);

    /* 设置垂直容器的布局为垂直布局 */
     vWidget->setLayout(vBoxLayout);

    /* 居中显示 */
     setCentralWidget(vWidget);

    /* 获取本地 ip */
     getLocalHostIP();

    /* 信号槽连接 */
     connect(pushButton[0], SIGNAL(clicked()),this, SLOT(bindPort()));
     connect(pushButton[1], SIGNAL(clicked()),this, SLOT(unbindPort()));
     connect(pushButton[2], SIGNAL(clicked()),this, SLOT(clearTextBrowser()));
     connect(pushButton[3], SIGNAL(clicked()),this, SLOT(sendMessages()));
     connect(pushButton[4], SIGNAL(clicked()),this, SLOT(sendBroadcastMessages()));
     connect(udpSocket, SIGNAL(readyRead()),this, SLOT(receiveMessages()));
     connect(udpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),this,SLOT(socketStateChange(QAbstractSocket::SocketState)));
}

 MainWindow::~MainWindow()
{

}

 void MainWindow::bindPort()
{
     quint16 port = spinBox[0]->value();

    /* 绑定端口需要在 socket 的状态为 UnconnectedState */
     if (udpSocket->state()!= QAbstractSocket::UnconnectedState)
        udpSocket->close();

     if (udpSocket->bind(port)){
        textBrowser->append("已经成功绑定端口:"+ QString::number(port));

        /* 设置界面中的元素的可用状态 */
         pushButton[0]->setEnabled(false);
         pushButton[1]->setEnabled(true);
         spinBox[1]->setEnabled(false);
    }
}

 void MainWindow::unbindPort()
{
    /* 解绑,不再监听 */
     udpSocket->abort();

    /* 设置界面中的元素的可用状态 */
     pushButton[0]->setEnabled(true);
     pushButton[1]->setEnabled(false);
     spinBox[1]->setEnabled(true);
}

/* 获取本地 IP */
 void MainWindow::getLocalHostIP()
{
    ///* 获取主机的名称 */
    // QString hostName = QHostInfo::localHostName();

    ///* 主机的信息 */
    // QHostInfo hostInfo = QHostInfo::fromName(hostName);

    ///* ip 列表,addresses 返回 ip 地址列表,注意主机应能从路由器获取到
    //* IP,否则可能返回空的列表(ubuntu 用此方法只能获取到环回 IP)*/

    // IPlist = hostInfo.addresses();
    // qDebug()<<IPlist<<endl;

    ///* 遍历 IPlist */
    // foreach (QHostAddress ip, IPlist){
    // if (ip.protocol()== QAbstractSocket::IPv4Protocol)
    // comboBox->addItem(ip.toString());
    //}

    /* 获取所有的网络接口,
    * QNetworkInterface 类提供主机的 IP 地址和网络接口的列表 */
     QList<QNetworkInterface> list = QNetworkInterface::allInterfaces();

    /* 遍历 list */
     foreach (QNetworkInterface interface, list){

        /* QNetworkAddressEntry 类存储 IP 地址子网掩码和广播地址 */
         QList<QNetworkAddressEntry> entryList= interface.addressEntries();

        /* 遍历 entryList */
         foreach (QNetworkAddressEntry entry, entryList){
            /* 过滤 IPv6 地址,只留下 IPv4 */
             if (entry.ip().protocol()== QAbstractSocket::IPv4Protocol){
                 comboBox->addItem(entry.ip().toString());
                /* 添加到 IP 列表中 */
                 IPlist<<entry.ip();
            }
        }
    }
}

/* 清除文本浏览框里的内容 */
 void MainWindow::clearTextBrowser()
{
    /* 清除文本浏览器的内容 */
     textBrowser->clear();
}

/* 客户端接收消息 */
 void MainWindow::receiveMessages()

{
    /* 局部变量,用于获取发送者的 IP 和端口 */
     QHostAddress peerAddr;
     quint16 peerPort;

    /* 如果有数据已经准备好 */
     while (udpSocket->hasPendingDatagrams()){
        /* udpSocket 发送的数据报是 QByteArray 类型的字节数组 */
         QByteArray datagram;

        /* 重新定义数组的大小 */
         datagram.resize(udpSocket->pendingDatagramSize());

        /* 读取数据,并获取发送方的 IP 地址和端口 */
         udpSocket->readDatagram(datagram.data(),datagram.size(),&peerAddr,&peerPort);
        /* 转为字符串 */
         QString str = datagram.data();

        /* 显示信息到文本浏览框窗口 */
         textBrowser->append("接收来自"+ peerAddr.toString() +":"+ QString::number(peerPort)+ str);
    }
}

/* 客户端发送消息 */
 void MainWindow::sendMessages()
{
    /* 文本浏览框显示发送的信息 */
     textBrowser->append("发送:"+ lineEdit->text());

    /* 要发送的信息,转为 QByteArray 类型字节数组,数据一般少于 512 个字节 */
     QByteArray data = lineEdit->text().toUtf8();

    /* 要发送的目标 Ip 地址 */
     QHostAddress peerAddr = IPlist[comboBox->currentIndex()];


    /* 要发送的目标端口号 */
     quint16 peerPort = spinBox[1]->value();

    /* 发送消息 */
     udpSocket->writeDatagram(data, peerAddr, peerPort);
}

 void MainWindow::sendBroadcastMessages()
{
    /* 文本浏览框显示发送的信息 */
     textBrowser->append("发送:"+ lineEdit->text());

    /* 要发送的信息,转为 QByteArray 类型字节数组,数据一般少于 512 个字节 */
     QByteArray data = lineEdit->text().toUtf8();

    /* 广播地址,一般为 255.255.255.255,
    * 同一网段内监听目标端口的程序都会接收到消息 */
     QHostAddress peerAddr = QHostAddress::Broadcast;

    /* 要发送的目标端口号 */
     quint16 peerPort = spinBox[1]->text().toInt();

    /* 发送消息 */
     udpSocket->writeDatagram(data, peerAddr, peerPort);
}
/* socket 状态改变 */
 void MainWindow::socketStateChange(QAbstractSocket::SocketState state)
{
     switch (state){
         case QAbstractSocket::UnconnectedState:
             textBrowser->append("scoket 状态:UnconnectedState");
             break;
         case QAbstractSocket::ConnectedState:
             textBrowser->append("scoket 状态:ConnectedState");
             break;
         case QAbstractSocket::ConnectingState:
             textBrowser->append("scoket 状态:ConnectingState");
             break;
         case QAbstractSocket::HostLookupState:
             textBrowser->append("scoket 状态:HostLookupState");
             break;

         case QAbstractSocket::ClosingState:
             textBrowser->append("scoket 状态:ClosingState");
             break;
         case QAbstractSocket::ListeningState:
             textBrowser->append("scoket 状态:ListeningState");
             break;
         case QAbstractSocket::BoundState:
             textBrowser->append("scoket 状态:BoundState");
             break;
         default:break;
    }
}
  1. 运行,
    在这里插入图片描述

12.多媒体

12.1 Qt 多媒体简介

Qt 多媒体模块提供了很多类,主要有 QMediaPlayer,QSound、QSoundEffect、QAudioOutput、QAudioInput、QAudioRecorder、QVideoWidget 等等。类太多了不一一作解释,可以直接复制名字到 Qt 的帮助文档里查看该解释。可以从名称大概了解它们是什么意思,具体类的使用直接看
想要在 Qt 里使用使用 Qt 多媒体模块,需要在 pro 项目文件里添加如下语句。QT += multimedia
注意:Qt 中的音乐播放器与视频播放器需要在 Ubuntu 里安装媒体解码器才能实现播放。
 Ubuntu16 / Ubuntu18,需要安装以下插件。播放音乐需要安装 Gst 解码插件。需要在终端输入如下指令
sudo apt-get install gstreamer1.0-plugins-base gstreamer1.0-plugins-bad gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly gstreamer1.0-pulseaudio gstreamer1.0-libav
 Windows 需要安装如 LAVFilters 解码器,只需要百度 LAVFilters,找到 LAVFilters 官网下载此软件即可

12.2 音效文件播放

播放音效文件,比如简短的提示音(按键音等),可以使用 Qt 的 QSoundEffect 和 QSound类来播放。Qt 的 QSoundEffect 和 QSound 类主要区别是 QSound(异步方式播放)只能播放本地的 WAV音效文件(WAV 音效文件是 PC 机上最为流行的声音文件格式,但其文件尺寸较大,多用于存储简短的声音片段,具有低延时性,不失真的特点),QSoundEffect 不仅可以播放网络文件,也可以播放本地音效文件,播放网络的文件一般使用到 QUrl 链接。
例 13_button_sound,按钮音效测试。本例大体流程,通过点击一个按钮,然后使用 QSound 来播放音效文件,模仿按键按下的声音。
项目文件 13_button_sound.pro 文件第一行添加的代码部分如下。QT += core gui multimedia

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QSound>
#include <QPushButton>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    /* 按钮 */
    QPushButton *pushButton;

private slots:
    /* 按钮点击槽函数 */
    void pushButtonClicked();

};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
    /* 设置主窗体的位置与大小 */
    this->setGeometry(0, 0, 800, 480);

    /* 实例化按钮 */
    pushButton = new QPushButton(this);

    /* 设置按钮文本 */
    pushButton->setText("按钮音效测试");

    /* 设置按钮的位置与大小 */
    pushButton->setGeometry(340, 220, 120, 40);

    /* 信号槽连接 */
    connect(pushButton, SIGNAL(clicked()),
            this, SLOT(pushButtonClicked()));
 }
 MainWindow::~MainWindow()
 {

 }
 void MainWindow::pushButtonClicked()
 {
    /* 异步的方式播放 */
    QSound::play(":/bell.wav");
 }
  1. 运行,
    在这里插入图片描述

12.3 音乐播放器

QMediaPlayer 类是一个高级媒体播放类。它可以用来播放歌曲、电影和网络广播等内容。一般用于播放 mp3 和 mp4 等等媒体文件。QMediaPlayer 类常常与 QMediaPlaylist 类一起使用。可以很轻松的设计一个自己喜欢的音乐播放器与视频播放器。
QMediaPlayer 提供了很多信号,我们可以使用这些信号来完成音乐播放器的一系列操作,比如媒体状态改变的信号stateChanged(QMediaPlayer::State state),判断这个 state 的状态就可以知道什么时候媒体暂停、播放、停止了。Qt 在媒体播放类已经提供了很多功能函数给我们使用,像直接使用 play()函数就可以实现音乐文件的播放,前提我们需要知道媒体文件的路径。pause()函数可以直接暂停媒体播放等等,这些都可以在 Qt 帮助文档里查看 QMediaPlayer 类的使用方法就可以知道。
例 14_musicplayer,音乐播放器(注意本例有用到 qss 样式文件,关于如何添加资源文件与 qss 文件请参考 7.1.3 小节。音乐播放器的功
能这些都为大家所熟知,不用笔者介绍了。
项目文件 14_musicplayer.pro 文件第一行添加QT += core gui multimedia

  1. mainwindow.h
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName   14_musicplayer
* @brief         mainwindow.h
* @author        Deng Zhimao
* @email         1252699831@qq.com
* @net           www.openedv.com
* @date          2021-04-20
*******************************************************************/
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QMediaPlayer>
#include <QMediaPlaylist>
#include <QPushButton>
#include <QSlider>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QListWidget>
#include <QLabel>
#include <QSpacerItem>
#include <QDebug>

/* 媒体信息结构体 */
struct MediaObjectInfo {
    /* 用于保存歌曲文件名 */
    QString fileName;
    /* 用于保存歌曲文件路径 */
    QString filePath;
};

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    /* 媒体播放器,用于播放音乐 */
    QMediaPlayer *musicPlayer;

    /* 媒体列表 */
    QMediaPlaylist *mediaPlaylist;

    /* 音乐列表 */
    QListWidget *listWidget;

    /* 播放进度条 */
    QSlider *durationSlider;

    /* 音乐播放器按钮 */
    QPushButton *pushButton[7];

    /* 垂直布局 */
    QVBoxLayout *vBoxLayout[3];

    /* 水平布局 */
    QHBoxLayout *hBoxLayout[4];

    /* 垂直容器 */
    QWidget *vWidget[3];

    /* 水平容器 */
    QWidget *hWidget[4];

    /* 标签文本 */
    QLabel *label[4];

    /* 用于遮罩 */
    QWidget *listMask;

    /* 音乐布局函数 */
    void musicLayout();

    /* 主窗体大小重设大小函数重写 */
    void resizeEvent(QResizeEvent *event);

    /* 媒体信息存储 */
    QVector<MediaObjectInfo> mediaObjectInfo;

    /* 扫描歌曲 */
    void scanSongs();

    /* 媒体播放器类初始化 */
    void mediaPlayerInit();

private slots:
    /* 播放按钮点击 */
    void btn_play_clicked();

    /* 下一曲按钮点击*/
    void btn_next_clicked();

    /* 上一曲按钮点击 */
    void btn_previous_clicked();

    /* 媒体状态改变 */
    void mediaPlayerStateChanged(QMediaPlayer::State);

    /* 列表单击 */
    void listWidgetCliked(QListWidgetItem*);

    /* 媒体列表项改变 */
    void mediaPlaylistCurrentIndexChanged(int);

    /* 媒体总长度改变 */
    void musicPlayerDurationChanged(qint64);

    /* 媒体播放位置改变 */
    void mediaPlayerPositionChanged(qint64);

    /* 播放进度条松开 */
    void durationSliderReleased();
};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName   14_musicplayer
* @brief         mainwindow.cpp
* @author        Deng Zhimao
* @email         1252699831@qq.com
* @net           www.openedv.com
* @date          2021-04-20
*******************************************************************/
#include "mainwindow.h"
#include <QCoreApplication>
#include <QFileInfoList>
#include <QDir>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 布局初始化 */
    musicLayout();

    /* 媒体播放器初始化 */
    mediaPlayerInit();

    /* 扫描歌曲 */
    scanSongs();

    /* 按钮信号槽连接 */
    connect(pushButton[0], SIGNAL(clicked()),
            this, SLOT(btn_previous_clicked()));
    connect(pushButton[1], SIGNAL(clicked()),
            this, SLOT(btn_play_clicked()));
    connect(pushButton[2], SIGNAL(clicked()),
            this, SLOT(btn_next_clicked()));

    /* 媒体信号槽连接 */
    connect(musicPlayer,
            SIGNAL(stateChanged(QMediaPlayer::State)),
            this,
            SLOT(mediaPlayerStateChanged(QMediaPlayer::State)));
    connect(mediaPlaylist,
            SIGNAL(currentIndexChanged(int)),
            this,
            SLOT(mediaPlaylistCurrentIndexChanged(int)));
    connect(musicPlayer, SIGNAL(durationChanged(qint64)),
            this,
            SLOT(musicPlayerDurationChanged(qint64)));
    connect(musicPlayer,
            SIGNAL(positionChanged(qint64)),
            this,
            SLOT(mediaPlayerPositionChanged(qint64)));

    /* 列表信号槽连接 */
    connect(listWidget, SIGNAL(itemClicked(QListWidgetItem*)),
            this, SLOT(listWidgetCliked(QListWidgetItem*)));

    /* slider信号槽连接 */
    connect(durationSlider, SIGNAL(sliderReleased()),
            this, SLOT(durationSliderReleased()));

    /* 失去焦点 */
    this->setFocus();
}

void MainWindow::musicLayout()
{
    /* 设置位置与大小,这里固定为800, 480 */
    this->setGeometry(0, 0, 800, 480);
    QPalette pal;

    /* 按钮 */
    for (int i = 0; i < 7; i++)
        pushButton[i] = new QPushButton();

    /* 标签 */
    for (int i = 0; i < 4; i++)
        label[i] = new QLabel();

    for (int i = 0; i < 3; i++) {
        /* 垂直容器 */
        vWidget[i] = new QWidget();
        vWidget[i]->setAutoFillBackground(true);
        /* 垂直布局 */
        vBoxLayout[i] = new QVBoxLayout();
    }

    for (int i = 0; i < 4; i++) {
        /* 水平容器 */
        hWidget[i] = new QWidget();
        hWidget[i]->setAutoFillBackground(true);
        /* 水平布局 */
        hBoxLayout[i] = new QHBoxLayout();
    }

    /* 播放进度条 */
    durationSlider = new QSlider(Qt::Horizontal);
    durationSlider->setMinimumSize(300, 15);
    durationSlider->setMaximumHeight(15);
    durationSlider->setObjectName("durationSlider");

    /* 音乐列表 */
    listWidget = new QListWidget();
    listWidget->setObjectName("listWidget");
    listWidget->resize(310, 265);
    listWidget->setVerticalScrollBarPolicy(
                Qt::ScrollBarAlwaysOff);
    listWidget->setHorizontalScrollBarPolicy(
                Qt::ScrollBarAlwaysOff);

    /* 列表遮罩 */
    listMask = new QWidget(listWidget);
    listMask->setMinimumSize(310, 50);
    listMask->setMinimumHeight(50);
    listMask->setObjectName("listMask");
    listMask->setGeometry(0,
                          listWidget->height() - 50,
                          310,
                          50);

    /* 设置对象名称 */
    pushButton[0]->setObjectName("btn_previous");
    pushButton[1]->setObjectName("btn_play");
    pushButton[2]->setObjectName("btn_next");
    pushButton[3]->setObjectName("btn_favorite");
    pushButton[4]->setObjectName("btn_mode");
    pushButton[5]->setObjectName("btn_menu");
    pushButton[6]->setObjectName("btn_volume");

    /* 设置按钮属性 */
    pushButton[1]->setCheckable(true);
    pushButton[3]->setCheckable(true);

    /* H0布局 */
    vWidget[0]->setMinimumSize(310, 480);
    vWidget[0]->setMaximumWidth(310);
    vWidget[1]->setMinimumSize(320, 480);
    QSpacerItem *hSpacer0 = new QSpacerItem(70, 480,
                                            QSizePolicy::Minimum,
                                            QSizePolicy::Maximum);

    QSpacerItem *hSpacer1 = new QSpacerItem(65, 480,
                                            QSizePolicy::Minimum,
                                            QSizePolicy::Maximum);

    QSpacerItem *hSpacer2 = new
            QSpacerItem(60, 480,
                        QSizePolicy::Minimum,
                        QSizePolicy::Maximum);

    hBoxLayout[0]->addSpacerItem(hSpacer0);//三个间隔空白区
    hBoxLayout[0]->addWidget(vWidget[0]);
    hBoxLayout[0]->addSpacerItem(hSpacer1);
    hBoxLayout[0]->addWidget(vWidget[1]);
    hBoxLayout[0]->addSpacerItem(hSpacer2);
    hBoxLayout[0]->setContentsMargins(0, 0, 0, 0);

    hWidget[0]->setLayout(hBoxLayout[0]);
    setCentralWidget(hWidget[0]);

    /* V0布局 */
    listWidget->setMinimumSize(310, 265);
    hWidget[1]->setMinimumSize(310, 80);
    hWidget[1]->setMaximumHeight(80);
    label[0]->setMinimumSize(310, 95);
    label[0]->setMaximumHeight(95);
    QSpacerItem *vSpacer0 = new QSpacerItem(310, 10,QSizePolicy::Minimum,QSizePolicy::Maximum);
    QSpacerItem *vSpacer1 = new QSpacerItem(310, 30, QSizePolicy::Minimum, QSizePolicy::Minimum);
    vBoxLayout[0]->addWidget(label[0]);
    vBoxLayout[0]->addWidget(listWidget);
    vBoxLayout[0]->addSpacerItem(vSpacer0);
    vBoxLayout[0]->addWidget(hWidget[1]);
    vBoxLayout[0]->addSpacerItem(vSpacer1);
    vBoxLayout[0]->setContentsMargins(0, 0, 0, 0);

    vWidget[0]->setLayout(vBoxLayout[0]);

    /* H1布局 */
    for (int i = 0; i < 3; i++) {
        pushButton[i]->setMinimumSize(80, 80);
    }
    QSpacerItem *hSpacer3 = new QSpacerItem(40, 80,QSizePolicy::Expanding,QSizePolicy::Expanding);
    QSpacerItem *hSpacer4 = new QSpacerItem(40, 80,QSizePolicy::Expanding,QSizePolicy::Expanding);
    hBoxLayout[1]->addWidget(pushButton[0]);
    hBoxLayout[1]->addSpacerItem(hSpacer3);
    hBoxLayout[1]->addWidget(pushButton[1]);
    hBoxLayout[1]->addSpacerItem(hSpacer4);
    hBoxLayout[1]->addWidget(pushButton[2]);
    hBoxLayout[1]->setContentsMargins(0, 0, 0, 0);

    hWidget[1]->setLayout(hBoxLayout[1]);

    /* V1布局 */
    QSpacerItem *vSpacer2 = new QSpacerItem(320, 40, QSizePolicy::Minimum, QSizePolicy::Maximum);
    QSpacerItem *vSpacer3 = new QSpacerItem(320, 20, QSizePolicy::Minimum,  QSizePolicy::Maximum);
    QSpacerItem *vSpacer4 = new  QSpacerItem(320, 30,
                         2 QSizePolicy::Minimum,
                        QSizePolicy::Minimum);
    label[1]->setMinimumSize(320, 320);
    QImage Image;
    Image.load(":/images/cd.png");
    QPixmap pixmap = QPixmap::fromImage(Image);
    int with = 320;
    int height = 320;
    QPixmap fitpixmap =
            pixmap.scaled(with, height,
                          Qt::IgnoreAspectRatio,
                          Qt::SmoothTransformation);
    label[1]->setPixmap(fitpixmap);
    label[1]->setAlignment(Qt::AlignCenter);
    vWidget[2]->setMinimumSize(300, 80);
    vWidget[2]->setMaximumHeight(80);
    vBoxLayout[1]->addSpacerItem(vSpacer2);
    vBoxLayout[1]->addWidget(label[1]);
    vBoxLayout[1]->addSpacerItem(vSpacer3);
    vBoxLayout[1]->addWidget(durationSlider);
    vBoxLayout[1]->addWidget(vWidget[2]);
    vBoxLayout[1]->addSpacerItem(vSpacer4);
    vBoxLayout[1]->setContentsMargins(0, 0, 0, 0);

    vWidget[1]->setLayout(vBoxLayout[1]);

    /* V2布局 */
    QSpacerItem *vSpacer5 = new
            QSpacerItem(300, 10,
                        QSizePolicy::Minimum,
                        QSizePolicy::Maximum);
    hWidget[2]->setMinimumSize(320, 20);
    hWidget[3]->setMinimumSize(320, 60);
    vBoxLayout[2]->addWidget(hWidget[2]);
    vBoxLayout[2]->addSpacerItem(vSpacer5);
    vBoxLayout[2]->addWidget(hWidget[3]);
    vBoxLayout[2]->setContentsMargins(0, 0, 0, 0);

    vWidget[2]->setLayout(vBoxLayout[2]);

    /* H2布局 */
    QFont font;

    font.setPixelSize(10);

    /* 设置标签文本 */
    label[0]->setText("Q Music,Enjoy it!");
    label[2]->setText("00:00");
    label[3]->setText("00:00");
    label[2]->setSizePolicy(QSizePolicy::Expanding,
                            QSizePolicy::Expanding);
    label[3]->setSizePolicy(QSizePolicy::Expanding,
                            QSizePolicy::Expanding);
    label[3]->setAlignment(Qt::AlignRight);
    label[2]->setAlignment(Qt::AlignLeft);
    label[2]->setFont(font);
    label[3]->setFont(font);

    pal.setColor(QPalette::WindowText, Qt::white);
    label[0]->setPalette(pal);
    label[2]->setPalette(pal);
    label[3]->setPalette(pal);

    hBoxLayout[2]->addWidget(label[2]);
    hBoxLayout[2]->addWidget(label[3]);

    hBoxLayout[2]->setContentsMargins(0, 0, 0, 0);
    hWidget[2]->setLayout(hBoxLayout[2]);

    /* H3布局 */
    QSpacerItem *hSpacer5 = new
            QSpacerItem(0, 60,
                        QSizePolicy::Minimum,
                        QSizePolicy::Maximum);
    QSpacerItem *hSpacer6 = new
            QSpacerItem(80, 60,
                        QSizePolicy::Maximum,
                        QSizePolicy::Maximum);
    QSpacerItem *hSpacer7 = new
            QSpacerItem(80, 60,
                        QSizePolicy::Maximum,
                        QSizePolicy::Maximum);
    QSpacerItem *hSpacer8 = new
            QSpacerItem(80, 60,
                        QSizePolicy::Maximum,
                        QSizePolicy::Maximum);
    QSpacerItem *hSpacer9 = new
            QSpacerItem(0, 60,
                        QSizePolicy::Minimum,
                        QSizePolicy::Maximum);

    for (int i = 3; i < 7; i++) {
        pushButton[i]->setMinimumSize(25, 25);
        pushButton[i]->setMaximumSize(25, 25);
    }

    hBoxLayout[3]->addSpacerItem(hSpacer5);
    hBoxLayout[3]->addWidget(pushButton[3]);
    hBoxLayout[3]->addSpacerItem(hSpacer6);
    hBoxLayout[3]->addWidget(pushButton[4]);
    hBoxLayout[3]->addSpacerItem(hSpacer7);
    hBoxLayout[3]->addWidget(pushButton[5]);
    hBoxLayout[3]->addSpacerItem(hSpacer8);
    hBoxLayout[3]->addWidget(pushButton[6]);
    hBoxLayout[3]->addSpacerItem(hSpacer9);
    hBoxLayout[3]->setContentsMargins(0, 0, 0, 0);
    hBoxLayout[3]->setAlignment(Qt::AlignHCenter);

    hWidget[3]->setLayout(hBoxLayout[3]);

    //hWidget[0]->setStyleSheet("background-color:red");
    //hWidget[1]->setStyleSheet("background-color:#ff5599");
    //hWidget[2]->setStyleSheet("background-color:#ff55ff");
    //hWidget[3]->setStyleSheet("background-color:black");
    //vWidget[0]->setStyleSheet("background-color:#555555");
    //vWidget[1]->setStyleSheet("background-color:green");
    //vWidget[2]->setStyleSheet("background-color:gray");

}

MainWindow::~MainWindow()
{
}

void MainWindow::btn_play_clicked()
{
    int state = musicPlayer->state();

    switch (state) {
    case QMediaPlayer::StoppedState:
        /* 媒体播放 */
        musicPlayer->play();
        break;

    case QMediaPlayer::PlayingState:
        /* 媒体暂停 */
        musicPlayer->pause();
        break;

    case QMediaPlayer::PausedState:
        musicPlayer->play();
        break;
    }
}

void MainWindow::btn_next_clicked()
{
    musicPlayer->stop();
    int count = mediaPlaylist->mediaCount();
    if (0 == count)
        return;

    /* 列表下一个 */
    mediaPlaylist->next();
    musicPlayer->play();
}

void MainWindow::btn_previous_clicked()
{
    musicPlayer->stop();
    int count = mediaPlaylist->mediaCount();
    if (0 == count)
        return;

    /* 列表上一个 */
    mediaPlaylist->previous();
    musicPlayer->play();
}

void MainWindow::mediaPlayerStateChanged(
        QMediaPlayer::State
        state)
{
    switch (state) {
    case QMediaPlayer::StoppedState:
        pushButton[1]->setChecked(false);
        break;

    case QMediaPlayer::PlayingState:
        pushButton[1]->setChecked(true);
        break;

    case QMediaPlayer::PausedState:
        pushButton[1]->setChecked(false);
        break;
    }
}

void MainWindow::listWidgetCliked(QListWidgetItem *item)
{
    musicPlayer->stop();
    mediaPlaylist->setCurrentIndex(listWidget->row(item));
    musicPlayer->play();
}

void MainWindow::mediaPlaylistCurrentIndexChanged(
        int index)
{
    if (-1 == index)
        return;

    /* 设置列表正在播放的项 */
    listWidget->setCurrentRow(index);
}

void MainWindow::musicPlayerDurationChanged(
        qint64 duration)
{
    durationSlider->setRange(0, duration / 1000);
    int second  = duration / 1000;
    int minute = second / 60;
    second %= 60;

    QString mediaDuration;
    mediaDuration.clear();

    if (minute >= 10)
        mediaDuration = QString::number(minute, 10);
    else
        mediaDuration = "0" + QString::number(minute, 10);

    if (second >= 10)
        mediaDuration = mediaDuration
                + ":" + QString::number(second, 10);
    else
        mediaDuration = mediaDuration
                + ":0" + QString::number(second, 10);

    /* 显示媒体总长度时间 */
    label[3]->setText(mediaDuration);
}

void MainWindow::mediaPlayerPositionChanged(
        qint64 position)
{
    if (!durationSlider->isSliderDown())
        durationSlider->setValue(position/1000);

    int second  = position / 1000;
    int minute = second / 60;
    second %= 60;

    QString mediaPosition;
    mediaPosition.clear();

    if (minute >= 10)
        mediaPosition = QString::number(minute, 10);
    else
        mediaPosition = "0" + QString::number(minute, 10);

    if (second >= 10)
        mediaPosition = mediaPosition
                + ":" + QString::number(second, 10);
    else
        mediaPosition = mediaPosition
                + ":0" + QString::number(second, 10);

    /* 显示现在播放的时间 */
    label[2]->setText(mediaPosition);
}

void MainWindow::resizeEvent(QResizeEvent *event)
{
    Q_UNUSED(event);
    listMask->setGeometry(0,
                          listWidget->height() - 50,310,50);
}

void MainWindow::durationSliderReleased()
{
    /* 设置媒体播放的位置 */
    musicPlayer->setPosition(durationSlider->value() * 1000);
}

void MainWindow::scanSongs()
{
    QDir dir(QCoreApplication::applicationDirPath()
             + "/myMusic");
    QDir dirbsolutePath(dir.absolutePath());
    /* 如果目录存在 */
    if (dirbsolutePath.exists()) {
        /* 定义过滤器 */
        QStringList filter;
        /* 包含所有.mp3后缀的文件 */
        filter << "*.mp3";
        /* 获取该目录下的所有文件 */
        QFileInfoList files =
                dirbsolutePath.entryInfoList(filter, QDir::Files);
        /* 遍历 */
        for (int i = 0; i < files.count(); i++) {
            MediaObjectInfo info;
            /* 使用utf-8编码 */
            QString fileName = QString::fromUtf8(files.at(i)
                                                 .fileName()
                                                 .replace(".mp3", "")
                                                 .toUtf8()
                                                 .data());
            info.fileName = fileName + "\n"
                    + fileName.split("-").at(1);
            info.filePath = QString::fromUtf8(files.at(i)
                                              .filePath()
                                              .toUtf8()
                                              .data());
            /* 媒体列表添加歌曲 */
            if (mediaPlaylist->addMedia(
                        QUrl::fromLocalFile(info.filePath))) {
                /* 添加到容器数组里储存 */
                mediaObjectInfo.append(info);
                /* 添加歌曲名字至列表 */
                listWidget->addItem(info.fileName);
            } else {
                qDebug()<<
                           mediaPlaylist->errorString()
                           .toUtf8().data()
                        << endl;
                qDebug()<< "  Error number:"
                         << mediaPlaylist->error()
                         << endl;
            }
        }
    }
}

void MainWindow::mediaPlayerInit()
{
    musicPlayer = new QMediaPlayer(this);
    mediaPlaylist = new QMediaPlaylist(this);
    /* 确保列表是空的 */
    mediaPlaylist->clear();
    /* 设置音乐播放器的列表为mediaPlaylist */
    musicPlayer->setPlaylist(mediaPlaylist);
    /* 设置播放模式,Loop是列循环 */
    mediaPlaylist->setPlaybackMode(QMediaPlaylist::Loop);
}

  1. 运行,先点击构建项目,项目构建完成后,再将本例的 myMusic 歌曲文件夹拷贝到可执行程序的文件夹同一级目录下,也就是Debug 目录下(windows 需要进入到 debug 目录)。再点击运行,就出现歌曲在列表里,如下图,点击播放即可播放歌曲,上一曲,下一曲也可以用。注意右下角的某些按钮功能,在本例没有继续去实现,比如音量控制,可以直接加一个垂直方向的滑条,然后控制媒体的软件音量即可。注意歌曲格式应严格为歌名 + “-” + 歌手名称.mp3,例如江南 - 林俊杰.mp3。中间的“-”是英文字符的短横杠,这样的目的是能够将歌曲名称和歌手分开显示到界面上。
    在这里插入图片描述

12.4 视频播放器

与音乐播放器一样使用 QMediaPlayer 类,不同的是需要使用 setVideoOutput(QVideoWidget*)设置一个视频输出窗口,好让视频在此窗口显示,其他步骤基本都一样
例 15_videoplayer,视频播放器。项目文件 15_videoplayer.pro 文件第一行添加。 QT += core gui multimedia multimediawidgets

  1. mainwindow.h
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName   15_videoplayer
* @brief         mainwindow.h
* @author        Deng Zhimao
* @email         1252699831@qq.com
* @net           www.openedv.com
* @date          2021-04-27
*******************************************************************/
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QMediaPlayer>
#include <QMediaPlaylist>
#include <QPushButton>
#include <QSlider>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QListWidget>
#include <QLabel>
#include <QSpacerItem>
#include <QVideoWidget>
#include <QDebug>

/* 媒体信息结构体 */
struct MediaObjectInfo {
    /* 用于保存视频文件名 */
    QString fileName;
    /* 用于保存视频文件路径 */
    QString filePath;
};

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    /* 媒体播放器,用于播放视频 */
    QMediaPlayer *videoPlayer;

    /* 媒体列表 */
    QMediaPlaylist *mediaPlaylist;

    /* 视频显示窗口 */
    QVideoWidget *videoWidget;

    /* 视频列表 */
    QListWidget *listWidget;

    /* 播放进度条 */
    QSlider *durationSlider;

    /* 音量条 */
    QSlider *volumeSlider;

    /* 视频播放器按钮 */
    QPushButton *pushButton[5];

    /* 水平布局 */
    QHBoxLayout *hBoxLayout[3];

    /* 水平容器 */
    QWidget *hWidget[3];

    /* 标签文本 */
    QLabel *label[2];

    /* 垂直容器 */
    QWidget *vWidget[2];

    /* 垂直界面 */
    QVBoxLayout *vBoxLayout[2];

    /* 视频布局函数 */
    void videoLayout();

    /* 主窗体大小重设大小函数重写 */
    void resizeEvent(QResizeEvent *event);

    /* 媒体信息存储 */
    QVector<MediaObjectInfo> mediaObjectInfo;

    /* 扫描本地视频文件 */
    void scanVideoFiles();

    /* 媒体初始化 */
    void mediaPlayerInit();
private slots:
    /* 播放按钮点击 */
    void btn_play_clicked();

    /* 下一个视频按钮点击 */
    void btn_next_clicked();

    /* 音量加 */
    void btn_volmeup_clicked();

    /* 音量减 */
    void btn_volmedown_clicked();

    /* 全屏 */
    void btn_fullscreen_clicked();

    /* 媒体状态改变 */
    void mediaPlayerStateChanged(QMediaPlayer::State);

    /* 列表单击 */
    void listWidgetCliked(QListWidgetItem*);

    /* 媒体列表项改变 */
    void mediaPlaylistCurrentIndexChanged(int);

    /* 媒体总长度改变 */
    void musicPlayerDurationChanged(qint64);

    /* 媒体播放位置改变 */
    void mediaPlayerPositionChanged(qint64);

    /* 播放进度条松开 */
    void durationSliderReleased();

    /* 音量条松开 */
    void volumeSliderReleased();
};
#endif // MAINWINDOW_H

  1. mainwindow.cpp
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
* @projectName   15_videoplayer
* @brief         mainwindow.cpp
* @author        Deng Zhimao
* @email         1252699831@qq.com
* @net           www.openedv.com
* @date          2021-04-27
*******************************************************************/
#include "mainwindow.h"
#include <QCoreApplication>
#include <QFileInfoList>
#include <QDir>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 视频播放器布局初始化 */
    videoLayout();

    /* 媒体初始化 */
    mediaPlayerInit();

    /* 扫描本地视频 */
    scanVideoFiles();

    /* 设置按钮的属性 */
    pushButton[0]->setCheckable(true);
    pushButton[4]->setCheckable(true);

    /* 按钮连接信号槽 */
    connect(pushButton[0], SIGNAL(clicked()),
            this, SLOT(btn_play_clicked()));
    connect(pushButton[1], SIGNAL(clicked()),
            this, SLOT(btn_next_clicked()));
    connect(pushButton[2], SIGNAL(clicked()),
            this, SLOT(btn_volmedown_clicked()));
    connect(pushButton[3], SIGNAL(clicked()),
            this, SLOT(btn_volmeup_clicked()));
    connect(pushButton[4], SIGNAL(clicked()),
            this, SLOT(btn_fullscreen_clicked()));

    /* 列表连接信号槽 */
    connect(listWidget, SIGNAL(itemClicked(QListWidgetItem*)),
            this, SLOT(listWidgetCliked(QListWidgetItem*)));

    /* 媒体连接信号槽 */
    connect(videoPlayer,
            SIGNAL(stateChanged(QMediaPlayer::State)),
            this,
            SLOT(mediaPlayerStateChanged(QMediaPlayer::State)));
    connect(mediaPlaylist,
            SIGNAL(currentIndexChanged(int)),
            this,
            SLOT(mediaPlaylistCurrentIndexChanged(int)));
    connect(videoPlayer, SIGNAL(durationChanged(qint64)),
            this,
            SLOT(musicPlayerDurationChanged(qint64)));
    connect(videoPlayer,
            SIGNAL(positionChanged(qint64)),
            this,
            SLOT(mediaPlayerPositionChanged(qint64)));

    /* slider信号槽连接 */
    connect(durationSlider, SIGNAL(sliderReleased()),
            this, SLOT(durationSliderReleased()));
    connect(volumeSlider, SIGNAL(sliderReleased()),
            this, SLOT(volumeSliderReleased()));
}

MainWindow::~MainWindow()
{
}

void MainWindow::videoLayout()
{
    /* 设置位置与大小,这里固定为800, 480 */
    this->setGeometry(0, 0, 800, 480);
    //    this->setMinimumSize(800, 480);
    //    this->setMaximumSize(800, 480);
    QPalette pal;
    pal.setColor(QPalette::WindowText, Qt::white);

    for (int i = 0; i < 3; i++) {
        /* 水平容器 */
        hWidget[i] = new QWidget();
        hWidget[i]->setAutoFillBackground(true);
        /* 水平布局 */
        hBoxLayout[i] = new QHBoxLayout();
    }

    for (int i = 0; i < 2; i++) {
        /* 垂直容器 */
        vWidget[i] = new QWidget();
        vWidget[i]->setAutoFillBackground(true);
        /* 垂直布局 */
        vBoxLayout[i] = new QVBoxLayout();
    }

    for (int i = 0; i < 2; i++) {
        label[i] = new QLabel();
    }

    for (int i = 0; i < 5; i++) {
        pushButton[i] = new QPushButton();
        pushButton[i]->setMaximumSize(44, 44);
        pushButton[i]->setMinimumSize(44, 44);
    }

    /* 设置 */
    vWidget[0]->setObjectName("vWidget0");
    vWidget[1]->setObjectName("vWidget1");
    hWidget[1]->setObjectName("hWidget1");
    hWidget[2]->setObjectName("hWidget2");
    pushButton[0]->setObjectName("btn_play");
    pushButton[1]->setObjectName("btn_next");
    pushButton[2]->setObjectName("btn_volumedown");
    pushButton[3]->setObjectName("btn_volumeup");
    pushButton[4]->setObjectName("btn_screen");

    QFont font;

    font.setPixelSize(18);
    label[0]->setFont(font);
    label[1]->setFont(font);

    pal.setColor(QPalette::WindowText, Qt::white);
    label[0]->setPalette(pal);
    label[1]->setPalette(pal);

    label[0]->setText("00:00");
    label[1]->setText("/00:00");

    durationSlider = new QSlider(Qt::Horizontal);
    durationSlider->setMaximumHeight(15);
    durationSlider->setObjectName("durationSlider");

    volumeSlider = new QSlider(Qt::Horizontal);
    volumeSlider->setRange(0, 100);
    volumeSlider->setMaximumWidth(80);
    volumeSlider->setObjectName("volumeSlider");
    volumeSlider->setValue(50);

    listWidget = new QListWidget();
    listWidget->setObjectName("listWidget");
    listWidget->setVerticalScrollBarPolicy(
                Qt::ScrollBarAlwaysOff);
    listWidget->setHorizontalScrollBarPolicy(
                Qt::ScrollBarAlwaysOff);
    //listWidget->setFocusPolicy(Qt::NoFocus);
    videoWidget = new QVideoWidget();
    videoWidget->setStyleSheet("border-image: none;"
                               "background: transparent;"
                               "border:none");

    /* H0布局 */
    vWidget[0]->setMinimumSize(300, 480);
    vWidget[0]->setMaximumWidth(300);
    videoWidget->setMinimumSize(500, 480);

    hBoxLayout[0]->addWidget(videoWidget);
    hBoxLayout[0]->addWidget(vWidget[0]);

    hWidget[0]->setLayout(hBoxLayout[0]);
    hBoxLayout[0]->setContentsMargins(0, 0, 0, 0);

    setCentralWidget(hWidget[0]);

    /* V0布局 */
    QSpacerItem *vSpacer0 = new
            QSpacerItem(0, 80,
                        QSizePolicy::Minimum,
                        QSizePolicy::Maximum);
    vBoxLayout[0]->addWidget(listWidget);
    vBoxLayout[0]->addSpacerItem(vSpacer0);
    vBoxLayout[0]->setContentsMargins(0, 0, 0, 0);

    vWidget[0]->setLayout(vBoxLayout[0]);

    /* V1布局 */
    /* 底板部件布局 */
    hWidget[1]->setMaximumHeight(15);
    hWidget[2]->setMinimumHeight(65);
    vBoxLayout[1]->addWidget(hWidget[1]);
    vBoxLayout[1]->addWidget(hWidget[2]);
    vBoxLayout[1]->setAlignment(Qt::AlignCenter);

    vWidget[1]->setLayout(vBoxLayout[1]);
    vWidget[1]->setParent(this);
    vWidget[1]->setGeometry(0, this->height() - 80, this->width(), 80);
    vBoxLayout[1]->setContentsMargins(0, 0, 0, 0);
    /* 位于最上层 */
    vWidget[1]->raise();

    /* H1布局 */
    hBoxLayout[1]->addWidget(durationSlider);
    hBoxLayout[1]->setContentsMargins(0, 0, 0, 0);
    hWidget[1]->setLayout(hBoxLayout[1]);

    /* H2布局 */
    QSpacerItem *hSpacer0 = new
            QSpacerItem(300, 80,
                        QSizePolicy::Expanding,
                        QSizePolicy::Maximum);

    hBoxLayout[2]->addSpacing(20);
    hBoxLayout[2]->addWidget(pushButton[0]);
    hBoxLayout[2]->addSpacing(10);
    hBoxLayout[2]->addWidget(pushButton[1]);
    hBoxLayout[2]->addSpacing(10);
    hBoxLayout[2]->addWidget(pushButton[2]);
    hBoxLayout[2]->addWidget(volumeSlider);
    hBoxLayout[2]->addWidget(pushButton[3]);
    hBoxLayout[2]->addWidget(label[0]);
    hBoxLayout[2]->addWidget(label[1]);
    hBoxLayout[2]->addSpacerItem(hSpacer0);
    hBoxLayout[2]->addWidget(pushButton[4]);
    hBoxLayout[2]->addSpacing(20);
    hBoxLayout[2]->setContentsMargins(0, 0, 0, 0);
    hBoxLayout[2]->setAlignment(Qt::AlignLeft | Qt::AlignTop);

    hWidget[2]->setLayout(hBoxLayout[2]);
}

void MainWindow::mediaPlayerInit()
{
    videoPlayer = new QMediaPlayer(this);
    mediaPlaylist = new QMediaPlaylist(this);
    /* 确保列表是空的 */
    mediaPlaylist->clear();
    /* 设置视频播放器的列表为mediaPlaylist */
    videoPlayer->setPlaylist(mediaPlaylist);
    /* 设置视频输出窗口 */
    videoPlayer->setVideoOutput(videoWidget);
    /* 设置播放模式,Loop是列循环 */
    mediaPlaylist->setPlaybackMode(QMediaPlaylist::Loop);
    /* 设置默认软件音量为50% */
    videoPlayer->setVolume(50);
}

void MainWindow::resizeEvent(QResizeEvent *event)
{
    Q_UNUSED(event);
    vWidget[1]->setGeometry(0, this->height() - 80, this->width(), 80);
}

void MainWindow::btn_play_clicked()
{
    int state = videoPlayer->state();
    switch (state) {
    case QMediaPlayer::StoppedState:
        /* 媒体播放 */
        videoPlayer->play();
        break;

    case QMediaPlayer::PlayingState:
        /* 媒体暂停 */
        videoPlayer->pause();
        break;

    case QMediaPlayer::PausedState:
        /* 设置视频输出窗口 */
        videoPlayer->play();
        break;
    }
}

void MainWindow::btn_next_clicked()
{
    videoPlayer->stop();
    int count = mediaPlaylist->mediaCount();
    if (0 == count)
        return;

    /* 列表下一个 */
    mediaPlaylist->next();
    videoPlayer->play();
}

void MainWindow::btn_volmeup_clicked()
{
    /* 点击每次音量+5 */
    volumeSlider->setValue(volumeSlider->value() + 5);
    videoPlayer->setVolume(volumeSlider->value());
}

void MainWindow::btn_fullscreen_clicked()
{
    /* 全屏/非全屏操作 */
    vWidget[0]->setVisible(!pushButton[4]->isChecked());
}

void MainWindow::btn_volmedown_clicked()
{
    /* 点击每次音量-5 */
    volumeSlider->setValue(volumeSlider->value() - 5);
    videoPlayer->setVolume(volumeSlider->value());
}

void MainWindow::mediaPlayerStateChanged(
        QMediaPlayer::State
        state)
{
    switch (state) {
    case QMediaPlayer::StoppedState:
        pushButton[0]->setChecked(false);
        break;

    case QMediaPlayer::PlayingState:
        pushButton[0]->setChecked(true);
        break;

    case QMediaPlayer::PausedState:
        pushButton[0]->setChecked(false);
        break;
    }
}

void MainWindow::listWidgetCliked(QListWidgetItem *item)
{
    videoPlayer->stop();
    mediaPlaylist->setCurrentIndex(listWidget->row(item));
    videoPlayer->play();
}

void MainWindow::mediaPlaylistCurrentIndexChanged(
        int index)
{
    if (-1 == index)
        return;

    /* 设置列表正在播放的项 */
    listWidget->setCurrentRow(index);
}

void MainWindow::musicPlayerDurationChanged(
        qint64 duration)
{
    durationSlider->setRange(0, duration / 1000);
    int second  = duration / 1000;
    int minute = second / 60;
    second %= 60;

    QString mediaDuration;
    mediaDuration.clear();

    if (minute >= 10)
        mediaDuration = QString::number(minute, 10);
    else
        mediaDuration = "0" + QString::number(minute, 10);

    if (second >= 10)
        mediaDuration = mediaDuration
                + ":" + QString::number(second, 10);
    else
        mediaDuration = mediaDuration
                + ":0" + QString::number(second, 10);

    /* 显示媒体总长度时间 */
    label[1]->setText("/" + mediaDuration);
}

void MainWindow::mediaPlayerPositionChanged(
        qint64 position)
{
    if (!durationSlider->isSliderDown())
        durationSlider->setValue(position / 1000);

    int second  = position / 1000;
    int minute = second / 60;
    second %= 60;

    QString mediaPosition;
    mediaPosition.clear();

    if (minute >= 10)
        mediaPosition = QString::number(minute, 10);
    else
        mediaPosition = "0" + QString::number(minute, 10);

    if (second >= 10)
        mediaPosition = mediaPosition
                + ":" + QString::number(second, 10);
    else
        mediaPosition = mediaPosition
                + ":0" + QString::number(second, 10);

    /* 显示现在播放的时间 */
    label[0]->setText(mediaPosition);
}

void MainWindow::durationSliderReleased()
{
    /* 设置媒体播放的位置 */
    videoPlayer->setPosition(durationSlider->value() * 1000);
}

void MainWindow::volumeSliderReleased()
{
    /* 设置音量 */
    videoPlayer->setVolume(volumeSlider->value());
}

void MainWindow::scanVideoFiles()
{
    QDir dir(QCoreApplication::applicationDirPath()
             + "/myVideo");
    QDir dirbsolutePath(dir.absolutePath());
    /* 如果目录存在 */
    if (dirbsolutePath.exists()) {
        /* 定义过滤器 */
        QStringList filter;
        /* 包含所有xx后缀的文件 */
        filter << "*.mp4" << "*.mkv" << "*.wmv" << "*.avi";
        /* 获取该目录下的所有文件 */
        QFileInfoList files =
                dirbsolutePath.entryInfoList(filter, QDir::Files);
        /* 遍历 */
        for (int i = 0; i < files.count(); i++) {
            MediaObjectInfo info;
            /* 使用utf-8编码 */
            info.fileName = QString::fromUtf8(files.at(i)
                                              .fileName()
                                              .toUtf8()
                                              .data());
            info.filePath = QString::fromUtf8(files.at(i)
                                              .filePath()
                                              .toUtf8()
                                              .data());
            /* 媒体列表添加视频 */
            if (mediaPlaylist->addMedia(
                        QUrl::fromLocalFile(info.filePath))) {
                /* 添加到容器数组里储存 */
                mediaObjectInfo.append(info);
                /* 添加视频名字至列表 */
                listWidget->addItem(info.fileName);
            } else {
                qDebug()<<
                           mediaPlaylist->errorString()
                           .toUtf8().data()
                        << endl;
                qDebug()<< "  Error number:"
                        << mediaPlaylist->error()
                        << endl;
            }
        }
    }
}

  1. 运行,先点击构建项目,项目构建完成后,再将本例的 myVideo 视频文件夹拷贝到可执行程序的文件夹同一级目录下,也就是 build-15_videoplayer-Desktop_Qt_5_12_9_GCC_64bit-Debug 目录下。再重新运行,就出现视频文件在列表里,如下图,点击播放即可播放视频。默认列表,未播放前。
    在这里插入图片描述

  1. mainwindow.h

  1. mainwindow.cpp

  1. 运行,

  1. mainwindow.h

  1. mainwindow.cpp

  1. 运行,

  1. mainwindow.h

  1. mainwindow.cpp

  1. 运行,

问题

  1. QTCreator中新加了ui文件,但是构建时候没有自动生成h头文件
    解决:在ui所在文件夹,命令行:uic -o XXX.h XXX.ui生成
  2. qt创建MSVC-32bit作为构建器时,只有pro文件,没有其它源文件
    解决:项目->管理构建套件->自动选择那里,把对应构建器设置为默认,如第二个,下面的两个编译器选择对应的,返回即可
    在这里插入图片描述
  3. 运行0007报错无法运行
    解决:
    1.onnxruntime 使用MSVC2019 + 64位 + release,并且release版本需要将onnxruntime.dll复制到release文件夹内的exe可执行文件所在目录
    2.使用vs2019文件夹中的dumpbin命令可以查看exe文件对库文件的依赖,dumpbin /dependents QtYOLOv9.exe查看
    3.注意检查项目->构建目录,有可能移动文件夹后构建目录没变
  4. 添加信号槽函数后运行报没有槽函数 No such slot…
    解决:moc_xxx.cpp文件中生成了有关槽函数的程序,新定义槽函数时这里面没修改,删除此文件重新运行程序。
QT creator 教程 一、Qt Creator的安装和hello world程序的编写 二、Qt Creator编写多窗口程序 三、Qt Creator登录对话框 四、Qt Creator添加菜单图标 五、Qt Creator布局管理器的使用 六、Qt Creator实现文本编辑 七、Qt Creator实现文本查找 八、Qt Creator实现状态栏显示 九、Qt Creator中鼠标键盘事件的处理实现自定义鼠标指针 十、Qt Creator中实现定时器和产生随机数 十一、Qt 2D绘图(一)绘制简单图形 十二、Qt 2D绘图(二)渐变填充 十三、Qt 2D绘图(三)绘制文字 十四、Qt 2D绘图(四)绘制路径 十五、Qt 2D绘图(五)显示图片 十六、Qt 2D绘图(六)坐标系统 十七、Qt 2D绘图(七)Qt坐标系统深入 十八、Qt 2D绘图(八)涂鸦板 十九、 Q t 2D 绘图(九)双缓冲绘图简介 二十、Qt 2D绘图(十)图形视图框架简介 二十一、Qt数据库(一)简介 二十二、Qt数据库(二)添加MySQL数据库驱动插件 二十三、Qt数据库(三)利用QSqlQuery类执行SQL语句(一) 二十四、Qt数据库(四)利用QSqlQuery类执行SQL语句(二) 二十五、Qt数据库(五)QSqlQueryModel 二十六、Qt数据库(六)QSqlTableModel 二十七、Qt数据库(七)QSqlRelationalTableModel 二十八、Qt数据库(八)XML(一) 二十九、Qt数据库(九)XML(二) 三十、Qt数据库(十)XML (三) 三十一、Qt 4.7.0及Qt Creator 2.0 beta版安装全程图解 三十二、第一个Qt Quick程序(QML程序) 三十三、体验QML演示程序 三十四、Qt Quick Designer介绍 三十五、QML 组件 三十六、QML项目之Image和BorderImage 三十七、Flipable、Flickable 和状态与动画 三十八、QML 视图 三十九、QtDeclarative模块 四十、使用Nokia Qt SDK开发Symbian和Maemo终端软件 四十一、Qt网络(一)简介四十二、Qt网络(二)HTTP编程 四十三、Qt网络(三)FTP(一) 四十四、Qt网络(四)FTP(二) 四十五、Qt网络(五)获取本机网络信 四十六、Qt网络(六)UDP 四十七、Qt网络(七)TCP(一) 四十八、Qt网络(八)TCP(二)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值