淘先锋技术网

首页 1 2 3 4 5 6 7

传智QT-day01

继承关系

QWidget是QMainWindow和QDialog的父类。

QWidget就是一个空窗口,什么都没有。

QMainWindow 比父类多了菜单栏和状态栏,还有工具。

QDialog 是对话框。

第一个程序

选择的是QWidget模式。

创建完成之后有四个文件分别是

  • mian.cpp
  • myWidget.h
  • myWidget.cpp
  • xxx.pro

主体文件分析

main.cpp

#include "mywidget.h"

#include <QApplication> //包含一个应用程序的头文件

//main程序入口 参数argc命令行变量的数据,argv命令行变量的数组
//在程序中有各种各样的操作,如鼠标点了 键盘键入了,都是由main函数的
//这两个参数来接收的,然后在下一行的a方法中传入这两个参数,
int main(int argc, char *argv[])    //
{
    //a应用程序对象,在Qt中,应用程序对象,有且仅有一个。
    QApplication a(argc, argv);

    //通过我创建的类,创建一个对象。通过myWidget类来实例化一个对象
    //窗口对象 myWidget父类 -> QWidget 空窗口
    myWidget w;
    //窗口对象,默认不会显示,必须要调用show方法显示窗口。
    w.show();
    //让应用程序对象进入消息混合机制。为了让程序不会一闪而过。进入消息循环
    //让代码阻塞到这一行。
    return a.exec();
}

xxx.pro文件

QT       += core gui	//Qt包含的模块

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets	//大于4版本以上 包含Qt的Widget模块

TARGET = 01_FirstProject	//生成应用程序.exe 的名称
TEMPLATE = app	//模板 应用程序模板 Application
CONFIG += c++11


DEFINES += QT_DEPRECATED_WARNINGS



SOURCES += \	//源文件
    main.cpp \
    mywidget.cpp

HEADERS += \	//头文件
    mywidget.h

FORMS += \
    mywidget.ui

qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

myWidget.h文件

#ifndef MYWIDGET_H
#define MYWIDGET_H  //这两行是防止头文件重复包含

#include <QWidget>  //包含一个头文件 这个头文件 QWidget 窗口类

QT_BEGIN_NAMESPACE
namespace Ui { class myWidget; }
QT_END_NAMESPACE

class myWidget : public QWidget //构建myWidget类,继承QWidget类
{
    Q_OBJECT    //Q_OBJECT,允许类中使用信号和槽的机制

public:
    myWidget(QWidget *parent = nullptr);    //构造函数
    ~myWidget();    //析构函数

private:
    Ui::myWidget *ui;   //ui组件
};
#endif // MYWIDGET_H

myWdiget.cpp文件 在此创建按钮

  • 添加按钮需要先添加依赖 #include
#include "mywidget.h"
#include "ui_mywidget.h"

#include <QPushButton>  //按钮控件的头文件

//命名规范
//类名 首字母大写,单词和单词之间首字母大写
//函数名 变量名称 首字母小写,单词和单词之间首字母大写

//快捷键
//注释 ctrl + /
//运行 ctrl + r
//编译 ctrl + b
//字体缩放 ctrl + 鼠标滚轮
//查找 ctrl + f
//整行移动 ctrl + shift + ↑ 或 ↓
//帮助文档 F1
//自动对齐 ctrl + i
//同名之间的.h 和.cpp 之间的切换

myWidget::myWidget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::myWidget)
{
#if 0
    //创建一个按钮
    QPushButton * btn = new QPushButton;
    btn->show();    //如果不指定父结构 会在主窗口(父亲结构)之外显示一个窗口里的按钮,
//    ui->setupUi(this);//show默认以顶层方式弹出窗口孔吉纳
    //让btn对象,依赖在myWidget窗口中
    btn->setParent(this);   //将myWidget的指针this放入参数中,
    //显示文本
    btn->setText("第一个按钮");
#endif
    //创建第二个按钮 按照控件大小创建窗口
    QPushButton * btn2 = new QPushButton("第二个按钮",this);
    //重置窗口大小 stl中重置分配内存的大小
    resize(600,400);    //默认会覆盖第一个按钮
    //移动btn2按钮 使用该方法到1h项目中移动按钮 addWidget 是不行的
    btn2->move(100,100);
    //按钮大小的设置
    btn2->resize(50,50);
    //设置窗口标题
    setWindowTitle("第一个窗口");
    //此时窗口可以随意缩放
    //禁止缩放 设置固定大小
    setFixedSize(600,400);
}

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


对象树

在Qt中创建对象的时候会提供一个Parent对象指针,下面来解释这个parent到底是干什么的。
	QObject是以对象树的形式组织起来的。
	当你创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。
这相当于,在创建QObject对象时,可以提供一个其父对象,我们创建的这个QObject对象会自动添加到其父对象的children()列表。
	当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类!)
这种机制在 GUI 程序设计中相当有用。例如,一个按钮有一个QShortcut(快捷键)对象作为其子对象。当我们删除按钮的时候,这个快捷键理应被删除。这是合理的。
	QWidget是能够在屏幕上显示的一切组件的父类。
	QWidget继承自QObject,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件。因此,它会显示在父组件的坐标系统中,被父组件的边界剪裁。例如,当用户关闭一个对话框的时候,应用程序将其删除,那么,我们希望属于这个对话框的按钮、图标等应该一起被删除。事实就是如此,因为这些都是对话框的子组件。
	当然,我们也可以自己删除子对象,它们会自动从其父对象列表中删除。比如,当我们删除了一个工具栏时,其所在的主窗口会自动将该工具栏从其子对象列表中删除,并且自动调整屏幕显示。
Qt 引入对象树的概念,在一定程度上解决了内存问题。
	当一个QObject对象在堆上创建的时候,Qt 会同时为其创建一个对象树。不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。
	任何对象树中的 QObject对象 delete 的时候,如果这个对象有 parent,则自动将其从 parent 的children()列表中删除;如果有孩子,则自动 delete 每一个孩子。Qt 保证没有QObject会被 delete 两次,这是由析构顺序决定的。
如果QObject在栈上创建,Qt 保持同样的行为。正常情况下,这也不会发生什么问题。来看下下面的代码片段:
{
    QWidget window;
    QPushButton quit("Quit", &window);
}
作为父组件的 window 和作为子组件的 quit 都是QObject的子类(事实上,它们都是QWidget的子类,而QWidget是QObject的子类)。这段代码是正确的,quit 的析构函数不会被调用两次,因为标准 C++要求,局部对象的析构顺序应该按照其创建顺序的相反过程。因此,这段代码在超出作用域时,会先调用 quit 的析构函数,将其从父对象 window 的子对象列表中删除,然后才会再调用 window 的析构函数。
但是,如果我们使用下面的代码:
{
    QPushButton quit("Quit");
    QWidget window;
    quit.setParent(&window);
}
情况又有所不同,析构顺序就有了问题。我们看到,在上面的代码中,作为父对象的 window 会首先被析构,因为它是最后一个创建的对象。在析构过程中,它会调用子对象列表中每一个对象的析构函数,也就是说, quit 此时就被析构了。然后,代码继续执行,在 window 析构之后,quit 也会被析构,因为 quit 也是一个局部变量,在超出作用域的时候当然也需要析构。但是,这时候已经是第二次调用 quit 的析构函数了,C++ 不允许调用两次析构函数,因此,程序崩溃了。
由此我们看到,Qt 的对象树机制虽然帮助我们在一定程度上解决了内存问题,但是也引入了一些值得注意的事情。这些细节在今后的开发过程中很可能时不时跳出来烦扰一下,所以,我们最好从开始就养成良好习惯,在 Qt 中,尽量在构造的时候就指定 parent 对象,并且大胆在堆上创建。

窗口在会自动进行析构,在析构之前会将窗口中的所有对象进行析构。

测试

点击项目右键,add new 选择C++ Class 创建一个QPushButton 选择继承与QWidget完成添加以后会自动添加对应的.h和.cpp文件

首先修改继承QWidget为QPushButton

.h文件

#ifndef MYPUSHBUTTON_H
#define MYPUSHBUTTON_H

#include <QPushButton>

class MyPushButton : public QPushButton
{
    Q_OBJECT
public:
    explicit MyPushButton(QWidget *parent = nullptr);
    //创建构建和析构函数
    MyPushButton();
    ~MyPushButton();

signals:

};

#endif // MYPUSHBUTTON_H

.cpp文件

#include "mypushbutton.h"
#include <QDebug>

MyPushButton::MyPushButton(QWidget *parent) : QPushButton(parent)
{
    qDebug() << "我的按钮类构造调用";
}

//MyPushButton::MyPushButton() 构造函数已经在上面写好了
//{

//}

MyPushButton::~MyPushButton()
{
    qDebug() << "我的按钮类析构调用";
}

然后转到主窗口类 mywidget,cpp

#include "mywidget.h"
#include "ui_mywidget.h"
#include "mypushbutton.h"
#include <QPushButton>  //按钮控件的头文件

//命名规范
//类名 首字母大写,单词和单词之间首字母大写
//函数名 变量名称 首字母小写,单词和单词之间首字母大写

//快捷键
//注释 ctrl + /
//运行 ctrl + r
//编译 ctrl + b
//字体缩放 ctrl + 鼠标滚轮
//查找 ctrl + f
//整行移动 ctrl + shift + ↑ 或 ↓
//帮助文档 F1
//自动对齐 ctrl + i
//同名之间的.h 和.cpp 之间的切换

myWidget::myWidget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::myWidget)
{
#if 0
    //创建一个按钮
    QPushButton * btn = new QPushButton;
    btn->show();    //如果不指定父结构 会在主窗口(父亲结构)之外显示一个窗口里的按钮,
//    ui->setupUi(this);//show默认以顶层方式弹出窗口孔吉纳
    //让btn对象,依赖在myWidget窗口中
    btn->setParent(this);   //将myWidget的指针this放入参数中,
    //显示文本
    btn->setText("第一个按钮");
#endif
    //创建第二个按钮 按照控件大小创建窗口
    QPushButton * btn2 = new QPushButton("第二个按钮",this);
    //重置窗口大小 stl中重置分配内存的大小
    resize(600,400);    //默认会覆盖第一个按钮
    //移动btn2按钮 使用该方法到1h项目中移动按钮 addWidget 是不行的
    btn2->move(100,100);
    //按钮大小的设置
    btn2->resize(50,50);
    //设置窗口标题
    setWindowTitle("第一个窗口");
    //此时窗口可以随意缩放
    //禁止缩放 设置固定大小
    setFixedSize(600,400);

    //创建一个字节的按钮对象
    MyPushButton *myBtn =  new MyPushButton;
    myBtn->setText("我字节的按钮");
    myBtn->move(200,0);
    myBtn->setParent(this);

}

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


信号与槽

conncet(信号的发送者,发送的具体信号,信号的接收者,信号的处理(槽))

信号槽的优点:松散耦合,信号发送端和接收端本身没有关联。

自定义信号和槽

创建一个QWidget项目 名称随意

创建好以后创建add new C++ /C++ Class

第一个C++ Class 是Teacher不属于控件和按钮 就选择QObject

同样创建一个Student类

  • 老师发送信号,发送信号在Teacher.h文件的signals中写自定义信号
  • 学生的.h文件中写槽函数,槽函数过去要写在public slots 中,现在随便写全局 public slots都行

teahcer.h

#ifndef TEACHER_H
#define TEACHER_H

#include <QObject>

class Teacher : public QObject
{
    Q_OBJECT
public:
    explicit Teacher(QObject *parent = nullptr);

signals:    //在此处写自定义信号
    //返回值是void 只需要声明不需要实现
    //可以有参数,可以重载
    void hungry();

};

#endif // TEACHER_H

teahcer.cpp

#include "teacher.h"

Teacher::Teacher(QObject *parent) : QObject(parent)
{

}

student.h

#ifndef STUDENT_H
#define STUDENT_H

#include <QObject>

class Student : public QObject
{
    Q_OBJECT
public:
    explicit Student(QObject *parent = nullptr);

signals:

public slots:
    //早期Qt版本,必须要写到public slots 高级版本可以写道public或者全局下
    //返回值 void 需要声明 也需要实现
    //可以有参数,可以发生重载
    void treat();

};

#endif // STUDENT_H

student.cpp

#include "student.h"
#include <QDebug>
Student::Student(QObject *parent) : QObject(parent)
{

}

void Student::treat()
{
    qDebug()<< "请老师吃饭";
}

信号和槽的连接

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include "teacher.h"
#include "student.h"

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

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

private:
    Ui::Widget *ui;
    //在此声明函数
    Teacher * zt;
    Student * st;
    
    void classIsOver();
};
#endif // WIDGET_H

.cpp

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

//Teacher 类 老师类
//Student 类 学生类
//下课后,来时会触发一个信号 -》饿了 ,学生相应信号,请客吃饭
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    //创建一个老师的对象 先在.h文件中声明
    this->zt = new Teacher(this);
    //创建一个学生的对象
    this->st = new Student(this);

    //老师饿了 学生请客的连接
    connect(zt,&Teacher::hungry,st,&Student::treat); //此时完成了连接 但是还未出发 是没有效果的

    //调用下课函数
    classIsOver();
}

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

void Widget::classIsOver()
{
    //下课函数,调用后 出发老师饿了的信号
    emit zt->hungry();  //完成函数编写后就可以调用了
}


重载版本

teacher.h

#ifndef TEACHER_H
#define TEACHER_H

#include <QObject>

class Teacher : public QObject
{
    Q_OBJECT
public:
    explicit Teacher(QObject *parent = nullptr);

signals:    //在此处写自定义信号
    //返回值是void 只需要声明不需要实现
    //可以有参数,可以重载
    void hungry();
    void hungry(QString foodName);
};

#endif // TEACHER_H

student.h

#ifndef STUDENT_H
#define STUDENT_H

#include <QObject>

class Student : public QObject
{
    Q_OBJECT
public:
    explicit Student(QObject *parent = nullptr);

signals:

public slots:
    //早期Qt版本,必须要写到public slots 高级版本可以写道public或者全局下
    //返回值 void 需要声明 也需要实现
    //可以有参数,可以发生重载
    void treat();
    void treat(QString foodName);

};

#endif // STUDENT_H

student.cpp

#include "student.h"
#include <QDebug>
Student::Student(QObject *parent) : QObject(parent)
{

}

void Student::treat()
{
    qDebug()<< "请老师吃饭";
}

void Student::treat(QString foodName)
{
    //QString会自动带引号 去掉引号只要转成char*就行了
    //先转成QByteArray 再转char*
    qDebug()<< "请老师吃饭" << foodName.toUtf8().data();
}

widget.cpp

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

//Teacher 类 老师类
//Student 类 学生类
//下课后,来时会触发一个信号 -》饿了 ,学生相应信号,请客吃饭
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    //创建一个老师的对象 先在.h文件中声明
    this->zt = new Teacher(this);
    //创建一个学生的对象
    this->st = new Student(this);

    //老师饿了 学生请客的连接
//    connect(zt,&Teacher::hungry,st,&Student::treat); //此时完成了连接 但是还未出发 是没有效果的

    //连接有参数的信号和槽
    //函数指针可以指向函数地址
    void(Teacher:: *teacherSignal)(QString) = &Teacher::hungry; //构造函数重载情况下要用
    void(Student:: *studentSolt)(QString) = &Student::treat;
    connect(zt,teacherSignal,st,studentSolt);//直接这样写是有问题的 无法自动判断构造器

    //调用下课函数
    classIsOver();
}

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

void Widget::classIsOver()
{
    //下课函数,调用后 出发老师饿了的信号
//    emit zt->hungry();  //完成函数编写后就可以调用了
    emit zt->hungry("宫保鸡丁"); //这样会有引号 char*类型不加引号 QString 类型会自动加引号
}

//================================================================================
    //点击一个下课的按钮,再出发下课
    QPushButton * btn = new QPushButton("下课",this);//简单模式
    //重置窗口大小
    this->resize(600,400);
    //点击按钮 出发下课
    connect(btn,&QPushButton::clicked,this,&Widget::classIsOver);

    //点击一个下课的按钮,再出发下课
    QPushButton * btn = new QPushButton("下课",this);//简单模式
    //重置窗口大小
    this->resize(600,400);
    //点击按钮 出发下课
    connect(btn,&QPushButton::clicked,this,&Widget::classIsOver);

    //无参信号和槽连接
    void(Teacher:: *teacherSignal2)(void) = &Teacher::hungry;
    void(Student:: *studentSolt2)(void) = &Student::treat;
    connect(zt,teacherSignal2,st,studentSolt2);
    //信号连接信号 这里不熟悉
    connect(btn,&QPushButton::clicked,zt,teacherSignal2);

    //断开信号
//    disconnect(zt,&teacherSignal2,st,studentSolt2);
    //拓展
    //1、信号是可以连接信号
    //2、一个信号可以连接多个槽函数、
    //3、多个信号 可以连接同一个槽函数
    //4、信号和槽函数的参数,必须类型一一对应
    //5、信号和槽的参数个数 是不是要一致?信号的参数个数 开业多于槽函数的参数个数

    //利用Qt4信号槽 连接无参版本
    connect(zt,SIGNAL(hungry()),st,SLOT(treat()));
    //Qt4版本优点 参数直观 缺点:类型不做检测
    //Qt5以上 支持Qt4的版本写法 反之不支持

LAMBDA表达式

//等号方式值传递
//上述代码中的btn 是一个值 这里使用拉姆达表达式改变其值  
[](){	//这里需要用到值传递 [=] 不然没法使用 也可以换成[btn]
	btn->setText("aaa");
}	//到此 需要调用一下 也就是加一个();才能调用
//修改拷贝
connect(myBtn,&QPushButton::clicked,this,[m] ()mutable {m = 100+10; qDebug() << m;});
connect(myBtn2,&QPushButton::clicked,this,[=]() {qDebug() << m;});
qDebug() << m;
//mutable关键字 用户修饰值传递的变量,修改的是拷贝,而不是本体
int ret = []()->int{return 1000;}();
qDebug() << "ret = "<<ret;

利用lambda表达式 实现点击按钮 关闭窗口

QPushButton * btn2 = new QPushButtonb;
btn2->setText("关闭");
btn2->move(100,0);

connect(btn2,&QPushButton::clicked,this,[](){
	this->close();
    //可以调用任何想要调用的函数
})