1. 简介
1.1 什么是Qt
Qt是一个1991年由Qt Company开发的跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。Qt是面向对象的框架,使用特殊的代码生成扩展以及一些宏,Qt很容易扩展,并且允许真正的组件编程。
Qt Creator是一个用于Qt开发的轻量级跨平台集成开发环境。Qt Creator可带来两大关键益处:提供首个专为支持跨平台开发而设计的集成开发环境(IDE),并确保首次接触Qt框架的开发人员能迅速上手和操作。即使不开发Qt应用程序,Qt Creator也是一个简单易用且功能强大的IDE。
Qt是一套应用程序开发库,但与MFC不同,Qt是跨平台的开发类库。跨平台意味着只需要编写一次程序,在不同平台上无需改动或只需要少许改动后在编译,就可以形成在不同平台上运行的版本。
1.2 Qt特征
1.面向对象
Qt具有模块设计和控件或元素的可重用性的特点。一个控件不需要找到它的内容和用途,通过Signal和slot与外界通信、交流。而且Qt的控件都可通过继承。
2.控件间的相互通信
Qt提供signal和slot概念,这是一种安全可靠的方法,它允许回调,并支持对象之间在彼此不知道对方信息的情况下,进行合作,这使Qt非常合适于真正的控件编程。
3.友好的联机帮助
Qt包括大量的联机参考文档,有超文本HTML方式、UNIX帮助页、man手册和补充的指南。对于初学者,指南将一步步地解释Qt编程。
4.用户自定义
其它的工具包在应用时都存在一个普遍的问题,就是经常没有真正适合需求的控件,生成的自定义控件对用户来说,也是一个黑匣子。比如,在Motif手册中就讨论了用户自定义的控件问题。而在Qt中,能够创建控件,具有绝对的优越性,生成自定义控件非常简单,并且容易修改控件。
5.方便性
由于Qt是一种跨平台的GUI工具包,所以它对编程者隐藏了在处理不同窗口系统时的潜在问题。为了将基于Qt程序更加方便,Qt包含了一系列类。该类能够使程序员避免了在文件处理、时间处理等方面存在依赖操作系统方面的细节问题。
2. Qt编译过程
1.编写代码
通过文本文档写入如下代码,后缀改为cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| #include <QApplication> #include <QLabel> #include <QLineEdit> #include <QPushButton> #include <QHBoxLayout> #include <QVBoxLayout> #include <QWidget>
int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel *infoLabel = new QLabel; QLabel *openLabel = new QLabel; QLineEdit *cmdLineEdit = new QLineEdit; QPushButton *commitButton = new QPushButton; QPushButton *cancelButton = new QPushButton; QPushButton *browseButton = new QPushButton;
infoLabel->setText("input cmd:"); openLabel->setText("open"); commitButton->setText("commit"); cancelButton->setText("cancel"); browseButton->setText("browse");
QHBoxLayout *cmdLayout = new QHBoxLayout; cmdLayout->addWidget(openLabel); cmdLayout->addWidget(cmdLineEdit);
QHBoxLayout *buttonLayout = new QHBoxLayout; buttonLayout->addWidget(commitButton); buttonLayout->addWidget(cancelButton); buttonLayout->addWidget(browseButton);
QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(infoLabel); mainLayout->addLayout(cmdLayout); mainLayout->addLayout(buttonLayout); QWidget w; w.setLayout(mainLayout); w.show(); return app.exec(); }
|
2.生成工程文件
3.生成makefile,终端输入qmake
4.输入mingw32-make,执行成功就可以在工程目录下的debug或release目录下生成可执行文件。
注:以上步骤可能存在环境变量文件,如果出现,想要配置
3. Qt Creator
在创建一个Qt的项目时,也就是创建一个主窗口,基类的选择有以下三种:
QmainWindow:继承QWidget,带有菜单栏、工具栏、状态栏的界面
QWidget:最基础的窗口类,qt里面能看到的东西的基类,不带菜单栏的界面,类似于QQ、微信的登录框
Qdialog:继承QWidget,对话框界面,完成一些业务时,弹出的对话框
Qt里面绝大部分的类都是继承自QObject,它是一个顶层类
下面创建了一个MainWindow窗口类,继承的基类是QWidget
下面是创建窗口后,提供的main()函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show();
return a.exec(); }
|
下面是窗口类的头文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class MainWindow : public QWidget { Q_OBJECT
public: MainWindow(QWidget *parent = nullptr); ~newhello();
private: Ui::MainWindow *ui; };
|
下面是窗口类的构造函数以及一些程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this);
QPushButton* btnA = new QPushButton(this); btnA->move(10,10); btnA->setFixedSize(200,200);
QPushButton* btnB = new QPushButton(btnA); btnB->move(10,10); btnB->setFixedSize(100,100);
QPushButton* btnC = new QPushButton(btnB); btnC->move(10,10); btnC->setFixedSize(50,50); }
|
在上面的程序中,ui 是一个指向UI类对象的指针,该对象通常通过UI设计器生成,并包含了所有UI组件的实例和布局信息。而setupUi 是UI类中的一个成员函数,它的作用是将UI组件添加到主窗口上,并设置它们的位置、大小等属性。this 是一个指向当前对象的指针,通常是主窗口的实例。setupUi 函数会使用这个指针来确定UI组件应该添加到哪个窗口上。
执行后的效果如下:
4. 基本知识1
QWidget类是所有用户界面对象的基类,它提供了创建和管理窗口部件的功能。另一方面,QObject类是Qt对象模型的基类,它提供了对象树、信号与槽机制、事件处理等基本功能。
4.1 父子关系
默认情况下按钮是没有认父亲的,也就是一个顶层窗口,想要按钮显示在窗口中,就要跟窗口构造父子关系,方法如下:
创建了一个MainWindow窗口类,继承的基类是QWidget
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; QPushButton btn1; btn1.setText("按钮1"); btn1.show();
QPushButton btn2; btn2.setText("按钮2"); btn2.setParent(&w);
QPushButton btn3("按钮3",&w); btn3.move(100,100);
w.show(); return a.exec(); }
|
4.2 基础
1.关于Qt的坐标系是以父窗口的左上角为(0,0)的,以向右的方向为x的正方向,以向下的方向为y的正方向,顶层窗口就是以屏幕左上角为(0,0)的。
2.常用的API:
- move:移动窗口到父窗口某个位置
- resize:重新设置窗口的大小
- setFixedSize():设置窗口的固定大小
- setWindowTitle():设置窗口标题
- setGeometry():同时设置窗口位置和大小,相当于是move和resize的结合体,参数(x轴,y轴,宽,高)
3.对象树
概念:各个窗口对象通过建立父子关系构造的一个关系树
内存管理:父对象释放的时候会自动释放各个子对象
Qt中有内存回收机制,即对象树,但不是所有被new出的对象被自动回收,满足下面两个条件才可以自动回收:
- 创建的对象必须是
QObject类的子类(间接子类也可以),QObject类是没有父类的,Qt中有很大一部分类都是从这个类派生出去的
- 创建出的类对象,必须要指定其父对象是谁,一般情况下有两种操作方式:
- 在构造对象时指定父对象
- 通过调用
QWidget的API指定父窗口对象,就是setParent()方法
下面创建了一个主窗口MainWindow窗口类,继承的基类是QWidget,又创建了一个MyPushButton窗口类,使它继承的基类是QWidget。
以下是主窗口MainWindow类的构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 13
| MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); QPushButton btn("按钮1",this); btn.show(); MyPushButton *btn2 = new MyPushButton(this); btn2->setText("按钮2"); }
|
5. 信号槽机制
信号:各种事件;槽:相应信号的动作
信号和槽本质都是函数
当某个事件发送后,如某个按钮被点击了一下,它就会发出一个被点击的信号(signal),当某个对象接到这个信号之后,就会做一些相关的处理动作(slot)。但是Qt对象不会无故收到某个信号,要想让一个对象收到另一个对象发出的信号。这时需要建立连接,即connect()。
connect(信号发送者,信号,信号接收者,槽);
1
| connect(btn, &QPushButton::clicked, this, &Widget::close);
|
使用connect的时候保留&符号的原因:
- 提高代码可读性(提示这是一个指针)
- 自动提示(会根据前面写的内容来提示你接下来可能输入的内容)
下面编写了多个类,类与类之间想要在Qt中使用信号槽机制,那么必须要满足如下条件:
- 这个类必须从
QObject类或者是其子类进行派生
- 在定义类的头文件种加入Q_OBJECT宏
5.1 自定义信号与槽
1自定义信号要求:
- 信号是类的成员函数
- 返回值是void类型
- 信号的名字可以根据实际情况进行指定
- 参数可以随意指定,信号也可以重载
- 信号需要使用signals关键字进行声明,类似于public
- 信号函数只需要声明,不需要定义(没有函数体实现)
- 在程序中发送自定义信号,发送的本质就是调用信号函数
建议:习惯性在信号函数前加关键字emit;其只是显示的声明信号被发送,没有特殊含义。底层emit == #define emit
2.槽函数就是信号的处理动作,自定义槽函数和自定义的普通函数写法是一样的,要求:
- 返回值是void类型
- 槽也是函数,因此也支持重载
- Qt中的槽函数的类型有类的成员函数、全局函数、静态函数和lambda表达式(匿名函数)
- 槽函数可以使用关键字进行声明:public slots、private slots、protected slots
3.信号槽使用扩展:
一个信号可以连接多个槽函数,发送一个信号有多个处理函数
- 需要写多个connect连接
- 槽函数的执行顺序是随机的,和connect函数的调用顺序没有关系
- 信号的接收者可以是一个对象,也可以是多个对象
一个槽函数可以连接多个信号,多个不同的信号,处理动作是相同的
信号可以连接信号:信号接收者可以接收信号,继续发出新的信号,即传递了数据,并没有进行处理
信号槽是可以断开的,通过disconnect()函数,其与connect函数的参数一样
4.参数的二义性问题:当某个类具有两个相同名字的信号,一个带参数,一个不带参数,那么接收该信号的类不知道该执行哪个槽函数,所以会报错,需要通过下面两种方法来解决:
- 使用函数指针赋值,让编译器自动挑选符合类型的函数
- 使用static_cast强制转换,让编译器自动挑选符合类型的函数
下面是一个窗口类的构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| MainWindow::MainWindow(QWidget *parent) : QWidget(parent) , ui(new Ui::MainWindow) { ui->setupUi(this);
tea = new teacher(this); stu = new Student(this); QPushButton* btn = new QPushButton("按钮1", this); connect(btn, &QPushButton::clicked, this, [&](){ emit tea->hungry(); emit tea->hungry("kfc"); }); void (teacher::*tq)(QString) = &teacher::hungry; void (Student::*sq)(QString) = &Student::treat; connect(tea, tq, stu, sq); connect(tea, static_cast<void (teacher::*)()>(&teacher::hungry), stu, static_cast<void (Student::*)()>(&Student::treat)); }
|
在上面程序中也可以信号连接信号,如果有同名但参数不同的信号,还需要进行转换
1
| connect(btn, &QPushButton::clicked, tea, &teacher::hungry);
|
5.Qt4中的信号和槽
使用两个宏 SIGNAL和SLOT,它们的原理是将后面的参数转换成字符串,使用方式为:
connect(信号发送者, SIGNAL(函数原型), 信号接收者, SLOT(函数原型));
- 好处:没有重载二义性的问题,有无参数可以直接写在connect里面
- 坏处:写错了,编译期间不报错(在项目中就很难排查错误)
6.QDebug输出QString默认会转义,比如说当给信号传入的参数是emit tea->hungry("kfc\r\n");,控制台输出的也是"kfc\r\n"。
解决办法:
- 将QString转成char*:
qDebug()<<"请吃"<<what.toUtf8().data();
- 使用qDebug().noquote():
qDebug().noquote()<<"请吃"<<what;
5.2 定时器程序
下面是一个非自定义的信号与槽,所以就需要先手动在ui界面添加两个按钮和两个标签。然后通过系统提供的按钮信号来触发槽函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this);
QTimer* timer = new QTimer(this);
timer->setTimerType(Qt::PreciseTimer);
connect(ui->loopBtn, &QPushButton::clicked, this, [=](){ if(timer->isActive()){ timer->stop(); ui->loopBtn->setText("开始"); }else{ ui->loopBtn->setText("关闭"); timer->start(1000); } }); connect(timer, &QTimer::timeout, this,[=](){ QTime tm = QTime::currentTime(); QString tmstr = tm.toString("hh:mm:ss.zzz"); ui->curTime->setText(tmstr); });
connect(ui->onceBtn, &QPushButton::clicked, this, [=](){ QTimer::singleShot(2000,this,[=](){ QTime tm = QTime::currentTime(); QString tmstr = tm.toString("hh:mm:ss.zzz"); ui->onceTime->setText(tmstr); }); }); }
|
6. QMainWindow
QMainWindow是一个为用户提供主窗口程序的类,包含一个菜单栏(menu bar)、多个工具栏(tool bars)、多个停靠部件(dock widgets)、一个状态栏(status bar)及一个中心部件(central widget),是许多应用程序的基础,如文本编辑器,图片编辑器等。具体结果如下:

1.下面创建了一个窗口类MainWindow,继承的基类是QMainWindow,在该窗口中添加各个部件内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this);
QMenuBar *mb = this->menuBar(); QMenu *menuFile = mb->addMenu("文件"); QMenu * menuEdit = mb->addMenu("编辑"); QAction *actionNew = menuFile->addAction("新建"); QAction *actionOpen = menuFile->addAction("打开");
menuFile->addSeparator();
QAction *actionRename = menuFile->addAction("重命名"); QMenu *menuRecent = menuFile->addMenu("最近打开的文件"); menuRecent->addAction("1.txt");
QToolBar *toolBar = this->addToolBar(""); toolBar->addAction(actionNew); toolBar->addAction(actionOpen); toolBar->setAllowedAreas(Qt::LeftToolBarArea|Qt::RightToolBarArea); toolBar->setFloatable(false); toolBar->setMovable(false);
QStatusBar *sb = this->statusBar(); QLabel *labelLeft = new QLabel("左侧信息", this); sb->addWidget(labelLeft); QLabel *labelRight = new QLabel("右侧信息", this); sb->addPermanentWidget(labelRight);
QDockWidget *dockWidget = new QDockWidget("停靠部件", this); this->addDockWidget(Qt::BottomDockWidgetArea, dockWidget);
QTextEdit *textEdit = new QTextEdit(this); this->setCentralWidget(textEdit); }
|
2.UI文件的使用
创建项目时保留UI,setupUI函数就是关联UI文件的代码到程序,原理就是qt将ui文件转化成了c++代码。
3.资源文件使用
创建资源文件:新建 —–> Qt —–> Qt Resource File —–> 名称为res
7. QDialog对话框
对话框是GUI程序中不可或缺的组成部分。很多不能或者不适合放入主窗口的功能组件都必须放在对话框中,比如用于完成一次性任务的功能(如登录功能、现在某个文件打开、保存文件)。对话框通常会是一个顶层窗口,出现在程序最上层,用于实习短期任务或者简洁的用户交互。
Qt中使用QDialog类实现对话框,但是声明一个QDialog对象的时候,不管这个对话框对象跟哪个窗口建立了父子关系,当它显示出来的时候都还是一个顶层的窗口。
1.对话框没有最大化、最小化按钮的窗口,其可以分为模态对话框和非模态对话框:
- 模态对话框:就是对话框还没有关闭前不能操作同一个进程的其它窗口
- 创建模态:QDialog::exec()函数,是一个阻塞的消息循环函数
- 非模态对话框:就是对话框没有关闭前也能操作同一个进程的其它窗口
- 使用show()函数来直接显示窗口就可以,非阻塞的情况下要使用new的方式来创建对话框对象,不然容易随着所在区域的释放而释放。
- 内存泄漏问题,模态对话框关闭后可能并不会马上释放,所以就得通过设置窗口的属性来让其关闭后自动释放:
dlg->setAttribute(Qt::WA_DeleteOnClose);
下面通过两个按钮的发送信号来触发槽函数,从而实现创建模态和非模态对话框
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| void MainWindow::on_model_clicked() { QDialog dlg(this); dlg.exec(); qDebug()<<"hello model dialog"; }
void MainWindow::on_nonmodel_clicked() { QDialog *dlg = new QDialog(this); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->show(); qDebug()<<"hello nonmodel dialog"; }
|
2.系统标准对话框
QMessageBox用来提示用户某条信息,分为以下几种:
- 错误提示框:
QMessageBox::critical();
- 警告提示框:
QMessageBox::warning();
- 信息提示框:
QMessageBox::information();
- 问题提示框:
QMessageBox::question();
1 2 3 4 5
| QMessageBox::critical(this, "错误", "critical"); QMessageBox::warning(this, "警告", "warning"); QMessageBox::information(this, "信息", "information"); QMessageBox::question(this, "ONE PIECE", "Does it really exist?", QMessageBox::Ok|QMessageBox::Cancel);
|
问题提示框相较于其它几个提示框比较特殊,可以指定对话框的按钮,通过返回值来获取用户点击了哪个按钮。
3.文件对话框
使用QFileDialog来打开一个文件对话框,常用的函数是getOpenFileName来选择单一某文件,返回值是用户选择的文件路径。
在下面程序的中,设置的过滤器表示为将打开的目录种类分为PNG、ICO和all类型,默认先显示的是PNG类别的文件。
1 2 3 4
| QString fileName = QFileDialog::getOpenFileName(this, "打开一个文件");
QString fileName = QFileDialog::getOpenFileName(this, "打开一个文件", "C:\\Qt\\Bird", "PNG (*.png) ;; ICO (*.ico) ;; all (*.*)");
|
4.布局
有如下两类布局:
- 静态:就是位置和大小不会跟着外部窗口变化而变化
- 动态:就是位置和大小会跟着外部窗口变化而变化
常用的动态布局:水平、垂直、栅格、表单布局,推荐使用widget的自带的布局功能
使用弹簧来调整布局的位置(居中),栅格布局可以将空间分为几行几列的表格,方便对齐
大小策略:默认情况下动态布局,子窗口的大小会跟着父窗口的大小变化而变化,调整水平或垂直策略,变为固定
调整子窗口和父窗口之间的间隙,设置父窗口的margin,调整子窗口之间的间隙就调整spacing
调整窗口的固定大小,就是将窗口的最大值和最小值都设为同一个值
5.按钮组
6.QListWidget窗口
往ListWidget窗口中添加内容有两种方式:
- 往ListWidget窗口中,通过addItem添加QListWidgetItem对象(一个一个添加,可以修改item的属性)
- 往ListWidget窗口中,通过addItems添加QStringList对象(一次性添加多个,不可以修改item的属性
1 2 3 4 5 6 7 8 9
| QListWidgetItem *item = new QListWidgetItem("嘻嘻哈哈"); item->setTextAlignment(Qt::AlignHCenter); ui->listWidget->addItem(item);
QStringList list; list<<"嘻嘻哈哈"<<"叽叽喳喳"<<"把卡把卡"; ui->listWidget->addItems(list);
|
7.treeWidget窗口
- 设置标题,会根据setHeaderLabels函数里面的成员数生成对应的列数
- 添加根节点通过addTopLevelItem函数
- 根节点下面添加子节点通过addChild函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| ui->treeWidget->setHeaderLabels(QStringList()<<"英雄"<<"简介");
QTreeWidgetItem *liliangItem = new QTreeWidgetItem(QStringList()<<"力量"); QTreeWidgetItem *minjieItem = new QTreeWidgetItem(QStringList()<<"敏捷"); QTreeWidgetItem *zhiliItem = new QTreeWidgetItem(QStringList()<<"智力"); ui->treeWidget->addTopLevelItem(liliangItem); ui->treeWidget->addTopLevelItem(minjieItem); ui->treeWidget->addTopLevelItem(zhiliItem);
QStringList heroL1, heroL2, heroM1, heroM2, heroZ1, heroZ2; heroL1<<"亚瑟"<<"对拼刺刀"; heroL2<<"坦克"<<"血条厚"; heroM1<<"李白"<<"花里胡哨"; heroM2<<"刺客"<<"太秀了"; heroZ1<<"孙策"<<"无证驾船"; heroZ2<<"上单"<<"横冲直撞"; liliangItem->addChild(new QTreeWidgetItem(heroL1)); liliangItem->addChild(new QTreeWidgetItem(heroL2)); minjieItem->addChild(new QTreeWidgetItem(heroM1)); minjieItem->addChild(new QTreeWidgetItem(heroM2)); zhiliItem->addChild(new QTreeWidgetItem(heroZ1)); zhiliItem->addChild(new QTreeWidgetItem(heroZ2));
|
得到的效果图如下

8.tableWidget窗口
- 设置行数通过setRowCount()函数、设置列数通过setColumnCount()函数
- 设置水平的标题通过setHorizontalHeaderLabels()函数
- 设置表格某行某列的数据setItem(row, col, item)
1 2 3 4 5 6 7 8 9 10 11 12 13
| ui->tableWidget->setRowCount(5); ui->tableWidget->setColumnCount(3);
ui->tableWidget->setHorizontalHeaderLabels(QStringList()<<"英雄"<<"性别"<<"年龄");
QStringList heroNames = QStringList()<<"亚瑟"<<"妲己"<<"安其拉"<<"赵云"<<"孙悟空"; QStringList herogenders = QStringList()<<"男"<<"女"<<"女"<<"男"<<"雄性"; for(int row=0; row<5; row++){ ui->tableWidget->setItem(row,0, new QTableWidgetItem(heroNames[row])); ui->tableWidget->setItem(row,1, new QTableWidgetItem(herogenders[row])); ui->tableWidget->setItem(row,2, new QTableWidgetItem(QString::number(row + 18))); }
|
得到的效果图如下:

9.容器
stacked Widget 页面切换需要我们自己去实现,一般使用按钮点击的时候切换,通过setCurrentIndex()方式切换到第几页,序号从0开始
10、显示控件
1 2 3
| QMovie *movie = new QMovie(":/Image/mario.gif", QByteArray(), this); ui->label_1->setMovie(movie); movie->start();
|
11.自定义控件
当系统提供的控件不能满足我们生产中的一个功能时,就需要自己创建一些控件。
比如说一个自定义一个按钮控件,需要先创建一个自定义按钮类MyButton,让它继承QPushButton类,在MyButton类的析构函数中实现一些想要的功能,最后将主窗口中的按钮(QPushButton类)提升为MyButton类,这样就可以实现自定义按钮的一些功能了。
8. 事件
事件(event)是由系统或者Qt应用程序本身在不同的时刻发出的。当用户按下鼠标、敲下键盘、或者是窗口需要重新绘制的时候,都会发出一个相应的事件。一些事件在对用户操作做出响应时发出,如键盘事件等;另一些事件则是由系统自动发出,如计时器事件。
在所有组件的父类QWidget中,定义了很多事件处理的函数,如:
- keyPressEvent():键盘按键按下事件
- keyReleaseEvent():键盘按键松开事件
- mouseDoubleClickEvent():鼠标双击事件
- mouseMoveEvent():鼠标移动事件
1.在下面程序中,先创建一个主窗口widget,将一个Label标签拖入其中,再创建一个自定义标签类MyLabel,使它继承QLabel类,然后在该类里面添加对应的事件功能函数,最后将主窗口的Label标签提升为自定义标签类MyLabel。
自定义标签类的头文件:
1 2 3 4 5 6 7 8 9 10 11
| protected: void mousePressEvent(QMouseEvent *ev) override; void mouseMoveEvent(QMouseEvent *ev) override; bool event(QEvent *e) override;
bool eventFilter(QObject *watched, QEvent *event);
|
自定义标签对应的事件函数实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| MyLabel::MyLabel(QWidget *parent) : QLabel(parent) { this->setMouseTracking(true); this->installEventFilter(this); }
void MyLabel::mousePressEvent(QMouseEvent *ev) { int x = ev->x(); int y = ev->y();
Qt::MouseButton btn = ev->button(); QString strbutton = ""; if(btn == Qt::LeftButton){ strbutton = "LeftButton"; } if(btn == Qt::RightButton){ strbutton = "RightButton"; } if(btn == Qt::MidButton){ strbutton = "MidButton"; }
QString str = QString("pres[%1,%2][%3]").arg(x).arg(y).arg(strbutton); this->setText(str); }
void MyLabel::mouseMoveEvent(QMouseEvent *ev) { int x = ev->x(); int y = ev->y();
Qt::MouseButtons btns = ev->buttons(); QString strbutton = ""; if(btns & Qt::LeftButton){ strbutton += "LeftButton;"; } if(btns & Qt::RightButton){ strbutton += "RightButton;"; } if(btns & Qt::MidButton){ strbutton += "MidButton;"; }
QString str = QString("move[%1,%2][%3]").arg(x).arg(y).arg(strbutton); this->setText(str); }
bool MyLabel::event(QEvent *e) { if(e->type() == QEvent::MouseMove){ return true; } return QLabel::event(e); }
bool MyLabel::eventFilter(QObject *watched, QEvent *event) { if(event->type() == QEvent::MouseMove){ return true; } return false; }
|
2.定时器事件timerEvent
闹钟就是定时器,闹钟响了就是定时器事件
在头文件中需要重写定时器事件:void timerEvent(QTimerEvent* event);里面实现的功能即为定时器时间到后执行的操作
在需要的位置通过startTimer来启动一个定时器,返回值就是定时器的id,参数是毫秒,每隔相应的时间就会触发一次定时器事件
通过函数killtimer()来杀死一个定时器,参数就是要杀死定时器的id
timerEvent定时器事件处理函数中可以通过event参数获取到当前事件是哪个定时器发出的,如:event->timerId()
3.系统封装好的定时器QTimer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); timer = new QTimer(this); connect(timer, &QTimer::timeout, [=](){ static int num = 1; this->ui->lcdNumber->display(num++); }); } void Widget::on_start_clicked() { timer->start(10); }
void Widget::on_end_clicked() { timer->stop(); }
|
4.绘图事件
什么时候画
- 绘图事件:窗口需要重新显示的时候(大小发生变化、窗口切换等),就会收到一个绘图事件paintEvent,收到绘图事件之后,窗口就要将自己画出来。
怎么画
- 通过定义一个画家QPainter,给出一个画图设备QPaintDevice(窗口)作为参数。
5.创建了一个窗口Widget,在该窗口头文件重写了画图事件,下面为该画图事件函数里面的功能实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| void Widget::paintEvent(QPaintEvent *event) { QPainter painter(this);
painter.translate(100,0);
QPen pen; pen.setColor(QColor(255,0,0)); pen.setWidth(3); pen.setStyle(Qt::DashLine); painter.setPen(pen);
QBrush brush; brush.setColor(Qt::cyan); brush.setStyle(Qt::Dense4Pattern); painter.setBrush(brush);
painter.drawLine(0,0,100,100);
painter.drawRect(20,20,50,50);
painter.drawEllipse(QPoint(100,100),50,50);
painter.drawText(200, 100, "好好学习,天天向上"); }
|
6.手动触发绘图事件
在一些应用场景,我们或许希望能手动的触发绘图事件,而不是等到窗口发生变化再触发绘图事件,所以就可以使用下面方法完成。
- 可以使用两个函数
- repaint:会马上触发绘图事件,当某一处函数调用了多次repaint时,会触发多次绘图事件
- update:update做了一些优化,当某一处函数调用了多次update时,只会触发一次绘图事件
注意:不要再绘图事件paintEvent()函数中再触发绘图事件,会导致无线循环
下面程序实现了将本地存在的图片绘制到窗口
1 2 3 4
| QPainter painter(this); QPixmap pixmap("C:\\Qt\\素材\\llfcchat-master\\llfcchat-master\\client\\llfcchat\\res\\head_5.jpg"); painter.drawPixmap(0,0,pixmap);
|
7.绘图设备
绘图设备是指继承QPainterDevice的子类,Qt一共提供了4个这样的类,分别是QPixmap、QBitmap、QImage和QPicture,其中:
- QPixmap专本为图像在屏幕上的显示做了优化
- QBitmap是QPixmap的一个子类,它的色深限定为1,可以使用QPixmap的isQBitmap()函数来确定这个QPixmap是不是一个QBitmap。
- QImage专门为图像的像素级访问做了优化
- QPicture则可以记录和重现QPainter的各条命令。