传智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();
//可以调用任何想要调用的函数
})