c++qt 学习笔记,与注意事项

1 构建一个应用

        构建一个简单的应用,需要进行如下步骤:

1.1 创建工程

        创建工程时,须注意编译形式

        qmake:出现一个.pro文件

        cmake: 出现一个cmakelist.txt文件

        上述两种编译方式均可对应修改从而使得应用添加对应的模块,实现拓展功能,如在.pro文件中对如下部分进行修改。 

        

        其次,是kit的选择,MingGW支持跨平台开发,MSVC支持visual studio IDE,且性能最佳 

        随后qt creator会自动生成下列文件:

        

1.2 widget.h文件修改

        a)根据需求,导入对应的按钮,文本编辑等头文件

#include <QWidget>
#include <QTextEdit>
#include <QPushButton>

        b)在widget类中定义需要的元素指针对象,便于在widget.cpp文件中new出实例。

class Widget : public QWidget
{
    Q_OBJECT
signals:
    void fileNamesSelected(const QStringList& fileNames);
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
private:
    //根据需求定义的元素指针对象
    QTextEdit *te_test;
    QPushButton *bt_filename;
    QPushButton *bt_getcolor;
    QPushButton *bt_getfont;
    QPushButton *bt_getinput;
    QPushButton *bt_error;
    QPushButton *bt_message;
    QPushButton *bt_progress;
};

1.3 widget.cpp文件修改

        a)include布局需要的文件或类:

#include "widget.h"

#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QFileDialog>
#include <QColorDialog>
#include <QFontDialog>
#include <QInputDialog>
#include <QErrorMessage>
#include <QMessageBox>
#include <QProgressDialog>

        b)根据1.2中定义的元素指针,在函数Widget中使用new实例化:

//添加需要的元素
    bt_filename = new QPushButton("获取文件名");
    bt_getcolor = new QPushButton("获取颜色");
    bt_getfont = new QPushButton("获取字体");
    bt_getinput = new QPushButton("获取输入");
    bt_error = new QPushButton("错误弹窗");
    bt_message= new QPushButton("消息弹窗");
    bt_progress = new QPushButton("进度条对话框");

    te_test = new QTextEdit;

        c)按照需求进行布局:

        通常的过程为:

        QLayoutType *layout = new QLayoutType  //先new一个Qt布局

        layout->addSome(之前实例化的按钮等对象)  //在布局中添加新布局或元素,可用方法如下:

        

        setLayOut(layout) // 设置布局

//定义布局
    QVBoxLayout *vbox = new QVBoxLayout;

    vbox->addWidget(bt_filename);
    vbox->addWidget(bt_getcolor);
    vbox->addWidget(bt_getfont);
    vbox->addWidget(bt_getinput);
    vbox->addWidget(bt_error);
    vbox->addWidget(bt_message);
    vbox->addWidget(bt_progress);

    QHBoxLayout *hbox = new QHBoxLayout;
    hbox->addLayout(vbox);
    hbox->addWidget(te_test);
    setLayout(hbox);

        d)使用connect函数,链接元素对象、事件、槽函数:

        即当该元素对象发生了某个事件时,会激发槽函数。

        connect(元素对象,事件,槽函数)

//这里槽函数可用lambda函数形式

//文件对话框示例
    connect(bt_filename, &QPushButton::clicked, [&](){
        //提取多个文件名的对话框,通过QFileDialog的getOpenFileNames来获取文件名
        QStringList filenames = QFileDialog::getOpenFileNames(this, "打开图片", ".", "Images (*.png *.xpm *.jpg)");
        for(int i=0; i<filenames.length(); i++)
            te_test->append(filenames[i]);
    });

    //颜色对话框
    connect(bt_getcolor, &QPushButton::clicked,[&](){
        QColor color = QColorDialog::getColor();//通过QColorDialog来选择颜色
        te_test->setTextColor(color);
    });

    //字体对话框
    connect(bt_getfont, &QPushButton::clicked, [&](){
        bool ok;
        QFont font = QFontDialog::getFont(&ok);//通过QFondDialog获取字体,并用引用的形式来返回是否进行字体选择
        if(ok)
            te_test->setCurrentFont(font);
    });

    //输入对话框
    connect(bt_getinput, &QPushButton::clicked, [&](){
        QString str = QInputDialog::getText(this, "xxxx","sdsaf");
        te_test->setText(str);
    });

    //消息对话框
    connect(bt_message, &QPushButton::clicked, [&](){
        QMessageBox::warning(this, "xxxx", "esaf",
                             QMessageBox::Open,
                             QMessageBox::Apply);
    });

    //错误消息对话框
    connect(bt_error, &QPushButton::clicked, [&](){
        QErrorMessage xx;
        xx.showMessage("xxxxxxxxxxxxxx");
    });

    //进度条对话框
    connect(bt_progress, &QPushButton::clicked, [&](){
        QProgressDialog x;
        x.setValue(88);
        x.exec();
    });

        最后点击这里进行调试:

 1.4 生成可执行exe文件

        a)找到编译构建后的文件,打开Debug,将其中的exe文件复制到一个新建的空文件夹中。

         b)根据创建的QT项目kit类型,从下面两个终端中选择合适的打开:

        

        c)cd到装有exe文件的文件夹路径下:

        d)执行如下命令:

windeployqt.exe filname.exe

        注:filename为exe文件名 

         最后,就可以点击该文件夹中的.exe文件执行qt应用了。

2 内置部件总结

        a)导入在头文件中的部件:

#include <QPushButton>  //普通按钮
#include <QToolButton>  //工具栏按钮
#include <QRadioButton> //单选按钮
#include <QCheckBox>    //复选按钮
#include <QLabel>       //标签

#include <QTextBrowser>     //文本浏览器
#include <QCalendarWidget>  //日历
#include <QLCDNumber>       //七段数码管
#include <QProgressBar>     //进度条

#include <QLineEdit>        //行编辑框
#include <QComboBox>        //组合框
#include <QFontComboBox>    //字体选择下拉框
#include <QTextEdit>        //文本编辑框
#include <QSpinBox>         //自旋框
#include <QDial>            //旋钮
#include <QScrollBar>       //滚动条
#include <QSlider>          //滑动杆

        b)导入在cpp文件中的部件:

#include <QHBoxLayout>          //垂直布局
#include <QVBoxLayout>          //水平布局
#include <QGridLayout>          //网格状布局

       

注意:后续内容为节省篇幅,均已经在widget.h中定义了相关的类指针。 

2.1 输入部件类

2.1.1 普通按钮QPushButton示例
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    pb = new QPushButton("ok");
    pb->setMinimumSize(200,100);        //设置最小尺寸
    pb->setFlat(true);                  //设置无边框
    pb->setIconSize(QSize(150,120));      //设置按钮图标大小
    pb->setIcon(QIcon("E:\\image_packages\\image4.jpeg"));  //设置按钮

    //添加布局
    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(pb);
    setLayout(vbox);
}

 2.1.2 工具按钮QToolButton
//写在widget.cpp中的Widget、textButton定义
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    tb = new QToolButton();
    tb->setMinimumSize(200,100);        //设置最小尺寸
    //    tb->setFlat(true);                  //设置无边框
    tb->setIconSize(QSize(150,120));      //设置按钮图标大小
    tb->setIcon(QIcon("E:\\image_packages\\image4.jpeg"));  //设置按钮
    tb->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_M));    //绑定快捷键

    //添加布局
    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(tb);
    setLayout(vbox);

    connect(tb,SIGNAL(clicked(bool)),this,SLOT(textButton()));
}
void Widget::textButton()
{
    qDebug()<<"xxxxxxxxxx";
}

//写在widget.h中的槽函数声明
public slots:
    void textButton();

 2.1.3 单选按钮QRadioButton

        单选意思是只能从多个内容中选一个。

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    radioButton1 = new QRadioButton("猫头鹰");
    radioButton2 = new QRadioButton("老虎");
    radioButton1->setIcon(QIcon("E:\\image_packages\\image4.jpeg"));
    radioButton2->setIcon(QIcon("E:\\image_packages\\image1.jpeg"));
    radioButton1->setIconSize(QSize(150,120));      //设置按钮图标大小
    radioButton2->setIconSize(QSize(150,120));      //设置按钮图标大小
    //添加布局
    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(radioButton1);
    vbox->addWidget(radioButton2);
    setLayout(vbox);
}

 

 2.1.4 复选按钮

        复选意为可以从多个按钮中选一个或多个。

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    checkBox1 = new QCheckBox("猫头鹰");
    checkBox2 = new QCheckBox("老虎");
    checkBox1->setIcon(QIcon("E:\\image_packages\\image4.jpeg"));
    checkBox2->setIcon(QIcon("E:\\image_packages\\image1.jpeg"));
    checkBox1->setIconSize(QSize(150,120));      //设置按钮图标大小
    checkBox2->setIconSize(QSize(150,120));      //设置按钮图标大小
    //添加布局
    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(checkBox1);
    vbox->addWidget(checkBox2);
    setLayout(vbox);
}

 2.1.5 connect中使用lambda函数作为槽函数
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    pb = new QPushButton("按我一下");

    checkBox1 = new QCheckBox("猫头鹰");
    checkBox2 = new QCheckBox("老虎");
    checkBox1->setIcon(QIcon("E:\\image_packages\\image4.jpeg"));
    checkBox2->setIcon(QIcon("E:\\image_packages\\image1.jpeg"));
    checkBox1->setIconSize(QSize(150,120));      //设置按钮图标大小
    checkBox2->setIconSize(QSize(150,120));      //设置按钮图标大小
    //添加布局
    QVBoxLayout *vbox = new QVBoxLayout;

    vbox->addWidget(checkBox1);
    vbox->addWidget(checkBox2);
    vbox->addWidget(pb);
    setLayout(vbox);

    connect(pb,&QPushButton::clicked, [&](){
        qDebug() << "这是一段调试输出。。。";
    });
}

 2.1.6 使用connect对按钮的勾选调试

        这里以复用按钮为例

//槽函数定义
    void debugCBox1(){
        qDebug() << "猫头鹰被勾选了!";
    }
    void debugCBox2(){
        qDebug() << "老虎被勾选了!";
    }

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    pb = new QPushButton("按我一下");

    checkBox1 = new QCheckBox("猫头鹰");
    checkBox2 = new QCheckBox("老虎");
    checkBox1->setIcon(QIcon("E:\\image_packages\\image4.jpeg"));
    checkBox2->setIcon(QIcon("E:\\image_packages\\image1.jpeg"));
    checkBox1->setIconSize(QSize(150,120));      //设置按钮图标大小
    checkBox2->setIconSize(QSize(150,120));      //设置按钮图标大小
    //添加布局
    QVBoxLayout *vbox = new QVBoxLayout;

    vbox->addWidget(checkBox1);
    vbox->addWidget(checkBox2);
    vbox->addWidget(pb);
    setLayout(vbox);

    connect(pb,&QPushButton::clicked, [&](){
        qDebug() << "这是一段调试输出。。。";
    });

    connect(checkBox1,SIGNAL(clicked(bool)),SLOT(debugCBox1()));
    connect(checkBox2,SIGNAL(clicked(bool)),SLOT(debugCBox2()));
}

2.1.7 行编辑框QLineEdit
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    le = new QLineEdit;
    cb = new QCheckBox("显示输入内容@@");
    le->setEchoMode(QLineEdit::Password);   //将行编辑框中的内容遮盖

    connect(cb, &QCheckBox::clicked, [&](bool x){
        le->setEchoMode(x?QLineEdit::Normal:QLineEdit::Password);
    });     //使用connect在勾选复选按钮时,显示被遮盖的行编辑框中的内容

    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget((le));
    vbox->addWidget(cb);
    setLayout(vbox);
}

2.1.8 组合框/下拉框QComboBox
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    comboBox = new QComboBox;
    comboBox->addItem("选我!");
    comboBox->addItem("选我,选我!");
    comboBox->addItem("选我啊!!");

    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(comboBox);
    setLayout(vbox);
    
    //由于输出为QString参数,而currentIndexChange信号为int型,故采用Qcombobox的索引来间接获取输出
    connect(comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), [&](int index) {
           QString selectedItem = comboBox->itemText(index);
           qDebug() << selectedItem;
       });
}

注意:由于输出为QString参数,而currentIndexChange信号为int型,故采用Qcombobox的索引来间接获取输出

2.1.9 字体选择框QFontComboBox
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    le = new QLineEdit;
    fontComboBox = new QFontComboBox;

    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(le);
    vbox->addWidget(fontComboBox);

    connect(fontComboBox, &QFontComboBox::currentFontChanged, [&](QFont x){
        le->setFont(x);
    });
    setLayout(vbox);
}

 2.1.10 文本编辑框QTextEdit
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    le = new QLineEdit;
    txtEdit = new QTextEdit;
    fontComboBox = new QFontComboBox;

    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(le);
    vbox->addWidget(txtEdit);
    vbox->addWidget(fontComboBox);

    connect(fontComboBox, &QFontComboBox::currentFontChanged, [&](QFont x){
        le->setFont(x);
        txtEdit->setCurrentFont(x);
    });
    setLayout(vbox);
}

2.1.11 自旋框QSpinBox/QLCDNumber

        这里使用connect将变化的spinBox参数映射展示到QLCDNumber框中。主要就是为了跟随变化。

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    spinBox = new QSpinBox;
    spinBox->setRange(20, 100);//设置上下限

    lcd = new QLCDNumber;
    lcd->setMinimumHeight(50);//设置最小高度

    connect(spinBox, SIGNAL(valueChanged(int)), lcd, SLOT(display(int)));//链接spinbox和lcd

    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(lcd);
    vbox->addWidget(spinBox);

    setLayout(vbox);
}

 2.1.12 旋钮 QDial

        在2.11的基础上加了旋钮的,并将其与QLCDNumber显示进行连接。

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    spinBox = new QSpinBox;
    spinBox->setRange(20, 100);//设置上下限

    lcd = new QLCDNumber;
    lcd->setMinimumHeight(50);//设置最小高度

    dial = new QDial; //旋钮实例化
    connect(dial, SIGNAL(valueChanged(int)), lcd, SLOT(display(int)));

    connect(spinBox, SIGNAL(valueChanged(int)), lcd, SLOT(display(int)));//链接spinbox和lcd

    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(lcd);
    vbox->addWidget(spinBox);
    vbox->addWidget(dial);

    setLayout(vbox);
}

2.1.13 滚动条QScrollBar
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    lcd = new QLCDNumber;
    lcd->setMinimumHeight(50);//设置最小高度

    spinBox = new QSpinBox;
    spinBox->setRange(20, 100);//设置上下限
    connect(spinBox, SIGNAL(valueChanged(int)), lcd, SLOT(display(int)));//链接spinbox和lcd

    dial = new QDial; //旋钮实例化
    connect(dial, SIGNAL(valueChanged(int)), lcd, SLOT(display(int)));

    scrollBar = new QScrollBar;//滚动条实例化
    scrollBar->setOrientation(Qt::Horizontal);//设置滚动条长轴沿水平方向
    connect(scrollBar, SIGNAL(valueChanged(int)), lcd, SLOT(display(int)));

    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(lcd);
    vbox->addWidget(spinBox);
    vbox->addWidget(dial);
    vbox->addWidget(scrollBar);

    setLayout(vbox);
}

 2.1.14滑动杆QSlider

        滑动杆与滚动条类似,此处只展示效果

 

2.2 输出部件类

2.2.1 标签QLabel(图片、gif加载)

        标签对齐、图片加载、图片缩放:

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    lb1 = new QLabel("我是一号标签");
    lb2 = new QLabel("我是二号标签");
    lb1->setAlignment(Qt::AlignCenter); //设置对齐方式
    lb2->setScaledContents(true);   //设置标签自动缩放加载的图片
    lb2->setPixmap(QPixmap("E:\\image_packages\\image2.jpeg"));

    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(lb1);
    vbox->addWidget(lb2);
    setLayout(vbox);
}

        标签,加载gif动图:

        需要先#include <QMovie> ,其对象不需要预先在widget.h中定义。

#include <QMovie>               //导入动画包
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    lb_gif = new QLabel;
    lb_gif->setAlignment(Qt::AlignCenter);
    QMovie *m = new QMovie("E:\\image_packages\\gif1.gif");
    lb_gif->setMovie(m);
    m->start();

    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(lb_gif);
    setLayout(vbox);
}

        

 2.2.2 文本浏览器QTextBroswer
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    txtBrowser = new QTextBrowser;
    txtBrowser->setHtml("</head>\
                        <body>\
                        <div class=\"wrapper\">\
                        <div class=\"tophead\">\
                            <div class=\"left\">\
                                <h1 class=\"logo\"><a href=\"http://www.laoqishu.com\">奇书网</a></h1>\
                            </div>\
                            <div class=\"fright\" id=\"loginbox\"><script type=text/javascript>login();</script></div>\
                        </div>");

    lb1 = new QLabel("一号标签");
    lb1->setScaledContents(true);
    lb1->setPixmap(QPixmap("E:\\image_packages\\image1.jpeg"));
    lb1->setFixedSize(200,120); //为标签设置固定尺寸

    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(txtBrowser);
    vbox->addWidget(lb1);
    setLayout(vbox);
}

 PS:这里我是随便找到个网站的源码复制了一部分,修改部分“”,并添加了换行符

 2.2.3 图形视图框架——日历QCalendarWidget、七段数码管QLCDNumber

        日历:

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    lb1 = new QLabel("一号标签");
    lb1->setScaledContents(true);
    lb1->setPixmap(QPixmap("E:\\image_packages\\image1.jpeg"));
    lb1->setFixedSize(200,120); //为标签设置固定尺寸

    calender = new QCalendarWidget;//实例化日历
    connect(calender, &QCalendarWidget::clicked, [&](QDate x){
        qDebug() << x;
    });//连接日历点击事件,qdebug输出
    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(calender);
    vbox->addWidget(lb1);
    setLayout(vbox);
}

        七段数码管QLCDNumber:

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    lb1 = new QLabel("一号标签");
    lb1->setScaledContents(true);
    lb1->setPixmap(QPixmap("E:\\image_packages\\image1.jpeg"));
    lb1->setFixedSize(200,120); //为标签设置固定尺寸

    calender = new QCalendarWidget;//实例化日历

    lcd = new QLCDNumber;
    lcd->setDigitCount(30); //范围设置
    lcd->setMinimumSize(400,100);//大小设置
    lcd->display(234);

    connect(calender, &QCalendarWidget::clicked, [&](QDate x){
        qDebug() << x;
        lcd->display(x.toString());//输出日期在七段数码管上
    });//连接日历点击事件,qdebug输出

    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(calender);
    vbox->addWidget(lb1);
    vbox->addWidget(lcd);
    setLayout(vbox);
}

 

 2.2.4 进度条QProgressBar
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    pBar = new QProgressBar;
    pBar->setValue(99);

    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(pBar);
    setLayout(vbox);
}

 2.2.5 定时器QTimer
#include <QTimer>               //导入QTimer定时器
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    pBar = new QProgressBar;
    pBar->setValue(0);

    QTimer *timer1 = new QTimer; //new一个定时器类
    connect(timer1, &QTimer::timeout, [&](){
        static int x = 0;
        pBar->setValue(x++);
    });
    timer1->start(100);
    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(pBar);
    setLayout(vbox);
}

这里进度条随着定时器的内置数值在变化。

 2.3 QT组合部件

        QColorDialog, QErrorMessage, QFileDialog, QFontDialog, QInputDialog, QMessageBox, QProgressDialog, and QWizard。

2.4 容器类

2.4.1 组合框QGroupBox

        组合框可以被某个布局使用addWidget添加:

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    //容器:组合框1
    groupBox1 = new QGroupBox(tr("a set of radio buttons"));
    radioButton1 = new QRadioButton(tr("&Radio button 1"));
    radioButton2 = new QRadioButton(tr("&Radio button 2"));
    radioButton1->setCheckable(true); //设置单选按钮1为默认勾选

    QVBoxLayout *vbox1 = new QVBoxLayout;
    vbox1->addWidget(radioButton1);
    vbox1->addWidget(radioButton2);
    vbox1->addStretch(1);
    groupBox1->setLayout(vbox1);

    //容器:组合框2
    groupBox2 = new QGroupBox(tr("a set of radio buttons"));
    radioButton3 = new QRadioButton(tr("&Radio button 3"));
    radioButton4 = new QRadioButton(tr("&Radio button 4"));
    radioButton3->setCheckable(true); //设置单选按钮3为默认勾选

    QVBoxLayout *vbox2 = new QVBoxLayout;
    vbox2->addWidget(radioButton3);
    vbox2->addWidget(radioButton4);
    vbox2->addStretch(1);
    groupBox2->setLayout(vbox2);

    QHBoxLayout *hbox = new QHBoxLayout;
    hbox->addWidget(groupBox1);
    hbox->addWidget(groupBox2);
    setLayout(hbox);
}

 2.4.2 滚动窗口容器QScrollArea
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    //导入一个图片,将其填入被滚动窗口容器内

    //首先构建一个label,填入图片
    lb1 = new QLabel;
    lb1->setPixmap(QPixmap("E:\\image_packages\\images\\cats2.jpeg"));

    //其次构建滚动窗口容器
    QScrollArea *sa = new QScrollArea;
    sa->setFixedSize(480,230);
    sa->setWidget(lb1); //将label填入sa

    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(sa);
    setLayout(vbox);
}

 2.4.3 工具箱QToolBox

        示例:构建两个文本编辑器,添加到QToolBox中

#include <QToolBox>             //工具箱容器
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    txtEdit1 = new QTextEdit("我是文本编辑器1的文字。。");
    txtEdit2 = new QTextEdit("我是文本编辑器2的文字!!");

    QToolBox *tbox = new QToolBox;
    tbox->addItem(txtEdit1, "txtEdit1");
    tbox->addItem(txtEdit2, "txtEdit2");

    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(tbox);
    setLayout(vbox);
}

 2.4.4 分页显示/选项卡QTabWidget

        示例:创建两个文本编辑器到选项卡中,并使用connect在触发选项卡关闭事件时,输出内容

#include <QTabWidget>           //选项卡
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    txtEdit1 = new QTextEdit("我是文本编辑器1的文字。。");
    txtEdit2 = new QTextEdit("我是文本编辑器2的文字!!");

    QTabWidget *twidget = new QTabWidget;
    twidget->setTabsClosable(true);
    twidget->addTab(txtEdit1, "txtEdit1");
    twidget->addTab(txtEdit2, "txtEdit2");

    connect(twidget, &QTabWidget::tabCloseRequested, [&](int x){
        qDebug() << "点击了一下关闭" << x;
    });

    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(twidget);
    setLayout(vbox);
}

 2.4.5 堆栈窗口容器类QStackedWidget

        堆栈窗口就是两个堆叠在一起的窗口。

        示例:使用堆栈窗口容器装载两个文本编辑器的文本,并使用组合框控制显示的文本编辑器。

#include <QStackedWidget>       //堆栈窗口容器
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    txtEdit1 = new QTextEdit("我是文本编辑器1的文字。。");
    txtEdit2 = new QTextEdit("我是文本编辑器2的文字!!");

    QStackedWidget *swidget = new QStackedWidget;
    swidget->addWidget(txtEdit1);
    swidget->addWidget(txtEdit2);

    comboBox = new QComboBox;
    comboBox->addItem("stuff1");
    comboBox->addItem("stuff2");

    connect(comboBox, SIGNAL(activated(int)),swidget, SLOT(setCurrentIndex(int)));

    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(comboBox);
    vbox->addWidget(swidget);
    setLayout(vbox);
}

 2.4.6 多媒体窗口QMdiArea

        示例:将两个文本编辑器作为多媒体窗口的子窗口

#include <QMdiArea>             //多媒体容器
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    txtEdit1 = new QTextEdit("我是文本编辑器1的文字。。");
    txtEdit2 = new QTextEdit("我是文本编辑器2的文字!!");

    QMdiArea *mdi = new QMdiArea;
    mdi->addSubWindow(txtEdit1);
    mdi->addSubWindow(txtEdit2);

    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(mdi);
    setLayout(vbox);
}

 2.5 实战:实现某登录界面

        

2.5.1 参考QQ登录窗口的分析

        元素构成:

        label:背景动画、账号文本显示、密码文本显示。

        行编辑框:账号、密码。

        复选按钮:自动登录、记住密码、显示密码。

        按钮:登录、找回密码、注册账号

2.5.2 代码与效果展示
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    //构建登录背景label
    lb_gif = new QLabel;
    lb_gif->setAlignment(Qt::AlignTop);
    QMovie *gif = new QMovie("E:\\image_packages\\gif1.gif");
    gif->setScaledSize(QSize(400,100));
    lb_gif->setMovie(gif);
    gif->start();

    //构建账号、密码行编辑框,设置垂直布局
    countLe = new QLineEdit;
    passwordLe = new QLineEdit;
    passwordLe->setEchoMode(QLineEdit::Password);
    QVBoxLayout *lineEditLayout = new QVBoxLayout;
    lineEditLayout->addWidget(countLe);
    lineEditLayout->addWidget(passwordLe);

    //为行编辑框设置文本浏览器,设置垂直布局
    txtLabel1 = new QLabel;
    txtLabel1->setText("账号");
    txtLabel2 = new QLabel;
    txtLabel2->setText("密码");
    QVBoxLayout *browserLayout = new QVBoxLayout;
    browserLayout->addWidget(txtLabel1);
    browserLayout->addWidget(txtLabel2);

    //整合账号密码相关元素
    QHBoxLayout * countKeySumLO = new QHBoxLayout;
    countKeySumLO->addLayout(browserLayout);
    countKeySumLO->addLayout(lineEditLayout);

    //构建自动登录、忘记密码、显示密码复选按钮
    autoLogIn = new QCheckBox("自动登录");
    rememberPassword = new QCheckBox("忘记密码");
    showPassword = new QCheckBox("显示密码");

    //构建显示密码点击事件
    connect(showPassword, &QCheckBox::clicked, [&](bool x){
        passwordLe->setEchoMode(x?QLineEdit::Normal:QLineEdit::Password);//根据bool值来决定显示还是屏蔽
    });

    //构建按钮
    logIn = new QPushButton("登录");
    logIn->setFixedSize(QSize(70,50));
    findKey = new QPushButton("找回密码");
    registe = new QPushButton("注册");
    findKey->setFlat(true);
    registe->setFlat(true);
    QHBoxLayout *pushButtonLayOut = new QHBoxLayout;
    pushButtonLayOut->addWidget(findKey);
    pushButtonLayOut->addWidget(logIn);
    pushButtonLayOut->addWidget(registe);

    //复选按钮水平布局
    QHBoxLayout *hbox = new QHBoxLayout;
    hbox->addWidget(autoLogIn);
    hbox->addWidget(rememberPassword);
    hbox->addWidget(showPassword);

    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(lb_gif);
    vbox->addLayout(countKeySumLO);
    vbox->addLayout(hbox);
    vbox->addLayout(pushButtonLayOut);
    setLayout(vbox);
}

+

         分析:在布局中,可直接采用网格布局简化操作,但在预先需要定义好各元素的位置。

2.5.3 拓展

        a)在常规登录窗口中,往往具有图像的堆叠,可以通过堆栈窗口容器实现。

        b)在账号行编辑器右方可添加组合框,实现下拉选择账号,并增加下拉选择事件与密码行编辑器的更改connect。

        c)账号、密码行编辑狂中具有虚存在的字。

3 窗口

3.1 窗口部件

        基础窗口部件(QWidget):通过指定图形部件的父对象来把图形部件放上基础窗口,或是通过自动布局工具把图形部件放上基础窗口。

        主窗口(QMain Window):提供典型应用程序的主窗口框架,典型模板包含有菜单栏 QMenuBar,工具栏 QToolBar和状态栏 QStatusBar。

3.2 布局管理器

        常用类包括:

        水平布局(QHBoxLayout)垂直布局(QVBoxLayout)网格布局(QGridLayout)

3.3 菜单QMenu、QMenuBar

3.3.1 初步使用

        示例:定义一个MenuBar(菜单块)作为菜单的一部分,并为其增加动作“打开”,并未动作设置快捷键

#include <QMenu>
#include <QMenuBar>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    QAction *actOpen = new QAction("打开"); //定义动作,并将其名称设置为打开
    actOpen->setIcon(QIcon("")); //设置动作图标
    actOpen->setShortcut(QKeySequence("Ctrl+P"));  //设置快捷动作

    QMenu *fileMenu = menuBar()->addMenu("&File"); //设置菜单名
    fileMenu->addAction(actOpen);
}

        注:这里只能通过主窗口(QMain Window)来定义菜单

3.3.2 添加资源文件

        a)在Source文件夹点击右键,选择添加新文件。

         

         b)文件需求选择,并定义好资源名

        

         c)添加具体文件,如下已经添加了一张图片

        

 3.4 工具栏QToolBar

#include <QAction>
#include <QMenu>
#include <QMenuBar>
#include <QFileDialog>
#include <QStatusBar>

#include <QToolBar>
#include <QFontComboBox>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    //定义打开、新建、保存、另存为四个动作并添加到菜单中
    QAction *actOpen = new QAction("打开"); //定义动作,并将其名称设置为打开
    actOpen->setIcon(QIcon(":/cats1.jpeg")); //设置动作图标
    actOpen->setShortcut(QKeySequence("Ctrl+O"));  //设置快捷动作

    QAction *newFile = new QAction("新建"); //定义动作,并将其名称设置为打开
    newFile->setIcon(QIcon(":/bus.jpg")); //设置动作图标
    newFile->setShortcut(QKeySequence("Ctrl+E"));  //设置快捷动作

    QAction *saveFile = new QAction("保存"); //定义动作,并将其名称设置为打开
    saveFile->setIcon(QIcon(":/cats2.jpeg")); //设置动作图标
    saveFile->setShortcut(QKeySequence("Ctrl+S"));  //设置快捷动作

    QAction *saveFile2Loc = new QAction("另存为"); //定义动作,并将其名称设置为打开
    saveFile2Loc->setIcon(QIcon(":/zebra1.jpg")); //设置动作图标
    saveFile2Loc->setShortcut(QKeySequence("Ctrl+Shift+S"));  //设置快捷动作

    QMenu *fileMenu = menuBar()->addMenu("&File"); //设置菜单名
    fileMenu->addAction(actOpen);
    fileMenu->addAction(newFile);
    fileMenu->addAction(saveFile);
    fileMenu->addAction(saveFile2Loc);

    QMenu *editMenu = menuBar()->addMenu("Edit");
    editMenu->addAction(actOpen);
    QMenu *helpMenu = menuBar()->addMenu("Help?");
    helpMenu->addAction(actOpen);
    //调用预定义的打开文件槽函数,将打开动作与之连接
    connect(actOpen, SIGNAL(triggered(bool)),this,SLOT(openFile()));

    //添加工具栏
    QToolBar *fileTool = new QToolBar("文件");    //随后将与文件相关的动作都添加进去
    QToolBar *editTool = new QToolBar("编辑");
    QToolBar *helpTool = new QToolBar("帮助");

    //将工具栏添加到当前窗口
    this->addToolBar(fileTool);
    this->addToolBar(editTool);
    this->addToolBar(helpTool);

    //为工具栏添加部件
    QFontComboBox *fc = new QFontComboBox;
    helpTool->addWidget(fc); //为帮助编辑框增加文字选择组合框
    button1 = new QPushButton("文件Button");
    fileTool->addWidget(button1);

    //工具栏状态设置
    helpTool->setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea);//允许停靠范围设置
    editTool->setFloatable(false);      //禁止浮动
    fileTool->setMovable(false);        //禁止移动
    fileTool->addSeparator();           //添加分割线
    fileTool->addAction(actOpen);       //将前面定义的动作传入文件工具栏
}

3.5 状态栏QStatusBar

        状态栏会输出简要信息,并出现在窗口底部。

        总结:

        状态栏是程序中输出简要信息的区域
        — QStatusBar 中可以添加任意的QWidget
        — QStatusBar 有自己内置的设计原则
        — QStatusBar 可以定制出各种形式的状态栏

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    //添加一个文本编辑器作为中央部件
    tedit1 = new QTextEdit;
    tedit1->setMinimumSize(640,480);
    this->setCentralWidget(tedit1);

    //添加状态栏
    QLabel *lb = new QLabel("temp.txt");
    this->statusBar()->addWidget(lb);       //将label作为状态栏
}

4 事件与图形系统

4.1 事件

        Qt 中所有事件类都继承于QEvent。在事件对象创建完毕后,Qt 将这个事件对象传递给QObject的event()函数。event()函数并不直接处理事件,而是按照事件对象的类型分派给特定的事件处理函数(event handler) 。

        事件主要有外接设备事件(鼠标键盘)模块定时/完成事件事件过滤器

        其中,外接设备事件已写为内置虚函数,需要在窗口类中将其进行重载

4.1.1 鼠标事件

        鼠标事件包括:单击、双击释放移动、滚轮

        点击事件示例:反馈鼠标点击的坐标和具体点击按钮。

head文件类定义
class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    void mousePressEvent(QMouseEvent *event); //重载鼠标点击函数
    ~Widget();

private:
    Ui::Widget *ui;
};
cpp文件函数定义
void Widget::mousePressEvent(QMouseEvent *event)
{
    //在输出窗口反馈鼠标点击事件,并使用QMouseEvent的pos函数展示点击坐标
    qDebug() << "点击了一下鼠标" << event -> pos();
    qDebug() << "点击了:" <<event -> button();//反馈点击的鼠标上的具体按钮
}

        

         双击、释放、移动、滚轮事件示例:

//类定义
class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    void mousePressEvent(QMouseEvent *event);       //鼠标单击事件函数
    void mouseDoubleClickEvent(QMouseEvent *event); //鼠标双击事件函数
    void mouseReleaseEvent(QMouseEvent *event);     //鼠标释放事件函数
    void mouseMoveEvent(QMouseEvent *event);        //鼠标移动事件函数

    void wheelEvent(QWheelEvent *event);            //鼠标滚轮事件
    ~Widget();

private:
    Ui::Widget *ui;
};
//cpp鼠标事件函数定义
void Widget::mousePressEvent(QMouseEvent *event)
{
    //在输出窗口反馈鼠标点击事件,并使用QMouseEvent的pos函数展示点击坐标
    qDebug() << "点击了一下鼠标" << event -> pos();
    qDebug() << "点击了:" <<event -> button();//反馈点击的鼠标上的具体按钮
}

void Widget::mouseDoubleClickEvent(QMouseEvent *event) //鼠标双击事件函数
{
    qDebug() << "双击鼠标," << event ->pos();
    qDebug() << "双击鼠标" << event ->button();
}
void Widget::mouseReleaseEvent(QMouseEvent *event)     //鼠标释放事件函数
{
    qDebug() << "鼠标释放" << event ->pos();
}
void Widget::mouseMoveEvent(QMouseEvent *event)        //鼠标移动事件函数
{
    qDebug() << "鼠标移动" << event -> pos();
}

void Widget::wheelEvent(QWheelEvent *event){
    qDebug() << "鼠标滚轮滚动" <<event ->angleDelta();
}

4.1.2 键盘事件
void Widget::keyPressEvent(QKeyEvent *event)          //键盘按键事件
{
    qDebug() << "键盘按键" << event->key();
}
void Widget::keyReleaseEvent(QKeyEvent *event)         //键盘按键释放事件
{
    qDebug() << "释放按键" << event->key();
}

        注:这里输出的是字符ASCII码值。

        

4.1.3 窗口关闭事件
void Widget::closeEvent(QCloseEvent *event)            //窗口关闭事件
{
    qDebug() << "关闭窗口";
}

 

4.2   2D绘图

        2D绘图基础类:QPainter、QPaintEngine、QPaintDevice。

QPainter绘制方法(即其中的成员函数)
drawArc画弧线
drawChord画弦
drawConvexPolygon画凸多边形
drawEllipse画椭圆
drawLine画线
drawPath画路径
drawPicture画QPicture表示的图
drawPixmap画QPixmap表示的图片
drawPoint画点
drawPolygon画多边形
drawText画文字
4.2.1 绘图事件

        绘图事件常常和鼠标事件搭配,如:触发鼠标点击,同时触发绘制,触发鼠标释放,中断绘制。

        示例:基础绘图事件定义

void paintEvent(QPaintEvent *event);                   //绘图事件

void Widget::paintEvent(QPaintEvent *event)            //绘图事件
{
    qDebug() << "触发绘制";
}

        

 4.2.2 画点drawPoint()

        步骤:

        a)在头文件定义QPoint对象,在绘图事件中实例化画家对象、画笔对象、设置画笔对象到画家对象,定义画家的绘画方式(这里当然是画点),并将前面定义的QPoint对象传入绘画方式中。

        b)在鼠标事件函数中将鼠标点击坐标与定义的QPoint链接,并使用update()成员函数手动激发绘画事件。

#include <QPainter>
void Widget::paintEvent(QPaintEvent *event)            //绘图事件
{
    QPainter t(this); //创建在当前窗口绘制的画家类
    QPen pen;   //创建画笔类
    pen.setWidth(5);   //设置笔宽
    pen.setColor(QColorConstants::Red); //设置画笔颜色
    t.setPen(pen); //设置画笔

    t.drawPoint(posBegin);  //画点
    qDebug() << "触发绘制" << posBegin << posEnd;
}
void Widget::mousePressEvent(QMouseEvent *event)
{
    posBegin = event->pos();
    update(); //手动激发界面绘制事件paintEvent
    //在输出窗口反馈鼠标点击事件,并使用QMouseEvent的pos函数展示点击坐标
    qDebug() << "点击了一下鼠标" << event -> pos();
    qDebug() << "点击了:" <<event -> button();//反馈点击的鼠标上的具体按钮
}
4.2.3 画线条drawLine()

        画线条与画点类似,不同在于:

        a)需要将用于线条终点定义的QPoint赋予鼠标移动事件的坐标值,即需要将移动时的坐标实时传入一个QPoint对象。

        b)去掉鼠标点击事件中的update()函数,添加在鼠标移动事件中,用以激发绘图事件。

void Widget::paintEvent(QPaintEvent *event)            //绘图事件
{
    QPainter t(this); //创建在当前窗口绘制的画家类
    QPen pen;   //创建画笔类
    pen.setWidth(5);   //设置笔宽
    pen.setColor(QColorConstants::Red); //设置画笔颜色
    t.setPen(pen); //设置画笔

//    t.drawPoint(posBegin);  //画点
    t.drawLine(posBegin, posEnd);
    qDebug() << "触发绘制" << posBegin << posEnd;
}

void Widget::mousePressEvent(QMouseEvent *event)
{
    posBegin = event->pos();
    //在输出窗口反馈鼠标点击事件,并使用QMouseEvent的pos函数展示点击坐标
    qDebug() << "点击了一下鼠标" << event -> pos();
    qDebug() << "点击了:" <<event -> button();//反馈点击的鼠标上的具体按钮
}
void Widget::mouseMoveEvent(QMouseEvent *event)        //鼠标移动事件函数
{
    posEnd = event->pos();  //将移动与终点QPoint链接,用于线条绘制
    update(); //手动激发界面绘制事件paintEvent
    qDebug() << "鼠标移动" << event -> pos();
}

4.2.4 画椭圆drawEllipse()

        画椭圆与画点一样,将原本的drawPoint相关改为drawEllipse即可。

 

 4.2.5 画矩形drawRect()

        与画椭圆相同。

void Widget::paintEvent(QPaintEvent *event)            //绘图事件
{
    QPainter t(this); //创建在当前窗口绘制的画家类
    QPen pen;   //创建画笔类
    pen.setWidth(5);   //设置笔宽
    pen.setColor(QColorConstants::Red); //设置画笔颜色
    t.setPen(pen); //设置画笔

    t.drawRect(posBegin.x(), posBegin.y(), 10, 10);
    t.drawLine(posBegin, posEnd);
    t.drawRect(posEnd.x(), posEnd.y(), 10, 10);
    qDebug() << "触发绘制" << posBegin << posEnd;
}

 4.2.6 画刷子QBrush

        刷子会与画笔一样,需要填充到画家实例中,刷子会自动填充画笔绘制的封闭多边形。

void Widget::paintEvent(QPaintEvent *event)            //绘图事件
{
    QPainter t(this); //创建在当前窗口绘制的画家实例

    QPen pen;   //创建画笔实例
    pen.setWidth(5);   //设置笔宽
    pen.setColor(QColorConstants::Red); //设置画笔颜色
    t.setPen(pen); //设置画笔

    QBrush brush;   //创建刷子实例
    brush.setColor(Qt::blue);   //设置刷子颜色
    brush.setStyle(Qt::SolidPattern);   //设置刷子风格
    t.setBrush(brush);  //将刷子实例添加到画家类实例中

    t.drawRect(posBegin.x(), posBegin.y(), 50, 50);
    t.drawLine(posBegin, posEnd);
    t.drawRect(posEnd.x(), posEnd.y(), 50, 50);
    qDebug() << "触发绘制" << posBegin << posEnd;
}

 4.2.7 画图片drawPixmap()

       用法与drawRect、drawEllipse相同。

void Widget::paintEvent(QPaintEvent *event)            //绘图事件
{
    QPainter t(this); //创建在当前窗口绘制的画家实例

    QPen pen;   //创建画笔实例
    pen.setWidth(5);   //设置笔宽
    pen.setColor(QColorConstants::Red); //设置画笔颜色
    t.setPen(pen); //设置画笔

    QBrush brush;   //创建刷子实例
    brush.setColor(Qt::blue);   //设置刷子颜色
    brush.setStyle(Qt::SolidPattern);   //设置刷子风格
    t.setBrush(brush);  //将刷子实例添加到画家类实例中

    t.drawRect(posBegin.x(), posBegin.y(), 50, 50);
    t.drawLine(posBegin, posEnd);
    t.drawPixmap(posEnd.x(), posEnd.y(), 50, 50, QPixmap("E:/image_packages/image2.jpeg"));
    qDebug() << "触发绘制" << posBegin << posEnd;
}

4.3 坐标系统

4.3.1 画家动作

        画家动作提供了移动(偏移作画)、缩放旋转等动作,即提供一个映射方法,将实际点映射到其它区域。

void Widget::paintEvent(QPaintEvent *event)
{
    QPainter p(this);
    p.translate(100, 100);   //移动画家
    p.scale(0.5, 0.5);       //缩放作画
    p.rotate(30);            //旋转作画
    p.drawEllipse(posBegin, 100, 100);
    p.drawLine(posBegin, QPoint(posBegin.x()+100, posBegin.y()));

    //定义另一个画家用于比较
    QPainter p2(this);
    QPen pen;
    pen.setColor(QColorConstants::Red);
    pen.setWidth(10);
    p2.setPen(pen);
    p2.drawPoint(posBegin);

    update();
}

         注:图中红点为实际点击点,圆和其半径线是经过画家动作后的画家1绘图情形。

4.3.2 时钟QTimer

        使用时钟,可以控制某些值随时间变化,进而利用这些随时间变化的值调控绘图。

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    angle = 0;
    QTimer *t = new QTimer;
    connect(t, &QTimer::timeout, [&](){
        angle += 6;
        update();
    });
    t->start(100);  //定义100ms后开始执行时钟
}
void Widget::paintEvent(QPaintEvent *event)
{
    QPainter p(this);
    p.translate(100, 100);   //移动画家
    p.scale(0.5, 0.5);       //缩放作画
    p.rotate(angle);            //旋转作画
    p.drawEllipse(posBegin, 100, 100);
    p.drawLine(posBegin, QPoint(posBegin.x()+100, posBegin.y()));

    //定义另一个画家用于比较
    QPainter p2(this);
    QPen pen;
    pen.setColor(QColorConstants::Red);
    pen.setWidth(10);
    p2.setPen(pen);
    p2.drawPoint(posBegin);

    update();
}

        

        解释:这里的图像在绕红点转动。 

4.3.3 双缓冲绘图

        即定义一个QPixmap对象,将绘制的线条都储存到其中,并在窗口中实时绘制线条,这样就会呈现一个绘制轨迹

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    pix = new QPixmap(640, 480);
    pix->fill();
}

void Widget::paintEvent(QPaintEvent *event)            //绘图事件
{
    QPainter t(pix); //创建在图pixmap中绘制的画家实例

    QPen pen;   //创建画笔实例
    pen.setWidth(5);   //设置笔宽
    pen.setColor(QColorConstants::Red); //设置画笔颜色
    t.setPen(pen); //设置画笔

    QBrush brush;   //创建刷子实例
    brush.setColor(Qt::blue);   //设置刷子颜色
    brush.setStyle(Qt::SolidPattern);   //设置刷子风格
    t.setBrush(brush);  //将刷子实例添加到画家类实例中

    t.drawRect(posBegin.x(), posBegin.y(), 50, 50);
    t.drawLine(posBegin, posEnd);
    t.drawPixmap(posEnd.x(), posEnd.y(), 50, 50, QPixmap("E:/image_packages/image2.jpeg"));

    //定义第二个画家
    QPainter p2(this);
    p2.drawPixmap(0, 0, 640, 480, *pix);
    qDebug() << "触发绘制" << posBegin << posEnd;
}

4.4 图形视图Graphic view

        Graphics View支持同 QPainter-样的几何变换:缩放、平移、旋转、扭转等等功能。也可以通过 QMatrix完成几何变换的值设置。

4.5 事件过滤

        事件过滤是一种机制,允许在事件到达目标对象之前或之后截获、处理和修改事件。可以用于拦截特定类型的事件并进行自定义处理,从而实现对事件的额外控制或修改。

        传统方法:

        a)根据需要,定义新的类继承需要使用的组件类,并在其中重载事件函数(该事件函数主要依据if语句来过滤)。

        b)在窗口函数中使用这些自定义类。

#.h
class myTextEdit : public QTextEdit
{
public:
    myTextEdit(){
        this->size = 1;
    }
    void wheelEvent(QWheelEvent *event){    //重载滚轮事件函数,当滚轮角度改变时,完成字体的变大变小
        qDebug() << event ->angleDelta();
        if(event->angleDelta().y() > 0){
            this->setFontPointSize(size++);
        }
        else{
            size -= 1;
            if(size == 0)
                size = 1;
            this->setFontPointSize(size);
        }
    }
    int size{};
};

#.cpp
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    myTE = new myTextEdit;  //使用继承方法实现的特定对象事件操作文本编辑器
    
    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(myTE);
    setLayout(vbox);
}

        内置方法:

        `QObject::installEventFilter`:用于在一个对象的父对象上安装事件过滤器,并且这个过滤器会监视并处理该对象接收的事件。该方法接受一个 QObject 类型的指针作为参数,指向实现事件过滤的过滤器对象。

        `QObject::eventFilter`:该方法是一个虚函数,可以在自定义的事件过滤器类中重写这个方法来实现特定事件的过滤逻辑。当事件到达被安装事件过滤器的对象时,会触发该对象的 eventFilter 方法。在这个方法中,可以根据事件类型、目标对象等信息对事件进行处理,然后根据需要返回 `true` 或 `false` 来决定是否继续传递事件。

        过程:

        a)定义在窗口类中定义eventFilter,通过其传参QObject、QEvent判断传参,从而实现事件过滤。

        b)在窗口构造函数定义中,使用installEventFilter,进行安装,即指定一个对象在某个窗口类中的过滤机制,一般为:QObject->installEventFilter(QWidget/QMainWindow)

        示例:分别使用继承与事件过滤机制,实现某对象的事件处理

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    //定义事件过滤器
    bool eventFilter(QObject *watched, QEvent *event)
    {
        if(watched == te){//使用if来判断被勾选的是不是文本编辑框te对象
            if(event->type() == QEvent::Wheel){//判断是否滚轮对象
                QWheelEvent *e = static_cast<QWheelEvent *>(event);
                if(e->angleDelta().y() > 0){
                    te->setFontPointSize(this->size++);
                }
                else{
                    size -= 1;
                    if(size == 0)
                        size = 1;
                    te->setFontPointSize(size);
                }
                return true;
            }
        }
        return false;
    }

    ~Widget();
    int size{};
    QTextEdit *te;
};

#include "widget.h"
#include <QVBoxLayout>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    te = new QTextEdit;     //使用事件过滤机制实现
    this->size = 1;
    te->installEventFilter(this);   //安装时间过滤

    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(te);
    setLayout(vbox);
}

        

5 文件处理

5.1 QFlie

        QFile是qt的文件操作类,

        为了简化操作文本及二进制数据问题,Qt还提供了如下类:

        QTextStream——操作文本流

        QDataStream——操作数据流的

        QTemporaryFile——操作临时文件

        QFileInfo——操作文件信息。

        QFlie相关类继承关系:

        QFile从QFileDevice继承出来,QFileDevice定义了文件错误类型、文件打开时的读与权限等;QFileDevice从QIODevice继承出来,QIODevice中定义了读写接口;QIODevice从Qobject继承出来,其支持信号槽。

5.1.1 文件加密工具实战

        首先新建一个mingw下的QWidget(支持图形化ui设计)的项目。

        使用ui设计界面如下:

        

        对按钮分别右键进行对象名修改,随后转到槽,为按钮添加事件操作(这里选择点击clicked()):

                                

         操作完成后,会在头文件出现如下函数声明,在cpp文件出现对应函数定义。

        

//.h文件
QString fileName;

//.cpp文件
void Widget::on_view_clicked()//为浏览按钮添加文本获取
{
    ui->progressBar->setValue(0);   //初始化进度条为0
    fileName =  QFileDialog::getOpenFileName();
    ui->lineEdit->setText(fileName);
}


void Widget::on_encode_clicked()
{
    //打开文件
    QFile f(fileName);
    if(!f.open(QIODevice::ReadWrite))   //读写验证
        return;
    //读出内容
    QByteArray buf = f.readAll();

    ui->progressBar->setRange(0, buf.length() - 1);     //根据内容大小设置进度条的范围

    //加密
    for(int i = 0;i <buf.length();i++){
        buf[i] = ~buf[i];   //取反加密
        ui->progressBar->setValue(i);   //更新进度条
    }
    //回写,即将加密后的文本写回到原文件中
    f.seek(0);
    f.write(buf);

    //关闭
    f.close();
}


void Widget::on_quit_clicked()
{
    this->close();
}

void Widget::on_decode_clicked()
{
    //打开文件
    QFile f(fileName);
    if(!f.open(QIODevice::ReadWrite))   //读写验证
        return;
    //读出内容
    QByteArray buf = f.readAll();

    ui->progressBar->setRange(0, buf.length() - 1);     //根据内容大小设置进度条的范围

    //加密
    for(int i = 0;i <buf.length();i++){
        buf[i] = ~buf[i];   //取反加密
        ui->progressBar->setValue(i);   //更新进度条
    }
    //回写,即将加密后的文本写回到原文件中
    f.seek(0);
    f.write(buf);

    //关闭
    f.close();
}

         

5.2 读写方式

        读写操作主要方法:

        read();

        readAll();

        readLine();

        write();

void Widget::on_pushButton_clicked()
{
    QFile file;
    QString f = QFileDialog::getOpenFileName(this, QString("选择文件"), QString("/"),QString("TEXT(*.txt)"));
    file.setFileName(f);
    if(file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        QByteArray t ;
        while(!file.atEnd())
        {
            t += file.readLine();
        }
        ui->text_r->setText(QString(t));
        file.close();
    }
}

void Widget::on_pushButton_2_clicked()
{
    QString e = ui->text_e->toPlainText();
    QFile file;
    file.setFileName(QFileDialog::getSaveFileName(this, QString("保存路径"), QString("/"),QString("TEXT(*.txt)")));
    file.open(QIODevice::WriteOnly | QIODevice::Text);
    file.write(e.toUtf8());
    file.close();
}

        参考:https://www.cnblogs.com/flowingwind/p/8336159.html

5.3 文本流QTextStream、数据流QDataStream

        Qt文件类型针对文本文件和二进制文件分别提供接口如下:

        QTextStream text(&file);——针对QIODevice、QString、QByteArray

        QDataStream data(&file);——对计算机来说,所有文件均为二进制文件

        示例:

//读
QTextStream stream(f);
QString line;
do
{
    line = stream.readLine();
}while(!line.isNull());


//写
QFile f("pathofyourfile.xxx");
if(f.open(QFile::WriteOnly | QFile::Truncate))
{
    QTextStream ts(f);
    ts << "File content" << qSetFileWidth(10) << someChar; //每隔10个字符就插入一个自定义字符
}

5.4 临时文件QTemporaryFile

        QTemporaryFile是用于创建临时文件的类,继承自`QFile`,主要作用是创建临时文件,并在使用后自动删除这些临时文件,确保不会产生过多的垃圾文件。

        示例:

```cpp
#include <QTemporaryFile>
#include <QDebug>

int main() {
    QTemporaryFile *tempFile{};
    if(tempFile->isOpen()){
        qDebug() << "successfully open" << tempFile->fileName();

        tempFile->write("hello here's some temp words");
        tempFile->close();

        qDebug() << "tempFile path:" << tempFile->fileName();
    }
    else{
        qDebug() << "false" << tempFile->errorString();
    }


    return 0;
}
```

        示例中,创建了一个`QTemporaryFile`对象`tempFile`,调用`open`函数打开临时文件,然后像普通的`QFile`一样对文件进行读写操作。临时文件创建后,使用`fileName`函数获取文件的路径。在对象离开作用域时,`QTemporaryFile`会自动删除临时文件,无需手动处理。

        注:临时文件的自动删除只在`QTemporaryFile`对象被销毁时才会触发。如果需要手动删除临时文件,可以调用`remove`函数。

5.5 目录QDir

        `QDir`是用于处理目录文件路径的类。例如创建目录列出目录内容检查文件是否存在等等。

        常用的`QDir`类的方法和功能:

a)构造函数:
   - `QDir`:默认构造函数,创建一个空的`QDir`对象。
   - `QDir(const QString &path)`:使用给定的路径构造一个`QDir`对象。

b)设置和获取目录路径:
   - `setPath(const QString &path)`:设置目录路径。
   - `path()`:获取目录路径。

c)列出目录内容:
   - `entryList(QDir::Filters filters = NoFilter, QDir::SortFlags sort = NoSort)`:列出目录中的所有文件和子目录。
   - `entryInfoList(QDir::Filters filters = NoFilter, QDir::SortFlags sort = NoSort)`:返回`QFileInfo`对象的列表,包含目录中的所有文件和子目录的信息。

d)创建和删除目录:
   - `mkdir(const QString &dirName)`:创建一个目录。
   - `rmdir(const QString &dirName)`:删除一个目录。

e)文件操作:
   - `exists(const QString &name)`:检查文件或目录是否存在。
   - `remove(const QString &fileName)`:删除一个文件。

f)其他功能:
   - `isRoot()`:检查是否是根目录。
   - `isReadable()`:检查目录是否可读。
   - `isWritable()`:检查目录是否可写。

        示例代码:使用`QDir`来操作目录和文件:

```cpp
#include <QDir>
#include <QDebug>

int main() {
    QString path = "/path/to/directory";

    QDir dir(path);

    // 列出目录内容
    QStringList entryList = dir.entryList();
    foreach (QString entry, entryList) {
        qDebug() << entry;
    }

    // 创建一个新的子目录
    QString newDirName = "new_directory";
    if (dir.mkdir(newDirName)) {
        qDebug() << "New directory created:" << newDirName;
    } else {
        qDebug() << "Failed to create directory:" << newDirName;
    }

    // 删除子目录
    QString dirToRemove = "directory_to_remove";
    if (dir.rmdir(dirToRemove)) {
        qDebug() << "Directory removed:" << dirToRemove;
    } else {
        qDebug() << "Failed to remove directory:" << dirToRemove;
    }

    // 检查文件是否存在
    QString fileName = "file.txt";
    if (dir.exists(fileName)) {
        qDebug() << "File exists:" << fileName;
    } else {
        qDebug() << "File does not exist:" << fileName;
    }

    return 0;
}
```

        官方文档链接:https://doc.qt.io/qt-5/qdir.html

5.6 文件信息QFileInfo

        QFileInfo`是用于获取操作文件信息的类。它可以访问文件的属性,如文件名、路径、大小、创建时间等。

        头文件: <QFileInfo>。

1. 构造函数:
   - `QFileInfo(const QString& file)`:创建 `QFileInfo` 对象,传入文件的路径名。
   - `QFileInfo(const QFile& file)`:创建 `QFileInfo` 对象,传入 `QFile` 对象。
   - `QFileInfo(const QDir& dir, const QString& file)`:创建 `QFileInfo` 对象,传入目录和文件名。

2. 文件信息获取:
   - `fileName()`:返回文件名(包含文件扩展名)。
   - `baseName()`:返回文件名(不包含文件扩展名)。
   - `suffix()`:返回文件扩展名。
   - `filePath()`:返回文件路径(包含文件名)。
   - `absoluteFilePath()`:返回文件的绝对路径。
   - `exists()`:判断文件是否存在。
   - `isFile()`:判断是否是文件。
   - `isDir()`:判断是否是目录。
   - `size()`:返回文件大小(字节数)。
   - `created()`:返回文件创建时间。
   - `lastModified()`:返回文件最后修改时间。

3. 路径操作:
   - `canonicalFilePath()`:返回规范化的绝对路径,消除符号链接和冗余路径。
   - `makeAbsolute()`:将文件路径转换为绝对路径。

4. 文件权限:
   - `isReadable()`:判断文件是否可读。
   - `isWritable()`:判断文件是否可写。
   - `isExecutable()`:判断文件是否可执行。

        示例:使用 `QFileInfo` 获取文件信息:

```cpp
#include <QCoreApplication>
#include <QFileInfo>
#include <QDebug>

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    QString filePath = "C:/path/to/your/file.txt";
    QFileInfo fileInfo(filePath);

    qDebug() << "File Name:" << fileInfo.fileName();
    qDebug() << "Base Name:" << fileInfo.baseName();
    qDebug() << "Suffix:" << fileInfo.suffix();
    qDebug() << "File Path:" << fileInfo.filePath();
    qDebug() << "Absolute File Path:" << fileInfo.absoluteFilePath();
    qDebug() << "File Exists:" << fileInfo.exists();
    qDebug() << "Is File:" << fileInfo.isFile();
    qDebug() << "Is Dir:" << fileInfo.isDir();
    qDebug() << "File Size:" << fileInfo.size() << "bytes";
    qDebug() << "Created:" << fileInfo.created();
    qDebug() << "Last Modified:" << fileInfo.lastModified();
    qDebug() << "Is Readable:" << fileInfo.isReadable();
    qDebug() << "Is Writable:" << fileInfo.isWritable();
    qDebug() << "Is Executable:" << fileInfo.isExecutable();

    return a.exec();
}
```

5.7 文件监控QFileSystemWatcher

        `QFileSystemWatcher` 用于监视特定文件或目录的修改删除重命名等操作,并在发生这些操作时发出信号通知应用程序。

        头文件 :`<QFileSystemWatcher>`。

1. 构造函数:
   - `QFileSystemWatcher(QObject *parent = nullptr)`:创建一个文件系统监视器对象。可选择传入一个父对象 `parent`,以便 Qt 在父对象销毁时自动释放 `QFileSystemWatcher`。

2. 添加监视:
   - `addPath(const QString &path)`:添加要监视的文件或目录路径。可以传入一个具体的文件路径或目录路径,也可以传入通配符(如 "*.txt")来监视符合条件的多个文件。

3. 移除监视:
   - `removePath(const QString &path)`:移除对指定路径的监视。

4. 清空监视:
   - `removePaths(const QStringList &paths)`:移除对多个路径的监视。

5. 获取监视路径:
   - `files()`:返回当前监视的所有文件路径。
   - `directories()`:返回当前监视的所有目录路径。

6. 信号:
   - `fileChanged(const QString &path)`:文件或目录发生更改时发出的信号。
   - `directoryChanged(const QString &path)`:目录发生更改时发出的信号。
   - `fileRenamed(const QString &oldPath, const QString &newPath)`:文件或目录重命名时发出的信号。

        示例:使用 `QFileSystemWatcher` 监视文件的更改:

```cpp
#include <QCoreApplication>
#include <QFileSystemWatcher>
#include <QDebug>

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    QString filePath = "C:/path/to/your/file.txt";
    QFileSystemWatcher watcher;

    watcher.addPath(filePath);

    QObject::connect(&watcher, &QFileSystemWatcher::fileChanged, [&](const QString &path) {
        qDebug() << "File Changed:" << path;
    });

    qDebug() << "Watching file changes...";

    return a.exec();
}
```

        上述代码中,创建了一个 `QFileSystemWatcher` 对象,并使用 `addPath()` 方法添加要监视的文件路径。然后,使用 `QObject::connect()` 建立了信号与槽的连接,当文件发生更改时,`fileChanged` 信号将触发槽函数,并输出文件路径。请将 `filePath` 替换为您要监视的文件的实际路径。

6 线程与同步互斥

6.1 线程QThread

        `QThread` 是 Qt 中用于多线程编程的类。它提供了一种方便的方式来创建和管理线程,使得在 Qt 应用程序中进行并发编程变得更加简单。

        使用 `QThread` 的一般步骤:

a)创建自定义线程类:首先,需要创建一个继承自 `QThread` 的自定义线程类。在这个类中,可以实现线程的具体逻辑。

b)重写 `run()` 方法:在自定义线程类中,重写 `run()` 方法。这个方法定义了线程的执行逻辑。当线程开始时,`run()` 方法将被执行。

c)创建线程对象:在主线程中,实例化您自定义的线程类,创建一个线程对象。

d)启动线程:调用线程对象的 `start()` 方法来启动线程。Qt 将会在一个新的线程中调用 `run()` 方法。

e)线程通信:如果需要在线程之间进行通信,可以使用信号和槽机制或其他线程间通信方式。

f)线程管理:可以使用 `QThread` 提供的一些方法来管理线程的状态,如 `wait()`, `quit()`, `terminate()`, `isRunning()`, 等。

        QThread成员函数:

explicit QThread(QObject *parent = nullptr):构造函数,创建一个新的线程对象。

void start():启动线程执行 run() 方法。

void run():线程的执行函数,需要在子类中重写,定义线程的逻辑。

void quit():请求线程退出,线程将在适当的时机退出。

bool wait(unsigned long time = ULONG_MAX):等待线程执行完成,如果 time 参数不为 ULONG_MAX,则最多等待 time 毫秒。

void terminate():强制终止线程,慎用,可能导致资源泄漏和不稳定行为。

void exit(int returnCode = 0):线程退出,returnCode 为线程的返回值。

bool isRunning() const:判断线程是否正在运行。

void setPriority(QThread::Priority priority):设置线程的优先级,有 IdlePriorityLowestPriorityLowPriorityNormalPriorityHighPriorityHighestPriorityTimeCriticalPriority 等选项。

QThread::Priority priority() const:获取线程的优先级。

void msleep(unsigned long msecs):让线程休眠指定的毫秒数。

void sleep(unsigned long secs):让线程休眠指定的秒数。

QThread::currentThreadId():静态函数,获取当前线程的 ID。

        QThread成员变量:

QThread::currentThread():静态函数,返回当前线程的指针。

QThread::idealThreadCount():静态函数,返回计算机支持的理想线程数,通常等于 CPU 核心数。

bool isFinished:线程是否已完成执行。

bool isInterruptionRequested:是否请求中断线程。

void requestInterruption():请求中断线程。

bool isEventDispatcherEnabled() const:检查事件调度器是否启用,如果禁用,则线程不会处理事件队列中的事件。

void setEventDispatcherEnabled(bool enabled):启用或禁用事件调度器。

        示例:使用 `QThread` 来创建一个简单的线程:

```cpp
#include <QCoreApplication>
#include <QThread>
#include <QDebug>

class MyThread : public QThread {
public:
    void run() override {
        for (int i = 0; i < 5; ++i) {
            qDebug() << "Running in thread: " << QThread::currentThread();
            sleep(1); // 模拟耗时操作,单位为秒
        }
    }
};

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    MyThread thread;
    thread.start();

    qDebug() << "Main thread: " << QThread::currentThread();

    // 等待线程执行完毕
    thread.wait();

    qDebug() << "Thread finished.";

    return a.exec();
}
```

        上述代码中,创建了一个自定义的线程类 `MyThread`,重写了 `run()` 方法,用于定义线程的执行逻辑。在 `main` 函数中,实例化 `MyThread` 类,创建一个线程对象 `thread`,然后调用 `start()` 方法启动线程。在主线程中,输出当前线程的信息,并使用 `wait()` 方法等待线程执行完毕。注意,`wait()` 会阻塞主线程,直到线程执行完毕。

        当线程运行时,`run()` 方法中的代码会在一个新的线程中被执行,而主线程会继续执行其他代码。

        示例:多线程编写练习

        通过ui设计创建如下界面:

         创建自定义线程类,并重载run():

#ifndef MYTASK_H
#define MYTASK_H

#include <QThread>

class MyTask : public QThread
{
    Q_OBJECT
signals:
    void progress(int);//用于链接进度条函数
public:
    MyTask(int delay);

    //重载run函数
    void run(){
        for(int i = 0;i < 100;i++){
            int x = time;
            while(x--);
            emit progress(i);
        }
    }
    int time;
};

#endif // MYTASK_H

        创建线程并使用start()启动:

void Widget::work(){
    //创建线程
    MyTask *th1 = new MyTask(20000000);
    MyTask *th2 = new MyTask(60000000);
    MyTask *th3 = new MyTask(30000000);

    connect(th1, SIGNAL(progress(int)), ui->thread1_progressBar, SLOT(setValue(int)));
    connect(th2, SIGNAL(progress(int)), ui->thread2_progressBar, SLOT(setValue(int)));
    connect(th3, SIGNAL(progress(int)), ui->thread3_progressBar, SLOT(setValue(int)));

    //启动线程
    th1->start();
    th2->start();
    th3->start();
}

        其余代码:

#include "widget.h"
#include "ui_widget.h"

#include "mytask.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //初始化进度条
    ui->thread1_progressBar->setValue(0);
    ui->thread2_progressBar->setValue(0);
    ui->thread3_progressBar->setValue(0);

}

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

void Widget::on_start_button_clicked()
{
    work();
}

         在执行过程中,三个进度条同时增长。

6.2 线程同步互斥

        在并发执行线程中,多个线程会竞争同一个资源,由其当多个线程同时修改同一个数据时,需要通过线程间的同步互斥来维持数据的稳定,防止一个线程在对数据修改的过程中,其余线程对被修改中数据的访问。

        QT用于同步互斥的技术:互斥锁、信号量、条件变量等,这些技术可以在共有数据被修改时,阻止其它线程的访问。

        死锁:在使用同步互斥手段时,出现的多个线程之间互相等待资源,使得程序不能正常执行下去的现象。

        锁粒度:锁粒度由临界区(同步互斥技术保护的区域)的范围决定,粒度的大小与临界区范围大小成反比,粒度越小,并发程度越高。

6.2.1 QT的互斥锁QMutex、QMutexlocker

        `QMutex` :互斥锁类,用于实现互斥访问共享资源。在多线程环境中,当多个线程尝试同时访问共享资源时,可能会导致竞态条件(Race Condition),从而导致数据不一致或错误的结果。使用 `QMutex`,可以确保在同一时刻只有一个线程能够访问共享资源,从而避免竞态条件。

        `QMutexLocker`: 便捷互斥锁类,用于自动管理 `QMutex`。当一个线程锁住互斥锁后,其他线程如果尝试再次锁住同一个互斥锁,则会被阻塞,直到该互斥锁解锁。为了避免忘记在适当的地方解锁互斥锁,可以使用 `QMutexLocker` 来自动管理互斥锁的加锁和解锁。

**常用的 `QMutex` 成员函数:**

1. `void lock()`:锁住互斥锁,如果互斥锁已被其他线程锁住,则当前线程会阻塞,直到互斥锁可用。
2. `bool tryLock()`:尝试锁住互斥锁,如果互斥锁已被其他线程锁住,则返回 false,否则返回 true。
3. `void unlock()`:解锁互斥锁,允许其他线程访问共享资源。

        **使用示例:**

```cpp
#include <QCoreApplication>
#include <QThread>
#include <QMutex>
#include <QMutexLocker>
#include <QDebug>

// 共享资源类
class SharedResource {
public:
    SharedResource() : count(0) {}

    void increment() {
        QMutexLocker locker(&mutex); // 使用QMutexLocker自动管理互斥锁
        ++count;
    }

    int getCount() const {
        QMutexLocker locker(&mutex); // 使用QMutexLocker自动管理互斥锁
        return count;
    }

private:
    int count;
    mutable QMutex mutex; // 使用mutable关键字,允许在const函数中锁住互斥锁
};

// 工作线程类
class WorkerThread : public QThread {
public:
    WorkerThread(SharedResource *resource) : resource(resource) {}

    void run() override {
        for (int i = 0; i < 10000; ++i) {
            resource->increment();
        }
    }

private:
    SharedResource *resource;
};

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    SharedResource resource;

    WorkerThread thread1(&resource);
    WorkerThread thread2(&resource);

    thread1.start();
    thread2.start();

    thread1.wait();
    thread2.wait();

    qDebug() << "Final count:" << resource.getCount(); // 输出最终的count值

    return a.exec();
}
```

        在上述示例中,创建了一个 `SharedResource` 类,其中包含一个计数器 `count` 和一个 `QMutex` 互斥锁用于保护计数器。在 `increment()` 和 `getCount()` 方法中,使用 `QMutexLocker` 来自动管理互斥锁的加锁和解锁。无论线程如何执行,都能保证对共享资源的安全访问。

6.2.2 读写锁QReadWriteLock

        `QReadWriteLock` 是 Qt 中用于读写锁的类,是一种特殊的锁机制,允许多个线程同时读取共享数据,但只有一个线程能够写入共享数据。这种机制可以提高并发性,适用于读多写少的场景。

        **使用 `QReadWriteLock` 的基本步骤:**

1. 创建一个 `QReadWriteLock` 对象。
2. 在需要读取共享数据的地方,使用 `QReadLocker` 对象加读锁,以允许多个线程同时读取共享数据。
3. 在需要写入共享数据的地方,使用 `QWriteLocker` 对象加写锁,以确保只有一个线程能够写入共享数据。

        **示例代码:**

```cpp
#include <QCoreApplication>
#include <QThread>
#include <QReadWriteLock>
#include <QDebug>

// 共享数据类
class SharedData {
public:
    void readData() {
        QReadLocker locker(&lock); // 加读锁
        qDebug() << "Reading data:" << data;
    }

    void writeData(int newData) {
        QWriteLocker locker(&lock); // 加写锁
        data = newData;
        qDebug() << "Writing data:" << data;
    }

private:
    int data = 0;
    QReadWriteLock lock; // 读写锁
};

// 工作线程类
class WorkerThread : public QThread {
public:
    WorkerThread(SharedData *sharedData) : sharedData(sharedData) {}

    void run() override {
        // 读取数据
        for (int i = 0; i < 3; ++i) {
            sharedData->readData();
            msleep(100); // 模拟读取数据耗时
        }

        // 写入数据
        sharedData->writeData(threadId());
    }

private:
    SharedData *sharedData;
};

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    SharedData sharedData;

    WorkerThread thread1(&sharedData);
    WorkerThread thread2(&sharedData);

    thread1.start();
    thread2.start();

    thread1.wait();
    thread2.wait();

    return a.exec();
}
```

        上述示例中,创建了一个 `SharedData` 类,其中包含共享数据 `data` 和 `QReadWriteLock` 读写锁用于保护共享数据。在 `readData()` 方法中,使用 `QReadLocker` 加读锁,以允许多个线程同时读取 `data` 数据。在 `writeData()` 方法中,使用 `QWriteLocker` 加写锁,以确保只有一个线程能够写入 `data` 数据。

6.2.3 条件变量QWatiCondition

        条件变量用于在线程之间进行通信,即提供一个线程完成任务时的通知,其余等待中线程的通知接受功能。

        **QWaitCondition 的基本用法:**

1. 创建一个 `QWaitCondition` 对象。
2. 在等待线程中使用 `wait()` 方法等待条件满足,此时线程会阻塞。
3. 在满足条件的情况下,通知等待线程继续执行,可以使用 `wakeOne()` 方法唤醒一个等待线程,或使用 `wakeAll()` 方法唤醒所有等待线程。

        **示例代码:**

```cpp
#include <QCoreApplication>
#include <QThread>
#include <QWaitCondition>
#include <QMutex>
#include <QDebug>

// 共享资源类
class SharedResource {
public:
    void waitForData() {
        QMutexLocker locker(&mutex);
        while (!dataAvailable) {
            // 等待数据可用的条件满足
            condition.wait(&mutex);
        }
        qDebug() << "Data received:" << data;
    }

    void setData(int newData) {
        QMutexLocker locker(&mutex);
        data = newData;
        dataAvailable = true;
        // 发送信号通知等待线程数据已经可用
        condition.wakeAll();
    }

private:
    int data = 0;
    bool dataAvailable = false;
    QMutex mutex;
    QWaitCondition condition;
};

// 工作线程类
class WorkerThread : public QThread {
public:
    WorkerThread(SharedResource *resource) : resource(resource) {}

    void run() override {
        resource->waitForData();
    }

private:
    SharedResource *resource;
};

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    SharedResource resource;

    WorkerThread thread;

    thread.start();

    // 模拟数据处理过程
    QThread::sleep(2); // 假设在2秒后数据准备完毕

    resource.setData(42); // 发送数据到等待线程

    thread.wait();

    return a.exec();
}
```

        上述示例中,创建了 `SharedResource` 类,其中包含共享数据 `data` 和 `QWaitCondition` 条件变量用于通知等待线程。在 `waitForData()` 方法中,用 `condition.wait(&mutex)` 来等待数据可用的条件满足,当数据可用时,线程会被唤醒并继续执行。

6.2.4 信号量Qsemaphore

        信号量用于线程之间的同步,控制对共享资源的并发访问。

        **QSemaphore 的基本用法:**

1. 创建一个 `QSemaphore` 对象,并指定初始的信号量计数值。
2. 在需要访问共享资源的线程中,使用 `acquire()` 方法获取信号量,并使得信号量减一,只有获取时的信号量大于0时,线程才可以继续执行,否则线程会阻塞等待。
3. 在访问共享资源的线程完成后,使用 `release()` 方法释放信号量并使得信号量加一,以便其他线程可以继续访问共享资源。

        **示例代码:**

```cpp
#include <QCoreApplication>
#include <QThread>
#include <QSemaphore>
#include <QDebug>

// 共享资源类
class SharedResource {
public:
    void processData(int data) {
        semaphore.acquire(); // 获取信号量
        qDebug() << "Processing data:" << data;
        QThread::msleep(1000); // 模拟数据处理过程
        qDebug() << "Data processed:" << data;
        semaphore.release(); // 释放信号量
    }

private:
    QSemaphore semaphore{1}; // 信号量,初始值为1
};

// 工作线程类
class WorkerThread : public QThread {
public:
    WorkerThread(SharedResource *resource, int data) : resource(resource), data(data) {}

    void run() override {
        resource->processData(data);
    }

private:
    SharedResource *resource;
    int data;
};

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    SharedResource resource;

    // 创建多个工作线程,并同时访问共享资源
    WorkerThread thread1(&resource, 1);
    WorkerThread thread2(&resource, 2);

    thread1.start();
    thread2.start();

    thread1.wait();
    thread2.wait();

    return a.exec();
}
```

        上述示例中,创建 `SharedResource` 类,其中包含一个共享资源,并用 `QSemaphore` 控制对共享资源的并发访问。在 `processData()` 方法中,使用 `acquire()` 获取信号量,当信号量计数值大于0时,线程可以继续执行,否则线程会阻塞等待。然后模拟数据处理过程,通过调用 `release()` 释放信号量,增加信号量计数值,以便其他线程可以继续访问共享资源。

7 进程间通信

7.1 进程QProcess

        `QProcess` 用于启动和与外部进程进行交互。通过 `QProcess`,可以执行外部命令、读取和写入外部进程的标准输入、输出和错误。

        使用 `QProcess` 的基本步骤:

1. 创建 `QProcess` 对象:首先,需要创建一个 `QProcess` 对象,用于管理外部进程。

2. 设置要执行的外部命令和参数:使用 `start()` 方法来设置要执行的外部命令和其参数。

3. 启动外部进程:调用 `start()` 方法启动外部进程。

4. 与进程交互:根据需要,读取进程的输出、写入进程的输入,或者等待进程执行完成。

5. 关闭进程:在处理完进程后,调用 `close()` 方法关闭进程。

        示例代码:使用 `QProcess` 启动外部进程,并读取其输出:

```cpp
#include <QCoreApplication>
#include <QProcess>
#include <QDebug>

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    // 创建 QProcess 对象
    QProcess process;

    // 设置要执行的外部命令和参数列表
    QString program = "ping";
    QStringList arguments = { "www.google.com" };

    // 启动外部进程
    process.start(program, arguments);

    // 等待进程执行完成
    if (process.waitForFinished()) {
        // 读取进程输出
        QByteArray output = process.readAllStandardOutput();
        qDebug() << "Process Output:";
        qDebug() << output;
    } else {
        qWarning() << "Process execution failed!";
        qWarning() << "Error:" << process.errorString();
    }

    return a.exec();
}
```

        上述示例中,创建了一个 `QProcess` 对象,并设置要执行的外部命令和参数列表。然后,调用 `start()` 方法启动外部进程,并使用 `waitForFinished()` 等待进程执行完成。一旦进程执行完成,使用 `readAllStandardOutput()` 读取进程的输出,并将其打印到控制台。

        **成员函数:**

`void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode = ReadWrite)`:启动外部进程。`program` 参数是要执行的外部命令的名称,`arguments` 参数是命令的参数列表,`mode` 参数指定进程的输入/输出模式。

`void close()`:关闭进程并释放相关资源。

`bool waitForStarted(int msecs = 30000)`:等待进程启动,最多等待 `msecs` 毫秒。

`bool waitForFinished(int msecs = 30000)`:等待进程执行完成,最多等待 `msecs` 毫秒。

`bool waitForReadyRead(int msecs = 30000)`:等待进程有数据可读,最多等待 `msecs` 毫秒。

`bool waitForBytesWritten(int msecs = 30000)`:等待数据被写入进程,最多等待 `msecs` 毫秒。

`void terminate()`:终止进程的执行。

`void kill()`:强制终止进程的执行。

`void setWorkingDirectory(const QString &dir)`:设置进程的工作目录。

`void setProcessChannelMode(QProcess::ProcessChannelMode mode)`:设置进程的通道模式,可以是 `QProcess::SeparateChannels`(默认)、`QProcess::MergedChannels` 或 `QProcess::ForwardedChannels`。

`void setReadChannel(QProcess::ProcessChannel channel)`:设置当前读取通道。

`void setWriteChannel(QProcess::ProcessChannel channel)`:设置当前写入通道。

        **成员变量:**

1. `QProcess::ProcessError error`:用于存储进程发生的错误。

2. `QProcess::ProcessState state`:用于存储进程的当前状态。

3. `QString program`:存储要执行的外部命令的名称。

4. `QStringList arguments`:存储外部命令的参数列表。

5. `QIODevice::OpenMode mode`:存储进程的输入/输出模式。

6. `QProcess::ProcessChannel readChannel`:存储当前读取通道。

7. `QProcess::ProcessChannel writeChannel`:存储当前写入通道。

7.2 共享内存QSharedMemory

        `QSharedMemory` 用于在进程之间共享内存的类。它允许多个进程在内存中创建一个共享区域,以便它们可以通过读写该内存区域来进行数据的交换。

        使用 `QSharedMemory` 的基本步骤:

1. 创建 `QSharedMemory` 对象:首先,创建一个 `QSharedMemory` 对象,用于管理共享内存。

2. 分配共享内存:调用 `create()` 方法来分配共享内存区域。可以指定共享内存的大小,并为其分配一个唯一的键,用于标识该共享内存区域。

3. 写入数据:在一个进程中,使用 `lock()` 方法锁定共享内存区域,并使用 `data()` 方法获取指向共享内存区域的指针,然后通过指针写入数据。

4. 读取数据:在另一个进程中,通过打开与之前指定相同键的 `QSharedMemory` 对象,并使用 `lock()` 方法锁定共享内存区域,并通过指针读取数据。

5. 释放共享内存:在使用完共享内存后,调用 `unlock()` 方法解锁共享内存,并在合适的时机调用 `detach()` 方法释放共享内存。

        示例代码:使用 `QSharedMemory` 在两个进程之间共享数据:

        **进程1:写入共享内存**

```cpp
#include <QCoreApplication>
#include <QSharedMemory>
#include <QDebug>

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    // 创建 QSharedMemory 对象,使用唯一的键 "MySharedMemory"
    QSharedMemory sharedMemory("MySharedMemory");

    // 分配共享内存,设置大小为 1024 字节
    if (!sharedMemory.create(1024)) {
        qWarning() << "Failed to create shared memory:" << sharedMemory.errorString();
        return 1;
    }

    // 锁定共享内存,并写入数据
    if (sharedMemory.lock()) {
        char *data = static_cast<char*>(sharedMemory.data());
        qstrcpy(data, "Hello from Process 1!");
        sharedMemory.unlock();
    } else {
        qWarning() << "Failed to lock shared memory:" << sharedMemory.errorString();
    }

    // 等待一段时间,确保进程2能读取到数据
    QThread::sleep(5);

    // 释放共享内存
    sharedMemory.detach();

    return a.exec();
}
```

        **进程2:读取共享内存**

```cpp
#include <QCoreApplication>
#include <QSharedMemory>
#include <QDebug>

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    // 创建 QSharedMemory 对象,使用唯一的键 "MySharedMemory"
    QSharedMemory sharedMemory("MySharedMemory");

    // 打开共享内存
    if (!sharedMemory.attach()) {
        qWarning() << "Failed to open shared memory:" << sharedMemory.errorString();
        return 1;
    }

    // 锁定共享内存,并读取数据
    if (sharedMemory.lock()) {
        char *data = static_cast<char*>(sharedMemory.data());
        qDebug() << "Process 2 reads:" << data;
        sharedMemory.unlock();
    } else {
        qWarning() << "Failed to lock shared memory:" << sharedMemory.errorString();
    }

    // 释放共享内存
    sharedMemory.detach();

    return a.exec();
}
```

        上述示例中,进程1首先创建了一个 `QSharedMemory` 对象,并分配了共享内存,然后通过锁定共享内存并写入数据。然后,进程2打开相同键的 `QSharedMemory` 对象,并通过锁定共享内存并读取数据,获得了进程1写入的数据。

        **成员函数:**

1. `QSharedMemory(const QString &key, QObject *parent = nullptr)`
   构造函数,创建一个 `QSharedMemory` 对象。`key` 参数指定用于标识共享内存的唯一键,`parent` 参数指定父对象。

2. `bool create(int size, QSharedMemory::AccessMode mode = QSharedMemory::ReadWrite)`
   创建共享内存区域。`size` 参数指定共享内存的大小(字节),`mode` 参数指定共享内存的访问模式,默认为 `QSharedMemory::ReadWrite`,即可读写。

3. `bool attach(QSharedMemory::AccessMode mode = QSharedMemory::ReadWrite)`
   打开共享内存区域。`mode` 参数指定共享内存的访问模式,默认为 `QSharedMemory::ReadWrite`,即可读写。

4. `void detach()`
   分离共享内存区域。在使用完共享内存后,必须调用此函数来释放共享内存。

5. `bool isAttached() const`
   返回共享内存是否已附加(打开)。

6. `bool lock()`
   锁定共享内存区域,以便访问共享数据。

7. `void unlock()`
   解锁共享内存区域。

8. `QString key() const`
   返回共享内存的唯一键。

9. `void clear()`
   清空共享内存中的数据,将共享内存区域中的所有数据清零。

        **成员变量:**

1. `QString key`
   共享内存的唯一键。

2. `QSharedMemory::SharedMemoryError error`
   共享内存的错误状态。

3. `int size`
   共享内存区域的大小(字节)。

4. `void *data`
   指向共享内存区域的指针。使用 `data` 可以直接读写共享内存中的数据。

7.3 QT网络编程

        QT网络编程是指使用Qt框架提供的网络模块开发网络应用程序的过程

        Qt的网络模块提供了各种类和函数,用于处理网络通信、数据传输和网络协议的实现。它支持多种网络协议,包括TCP、UDP、HTTP、FTP等,同时也提供了SSL/TLS支持,使得网络通信更加安全。

在QT网络编程中,通常会涉及以下主要的类和概念:

1. `QTcpSocket` 和 `QUdpSocket`:用于创建TCP和UDP套接字,实现基于TCP和UDP的网络通信。

2. `QTcpServer`:用于创建TCP服务器,接受来自客户端的连接请求,并处理客户端的请求。

3. `QNetworkAccessManager`:用于发送HTTP请求,实现与Web服务的交互。

4. `QNetworkReply`:用于处理HTTP请求的响应,获取服务器返回的数据。

5. `QHostAddress`:用于表示IP地址和端口号。

通过使用这些类和概念,开发者可以轻松地创建网络应用程序,实现数据传输、网络通信和与服务器的交互。QT网络编程具有良好的跨平台性能,可以在不同操作系统上运行,并且提供了丰富的网络功能和灵活的网络通信机制。

7.4 QTcpSocket、QUdpSocket

49.Qt-网络编程之QTCPSocket和QTCPServer(实现简易网络调试助手) - 诺谦 - 博客园 (cnblogs.com)

7.6 Qt数据库编程 QSqlDatabase

QT 的数据库操作(QSqlDatabase、QSqlQuery)_qsqldatabase qoci_COSummer的博客-CSDN博客

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值