实现helloworld
图形化方式
在界面上创建出一个控件,显示hello world
双击widge.ui文件,打开图形化界面
左侧底下有一个Label控件
将这个控件拖到主界面中
插入文本hello world
Qt Designer右上角,通过树形结构,显示出了当前界面都有哪些控件
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Widget</class>
<widget class="QWidget" name="Widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>Widget</string>
</property>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>280</x>
<y>210</y>
<width>201</width>
<height>121</height>
</rect>
</property>
<property name="text">
<string>hello world</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>
ui文件自动多了helloworld的一个label标签
刚才往界面上拖拽了一个QLabel控件
此时,ui文件的xml中就会多出来这一段代码
进一步,qmake就会在编译项目的时候,基于这个内容生成一段C++代码,通过这个C++代码构建出界面的内容
点击编译,多出hello world的内容
ui_widget.h,就是qmake根据xml文件生成的头文件,完成界面的初始化和创建label
void setupUi(QWidget *Widget)
{
if (Widget->objectName().isEmpty())
Widget->setObjectName(QString::fromUtf8("Widget"));
Widget->resize(800, 600);
label = new QLabel(Widget);
label->setObjectName(QString::fromUtf8("label"));
label->setGeometry(QRect(280, 210, 201, 121));
retranslateUi(Widget);
QMetaObject::connectSlotsByName(Widget);
} // setupUi
纯代码方式
通过编写代码,在界面上创建控件,显示hello world
一般通过代码构造函数的时候,通常会把构造界面的代码放到widget的构造函数中
Qt中每个类都有一个对应的头文件
最早时候。Qt用的是qlabel.h
C++98标准,统一使用cstdio把stdio.h替代
QLabel* label = new QLabel(this);
//QLabel label;
创建对象的时候,可以在堆上创建,也可以在栈上创建,Qt中更推荐堆中创建
this,给当前这个label对象,指定一个“父对象”this,也就是这个构造函数所对应的对象,也就是main函数里的创建的widget对象w
Widget w;
设置控件中,要显示的文本是QString
由于Qt诞生久远,当时C++还没有标准,所以Qt自己搞了一套基础类。虽然与标准库共存。
Qt原生的api中,涉及到的接口,用的是Qt自己的容器。所以相比std::string,还是用Qstring
label->setText("hello world");
在QString中提供了c风格字符串作为参数的构造函数,不显示构造QString,c风格字符串会隐式构造成QString对象
QString对应的头文件,已经被很多Qt内置的其他类给间接包含了,一般不需要显式包含QString头文件
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QLabel>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QLabel* label = new QLabel(this);
label->setText("hello world");
}
Widget::~Widget()
{
delete ui;
}
QLabel默认是左上角
QLabel* label = new QLabel(this);
对象树
new了对象之后,为什么没有delete,会不会出现内存泄漏
上述代码在Qt中不会出现内存泄漏。label对象会在合适的时候被析构函数,虽然没有手写delete,确实能释放。
之所以能把对象释放掉,主要是因为把这个对象挂到了对象树上。
前端开发也涉及到类似的对象书(DOM),本质上也是一个树形结构(N叉树),通过树形结构把界面上的各种元素组织起来
Qt中也是类似,也是搞了一个对象树,也是N叉树,把界面上的各种元素组织起来了。
通过这个树形结构,把界面上要显示的这些控件对象就组织起来了
使用对象树,把这些内容组织起来,最主要的目的,就是为了能够在合适的时机把这些对象统一进行释放(窗口关闭/销毁的时候)
这里的树上的这些对象,统一销毁是再好不过的。
如果某个对象提前销毁,此时就会导致对应的控件就在界面上不存在了
通过new的方式创建对象,也就是为了把这个对象的生命周期,交给Qt的对象树来统一管理
如果这个对象是按照栈上的变量创建的,就可能会存在提前释放的问题
QLabel label(this);
label.setText("hello world");
当把对象改成在栈上创建,此时就可以看到,运行起来的程序无法显示出hello world
此时label对象随着构造函数的结束,就销毁了
自定义一个Label类来实验析构函数
新建一个类
类名是MyLabel,继承QLabel
左侧多出文件
Qt Creator生成了一些代码,但是没完全生成,头文件没有自动包含,需要自己手动包含
在Qt Creator中,可以通过F4切换头文件和对应的.cpp文件(C++IDE常用功能)
调用父类构造函数,将自己类的对象加入到对象树中
创建自定义的类,最主要的目的,是自定义一个析构函数,在析构函数中,完成打印,方便看到最终的自动销毁对象的效果
写完一个函数的声明以后,按下alt+enter,自动跳转到对应的cpp文件中添加函数的定义
widge.cpp
#include "widget.h"
#include "ui_widget.h"
#include "mylabel.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
MyLabel* label = new MyLabel(this);
label->setText("Hello World");
}
Widget::~Widget()
{
delete ui;
}
使用自己定义的MyLabel代替原来的QLabel,所谓的继承本质上是扩展,保持原有功能不变的基础上,给对象扩展出一个析构函数,通过析构函数,打印一个自定义的内容,方便观察程序运行效果
输出hello world
点击关闭窗口,销毁以后,输出了自定义的内容
说明析构函数是执行了
虽然没有手动delete,但是由于把MyLabel挂到了对象树上,此时窗口被销毁的时候,就会自动销毁对象中的所有对象,MyLabel的析构是执行到了
编码乱码
发现输出的内容有乱码,表示编码方式不匹配
计算机中存储的是二进制数字
英文字母用ASCII码表表示,规定了每个字符都有一个对应的数字表示
只是表示英文,一个字节就够了
中文中日常的常用字有4k个,总数有60k
仍然使用一个大表格,给每个汉字,分配一个数字
字符集,表示汉字的字符集,有很多种,不同的字符集,表示同一个汉字,使用的数字不相同
目前表示汉字字符集,主要有2种
- GBK,使用两个字符表示一个汉字,windows默认是GBK
- UTF-8 / utf8,变长编码,表示一个符号,使用的字节数有变化,在utf8中,一个汉字,一般是3个字节,Linux默认是utf8
如果字符串本身是utf8编码的,但是终端(控制台)是按照gbk的方式来解析显示的,此时就会出现乱码
拿着utf8这里的数值,去查询gbk的码表,此时就会出现乱码
这里的字符串使用的编码方式,和当前mylabel.cpp文件的编码方式是一致的
选中文件,在explorer中显示
使用记事本打开
点击另存为
下方会显示编码方式,这个文件就是utf8编码
如果是ANSI,说明就是GBK编码
当前表示中文的主流方式,是utf8,支持各种语言的文字
Qt中的QString,可以帮助自动处理编码方式的问题
Qt中还提供了专门用来打印日志的工具,也能自动处理编码方式
qDebug(),借助这个,就可以完成打印日志的过程,很好地处理字符编码
#include "mylabel.h"
#include <iostream>
#include <QDebug>
MyLabel::MyLabel(QWidget* parent) : QLabel(parent)
{
}
MyLabel::~MyLabel()
{
//std::cout << "MyLabel 被销毁!" << std::endl;
qDebug() << "MyLabel 被销毁!";
}
qDebug()这个宏,封装了QDebug对象,直接使用qDebug(),可以当作cout使用
再次测试,没有出现乱码
后续如果需要通过打印日志的方式,输出调试信息,都优先使用qDebug,cout对编码的处理不太好,window上容易出现乱码,Linux一般没事,Linux默认编码是utf8
使用qDebug,打印的调试日志,是可以统一进行关闭的,这样程序发布的时候,用户就看不到日志,可以通过编译开关,统一关闭
使用编辑框实现
完成hello world可以使用多种控件来实现
使用编辑框来实现hello world
单行编辑框QLineEdit
多行编辑框QTextEdit
Line Edit
可以在右下角进行编辑
运行
使用纯代码实现上述功能
#include "widget.h"
#include "ui_widget.h"
#include <QLineEdit>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QLineEdit* edit = new QLineEdit(this);
edit->setText("hello world");
}
Widget::~Widget()
{
delete ui;
}
通过按钮的方式实现
Push Button 普通按钮
运行
Qt中的信号槽机制
本质就是给按钮的点击操作,关联上一个处理函数
当用户点击的时候,就会执行这个处理函数
connect();
Linux网络编程中,学过一个函数叫connect
这个函数用来给TCPsocket建立连接的,写TCP客户端的时候,就需要先建立连接,然后才能读写数据
Qt中的connect是QObject这个类提供的静态函数,这个函数的作用就是连接信号和槽
ui->pushButton:谁发出的信号
访问到form file(ui文件)中创建的控件
在Qt Desinger 中创建一个控件的时候,此时就会给这个控件分配一个objectName属性,这个属性的值,要求是在界面中得是唯一个,不能和别人重复
qmake在预处理.ui文件的时候,就会根据这里的objectName生成对应的 C++代码.
C++代码中该QPushButton对象的变量名字就是这里的objectName.
这个变量就是ui属性中的成员变量
&QPushButton::clicked:发出了啥信号
点击按钮的时候就会自动触发这个信号
this:谁来处理这个信号
&Widget::handleClick:具体怎么处理
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->pushButton, &QPushButton::clicked, this, &Widget::handleClick);
}
Widget::~Widget()
{
delete ui;
}
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void handleClick();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->pushButton, &QPushButton::clicked, this, &Widget::handleClick);
}
Widget::~Widget()
{
delete ui;
}
void Widget::handleClick()
{
//当按钮被点击之后,就把按钮中的文本进行切换
ui->pushButton->setText("hello qt");
}
点击之后切换为hello qt
void Widget::handleClick()
{
//当按钮被点击之后,就把按钮中的文本进行切换
if (ui->pushButton->text() == QString("Hello world")){
ui->pushButton->setText("hello qt");
}
else{
ui->pushButton->setText("Hello world");
}
}
通过添加条件判断实现文本来回切换
ui指的就是widget.h里的ui成员
pushButton就是在build文件夹里的ui_widget.h里的成员
可以修改objectName为myButton,再修改代码里的pushButton为myButton,再编译就会顺利执行
可以运行
此时ui_widget.h里的pushButton变为了myButton
在objectName中,设置成什么值,生成的变量名就叫啥名字,就可以根据这个名字来获取对应的控件的变量
代码实现通过按钮输出hello world
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QPushButton* myButton = new QPushButton(this);
myButton->setText("hello world");
}
Widget::~Widget()
{
delete ui;
}
目前muButton是Widget的局部变量,handleClick无法访问
改为在widget.h中,加上头文件QPushButton,加上成员变量myButton
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QPushButton>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void handleClick();
private:
Ui::Widget *ui;
QPushButton* myButton;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
myButton = new QPushButton(this);
myButton->setText("hello world");
connect(myButton, &QPushButton::clicked, this, &Widget::handleClick);
}
Widget::~Widget()
{
delete ui;
}
void Widget::handleClick()
{
if (myButton->text() == QString("hello world")){
myButton->setText("hello qt");
}
else{
myButton->setText("hello world");
}
}
图形化创建代码创建
对于纯代码版本,按钮对象是自己new的
为了保证其他函数中能够访问到这个变量,就需要把按钮对象设定为Widget类的成员变量
而通过控件自动生成的按钮对象,不需要咱们自己new。new对象的操作已经是被Qt自动生成了,而且这个按钮对象,已经作为ui对象里的一个成员变量了. 也无需作为Widget的成员
如果当前程序界面,界面内容是比较固定的,此时就会以图形化的方式来构造界面,但是如果程序界面,经常要动态变化 此时就会以代码的方式来构造界面
这两种方式,哪种方便就用哪种,而且这两种方式也可以配合使用
坐标系
坐标体系:以左上⻆为原点(0,0),X向右增加,Y向下增加
对于嵌套窗⼝,其坐标是相对于⽗窗⼝来说的
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QPushButton* button = new QPushButton(this);
button->setText("按钮");
}
Widget::~Widget()
{
delete ui;
}
默认情况下,按钮在0,0左上角
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QPushButton* button = new QPushButton(this);
button->setText("按钮");
button->move(200, 300);
this->move(100, 0);
}
Widget::~Widget()
{
delete ui;
}