打开Qt Creator 界面选择 New Project或者选择菜单栏 【文件】-【新建文件或项目】菜单项
弹出New Project对话框,选择Qt Widgets Application,
选择【Choose】按钮,弹出如下对话框
设置项目名称和路径,按照向导进行下一步,(不能有中文和空格)
选择编译套件
向导会默认添加一个继承自CMainWindow的类,可以在此修改类的名字和基类。默认的基类有QMainWindow、QWidget以及QDialog三个,我们可以选择QWidget(类似于空窗口),这里我们可以先创建一个不带UI的界面,继续下一步
系统会默认给我们添加main.cpp、mywidget.cpp、 mywidget.h和一个.pro项目文件,点击完成,即可创建出一个Qt桌面程序。
对main原始代码的一些解释
#include "widget.h"
#include //包含一个应用程序类的头文件
//main程序入口 argc命令行变量的数量 argv命令行变量的数组
int main(int argc, char *argv[])
{
//a应用程序对象,在gt中,应用程序对象 有且仅有一个
QApplication a(argc, argv);
//窗口对象 mywidget父类 ->Qwidget
Widget w;
//窗口对象 默认不会显示,必须要调用show方法显示窗口
w.show();
//让应用程序对象进入消息循环//当代码阻塞到这行
return a.exec();
// while (true)
// {
// if(点击叉子)
// {
// break;
// }
// }
}
QT += core gui //Qt包含的模块
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets //大于4版本以上 包含 widget模块
TARGET = QTtest //目标 生成的exe程序的名称
TEMPLATE = app
DEFINES += QT_DEPRECATED_WARNINGS
before Qt 6.0.0
SOURCES += \ //源文件
main.cpp \
widget.cpp
HEADERS += \ //头文件
widget.h
帮助文档 第一种方式 F1 第二种 左侧按钮 第三种 D:\QT\5.11.1\mingw53_32\bin
// 命名规范
// 类名 首字母大写,单词和单词之间首字母大写
// 函数名 变量名称 首字母小写,单词和单词之间首字母大写
//快捷键//注释 ctrl+/
//运行 etrl + r//编译 ctrl + b
//字体缩放 ctrl + 鼠标滚轮
//查找 ctrl + f
//整行移动 ctrl + shift+,或者↓
//帮助文档 F1
//自动对齐 ctrl + i;
//同名之间的.h 和.cpp切换 F4
mywidget.h
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include
class myWidget : public QWidget //继承的语法
{
Q_OBJECT
public:
myWidget(QWidget *parent = 0); //有参构造函数
~myWidget();
};
#endif // MYWIDGET_H
mypushbutton.h
#ifndef MYPUSHBUTTON_H
#define MYPUSHBUTTON_H
#include
class MyPushButton : public QPushButton
{
Q_OBJECT
public:
explicit MyPushButton(QWidget *parent = 0);
~MyPushButton();
signals:
public slots:
};
#endif // MYPUSHBUTTON_H
main.cpp
#include "mywidget.h"
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
myWidget w; //窗口对象 myWidget的父类 -> QWidget
w.show(); //窗口对象 默认不会显示,必须要调用show方法显示窗口
return a.exec();//让应用程序对象进入消息循环//当代码阻塞到这行
// while(true)
// {
// if(点击叉子)
// {
// break;
// }
// }
}
mypushbutton.cpp
#include "widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
//创建一个按钮
QPushButton * btn = new QPushButton;
//btn->show(); //show以顶层方式弹出窗口控件
//让btn对象依赖在mywidget窗口中
btn->setParent (this);
//显示文本
btn->setText ("第一个按钮");
//创建第二个按钮 按照控件的大小创建窗口
QPushButton * btn2 = new QPushButton("第二个按钮",this):
//移动btn2按钮
btn2->move (100,100);
//重置窗口大小
resize (600, 400);
//设置固定窗口大小
setFixedSize (600, 400);
//设置窗口标题
setwindowTitle("第一个窗口");
Widget::~Widget()
{
}
mywidget.h
#include "mywidget.h"
#include //按钮控件的头文件
#include "mypushbutton.h"
#include
myWidget::myWidget(QWidget *parent)
: QWidget(parent)//初始化列表
{
//创建一个按钮
QPushButton * btn = new QPushButton;
//btn->show(); //show以顶层方式弹出窗口控件
//让btn对象 依赖在 myWidget窗口中
btn->setParent(this);
//显示文本
btn->setText("第一个按钮");
//创建第二个按钮 按照控件的大小创建窗口
QPushButton * btn2 = new QPushButton("第二个按钮",this);
//移动btn2按钮
btn2->move(100,100);
//按钮可不可以 重新制定大小 可以!
btn2->resize(50,50);
//重置窗口大小
resize(600,400);
//设置固定窗口大小
setFixedSize(600,400);
//设置窗口标题
setWindowTitle("第一个窗口");
//创建一个自己的按钮对象
MyPushButton * myBtn = new MyPushButton;
myBtn->setText("我自己的按钮");
myBtn->move(200,0);
myBtn->setParent(this); //设置到对象树中
//需求 点击我的按钮 关闭窗口
//参数1 信号的发送者 参数2发送的信号(函数的地址)参数3 信号的接受者 参数4 处理的槽函数
connect( myBtn, &MyPushButton::clicked, this, &myWidget::close );
connect( myBtn, &QPushButton::clicked, this, &QWidget::close );
}
myWidget::~myWidget()
{
}
在Qt中创建对象的时候会提供一个Parent对象指针,下面来解释这个parent到底是干什么的。
Qt 引入对象树的概念,在一定程度上解决了内存问题。
如果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 对象,并且大胆在堆上创建。
坐标体系:
以左上角为原点(0,0),X向右增加,Y向下增加。
对于嵌套窗口,其坐标是相对于父窗口来说的。
信号槽是 Qt 框架引以为豪的机制之一。所谓信号槽,实际就是观察者模式。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。
信号:各种事件
槽: 响应信号的动作
当某个事件发生后,如某个按钮被点击了一下,它就会发出一个被点击的信(signal)。
某个对象接收到这个信号之后,就会做一些相关的处理动作(称为槽slot)。
但是Qt对象不会无故收到某个信号,要想让一个对象收到另一个对象发出的信号,这时候需要建立连接(connect)
下面我们完成一个小功能,上面我们已经学习了按钮的创建,但是还没有体现出按钮的功能,按钮最大的功能也就是点击后触发一些事情,比如我们点击按钮,就把当前的窗口给关闭掉,那么在Qt中,这样的功能如何实现呢?
如下图:
connect( 信号的发送者,发送的具体信号,信号的接受者,信号的处理(槽) )
信号槽的优点,松散耦合,信号发送端和接受端本身是没有关联的,通过connect连接,将两端耦合在一起
其实两行代码就可以搞定了,我们看下面的代码
QPushButton * quitBtn = new QPushButton("关闭窗口",this);
connect(quitBtn,&QPushButton::clicked,this,&MyWidget::close);
第一行是创建一个关闭按钮,这个之前已经学过,第二行就是核心了,也就是信号槽的使用方式
connect函数是建立信号发送者、信号、信号接收者、槽四者关系的函数:
connect(sender, signal, receiver, slot);
参数解释:
1)sender:信号发送者
2)signal:信号
3)receiver:信号接收者
4)slot:接收对象在接收到信号之后所需要调用的函数(槽函数)
这里要注意的是connect的四个参数都是指针,信号和槽是函数指针。
系统自带的信号和槽如何查找呢,这个就需要利用帮助文档了,在帮助文档中比如我们上面的按钮的点击信号,在帮助文档中输入QPushButton,首先我们可以在Contents中寻找关键字 signals,信号的意思,但是我们发现并没有找到,这时候我们应该想到也许这个信号的被父类继承下来的,因此我们去他的父类QAbstractButton中就可以找到该关键字,点击signals索引到系统自带的信号有如下几个。
这里的clicked就是我们要找到,槽函数的寻找方式和信号一样,只不过他的关键字是slot。
使用connect()可以让我们连接系统提供的信号和槽。但是,Qt 的信号槽机制并不仅仅是使用系统提供的那部分,还会允许我们自己设计自己的信号和槽。
- 声明在类的signals域下
- 没有返回值,void类型的函数
- 只有函数声明,没有定义
- 可以有参数,可以重载
- 通过emit关键字来触发信号,形式:emit object->sig(参数);
定义场景:下课了,老师跟同学说肚子饿了(信号),学生请老师吃饭(槽)
首先定义一个学生类和老师类:
老师类中声明信号 饿了 hungry
signals:
void hungry();
学生类中声明槽 请客treat
public slots:
void treat();
在窗口中声明一个公共方法下课,这个方法的调用会触发老师饿了这个信号,而响应槽函数学生请客
void MyWidget::ClassIsOver()
{
//发送信号
emit teacher->hungry();
}
学生响应了槽函数,并且打印信息
//自定义槽函数 实现
void Student::treat()
{
qDebug() << "Student treat teacher";
}
在窗口中连接信号槽
teacher = new Teacher(this);
student = new Student(this);
connect(teacher,&Teacher::hungury,student,&Student::treat);
并且调用下课函数,测试打印出相应log
1.一个信号可以和多个槽相连
如果是这种情况,这些槽会一个接一个的被调用,但是槽函数调用顺序是不确定的。像上面的例子,可以将一个按钮点击信号连接到关闭窗口的槽函数,同时也连接到学生请吃饭的槽函数,点击按钮的时候可以看到关闭窗口的同时也学生请吃饭的log也打印出来。
2. 多个信号可以连接到一个槽
只要任意一个信号发出,这个槽就会被调用。如:一个窗口多个按钮都可以关闭这个窗口。
3.一个信号可以连接到另外的一个信号
当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。注意这里还是使用connect函数,只是信号的接收者和槽函数换成另一个信号的发送者和信号函数。如上面老师饿了的例子,可以新建一个按钮btn。
connect(btn,&QPushButton::clicked,teacher,&Teacher::hungry);
4.信号和槽可以断开连接
可以使用disconnect函数,当初建立连接时connect参数怎么填的,disconnect里边4个参数也就怎么填。这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。
5.信号和槽函数参数类型和个数必须同时满足两个条件
1)信号函数的参数个数必须大于等于槽函数的参数个数
2)信号函数的参数类型和槽函数的参数类型必须一一对应
自定义信号槽需要注意的事项:
- 发送者和接收者都需要是QObject的子类(当然,槽函数是全局函数、Lambda 表达式等无需接收者的时候除外);
- 信号和槽函数返回值是 void
- 信号只需要声明,不需要实现
- 槽函数需要声明也需要实现
- 槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响;
- 使用 emit 在恰当的位置发送信号;
- 使用connect()函数连接信号和槽。
- 任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数
- 信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致。
- 如果信号和槽的参数不一致,允许的情况是,槽函数的参数可以比信号的少,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少)。
connect(
teacher,
SIGNAL(hungry(QString)),
student,
SLOT(treat(QString))
);
这里使用了SIGNAL和SLOT这两个宏,宏的参数是信号函数和槽函数的函数原型。
因为直接填入了函数原型,所有这里边编译不会出现因为重载导致的函数指针二义性的问题。但问题是如果函数原型填错了,或者不符合信号槽传参个数类型约定,编译期间也不会报错,只有运行期间才会看到错误log输出。
原因就是这两个宏将后边参数(函数原型)转化成了字符串。目前编译器还没有那么智能去判断字符串里边的内容符不符合运行条件。
C++11中的Lambda表达式用于定义匿名的函数对象,以简化编程工作。首先看一下Lambda表达式的基本构成:
分为四个部分:[局部变量捕获列表]、(函数参数)、函数额外属性设置opt、函数返回值->retype、{函数主体}
[capture](parameters) opt ->retType
{
……;
}
[ ],标识一个Lambda的开始。由于lambda表达式可以定义在某一个函数体A里边,所以lambda表达式有可能会去访问A函数中的局部变量。中括号里边内容是描述了在lambda表达式里边可以使用的外部局部变量的列表:
[]
表示lambda表达式不能访问外部函数体的任何局部变量
[a]
在函数体内部使用值传递的方式访问a变量
[&b]
在函数体内部使用引用传递的方式访问b变量
[=]
函数外的所有局部变量都通过值传递的方式使用, 函数体内使用的是副本
[&]
引用的方式使用lambda表达式外部的所有变量
[=, &foo]
foo使用引用方式, 其余是值传递的方式
[&,foo]
foo使用值传递方式, 其余是引用传递的方式
[this]
在函数内部可以使用类的成员函数和成员变量,=和&形式也都会默认引入
由于引用方式捕获对象会有局部变量释放了而lambda函数还没有被调用的情况。如果执行lambda函数那么引用传递方式捕获进来的局部变量的值不可预知。
所以在无特殊情况下建议使用[=](){}的形式
(params)表示lambda函数对象接收的参数,类似于函数定义中的小括号表示函数接收的参数类型和个数。参数可以通过按值(如:(int a,int b))和按引用(如:(int &a,int &b))两种方式进行传递。函数参数部分可以省略,省略后相当于无参的函数。
Opt 部分是可选项,最常用的是mutable声明,这部分可以省略。外部函数局部变量通过值传递引进来时,其默认是const,所以不能修改这个局部变量的拷贝,加上mutable就可以
int a = 10 ;
[=]()
{
a=20;//编译报错,a引进来是const
}
[=]()mutable
{
a=20;//编译成功
};
{},标识函数的实现,这部分不能省略,但函数体可以为空。
以QPushButton点击事件为例:
connect(btn,&QPushButton::clicked,[=](){
qDebug()<<"Clicked";
});
这里可以看出使用Lambda表达式作为槽的时候不需要填入信号的接收者。当点击按钮的时候,clicked信号被触发,lambda表达式也会直接运行。当然lambda表达式还可以指定函数参数,这样也就能够接收到信号函数传递过来的参数了。
由于lambda表达式比我们自己自定义槽函数要方便而且灵活得多,所以在实现槽函数的时候优先考虑使用Lambda表达式。一般我们的使用习惯也是lambda表达式外部函数的局部变量全部通过值传递捕获进来,也就是:
[=](){ }的形式
student.h
teacher.h
widget.h
main.cpp
student.cpp
teacher.cpp
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include
#include
//Teacher 类 老师类
//Student 类 学生类
//下课后 ,老师会触发一个信号,饿了 ,学生响应信号,请客吃饭
void func()
{
qDebug() <<"aaa";
}
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//创建一个老师对象
this->zt = new Teacher(this);
//创建一个学生对象
this->st = new Student(this);
// //老师饿了 学生请客的连接
// connect(zt,&Teacher::hungry,st,&Student::treat);
// //调用下课函数
// classIsOver();
//连接带参数的 信号和槽
//指针 -> 地址
// 函数指针 -> 函数地址
void(Teacher:: *teacherSignal)(QString ) = &Teacher::hungry;
void(Student:: *studentSlot)(QString ) = &Student::treat;
connect(zt,teacherSignal,st,studentSlot);
// 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:: *studentSlot2)(void) = &Student::treat;
//connect(zt,teacherSignal2,st,studentSlot2);
//信号连接信号
connect(btn,&QPushButton::clicked, zt, teacherSignal2);
//断开信号
//disconnect(zt,teacherSignal2,st,studentSlot2);
//拓展
//1、信号是可以连接信号
//2、一个信号可以连接多个槽函数
//3、多个信号 可以连接 同一个槽函数
//4、信号和槽函数的参数 必须类型一一对应
//5、信号和槽的参数个数 是不是要一致?信号的参数个数 可以多余槽函数的参数个数
//Qt4版本以前的信号和槽连接方式
//利用Qt4信号槽 连接无参版本
//Qt4版本 底层SIGNAL("hungry") SLOT( "treat")
connect(zt,SIGNAL(hungry()) , st , SLOT(treat()));
//Qt4版本优点:参数直观,缺点 :类型不做检测
//Qt5以上 支持 Qt4的版本写法,反之不支持
// QPushButton * btn2 = new QPushButton;
// [btn](){
// btn->setText("aaaa");
// btn2->setText("bbb"); //btn2看不到
// }();
// mutable关键字 用于修饰值传递的变量,修改的是拷贝,而不是本体
// QPushButton * myBtn = new QPushButton (this);
// QPushButton * myBtn2 = new QPushButton (this);
// myBtn2->move(100,100);
// int m = 10;
// connect(myBtn,&QPushButton::clicked,this,[m] ()mutable { m = 100 + 10; qDebug() << m; });
// connect(myBtn2,&QPushButton::clicked,this,[=] () { qDebug() << m; });
// qDebug() << m;
// int ret = []()->int{return 1000;}();
// qDebug() << "ret = " << ret ;
//利用lambda表达式 实现点击按钮 关闭窗口
QPushButton * btn2 = new QPushButton ;
btn2->setText("关闭");
btn2->move(100,0);
btn2->setParent(this);
connect(btn2,&QPushButton::clicked, [=](){
// this->close();
// emit zt->hungry("宫保鸡丁");
btn2->setText("aaaa");
});
//lambda表达式 最常用 [=](){}
}
void Widget::classIsOver()
{
//下课函数,调用后 触发老师饿了的信号
//emit zt->hungry();
emit zt->hungry("宫保鸡丁");
}
Widget::~Widget()
{
delete ui;
}
QMainWindow是一个为用户提供主窗口程序的类,包含一个菜单栏(menu bar)、多个工具栏(tool bars)、多个锚接部件(dock widgets)、一个状态栏(status bar)及一个中心部件(central widget),是许多应用程序的基础,如文本编辑器,图片编辑器等。
一个主窗口最多只有一个菜单栏。位于主窗口顶部、主窗口标题栏下面。
1.通过QMainWindow类的menubar()函数获取主窗口菜单栏指针,如果当前窗口没有菜单栏,该函数会自动创建一个。
QMenuBar * menuBar() const;
2.创建菜单,调用QMenu的成员函数addMenu来添加菜单
QAction* addMenu(QMenu * menu); QMenu* addMenu(const QString & title); QMenu* addMenu(const QIcon & icon, const QString & title);
3.创建菜单项,调用QMenu的成员函数addAction来添加菜单项
QAction* activeAction() const; QAction* addAction(const QString & text); QAction* addAction(const QIcon & icon, const QString & text); QAction* addAction(const QString & text, const QObject * receiver, const char * member, const QKeySequence & shortcut = 0); QAction* addAction(const QIcon & icon, const QString & text, const QObject * receiver, const char * member, const QKeySequence & shortcut = 0);
Qt 并没有专门的菜单项类,只是使用一个QAction类,抽象出公共的动作。当我们把QAction对象添加到菜单,就显示成一个菜单项,添加到工具栏,就显示成一个工具按钮。用户可以通过点击菜单项、点击工具栏按钮、点击快捷键来激活这个动作。
主窗口的工具栏上可以有多个工具条,通常采用一个菜单对应一个工具条的的方式,也可根据需要进行工具条的划分。
1.调用QMainWindowd对象的成员函数addToolBar(),该函数每次调用都会创建一个新的工具栏,并且返回该工具栏的指针。
2.插入属于工具条的项,这时工具条上添加项也是用QAction。通过QToolBar类的addAction函数添加。
1)Qt::LeftToolBarArea 停靠在左侧
2)Qt::RightToolBarArea 停靠在右侧
3)Qt::TopToolBarArea 停靠在顶部
4)Qt::BottomToolBarArea 停靠在底部
5)Qt::AllToolBarAreas 以上四个位置都可停靠
使用setAllowedAreas()函数指定停靠区域:
setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea)
使用setFloatable(trueOrFalse)函数来设定工具栏可否浮动
使用setMoveable(trueOrFalse)函数设定工具栏的可移动性:
setMoveable(false)//工具条不可移动, 只能停靠在初始化的位置上
一个QMainWindow的程序最多只有一个状态栏。QMainWindow中可以有多个的部件都使用add…名字的函数,而只有一个的部件,就直接使用获取部件的函数,如menuBar。同理状态栏也提供了一个获取状态栏的函数statusBar(),没有就自动创建一个并返回状态栏的指针。
QMenuBar * menuBar() const;
1.添加小部件(从状态栏左侧添加)
void addWidget(QWidget * widget, int stretch = 0);
//插入小部件
int insertWidget(int index, QWidget * widget, int stretch = 0);
//删除小部件
void removeWidget(QWidget * widget);
2.添加小部件(从状态栏右侧添加)
void addPermenentWidget (QWidget *widget, int stretch = 0);
停靠部件 QDockWidget,也称浮动窗口,可以有多个。
QDockWidget * dock = new QDockWidget("标题",this);
//添加停靠部件到mainWindow中,并且设定默认停靠在左边
addDockWidget(Qt::LeftDockWidgetArea,dock);
//设定停靠部件允许停靠的范围
dock->setAllowedAreas(Qt::LeftDockWidgetArea |
Qt::RightDockWidgetArea | Qt::TopDockWidgetArea);
除了以上几个部件,中心显示的部件都可以作为核心部件,例如一个记事本程序中,就是一个QTextEdit(编辑框控件)做核心部件
QTextEdit * edit = new QTextEdit(this);
//设置mainWindow的核心部件
setCentralWidget(edit);
#include "mainwindow.h"
#include
#include
#include
#include
#include
#include
#include
#include
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
resize(600,400); //重置窗口大小
//菜单栏 只能最多有一个
QMenuBar * bar = menuBar(); //菜单栏创建
setMenuBar(bar); //将菜单栏放入到窗口中
//创建菜单
QMenu * fileMenu = bar->addMenu("文件");
QMenu * editMenu = bar->addMenu("编辑");
//创建菜单项
QAction * newAction = fileMenu->addAction("新建");
fileMenu->addSeparator(); //添加分割线
QAction * openAction = fileMenu->addAction("打开");
//工具栏 可以有多个
QToolBar * toolBar = new QToolBar(this);
addToolBar(Qt::LeftToolBarArea,toolBar);
toolBar->setAllowedAreas( Qt::LeftToolBarArea | Qt::RightToolBarArea );//后期设置 只允许 左右停靠
//设置浮动
toolBar->setFloatable(false);
//设置移动 (总开关)
toolBar->setMovable(false);
//工具栏中可以设置内容
toolBar->addAction(newAction);
//添加分割线
toolBar->addSeparator();
toolBar->addAction(openAction);
//工具栏中添加控件
QPushButton * btn = new QPushButton("aa" , this);
toolBar->addWidget(btn);
//状态栏 最多有一个
QStatusBar * stBar = statusBar();
setStatusBar(stBar);//设置到窗口中
//放标签控件
QLabel * label = new QLabel("提示信息",this);
stBar->addWidget(label);//显示出来
QLabel * label2 = new QLabel("右侧提示信息",this);
stBar->addPermanentWidget(label2);
//铆接部件 (浮动窗口) 可以有多个
QDockWidget * dockWidget = new QDockWidget("浮动",this);
addDockWidget(Qt::BottomDockWidgetArea,dockWidget);
//设置后期停靠区域,只允许上下
dockWidget->setAllowedAreas( Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea );
//设置中心部件 只能一个
QTextEdit * edit = new QTextEdit(this);
setCentralWidget(edit);
}
MainWindow::~MainWindow()
{
}
Qt 资源系统是一个跨平台的资源机制,用于将程序运行时所需要的资源以二进制的形式存储于可执行文件内部。如果你的程序需要加载特定的资源(图标、文本翻译等),那么,将其放置在资源文件中,就再也不需要担心这些文件的丢失。也就是说,如果你将资源以资源文件形式存储,它是会编译到可执行文件内部。
使用 Qt Creator 可以很方便地创建资源文件。我们可以在工程上点右键,选择“添加新文件…”,可以在 Qt 分类下找到“Qt 资源文件”:
中间老师使用本地路径的我省略,,下面采样将资源文件添加到项目
接下来
名字起一个一般我用res
使用方法:
//使用添加Qt资源 ": + 前缀名 + 文件名 " ui->actionnew->setIcon(QIcon(":/image/sw.jpeg")); ui->actionopen->setIcon(QIcon(":/image/kk.jpg"));
6.1 基本概念
对话框是 GUI 程序中不可或缺的组成部分。很多不能或者不适合放入主窗口的功能组件都必须放在对话框中设置。对话框通常会是一个顶层窗口,出现在程序最上层,用于实现短期任务或者简洁的用户交互。
Qt 中使用QDialog类实现对话框。就像主窗口一样,我们通常会设计一个类继承QDialog。QDialog(及其子类,以及所有Qt::Dialog类型的类)的对于其 parent 指针都有额外的解释:如果 parent 为 NULL,则该对话框会作为一个顶层窗口,否则则作为其父组件的子对话框(此时,其默认出现的位置是 parent 的中心)。顶层窗口与非顶层窗口的区别在于,顶层窗口在任务栏会有自己的位置,而非顶层窗口则会共享其父组件的位置。
对话框分为模态对话框和非模态对话框。
1.模态对话框,当对话框打开时,不能操作同一个应用程序的其他窗口,只有当对话框关闭的时候才可以操作。
模态对话框很常见,比如“打开文件”功能。你可以尝试一下记事本的打开文件,当打开文件对话框出现时,我们是不能对除此对话框之外的窗口部分进行操作的。
2.此相反的是非模态对话框,例如查找对话框,我们可以在显示着查找对话框的同时,继续对记事本的内容进行编辑。
Qt 有两种级别的模态对话框:
1.应用程序级别的模态
当该种模态的对话框出现时,用户必须首先对对话框进行交互,直到关闭对话框,然后才能访问程序中其他的窗口。
使用QDialog::exec()实现应用程序级别的模态对话框
2.窗口级别的模态
该模态仅仅阻塞与对话框关联的窗口,但是依然允许用户与程序中其它窗口交互。窗口级别的模态尤其适用于多窗口模式。
使用QDialog::open()实现窗口级别的模态对话框
一般情况下我们只使用应用程序级别的模态对话框。
在下面的示例中,我们调用了exec()将对话框显示出来,因此这就是一个模态对话框。当对话框出现时,我们不能与主窗口进行任何交互,直到我们关闭了该对话框。
QDialog dialog;
dialog.setWindowTitle(tr("Hello, dialog!"));
dialog.exec();
下面我们试着将exec()修改为show(),看看非模态对话框:
QDialog dialog(this);
dialog.setWindowTitle(tr("Hello, dialog!"));
dialog.show();
是不是事与愿违?对话框竟然一闪而过!这是因为,show()函数不会阻塞当前线程,对话框会显示出来,然后函数立即返回,代码继续执行。注意,dialog 是建立在栈上的,show()函数返回,MainWindow::open()函数结束,dialog 超出作用域被析构,因此对话框消失了。知道了原因就好改了,我们将 dialog 改成堆上建立,当然就没有这个问题了:
QDialog *dialog = new QDialog(this);
dialog->setWindowTitle(tr("Hello, dialog!"));
dialog->show();
如果你足够细心,应该发现上面的代码是有问题的:dialog 存在内存泄露!(七天七夜一直直点新建再点叉)dialog 使用 new 在堆上分配空间,却一直没有 delete。解决方案也很简单:将 MainWindow 的指针赋给 dialog 即可。还记得我们前面说过的 Qt 的对象树吗?
不过,这样做有一个问题:如果我们的对话框不是在一个界面类中出现呢?由于QWidget的 parent 必须是QWidget指针,那就限制了我们不能将一个普通的 C++ 类指针传给 Qt 对话框。另外,如果对内存占用有严格限制的话,当我们将主窗口作为 parent 时,主窗口不关闭,对话框就不会被销毁,所以会一直占用内存。在这种情景下,我们可以设置 dialog 的WindowAttribute:
dialog->setAttribute(Qt::WA_DeleteOnClose);//55号属性
QDialog *dialog = new QDialog;
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setWindowTitle(tr("Hello, dialog!"));
dialog->show();
setAttribute()函数设置对话框关闭时,自动销毁对话框。
所谓标准对话框,是 Qt 内置的一系列对话框,用于简化开发。事实上,有很多对话框都是通用的,比如打开文件、设置颜色、打印设置等。这些对话框在所有程序中几乎相同,因此没有必要在每一个程序中都自己实现这么一个对话框。
Qt 的内置对话框大致分为以下几类:
1.QMessageBox: 模态对话框,用于显示信息、询问问题等;
2.QColorDialog: 选择颜色;
3.QFontDialog: 选择字体;
4.QFileDialog: 选择文件或者目录;
5.QInputDialog: 允许用户输入一个值,并将其值返回;
6.QPageSetupDialog: 为打印机提供纸张相关的选项;
7.QPrintDialog: 打印机配置;
8.QPrintPreviewDialog:打印预览;
9.QProgressDialog: 显示操作过程。
QMessageBox用于显示消息提示。我们一般会使用其提供的几个 static 函数:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include
#include
#include
#include
#include
#include
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//点击新建按钮 弹出一个对话框
connect(ui->actionnew,&QAction::triggered,[=](){
//对话框 分类
//模态对话框 (不可以对其他窗口进行操作) 非模态对话框 (可以对其他窗口进行操作)
//模态创建 阻塞
// QDialog dlg(this);
// dlg.resize(200,100);
// dlg.exec();//阻塞
// qDebug() << "模态对话框弹出了";
//非模态对话框
// QDialog * dlg2 = new QDialog (this);//创建在堆区,防止一闪而过
// dlg2->resize(200,100);
// dlg2->show();
// dlg2->setAttribute(Qt::WA_DeleteOnClose); //55号 属性,点击弹出来的对话框会释放内存
// qDebug() << "非模态对话框弹出了";
//消息对话框
QMessageBox::critical(this,"critical","错误");
//提问对话框
// if (QMessageBox::Save == QMessageBox::question(this,"ques","提问",QMessageBox::Save|QMessageBox::Cancel,QMessageBox::Cancel))
// {
// qDebug() << "选择的是保存";
// }
// else
// {
// qDebug() << "选择的是取消";
// }
//警告对话框
// QMessageBox::warning(this,"warning","警告");
//其他标准对话框
//颜色对话框
// QColor color = QColorDialog::getColor(QColor(255,0,0));
// qDebug() << "r = " << color.red() << " g = " << color.green() << " b = " << color.blue() ;
//文件对话框 参数 1 父亲 参数2 标题 参数3 默认打开路径 参数4 过滤文件格式
//返回值是 选取的路径
// QString str = QFileDialog::getOpenFileName(this,"打开文件","C:\\Users\\ldbh\\Desktop","(*.txt)");
// qDebug() << str;
//字体对话框
// bool flag;
// QFont font = QFontDialog::getFont(&flag,QFont("华文彩云",36));
// qDebug() << "字体:" << font.family().toUtf8().data() << " 字号 "<< font.pointSize() << " 是否加粗"<< font.bold() << " 是否倾斜"<
等等代码中有,图片不展示了
所谓 GUI 界面,归根结底,就是一堆组件的叠加。我们创建一个窗口,把按钮放上面,把图标放上面,这样就成了一个界面。在放置时,组件的位置尤其重要。我们必须要指定组件放在哪里,以便窗口能够按照我们需要的方式进行渲染。这就涉及到组件布局定位的机制。
Qt 提供了两种组件布局定位机制:绝对定位和布局定位。
1.绝对定位就是一种最原始的定位方法:给出这个组件的坐标和长宽值
这样,Qt 就知道该把组件放在哪里以及如何设置组件的大小。但是这样做带来的一个问题是,如果用户改变了窗口大小,比如点击最大化按钮或者使用鼠标拖动窗口边缘,采用静态布局的组件是不会有任何响应的。这也很自然,因为你并没有告诉 Qt,在窗口变化时,组件是否要更新自己以及如何更新。或者,还有更简单的方法:禁止用户改变窗口大小。但这总不是长远之计。
2.布局定位:
你只要把组件放入某一种布局(layout),布局由专门的布局管理器进行管理。当需要调整大小或者位置的时候,Qt 使用对应的布局管理器进行调整。
布局定位完美的解决了使用绝对定位的缺陷。
Qt 提供的布局中以下三种是我们最常用的:
- QHBoxLayout:按照水平方向从左到右布局;
- QVBoxLayout:按照竖直方向从上到下布局;
- QGridLayout:在一个网格中进行布局,类似于 HTML 的 table;
这4个为系统给我们提供的布局的控件,但是使用起来不是非常的灵活,这里就不详细介绍了。
第二种布局方式是利用控件里的widget来做布局,在Containers中
在widget中的控件可以进行水平、垂直、栅格布局等操作,比较灵活。
再布局的同时我们需要灵活运用弹簧的特性让我们的布局更加的美观,下面是一个登陆窗口,利用widget可以搭建出如下登陆界面:
拖拽后的图形界面系统UI文件中的代码,gpt翻译后如下:
这是QT的 .ui文件的代码,我设计了一个界面 请将代码“翻译”成我可以看懂的C++代码
#include
#include
#include
#include
#include
#include
#include
#include
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr) : QMainWindow(parent)
{
setGeometry(0, 0, 350, 240);
setMinimumSize(350, 240);
setMaximumSize(350, 240);
setWindowTitle("QQ登录");
centralWidget = new QWidget(this);
QVBoxLayout *verticalLayout = new QVBoxLayout(centralWidget);
widget = new QWidget(centralWidget);
QGridLayout *gridLayout = new QGridLayout(widget);
// Username
QLabel *label = new QLabel("用户名:", widget);
gridLayout->addWidget(label, 0, 1);
QLineEdit *lineEdit = new QLineEdit(widget);
gridLayout->addWidget(lineEdit, 0, 2);
// Password
QLabel *label_2 = new QLabel("密码:", widget);
gridLayout->addWidget(label_2, 2, 1);
QLineEdit *lineEdit_2 = new QLineEdit(widget);
lineEdit_2->setEchoMode(QLineEdit::Password);
gridLayout->addWidget(lineEdit_2, 2, 2);
verticalLayout->addWidget(widget);
// Login and Exit buttons
QWidget *widget_3 = new QWidget(centralWidget);
QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(widget_3);
horizontalLayout_2->addItem(new QSpacerItem(40, 20, QSizePolicy::Fixed, QSizePolicy::Minimum));
QPushButton *pushButton_3 = new QPushButton("登录", widget_3);
horizontalLayout_2->addWidget(pushButton_3);
horizontalLayout_2->addItem(new QSpacerItem(30, 20, QSizePolicy::Fixed, QSizePolicy::Minimum));
QPushButton *pushButton_2 = new QPushButton("退出", widget_3);
horizontalLayout_2->addWidget(pushButton_2);
horizontalLayout_2->addItem(new QSpacerItem(40, 20, QSizePolicy::Fixed, QSizePolicy::Minimum));
verticalLayout->addWidget(widget_3);
setCentralWidget(centralWidget);
}
private:
QWidget *centralWidget;
QWidget *widget;
};
#include "mainwindow.moc" // Required for the Qt Meta-Object Compiler (MOC)
QLabel是我们最常用的控件之一,其功能很强大,我们可以用来显示文本,图片和动画等。
显示图片,使用方式添加图片,代码方式去看5.6
首先要添加资源文件去看5.6,然后开始
改变大小
显示图片一般用这个
目前运行没有默认值,一般界面用UI设计,逻辑在代码中设计
还有一些不常用的不介绍了
//利用listWidget写诗
QListWidgetItem * item = new QListWidgetItem("锄禾日当午");
//将一行诗放入到listWidget控件中
ui->listWidget->addItem(item);
item->setTextAlignment(Qt::AlignHCenter);
setTextAlignment 设置位置
可以利用 additems.一次性添加整个诗内容,但是就无法改变例如居中的位置了,如果想居中就用上面的一句一句打
//QStringList QList
QStringList list ;
list << "锄禾日当午" << "旱地和下土" << "谁知盘中餐"<< "粒粒皆辛苦";
ui->listWidget->addItems(list);
代码:
#include "widget.h"
#include "ui_widget.h"
#include
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//设置单选按钮 男默认选中
ui->rBtnMan->setChecked(true);
//选中女后 打印信息
connect(ui->rBtnWoman,&QRadioButton::clicked,[=](){
qDebug() << "选中了女了!";
});
//多选按钮 2是选中 0是未选中 1是半选
connect(ui->cBox,&QCheckBox::stateChanged,[=](int state){
qDebug() << state;
});
//利用listWidget写诗
// QListWidgetItem * item = new QListWidgetItem("锄禾日当午");
// //将一行诗放入到listWidget控件中
// ui->listWidget->addItem(item);
// item->setTextAlignment(Qt::AlignHCenter);
//QStringList QList
QStringList list ;
list << "锄禾日当午" << "旱地和下土" << "谁知盘中餐"<< "粒粒皆辛苦";
ui->listWidget->addItems(list);
}
Widget::~Widget()
{
delete ui;
}
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//treeWidget树控件使用
//设置水平头
ui->treeWidget->setHeaderLabels(QStringList()<<"英雄"<<"英雄介绍"); //语法: 匿名对象
QTreeWidgetItem *LiItem = new QTreeWidgetItem(QStringList()<<"力量");
QTreeWidgetItem *MinItem = new QTreeWidgetItem(QStringList()<<"敏捷");
QTreeWidgetItem *ZhiItem = new QTreeWidgetItem(QStringList()<<"智力");
//加载顶层的节点
ui->treeWidget->addTopLevelItem(LiItem);
ui->treeWidget->addTopLevelItem(MinItem);
ui->treeWidget->addTopLevelItem(ZhiItem);
//追加子节点
QStringList heroL1;
QStringList heroL2;
heroL1 << "刚被猪" << "前排坦克,能在吸收伤害的同时造成可观的范围输出";
heroL2 << "船长" << "前排坦克,能肉能输出能控场的全能英雄";
QTreeWidgetItem *L1 = new QTreeWidgetItem(QStringList(heroL1));
LiItem->addChild(L1);
QTreeWidgetItem *L2 = new QTreeWidgetItem(QStringList(heroL2));
LiItem->addChild(L2);
QStringList heroM1;
QStringList heroM2;
heroM1 << "月骑" << "中排物理输出,可以使用分裂利刃攻击多个目标";
heroM2 << "小鱼人" << "前排战士,擅长偷取敌人的属性来增强自身战力";
QTreeWidgetItem *M1 = new QTreeWidgetItem(QStringList(heroM1));
MinItem->addChild(M1);
QTreeWidgetItem *M2 = new QTreeWidgetItem(QStringList(heroM2));
MinItem->addChild(M2);
QStringList heroZ1;
QStringList heroZ2;
heroZ1 << "死灵法师" << "前排法师坦克,魔法抗性较高,拥有治疗技能";
heroZ2 << "巫医" << "后排辅助法师,可以使用奇特的巫术诅咒敌人与治疗队友";
QTreeWidgetItem *Z1 = new QTreeWidgetItem(QStringList(heroZ1));
ZhiItem->addChild(Z1);
QTreeWidgetItem *Z2 = new QTreeWidgetItem(QStringList(heroZ2));
ZhiItem->addChild(Z2);
}
Widget::~Widget()
{
delete ui;
}
1设置头
ui->treeWidget->setHeaderLabels(QStringList()<< "英雄"<< "英雄介绍");
2创建根节点
QTreeWidgetItem * liItem = new QTreeWidgetItem(QStringList()<< "力量");
3添加根节点 到 树控件上
ui->treeWidget->addTopLevelItem(liItem);
4添加子节点
liItem->addChild(L1);
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//TableWidget控件
//设置列数
ui->tableWidget->setColumnCount(3);
//设置水平表头
ui->tableWidget->setHorizontalHeaderLabels(QStringList()<<"姓名"<< "性别"<< "年龄");
//设置行数
ui->tableWidget->setRowCount(5);
//设置正文
//ui->tableWidget->setItem(0,0,new QTableWidgetItem("亚瑟"));
QStringList nameList;
nameList<<"亚瑟"<<"赵云"<<"张飞"<<"关羽"<<"花木兰";
QList sexList;
sexList << "男"<< "男"<< "男"<< "男"<< "女";
for(int i = 0 ; i < 5 ; i++)
{
int col = 0;
ui->tableWidget->setItem(i,col++,new QTableWidgetItem(nameList[i])); //nameList[i]和sexList.at(i)的区别是
//nameList数组越界就会挂掉但是和sexList会抛除异常
ui->tableWidget->setItem(i,col++,new QTableWidgetItem(sexList.at(i)));
//int 转 QString
ui->tableWidget->setItem(i,col++,new QTableWidgetItem(QString::number(i+18)));
}
}
Widget::~Widget()
{
delete ui;
}
1设置列数
ui->tableWidget->setColumnCount(3);
2设置水平表头
ui->tableWidget->setHorizontalHeaderLabels(QStringList()<<"姓名"<< "性别"<< "年龄");
3设置行数
ui->tableWidget->setRowCount(5);
4设置正文
ui->tableWidget->setItem(0,0, new QTableWidgetItem("亚瑟"));
程序:
复制Image文件
添加后编译一下
还有如下
在搭建Qt窗口界面的时候,在一个项目中很多窗口,或者是窗口中的某个模块会被经常性的重复使用。一般遇到这种情况我们都会将这个窗口或者模块拿出来做成一个独立的窗口类,以备以后重复使用。
在使用Qt的ui文件搭建界面的时候,工具栏栏中只为我们提供了标准的窗口控件,如果我们想使用自定义控件怎么办?
接下来要对滑块和SpinBox进行关联
SpinBox的数字变化会有信号,查找方法如下:
上面Signals函数有两个,他们的名字一样,所以要重载,C++中有讲过
这个只有一个所以不用重载,注意代码区别
#include "smallwidget.h"
#include "ui_smallwidget.h"
SmallWidget::SmallWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::SmallWidget)
{
ui->setupUi(this);
//QSpinBox移动 QSlider跟着移动
void(QSpinBox:: * spSignal )(int) = &QSpinBox::valueChanged;
connect(ui->spinBox , spSignal , ui->horizontalSlider , &QSlider::setValue);
//QSlider滑动 QSpinBox数字跟着改变
connect(ui->horizontalSlider, &QSlider::valueChanged,ui->spinBox,&QSpinBox::setValue);
}
SmallWidget::~SmallWidget()
{
delete ui;
}
再来一些自定义功能
事件(event)是由系统或者 Qt 应用程序本身在不同的时刻发出的。当用户按下鼠标、敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件。一些事件在对用户操作做出响应时发出,如键盘事件等;另一些事件则是由系统自动发出,如计时器事件。
在所有组件的父类QWidget中,定义了很多事件处理的函数,如
1.keyPressEvent():键盘按键按下事件
2.keyReleaseEvent():键盘按键松开事件
3.mouseDoubleClickEvent():鼠标双击事件
4.mouseMoveEvent():鼠标移动事件
5.mousePressEvent():鼠标按键按下事件
6.mouseReleaseEvent() :鼠标按键松开事件
7.等等
这些函数都是 protected virtual 的,也就是说,我们可以在子类中重新实现这些函数。
鼠标左键按下才有反应:
void myLabel::mousePressEvent(QMouseEvent *ev) { //当鼠标左键按下打印,右键不打印 //当鼠标左键按下 提示信息 if( ev->button() == Qt::LeftButton) { QString str = QString( "鼠标按下了 x = %1 y = %2 globalX = %3 globalY = %4 " ).arg(ev->x()).arg(ev->y()).arg(ev->globalX()).arg(ev->globalY()); qDebug() << str; } }
只有左键按下才打印
。
鼠标默认捕获,不用按下,移动鼠标也打印
1.EventLabel继承了QLabel,覆盖了mousePressEvent()、mouseMoveEvent()和MouseReleaseEvent()三个函数。我们并没有添加什么功能,只是在鼠标按下(press)、鼠标移动(move)和鼠标释放(release)的时候,把当前鼠标的坐标值显示在这个Label上面。
2.QString的arg()函数可以自动替换掉QString中出现的占位符。其占位符以 % 开始,后面是占位符的位置,例如 %1,%2 这种。
QString("[%1, %2]").arg(x).arg(y);
语句将会使用x替换 %1,y替换 %2,因此,生成的QString为[x, y]。
代码:
mylabel.h
#ifndef MYLABEL_H
#define MYLABEL_H
#include
#include
#include
class myLabel : public QLabel
{
Q_OBJECT
public:
explicit myLabel(QWidget *parent = nullptr);
//鼠标进入事件
void enterEvent(QEvent *event);
//鼠标离开事件
void leaveEvent(QEvent *);
//鼠标按下
virtual void mousePressEvent(QMouseEvent *ev);
//鼠标释放
virtual void mouseReleaseEvent(QMouseEvent *ev);
//鼠标移动
virtual void mouseMoveEvent(QMouseEvent *ev);
//通过event事件分发器 拦截 鼠标按下事件
// bool event(QEvent *e);
signals:
public slots:
};
#endif // MYLABEL_H
mylabel.cpp
#include "mylabel.h"
#include
#include
myLabel::myLabel(QWidget *parent) : QLabel(parent)
{
//设置鼠标追踪状态
setMouseTracking(true);
}
//鼠标进入事件
void myLabel::enterEvent(QEvent *event)
{
// qDebug() << "鼠标进入了";
}
//鼠标离开事件
void myLabel::leaveEvent(QEvent *)
{
// qDebug() << "鼠标离开了";
}
//鼠标按下
void myLabel::mousePressEvent(QMouseEvent *ev)
{
//当鼠标左键按下打印,右键不打印
//当鼠标左键按下 提示信息
// if( ev->button() == Qt::LeftButton)
// {
QString str = QString( "鼠标按下了 x = %1 y = %2 globalX = %3 globalY = %4 " ).arg(ev->x()).arg(ev->y()).arg(ev->globalX()).arg(ev->globalY());
qDebug() << str;
// }
}
//鼠标释放
void myLabel::mouseReleaseEvent(QMouseEvent *ev)
{
// if( ev->button() == Qt::LeftButton)
// {
QString str = QString( "鼠标释放了 x = %1 y = %2 globalX = %3 globalY = %4 " ).arg(ev->x()).arg(ev->y()).arg(ev->globalX()).arg(ev->globalY());
qDebug() <buttons() & Qt::LeftButton)
// {
QString str = QString( "鼠标移动了 x = %1 y = %2 globalX = %3 globalY = %4 " ).arg(ev->x()).arg(ev->y()).arg(ev->globalX()).arg(ev->globalY());
qDebug() <
if( ev->buttons() & Qt::LeftButton) 详细解释
这是一个条件语句,用于检查鼠标事件中是否按下了左键。让我们分解这个语句:
ev->buttons()
: 这是一个QMouseEvent
对象的方法,用于获取当前鼠标事件中的按钮状态。返回的是一个表示按钮状态的枚举值。
Qt::LeftButton
: 这是 Qt 框架中的一个枚举值,表示鼠标的左键。在 Qt 中,鼠标按键有很多可能的状态,如左键、右键、中键等。
&
: 这是位运算中的按位与操作符。在这个上下文中,它用于检查ev->buttons()
的返回值中是否包含Qt::LeftButton
,如果包含,则条件成立。所以,整个语句
if( ev->buttons() & Qt::LeftButton)
的意思是:“如果鼠标事件中按下了左键,执行以下代码块”。这样的检查通常用于响应鼠标事件,以便根据鼠标按键的状态执行相应的操作。
为什么不写在mylabel.h下,是因为在ui拉出来的label_2控件是属于widget本身的,并没有进行提升操作
定时器是一毫秒,变量需要设置成静态变量,不然每次都是1
1、利用事件 void timerEvent ( QTimerEvent * ev)
2、启动定时器 startTimer( 1000) 毫秒单位
3、timerEvent 的返回值是定时器的唯一标示 可以和ev->timerId 做比较
1、 利用定时器类 QTimer
2、 创建定时器对象 QTimer * timer = new QTimer(this)
3、 启动定时器 timer->start(毫秒)
4、每隔一定毫秒,发送信号 timeout ,进行监听
5、暂停 timer->stop
事件对象创建完毕后,Qt 将这个事件对象传递给QObject的event()函数。event()函数并不直接处理事件,而是将这些事件对象按照它们不同的类型,分发给不同的事件处理器(event handler)。
如上所述,event()函数主要用于事件的分发。所以,如果你希望在事件分发之前做一些操作,就可以重写这个event()函数了。例如,我们希望在一个QWidget组件中监听 tab 键的按下,那么就可以继承QWidget,并重写它的event()函数,来达到这个目的:
//通过event事件分发器 拦截 鼠标按下事件
bool myLabel::event(QEvent *e)
{
//如果是鼠标按下 ,在event事件分发中做拦截操作
if(e->type() == QEvent::MouseButtonPress)
{
QMouseEvent * ev = static_cast(e);
QString str = QString( "Event函数中::鼠标按下了 x = %1 y = %2 globalX = %3 globalY = %4 " ).arg(ev->x()).arg(ev->y()).arg(ev->globalX()).arg(ev->globalY());
qDebug() << str;
return true; //true代表用户自己处理这个事件,不向下分发
}
//其他事件 交给父类处理 默认处理
return QLabel::event(e);
}
1、 用途:用于事件的分发
2、也可以做拦截操作,不建议
3 、bool event( QEvent * e);
4、返回值 如果是true 代表用户处理这个事件,不向下分发了
5、e->type() == 鼠标按下 …
gpt对代码的解释:
在这段代码中,
event
函数的目的是处理不同类型的事件,而在鼠标按下事件的情况下,你需要使用static_cast
来将QEvent
类型的指针转换为QMouseEvent
类型的指针,以便你能够访问鼠标事件的特定信息。如果不进行类型转换,直接使用
QEvent
类型的指针,编译器将无法识别和访问QMouseEvent
特有的成员函数和数据成员,因此会导致编译错误。这是因为
QEvent
类型是一个基类,而QMouseEvent
是其派生类。当你在处理特定类型的事件时,需要将基类指针转换为派生类指针,以便能够使用派生类中定义的成员。这就是为什么你需要使用static_cast
进行类型转换的原因。
这行代码的解释如下:
static_cast
是 C++ 中的一个类型转换运算符,用于执行编译时的类型转换。表示将
e
转换为QMouseEvent*
类型。这是一个指针类型转换,告诉编译器将e
视为指向QMouseEvent
对象的指针。ev
是转换后的指针变量的名称,这样你就可以使用它来访问QMouseEvent
类型的成员函数和数据成员。总的来说,这一行代码的目的是在处理鼠标事件时,将基类
QEvent
指针转换为派生类QMouseEvent
指针,以便能够访问鼠标事件的具体信息。这是为了在鼠标按下事件中获取鼠标的坐标等信息而执行的操作。
有时候,对象需要查看、甚至要拦截发送到另外对象的事件。例如,对话框可能想要拦截按键事件,不让别的组件接收到;或者要修改回车键的默认处理。
通过前面的章节,我们已经知道,Qt 创建了QEvent事件对象之后,会调用QObject的event()函数处理事件的分发。显然,我们可以在event()函数中实现拦截的操作。由于event()函数是 protected 的,因此,需要继承已有类。如果组件很多,就需要重写很多个event()函数。这当然相当麻烦,更不用说重写event()函数还得小心一堆问题。好在 Qt 提供了另外一种机制来达到这一目的:事件过滤器。
QObject有一个eventFilter()函数,用于建立事件过滤器。函数原型如下:
virtual bool QObject::eventFilter ( QObject * watched, QEvent * event );
这个函数正如其名字显示的那样,是一个“事件过滤器”。所谓事件过滤器,可以理解成一种过滤代码。事件过滤器会检查接收到的事件。如果这个事件是我们感兴趣的类型,就进行我们自己的处理;如果不是,就继续转发。这个函数返回一个 bool 类型,如果你想将参数 event 过滤出来,比如,不想让它继续转发,就返回 true,否则返回 false。事件过滤器的调用时间是目标对象(也就是参数里面的watched对象)接收到事件对象之前。也就是说,如果你在事件过滤器中停止了某个事件,那么,watched对象以及以后所有的事件过滤器根本不会知道这么一个事件。
这里把过滤器写在Widget的文件中是因为label这个控件的过滤器是由Widget安装的
bool Widget::eventFilter(QObject * obj , QEvent * e)
{
if(obj == ui->label)
{
if(e->type() == QEvent::MouseButtonPress)
{
QMouseEvent * ev = static_cast(e);
QString str = QString( "事件过滤器中::鼠标按下了 x = %1 y = %2 globalX = %3 globalY = %4 " ).arg(ev->x()).arg(ev->y()).arg(ev->globalX()).arg(ev->globalY());
qDebug() << str;
return true; //true代表用户自己处理这个事件,不向下分发
}
}
//其他默认处理
return QWidget::eventFilter(obj,e);
}
- 在程序将时间分发到事件分发器前,可以利用过滤器做拦截
- 步骤
- 重写 eventFilter函数 (obj , ev)
- 给控件安装事件过滤器
全部代码:
mylabel.h:
#ifndef MYLABEL_H
#define MYLABEL_H
#include
#include
#include
class myLabel : public QLabel
{
Q_OBJECT
public:
explicit myLabel(QWidget *parent = nullptr);
//鼠标进入事件
void enterEvent(QEvent *event);
//鼠标离开事件
void leaveEvent(QEvent *);
//鼠标按下
virtual void mousePressEvent(QMouseEvent *ev);
//鼠标释放
virtual void mouseReleaseEvent(QMouseEvent *ev);
//鼠标移动
virtual void mouseMoveEvent(QMouseEvent *ev);
//通过event事件分发器 拦截 鼠标按下事件
bool event(QEvent *e);
signals:
public slots:
};
#endif // MYLABEL_H
Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
//重写定时器的事件
void timerEvent(QTimerEvent *);
int id1; //定时器1的唯一标示
int id2; //定时器2的唯一标示
//重写事件过滤器的事件
bool eventFilter(QObject *, QEvent *);
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
myLabel.cpp
#include "mylabel.h"
#include
#include
myLabel::myLabel(QWidget *parent) : QLabel(parent)
{
//设置鼠标追踪状态
setMouseTracking(true);
}
//鼠标进入事件
void myLabel::enterEvent(QEvent *event)
{
// qDebug() << "鼠标进入了";
}
//鼠标离开事件
void myLabel::leaveEvent(QEvent *)
{
// qDebug() << "鼠标离开了";
}
//鼠标按下
void myLabel::mousePressEvent(QMouseEvent *ev)
{
//当鼠标左键按下打印,右键不打印
//当鼠标左键按下 提示信息
// if( ev->button() == Qt::LeftButton)
// {
QString str = QString( "鼠标按下了 x = %1 y = %2 globalX = %3 globalY = %4 " ).arg(ev->x()).arg(ev->y()).arg(ev->globalX()).arg(ev->globalY());
qDebug() << str;
// }
}
//鼠标释放
void myLabel::mouseReleaseEvent(QMouseEvent *ev)
{
// if( ev->button() == Qt::LeftButton)
// {
QString str = QString( "鼠标释放了 x = %1 y = %2 globalX = %3 globalY = %4 " ).arg(ev->x()).arg(ev->y()).arg(ev->globalX()).arg(ev->globalY());
qDebug() <buttons() & Qt::LeftButton)
// {
QString str = QString( "鼠标移动了 x = %1 y = %2 globalX = %3 globalY = %4 " ).arg(ev->x()).arg(ev->y()).arg(ev->globalX()).arg(ev->globalY());
qDebug() <type() == QEvent::MouseButtonPress)
{
QMouseEvent * ev = static_cast(e);
QString str = QString( "Event函数中::鼠标按下了 x = %1 y = %2 globalX = %3 globalY = %4 " ).arg(ev->x()).arg(ev->y()).arg(ev->globalX()).arg(ev->globalY());
qDebug() << str;
return true; //true代表用户自己处理这个事件,不向下分发
}
//其他事件 交给父类处理 默认处理
return QLabel::event(e);
}
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include //定时器类
#include
#include
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//启动定时器
id1 = startTimer(1000);//参数1 间隔 单位 毫秒
id2 = startTimer(2000);//参数1 间隔 单位 毫秒
//定时器第二种方式
QTimer * timer = new QTimer(this);
//启动定时器
timer->start(500);
connect(timer,&QTimer::timeout,[=](){
//label_4 每隔0.5秒加一
static int num = 1;//定时器是一毫秒,变量需要设置成静态变量,不然每次都是1
ui->label_4->setText( QString::number(num++));
});
//点击按暂停钮 实现停止定时器
connect(ui->btn,&QPushButton::clicked,[=](){
timer->stop();
});
//点击口输出停钮 实现开始定时器
connect(ui->btn_2,&QPushButton::clicked,[=](){
timer->start(500);
});
//给label1 安装事件过滤器
// 步骤1 安装事件过滤器
ui->label->installEventFilter(this);
}
bool Widget::eventFilter(QObject * obj , QEvent * e)
{
if(obj == ui->label)
{
if(e->type() == QEvent::MouseButtonPress)
{
QMouseEvent * ev = static_cast(e);
QString str = QString( "事件过滤器中::鼠标按下了 x = %1 y = %2 globalX = %3 globalY = %4 " ).arg(ev->x()).arg(ev->y()).arg(ev->globalX()).arg(ev->globalY());
qDebug() << str;
return true; //true代表用户自己处理这个事件,不向下分发
}
}
//其他默认处理
return QWidget::eventFilter(obj,e);
}
void Widget::timerEvent(QTimerEvent * ev)
{
if(ev->timerId() == id1 )
{
//label_2 每隔一秒加一
static int num = 1;//定时器是一毫秒,变量需要设置成静态变量,不然每次都是1
ui->label_2->setText( QString::number(num++));
}
if(ev->timerId() == id2 )
{
//label_3 每隔一秒加一
static int num2 = 1;//定时器是一毫秒,变量需要设置成静态变量,不然每次都是1
ui->label_3->setText( QString::number(num2++));
}
}
Widget::~Widget()
{
delete ui;
}
void Widget:: paintEvent(QPaintEvent *)
{
// //实例化画家对象 this指定的是绘图设备
QPainter painter(this);
// //设置画笔
QPen pen(QColor(0,20,255));
// //设置画笔宽度
pen.setWidth(3);
// //设置画笔风格
pen.setStyle(Qt::DashDotDotLine);
// //让画家 使用这个笔
painter.setPen(pen);
// //设置画刷
QBrush brush(Qt::red);
// //设置画刷风格
brush.setStyle(Qt::DiagCrossPattern);
// //让画家使用画刷
painter.setBrush(brush);
// //画线
painter.drawLine(QPoint(0,0) , QPoint(100,100));
// //画圆 椭圆
painter.drawEllipse( QPoint(100,100) , 50,50);
// //画矩形
painter.drawRect(QRect(20,20,50,50));
// //画文字
painter.drawText(QRect(10,200,150,50) , "好好学习,天天向上");
}
1、绘图事件 void paintEvent()
2、声明一个画家对象 QPainter painter(this) this指定绘图设备
3、画线、画圆、画矩形、画文字
4、设置画笔 QPen 设置画笔宽度 、风格
5、设置画刷 QBrush 设置画刷 风格
//高级设置 ///
QPainter painter(this);
// painter.drawEllipse(QPoint(100,50) , 50,50);
// // //设置 抗锯齿能力 效率较低
// painter.setRenderHint(QPainter::Antialiasing);
// painter.drawEllipse(QPoint(200,50) , 50,50);
//画矩形
painter.drawRect(QRect(20,20,50,50));
// //移动画家
painter.translate(100,0);
// //保存画家状态
painter.save();
painter.drawRect(QRect(20,20,50,50));
painter.translate(100,0);
// //还原画家保存状态
painter.restore();
painter.drawRect(QRect(20,20,50,50));
1、抗锯齿 效率低 painter.setRenderHint(QPainter::Antialiasing);
2、对画家进行移动 painter.translate(100,0);
3、保存状态 save
4、还原状态 restore
添加资源文件。已经搞过很多次了
绘图设备是指继承QPainterDevice的子类。Qt一共提供了四个这样的类,分别是QPixmap、QBitmap、QImage和 QPicture。其中,
1.QPixmap专门为图像在屏幕上的显示做了优化
2.QBitmap是QPixmap的一个子类,它的色深限定为1,可以使用 QPixmap的isQBitmap()函数来确定这个QPixmap是不是一个QBitmap。
3.QImage专门为图像的像素级访问做了优化。
4.QPicture则可以记录和重现QPainter的各条命令。
QPixmap继承了QPaintDevice,因此,你可以使用QPainter直接在上面绘制图形。QPixmap也可以接受一个字符串作为一个文件的路径来显示这个文件,比如你想在程序之中打开png、jpeg之类的文件,就可以使用 QPixmap。使用QPainter的drawPixmap()函数可以把这个文件绘制到一个QLabel、QPushButton或者其他的设备上面。QPixmap是针对屏幕进行特殊优化的,因此,它与实际的底层显示设备息息相关。注意,这里说的显示设备并不是硬件,而是操作系统提供的原生的绘图引擎。所以,在不同的操作系统平台下,QPixmap的显示可能会有所差别。
QBitmap继承自QPixmap,因此具有QPixmap的所有特性,提供单色图像。QBitmap的色深始终为1. 色深这个概念来自计算机图形学,是指用于表现颜色的二进制的位数。我们知道,计算机里面的数据都是使用二进制表示的。为了表示一种颜色,我们也会使用二进制。比如我们要表示8种颜色,需要用3个二进制位,这时我们就说色深是3. 因此,所谓色深为1,也就是使用1个二进制位表示颜色。1个位只有两种状态:0和1,因此它所表示的颜色就有两种,黑和白。所以说,QBitmap实际上是只有黑白两色的图像数据。
由于QBitmap色深小,因此只占用很少的存储空间,所以适合做光标文件和笔刷。
QPixmap使用底层平台的绘制系统进行绘制,无法提供像素级别的操作,而QImage则是使用独立于硬件的绘制系统,实际上是自己绘制自己,因此提供了像素级别的操作,并且能够在不同系统之上提供一个一致的显示形式。
我们声明了一个QImage对象,大小是300 x 300,颜色模式是RGB32,即使用32位数值表示一个颜色的RGB值,也就是说每种颜色使用8位。然后我们对每个像素进行颜色赋值,从而构成了这个图像。我们可以把QImage想象成一个RGB颜色的二维数组,记录了每一像素的颜色。
最后一个需要说明的是QPicture。这是一个可以记录和重现QPainter命令的绘图设备。 QPicture将QPainter的命令序列化到一个IO设备,保存为一个平台独立的文件格式。这种格式有时候会是“元文件(meta- files)”。Qt的这种格式是二进制的,不同于某些本地的元文件,Qt的pictures文件没有内容上的限制,只要是能够被QPainter绘制的元素,不论是字体还是pixmap,或者是变换,都可以保存进一个picture中。
QPicture是平台无关的,因此它可以使用在多种设备之上,比如svg、pdf、ps、打印机或者屏幕。回忆下我们这里所说的QPaintDevice,实际上是说可以有QPainter绘制的对象。QPicture使用系统的分辨率,并且可以调整 QPainter来消除不同设备之间的显示差异。
如果我们要记录下QPainter的命令,首先要使用QPainter::begin()函数,将QPicture实例作为参数传递进去,以便告诉系统开始记录,记录完毕后使用QPainter::end()命令终止。
全部代码:
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include
#include
#include
#include
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
// //Pixmap绘图设备 专门为平台做了显示的优化
// QPixmap pix(300,300);
// //填充颜色
// pix.fill(Qt::white);
// //声明画家
// QPainter painter(&pix);
// painter.setPen(QPen(Qt::green));
// painter.drawEllipse(QPoint(150,150) , 100,100);
// //保存
// pix.save("D:/QT_studyheima/TuiPian/pix.png");
//QPicture 绘图设备 可以记录和重现 绘图指令
QPicture pic;
QPainter painter;
painter.begin(&pic);
painter.setPen(QPen(Qt::black));
painter.drawEllipse(QPoint(150,150) , 100,100);
painter.end();
//保存
pic.save("D:/QT_studyheima/TuiPian/pic.zt");
}
//绘图事件
void Widget::paintEvent(QPaintEvent *)
{
// QPainter painter(this);
// //利用QImage 对像素进行修改
// QImage img;
// img.load(":/image/kk.jpg");
// QSize scaledSize = img.size().scaled(this->size(), Qt::KeepAspectRatio);
//修改像素点
// for(int i = 50 ;i < 100 ; i++)
// {
// for(int j = 50 ; j < 100;j++)
// {
// QRgb value = qRgb(255,0,0);
// img.setPixel(i,j,value);
// }
// }
// painter.drawImage(QRect(0, 0, scaledSize.width(), scaledSize.height()), img);
QPainter painter(this);
//重现QPicture的绘图指令
QPicture pic;
pic.load("D:\\QT_studyheima\\TuiPian\\pic.zt");
painter.drawPicture(0,0,pic);
}
Widget::~Widget()
{
delete ui;
}
#include "widget.h"
#include "ui_widget.h"
#include
#include
#include
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//点击选取文件的按钮,弹出文件对话框
connect(ui->pushButton,&QPushButton::clicked,[=](){
QString path = QFileDialog::getOpenFileName(this,"打开文件","C:\\Users\\ldbh\\Desktop");
//将路径放入到lineEdit中
ui->lineEdit->setText(path);
//编码格式类
QTextCodec * codec = QTextCodec::codecForName("gbk");
//读取内容 放入到 textEdit中
// QFile默认支持的格式是 utf-8
QFile file(path); //参数就是读取文件的路径
//设置打开方式
file.open(QIODevice::ReadOnly);
// QByteArray array = file.readAll();
QByteArray array;
while( !file.atEnd())
{
array += file.readLine(); //按行读
}
//将读取到的数据 放入textEdit中
ui->textEdit->setText(array);
//ui->textEdit->setText( codec->toUnicode(array) );
//对文件对象进行关闭
file.close();
//进行写文件
file.open(QIODevice::Append); //用追加方式进行写
file.write("啊啊啊啊啊");
file.close();
});
}
Widget::~Widget()
{
delete ui;
}
QFile 对文件进行读写操作
1、QFile进行读写操作
2、QFile file( path 文件路径)
3、读
1. file.open(打开方式) QIODevice::readOnly
2.全部读取 file.readAll() 按行读 file.readLine() atend()判断是否读到文件尾3.默认支持编码格式 utf-8
4.利用编码格式类 指定格式 QTextCodeC
5.QTextCodec * codec = QTextCodec::codecForName("gbk");
6. //ui->textEdit->setText( codec->toUnicode(array) );
7.文件对象关闭 close
.4、写
1.file.open( QIODevice::writeOnly / Append)
2.file.write(内容)
3.file.close 关闭
#include "widget.h"
#include "ui_widget.h"
#include
#include
#include
#include
#include
#include
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//点击选取文件的按钮,弹出文件对话框
connect(ui->pushButton,&QPushButton::clicked,[=](){
QString path = QFileDialog::getOpenFileName(this,"打开文件","C:\\Users\\ldbh\\Desktop");
//将路径放入到lineEdit中
ui->lineEdit->setText(path);
//编码格式类
QTextCodec * codec = QTextCodec::codecForName("gbk");
//读取内容 放入到 textEdit中
// QFile默认支持的格式是 utf-8
QFile file(path); //参数就是读取文件的路径
//设置打开方式
file.open(QIODevice::ReadOnly);
// QByteArray array = file.readAll();
QByteArray array;
while( !file.atEnd())
{
array += file.readLine(); //按行读
}
//将读取到的数据 放入textEdit中
ui->textEdit->setText(array);
//ui->textEdit->setText( codec->toUnicode(array) );
//对文件对象进行关闭
file.close();
//进行写文件
file.open(QIODevice::Append); //用追加方式进行写
file.write("啊啊啊啊啊");
file.close();
//QFileInfo 文件信息类
QFileInfo info(path);
qDebug() << "大小:" << info.size() << " 后缀名:" << info.suffix() << " 文件名称:"<
1、QFileInfo 读取文件信息
1.QFileInfo info(路径)
2.qDebug() << "大小:" << info.size() << " 后缀名:" << info.suffix() << " 文件名称:"<
3.qDebug() << "创建日期:" << info.created().toString("yyyy/MM/dd hh:mm:ss"); 4.qDebug() << "最后修改日期:"<