Qt快速上手指南

Transparent

2020-03-13 20:04:09

Personal

------- ### 写在前面的话 ------- 你是否想过要让你的程序**图形化**? 你是否想过要让你的程序像其他程序一样**拥有界面**, 拥有**多个对话框**,可以让别人方便地使用? 除了再控制台上做手脚, 你还可以试试 **Qt** 。 说起它,你是否认为它很复杂? 其实你错了,想要上手, 其实很**简单**。 这篇文章绝对不会像这个一样: ![8lb83j.jpg](https://s1.ax1x.com/2020/03/14/8lb83j.jpg) 一定会为你把那些细节讲得**简单又清楚**, 现在就开始吧! 文章较长,可以分板块选择性阅读。 ------- ### Part 1 :[前置芝士](https://www.luogu.com.cn/blog/blogAdminsetup/qt-download-and-install) ------- 下载与安装。 提示:版本不要过低, 本文使用代码按照 **Qt 5** 标准编写, Qt 4 **很可能无法成功编译**(其实主要是 `connect()` 的定义有锅)!!! 注意这**不是 Qt Creator 版本号**!而是 **Qt 库的版本号**! ------- ### Part 2 Qt 简介 ------- Qt 是一个 $1991$ 年由 Qt Company 开发的跨平台**C++图形用户界面应用程序**开发框架。 它既可以开发 **GUI 程序**,也可用于开发**非 GUI 程序**,比如**控制台工具**和**服务**器。 Qt 是面向对象的框架,使用特殊的代码生成扩展以及一些宏,Qt 很容易扩展,并且允许**真正地组件编程**。 ——百度百科 重点:使用**C++**,**跨平台**,**图形用户界面应用程序**! ------- ### Part 3 Qt 工作机制的介绍 ------- Qt 主要使用一种**信号与槽**的机制, 由一个组件发出一个信号, 从而使一个或多个对应的槽触发事件。 这个信号有很多, 例如鼠标滑过,鼠标按下等。 如对于一个按钮(PushButton)的按下, 可以使用 `->clicked()` 来表示**信号**, 即: ```cpp QPushButton *btn=new QPushButton(); btn->clicked(); ``` 而**槽**是一个**函数** (可以**自定义**,也可以使用**自带**的), 当收到信号, 就会**自动调用对应的槽函数**。 要让一个信号和一个槽产生关联, 就需要用到 `connect` 函数。 下面的这个例子就可以很好地演示 (你需要先在 Qt Creator 中创建一个 Qt Widgets Application 项目,中间一直点下一步就行了): 以下文件在创建项目时就已经创建,大部分代码也已经为你写好,但是窝删掉了 `UI` 类。 \Header\mainwindow.h: ```cpp #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QPushButton> //头文件,Qt的每一个组件都对应一个头文件 //(当然没有像 bits/stdc++.h 一样的万能文件,就算有,也会严重拖慢编译速度) class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0);//窗口主函数 ~MainWindow();//没啥用 private: }; #endif // MAINWINDOW_H ``` \Source\main.cpp: ```cpp #include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w;//创建窗口类 w.show();//显示窗口 return a.exec(); } ``` \Source\mainwindow.cpp: ```cpp #include "mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { QPushButton *btn=new QPushButton(this); //创建PushButton,初始化函数的参数表示它的父节点,即它将显示在那个窗口上 //若没有,则会另开一个窗口 btn->setText("Quit");//设置显示字体 connect(btn,&QPushButton::clicked,this,&MainWindow::close); //关键部分:连接按钮和槽函数 //第一个参数为信号发出者,第二个参数为信号(在输入&时有提示) //第三个参数为接收者,第四个参数为槽函数 //其中close是库中定义的函数,作用是关闭窗口 } MainWindow::~MainWindow()//这个函数留空即可 { } ``` 按下 Ctrl+R ,你将看见这样的一个小窗口: ![8Kflaq.png](https://s1.ax1x.com/2020/03/13/8Kflaq.png) **点一下**那个按钮, 窗口是不是**消失**了呢? 如果是,那恭喜你, 你的第一个 Qt 程序成功啦!~~庆祝一下吧~~ 其实 `connect` 函数不是只有这种用法, 但是这种用法最常用,**其它的也可以用这个来代替**, 就只介绍这一种吧(雾。 在新建项目时,你会发现代码里自动创建了一个 UI 类, 其实它是可以**图形化设计**的, 但是它的**灵活性**远不及代码设计, 因为它很**简单**,所以读者可以自行尝试打开 Forms 目录下的 ui 文件, 进入设计界面设计窗口。 ------- ### Part 4 菜单栏与工具栏的动作 ------- Qt 使用 `QAction` 类来表示一个**活动**, 一般用它来处理**菜单栏和工具栏**的**各种事件**。 考虑到读者可能不了解菜单栏和工具栏的位置, 就在这里放个图示吧(徒手绘制,别嫌丑qwq): ![8MSKOA.png](https://s1.ax1x.com/2020/03/13/8MSKOA.png) 由于工具栏和菜单栏的**操作极其相似**(甚至可以说是相同), 所以**只用一个类**就可以实现对它们的控制。 - `QAction` 类的食用方法 > > ~~开袋即食~~ > > **声明**: > ```cpp > QAction *Aaction=new QAction(QIcon(""),tr("abc"),this); > //QIcon()中的参数代表图标的位置 > //在菜单栏中会显示在最前面 > //在工具栏中是主要组成部分。 > //tr()中的参数为对这个事件的描述,即显示在菜单栏上的名称 > //而在工具栏中,如果没有可用图片,就会显示这个文字代替。 > //其实也可以直接使用引号包裹的字符串 > //但是使用tr()之后可以利用 Qt 提供的文件翻译成多国语言。 > //this 即父节点。 > ``` > > **设置们**: > > ```cpp > Aaction->setShortcut(tr("Ctrl+H")); > //设置这个事件的快捷键 > //快捷键直接把键盘上的按键名称用加号连接即可。 > Aaction->setStatusTip(tr("Tip")); > //设置这个对于这个事件的描述,将会显示在窗口下方。 > //这个提示不需要对槽进行连接 > //但是要在最后调用statusBar()。 > ``` > > **连接槽**: > ```cpp > connect(Aaction, &QAction::triggered,this,&MainWindow::Baction); > //前面讲机制时已经讲过各个参数的意义 > //QAction::triggered 即为菜单栏或工具栏被的点击事件。 > 到此已经完成**对它的设置**,完整代码如下: \Header\mainwindow.h: ```cpp #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = 0); ~MainWindow(); private: void Baction(); QAction *Aaction; }; #endif // MAINWINDOW_H ``` \Source\main.cpp: ```cpp #include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); } ``` \Source\mainwindow.cpp: ```cpp #include <QAction> #include <QMenuBar> #include <QMessageBox> #include <QStatusBar>//显示提示用 #include <QToolBar> #include "mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { Aaction=new QAction(QIcon(""),tr("abc"),this); Aaction->setShortcut(tr("Ctrl+H")); Aaction->setStatusTip(tr("Tips")); connect(Aaction, &QAction::triggered,this,&MainWindow::Baction); } MainWindow::~MainWindow() { } void MainWindow::Baction() { //do something... } ``` 虽然现在**完成了设置**, 但是你会发现,窗口中并没有出现菜单栏或工具栏。 因为还没有把它添加到窗口中。 - **菜单栏**的**添加**方法: > > **类名** : `QMenu` ; > > **对于 `MainWindow` 类的主窗口**而言, > 可以使用函数 `menuBar()` > 来获得这个**窗口的菜单栏的指针**; > > **添加一个子目** : `addMenu(QString)` , > 这个子目在点击时只会展开下级目录, > **不会执行任何活动**。 > > **重点**:添加一个活动: `addAction(QAction)` 。 > 活动是**不能再向下展开**的,而子目下可以添加更多的子目。 - 工具栏的添加方法: > > **类名** : `QToolBar`,注意这里不是 `QTool`; > > 对于一个窗口而言, > 可以使用函数 `addToolBar(QString)` > 来为这个窗口**添加工具栏**,中间的文字并不会显示(雾; > > **重点**:添加一个**工具**(即**活动**): `addAction(QAction)` 。 另:在**主函数末尾**加上 `statusBar();` 来**显示StatusTips**。 至此,已经将它们插入窗口。 接下来还需要**导入图片**,作为图标。 `QIcon(QString)` 的参数是图片的地址, 但是直接输入**常规地址是找不到**的, 这是就需要在 Qt 项目中**加入存放图片的目录**。 为了让 Qt 找到图片的位置, 还需要借助一个 **QRC** 文件。 首先打开项目中**存放源代码**的文件夹, 找不到可以右键 Qt Creator 中的 Sources 目录, 选“在 Explorer 中显示”(如图): ![8QkWSx.png](https://s1.ax1x.com/2020/03/14/8QkWSx.png) 新建一个文件夹,命名为 images , 当然其它名字也行: ![8QArgP.png](https://s1.ax1x.com/2020/03/14/8QArgP.png) 把一个图片文件存入这个文件夹,格式为 .png (这里用你谷的 Logo 为例): ![8QAWNj.png](https://s1.ax1x.com/2020/03/14/8QAWNj.png) **回到刚才的目录**,创建一个 .qrc 的文件, **用记事本**打开: ![8QESv6.png](https://s1.ax1x.com/2020/03/14/8QESv6.png) 在里面输入以下内容,并**保存**: ``` <RCC> <qresource prefix="/"> <file>images/logo.png</file> </qresource> </RCC> ``` 其实有意义的不多, 就第 $3$ 行:声明**一个文件**,在/images目录下, 文件名为Logo.png 。 还要加入更多图片的话, 可以**继续在 `<qresource>` 标签内添加 `<file>` 标签**。 回到 Qt Creator,右键项目文件夹,选添加现有文件 (这时你的 Qt Creator 中是没有 Resources 目录的): ![8QVuWR.png](https://s1.ax1x.com/2020/03/14/8QVuWR.png) 选择刚才创建的那个 QRC 文件,打开: ![8QVs0S.png](https://s1.ax1x.com/2020/03/14/8QVs0S.png) **依次展开** Resources 目录, 找到 logo.png ,双击打开, 如果你看到的界面是这个样子的: ![8QZEct.png](https://s1.ax1x.com/2020/03/14/8QZEct.png) 那么恭喜,你的图片插入成功了。 接下来就可以**为工具栏添加图标**啦! **完整代码**如下: \Header\mainwindow.h: ```cpp #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = 0); ~MainWindow(); private: void Baction(); QAction *Aaction; }; #endif // MAINWINDOW_H ``` \Sources\main.cpp: ```cpp #include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); } ``` \Sources\mainwindow.cpp: ```cpp #include <QAction> #include <QMenuBar> #include <QMessageBox> #include <QStatusBar> #include <QToolBar> #include "mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { Aaction=new QAction(QIcon(":/images/logo.png"),tr("abc"),this); //在这里给这个事件设置了图标,用:代表项目目录 Aaction->setShortcut(tr("Ctrl+H")); Aaction->setStatusTip(tr("Tips")); connect(Aaction, &QAction::triggered,this,&MainWindow::Baction); QMenu *Menu=menuBar()->addMenu(tr("Something")); Menu->addAction(Aaction); QToolBar *Tool=addToolBar(tr("Something")); Tool->addAction(Aaction); statusBar(); } MainWindow::~MainWindow() { } void MainWindow::Baction() { //do something } ``` 窗口**图示**: ![8QZRED.png](https://s1.ax1x.com/2020/03/14/8QZRED.png) 菜单栏: ![8QZHDf.png](https://s1.ax1x.com/2020/03/14/8QZHDf.png) 工具栏: ![8QeuKx.png](https://s1.ax1x.com/2020/03/14/8QeuKx.png) ------- ### Part 5 QMessageBox ------- 一个**完整的**窗口应用程序, 一定不**只有一个窗口**, 除了主窗口之外, 还会有一些其它的**对话框**。 现在就先介绍**最简单**的一种: `QMessageBox` 头文件: `#include<QMessageBox>` `QMessageBox` 可分成**很多种类型**, 每种类型都**对应一种函数**,下面就分开讲解: - About窗口: > 定义:`void about(QWidget,const QString&,const QString)` > > 第一个参数表示**父节点**,即弹出它的窗口; > > 第二个参数表示**对话框标题**; > > 第三个参数表示对**话框显示的内容**。 > > 这个对话框**只有一个 OK 按钮**。 > > 这个对话框**没有图片**。 > > 这个对话框弹出时**没有音效**。 > > 执行 `QMessageBox::about(this,"About","About this:...");`的效果: > > ![8QlX3F.png](https://s1.ax1x.com/2020/03/14/8QlX3F.png) > - Information窗口: > 定义:`StandardButton information(QWidget, const QString&,const QString,StandardButtons, StandardButton)` > > 第一个参数表示**父节点**,即弹出它的窗口; > > 第二个参数表示**对话框标题**; > > 第三个参数表示**对话框显示的内容**。 > > 第四个参数表示对话框的**一些按钮**,默认为**OK**,**可以不填**。 > > 第五个参数表示对话框的**其它按钮**,默认为**没有**,**可以不填**。 > > 这个对话框有一个**蓝色 i 的信息图标**。 > > 这个窗口有弹出音效,是**系统设置的弹出信息**的音效。 > > 执行 `QMessageBox::information(this,"Information","Information:...");`的效果: > > ![8QUSkd.png](https://s1.ax1x.com/2020/03/14/8QUSkd.png) > - Critical窗口: > 定义: `StandardButton critical(QWidget*,const QString&,constQString&, StandardButtons, StandardButton)` > > 第一个参数表示**父节点**,即弹出它的窗口; > > 第二个参数表示**窗口标题**; > > 第三个参数表示**窗口内容**; > > 第四个参数表示窗口的**一个按钮**,默认为**OK**,**可以不填**; > > 第五个参数表示窗口的**另一个按钮**,默认为没有,可以不填。 > > 这个窗口有一个**红叉的错误图标**。 > > 这个窗口一般用来显示**严重错误**。 > > 这个窗口有弹出音效,是**系统设置的出错**的音效。 > > 执行 `QMessageBox::critical(this,"Error","Error:...");`的效果: > > ![8Q36W8.png](https://s1.ax1x.com/2020/03/14/8Q36W8.png) > - Warning窗口: > 定义: `StandardButton warning(QWidget*,const QString&,const QString&, StandardButtons, StandardButton)` > > 第一个参数表示**父节点**,即弹出它的窗口; > > 第二个参数表示**窗口标题**; > > 第三个参数表示**窗口内容**; > > 第四个参数表示窗口的**一些按钮**,默认为**OK**,**可以不填**; > > 第五个参数表示窗口的**另一些按钮**,默认为**没有**,**可以不填**。 > > 这个窗口有一个**黄色感叹号的警告图标**。 > > 这个窗口一般用来显示**警告**或**不严重错误**。 > > 这个窗口有弹出音效,是**系统设置的警告**的音效。 > > 运行 `QMessageBox::warning(this,"Warning","Warning:...");`的效果: > > ![8QJRyD.png](https://s1.ax1x.com/2020/03/14/8QJRyD.png) - Question窗口: > 定义: `StandardButton question(QWidget*,const QString&,const QString&,StandardButtons, StandardButton)` > > 第一个参数表示**父节点**,即弹出它的窗口; > > 第二个参数表示**窗口标题**; > > 第三个参数表示**窗口内容**; > > 第四个参数表示窗口的**一些按钮**,默认为**Yes和No**,**可以不填**; > > 第五个参数表示窗口的**另一些按钮**,默认为没有,**可以不填**。 > > 这个窗口有一个**蓝色问号的问题图标**。 > > 这个窗口一般用来显示问题,并**获取回答**。 > > 这个窗口**没有有弹出音效**。 > > 运行`QMessageBox::question(this,"Question","Do you want to...");`的效果: > > ![8QNsFs.png](https://s1.ax1x.com/2020/03/14/8QNsFs.png) 前 $4$ 种窗口多数情况下都**不需要获取用户的操作**, 但最后一种**需要获取用户的选择**, 所以接下来介绍如何**拉取用户点击的按钮**。 注意到后面几种对话框都是**有返回值**的, 而这个返回值就是用户点击了的按钮**对应的一个值**, 所以只需要判断**返回值**与那个**按钮对应的值**相等就可以了。 以前面**菜单栏和工具栏最后的那个程序**为例, 把 `void Baction()` 的内容更改为: ```cpp void MainWindow::Baction() { if(QMessageBox::question(this,tr("Question"),tr("Do you want to save?")) ==QMessageBox::Yes)//按下了Yes { QMessageBox::information(this,tr("Information"),tr("Yes"));//显示Yes } else { QMessageBox::information(this,tr("Information"),tr("No"));//显示No } } ``` 由于用户的这次选择操作**只需要使用一次**, 所以不需要用变量来存储。 运行后点击你谷的 logo 或按下 Ctrl+H, 就会弹出问题窗口。 效果图: ![8Q0Vvq.png](https://s1.ax1x.com/2020/03/14/8Q0Vvq.png) 按下Yes: ![8Q0YKx.png](https://s1.ax1x.com/2020/03/14/8Q0YKx.png) 按下No: ![8Q0csP.png](https://s1.ax1x.com/2020/03/14/8Q0csP.png) 但是有些时候不仅需要两个选项, 这是就需要对**可以不填**的**第四个参数**进行修改了。 由于 Qt 的每个按钮类型都对应**二进制下**的某一位上的 $1$ 。 而要让一个窗口**有多个按钮**,就只需要把它们用**按位或** (即 `|` 运算符,有一出一,无一出零)连接就可以表示多个按键啦! 接下来就尝试**高仿** Dev-C++ 在退出时有文件未保存的提示窗口吧! **预期效果**(大家都见过吧?): ![8QDM9J.png](https://s1.ax1x.com/2020/03/14/8QDM9J.png) 这三个按键都是`QMessageBox`**支持**的, 所以表示显示这三个按钮的数值就是 `(QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel)` 注意:**窗口被关闭时**,返回值为`QMessageBox::Cancel`。 现在就可以对 `Baction()` 函数再次进行修改: ```cpp void MainWindow::Baction() { int ret=QMessageBox::question(this,tr("Confirm"),tr("保存为......"), (QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel)); //这里因为需要进行两次判断,所以用了一个int来存储返回值。 if(ret==QMessageBox::Yes) { QMessageBox::information(this,tr("Information"),tr("Yes")); } else if(ret==QMessageBox::No) { QMessageBox::information(this,tr("Information"),tr("No")); } else { QMessageBox::information(this,tr("Information"),tr("Cancel")); } } ``` 这样就可以高仿 Dev-C++ 的退出窗口啦! 效果图: ![8Qy4vd.png](https://s1.ax1x.com/2020/03/14/8Qy4vd.png) **点击各个按钮**或**关闭窗口**都会有反馈。 ------- ### Part 6 QLabel ------- 前面了解了一些**窗口控件**, 但是你总不可能用它们来**显示信息**鸭! 所以现在就介绍 Qt 中显示**文字**和**图像**的 `QLabel`。 头文件 `#include<QLabel>` 简单使用:显示**一段文字** ```cpp QLabel *label=new QLabel(this);//定义QLabel类的指针并创建新的QLabel //将父节点设置为this,即显示在当前窗口上。 label->setText(tr("abc"));//设置显示的文字 ``` 还是以**前面菜单栏和工具栏最后的那个程序**为例, 修改 `MainWindow::MainWindow(QWidget *parent)` 为: ```cpp MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { Aaction=new QAction(QIcon(":/images/logo.png"),tr("abc"),this); Aaction->setShortcut(tr("Ctrl+H")); Aaction->setStatusTip(tr("Tips")); connect(Aaction, &QAction::triggered,this,&MainWindow::Baction); QMenu *Menu=menuBar()->addMenu(tr("Something")); Menu->addAction(Aaction); QToolBar *Tool=addToolBar(tr("Something")); Tool->addAction(Aaction); QLabel *label=new QLabel(this); label->setText(tr("abc")); statusBar(); } ``` 在中间加入了上面的两行代码, 效果图: ![8QgTbR.png](https://s1.ax1x.com/2020/03/14/8QgTbR.png) 可以看到, 文字显示的**位置**并不理想, 和菜单栏**冲突**了。 为了**移动**它,就需要引入 `move(int,int)`函数, 它在很多控件中都有定义, 作用时移动当前控件**到 (x,y) 位置**, 这个坐标系得原点是**它的父节点的左上角**。 坐标加一,表示右移或下移 $1$ **像素**, 坐标减一,表示左移或上移 $1$ **像素**。 注意这里的两个参数都是**非负数**。 所以在代码后加上`label->move(0,50);` 就可以把它**向下移动**到正确的位置啦! 这个位置**可能在不同的电脑上有不同的效果**。 再把`MainWindow::MainWindow(QWidget *parent)` 修改为: ```cpp MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { Aaction=new QAction(QIcon(":/images/logo.png"),tr("abc"),this); Aaction->setShortcut(tr("Ctrl+H")); Aaction->setStatusTip(tr("Tips")); connect(Aaction, &QAction::triggered,this,&MainWindow::Baction); QMenu *Menu=menuBar()->addMenu(tr("Something")); Menu->addAction(Aaction); QToolBar *Tool=addToolBar(tr("Something")); Tool->addAction(Aaction); QLabel *label=new QLabel(this); label->setText(tr("abc")); label->move(0,50);//移动label statusBar(); } ``` 效果图: ![8Q2HyQ.png](https://s1.ax1x.com/2020/03/14/8Q2HyQ.png) 位置不就好多了! 最后的说明: `QLabel` 中的文字是**不可以选中**的! ------- ### Part 7 QFont ------- `QLabel`设置完毕,可以在窗口中**显示文字**, 但是你真的愿意食用 Qt **默认的字体**(宋体)吗? 你真的愿意只让字体有这种大小吗? 你真的愿意字体中规中矩,没有斜体粗体吗? 相信你是不愿意的。 为了设置字体,这里需要引进一个新的类: `QFont` 。 头文件 `#include<QFont>` 支持的操作: 1. `setFamily(const QString&)` > 作用:设置**字体**;其中参数为字体的名称,如“宋体”。 2. `setPixelSize(int)` > 作用:设置字体**大小**(按照**像素**)。 3. `setPointSize(int)` > 作用:设置字体**大小**(你**在Word中的**设置的大小)。 4. `setUnderline(bool)` > 作用:true为加**下划线**,false为不加。 5. `setStrikeOut(bool)` > 作用:true为加**删除线**,false为不加。 6. `setOverline(bool)` > 作用:true为在**文本上方添加线**(真不知该叫什么),false为不加。 7. `setItalic(bool)` > 作用:true为**斜体**,false为正常。 8. `setBold(bool)` > 作用:true为**粗体**,false为正常。 其实还有一些操作可用,只是不常用, 所以这里就只介绍这 $8$ 种。 对于很多控件,它们都支持 `setFont(QFont&)` 操作, 这是 `QFont` 就能派上用场,美化界面了。 ------- ### Part 8 QTimer ------ 相信你也发现了, Qt 的窗口控件都是**一次成形**的, 那么如果窗口中有需要**动态更新**的控件怎么办呢? 因此还需要引进一个类: `QTimer` **头文件**: `#include<QTimer>` **声明方法**: `QTimer *timer=new QTimer();` **作用**:顾名思义, 这就是一个**计时器**。 但是这不是一个简单的计时器, 它在计时过程中, 且程序正常运行时 ~~废话~~, 在时间到的时候, 它会**发出一个 `timeout` 的信号**, 而通常**利用**的就是它的**这个信号**。 设置时间**并开始**:`timer->start(int);`。 其中参数表示时长, 单位**毫秒**(ms)。 连接槽函数: `connect(timer,&QTimer::timeout,this,&MainWindow::Baction);` 这个时长**可以为 0** 但**不能为负数**。 一下为持续执行活动的代码 ```cpp QTimer *timer=new QTimer(); timer->start(0); connect(timer,&QTimer::timeout,this,&MainWindow::Baction); ``` 其中 `Baction` 为 `MainWindow` 类中**自定义**的一个函数, 这个函数将会在 `MainWindow` 被在 main.cpp 中**声明后**开始**持续循环调用**。 ------- ### Part 9 一些复杂一点的其它窗口 ------- 对于一个窗口应用程序, 弹出的其它**窗口**肯定不只是像 `QMessageBox` 一样**简单**, 它可能会包含一些**复杂的东西**。 放在这里,是因为在这些复杂的窗口中, 可能会用到之**前介绍到的一些东西**。 首先你应该知道, 前面的 `QLabel` 也好, `QMessagesBox` 也好, 在它们的设置中都有一个**父节点指针**, `QMessageBox` 可能看不出它的用处, 但是 `QLabel`中,它的作用就**十分明显**了, 因为它将显示在**父节点指针指向的窗口**上。 我们常用 `QDialog` 类来实现弹窗, 但是 `QDialog` 类**不能作为一个父节点**, 所以这是就需要借助到 `QWidget` 类, 因为父节点指针就是 `QWidget` 类的。 `QWidget` **头文件**: `#include<QWidget>` `QDialog` **头文件**: `#include<QDialog>` `Qwidget` 其实本身就可以**当作一个窗口**来使用, 但是 `QWidget` 只能创建**非模态窗口**, 而 `QDialog` 可以**自由选择**模态和非模态。 解释一下**模态**和**非模态**窗口: 当一个**模态**窗口被创建后, 它的父节点指向的窗口是**非活动**的, 即,**不能**对它**进行任何操作**, 所以模态窗口就要求用户**先处理弹窗**后才能继续其它操作。 因此创建模态窗口的 `exec()` 函数会**等待窗口被关闭后才退出**。 而**非模态**窗口则相反, 它的父节点是活动的, 即,即使弹出了这个窗口, 用户也可以**再次选中**它的父节点指向的窗口, 并可以**不受影响地**进行其它操作, 之后再来处理弹窗。 因此创建模态窗口的 `show()` 函数的作用也就只是显示窗口, **不会阻碍**代码的继续运行。 其实让 `QDialog` 和 `QWidget` 产生联系也就两行代码, 其它情况下当作QWidget处理就行了。 ```cpp QDialog *dialog=new QDialog(this); QWidget *widget=new QWidget(dialog); ```` 接下来所有的控件都可以像这个 `QWidget` 添加, 而 `QWidget` 本身就**可以作为父节点**, 所以以切都变得简单起来…… 示例:向这个窗口插入一个 `QLabel` ```cpp QLabel *label=new QLabel(widget);//由于Parent指针本身就是QWidget类型的 //所以可以直接将widget作为父节点,使这个label与dialog产生关系 //或者说是让它显示在dialog上。 label->setText(tr("aaa")); ``` 对于 `QWidget` 的操作和 `MainWindow` 的操作**相差不大**, 只是**不能使用 `menuBar()` 了**。 最后是产生窗口的方法: **非模态**窗口: ```cpp dialog->show(); ``` **模态**窗口: ```cpp dialog->exec(); ``` 最后附上一个**完整代码**, 便于理解: \Header\mainwindow.h: ```cpp #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QApplication> #include <QPushButton> #include <QMenu> #include <QToolBar> #include <QWidget> #include <QDialog> #include <QLabel> #include <QTimer> class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private: }; #endif // MAINWINDOW_H ``` \Sources\main.cpp: ```cpp #include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); } ``` \Sources\mainwindow.cpp: ```cpp #include "mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { QDialog *dialog=new QDialog(this); QWidget *widget=new QWidget(dialog); QLabel *label=new QLabel(widget); label->setText(tr("aaa")); dialog->exec();//模态 dialog->show();//非模态 } MainWindow::~MainWindow() { } ``` 效果图: 先弹出一个**模态窗口**, 主窗口并**没有弹出**,说明模态窗口**阻拦了代码**的继续运行: ![8l0N0f.png](https://s1.ax1x.com/2020/03/14/8l0N0f.png) 弹出一个**非模态窗口**,主窗口**弹出**, 且**主窗口被选中**,说明主窗口**后弹出**, 所以非模态窗口并**未对代码的继续运行产生任何影响**: ![8l0r1s.png](https://s1.ax1x.com/2020/03/14/8l0r1s.png) ------- ### Part 11 Qt 中的一些其它常用控件及食用方法 ------- 1. `QPushButton` > > 早在Part 3介绍机制时,QPushButton就已经被提到。 > > **头文件**:`#include<QPushButton>` > > 常见**声明方法**:`QPushButton *btn=new QPushButton(QWidget*)` 其中参数为**父节点**。 > > 常用的设置: >> `setText(const QString&)`:设置**按钮上的文字**; >> >> `setParent(QWidget*)`:设置**父节点**; >> >> `resize(int,int)`:设置**宽、高**,即大小; >> >> `move(int,int)`:设置**位置**; >> >> `setFont(const QFont&)`:设置文字的**字体**。 > 常用的信号: >> `clicked`:按钮**被按下**。 > 2. `QLineEdit` > > 除了处理按钮,程序当然还要**处理文字输入**,这时就需要借助 `QLineEdit` 来输入文字了。 > > **头文件**:`#include<QLineEdit>` > > 常见**声明方法**:`QLineEdit *edit=new QLineEdit(this);` 其中参数为**父节点**。 > > 常用的设置: >> `setParent(QWidget*)`:设置**父节点**; >> >> `resize(int,int)`:设置**宽、高**,即大小; >> >> `move(int,int)`:设置**位置**; >> >> `displayText()`:返回一个 `QString` ,表示当前输入**框中的文本** > 常用的信号: >> `returnPressed`:光标在输入框内,且**回车被按下**,一般用来认定输入结束。 > 3. `QCheckBox` > > 有时需要用户**进行多选**,一直弹窗肯定不好,食用 `QCheckBox` 来进行选择,就方便多了。 > > **头文件**:`#include<QCheckBox>` > > 常见**声明方法**:`QCheckBox *checkbox=new QCheckBox(this);`,其中参数为**父节点**。 > > 常见的设置: >> `setParent(QWidget*)`:设置**父节点**; >> >> `toggle()`:使它进入**选中**状态; >> >> `setEnable(bool)`:true为**允许更改**,false为不允许更改,默认为true; >> >> `isChecked()`:返回值为bool,表示**是否选择**。 >> >> `resize(int,int)`:设置**宽、高**,即大小; >> >> `move(int,int)`:设置**位置**; > 最后再附上一个**综合应用**的示例, 读者也可以**试着自己写一下**: 用一个文本输入框来**获取输入**, 并把输入的文字**同步**到下方的一个位置。 用一个 CheckBox **是否选中**来决定**是否显示**同步的文字。 **示例代码**: \Header\mainwindow.h ```cpp #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QApplication> #include <QPushButton> #include <QMenu> #include <QToolBar> #include <QWidget> #include <QDialog> #include <QLabel> #include <QTimer> #include <QLineEdit> #include <QCheckBox> class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private: //建议像这样,把需要再mainwindow中用到的变量都定义在这个类的private里面 //一是更安全,二是Qt会为定义再这里的变量使用另一种颜色标记。 QLabel *label; QLineEdit *edit; QCheckBox *checkbox; void changeText();//槽函数就必须定义在这里了 //不然connect没有信号接收者(难不成些nullptr?) }; #endif // MAINWINDOW_H ``` \Sources\main.cpp ```cpp #include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); } ``` \Sources\mainwindow.cpp ```cpp #include "mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { checkbox=new QCheckBox(this); checkbox->move(20,100); checkbox->resize(100,20); checkbox->setText("Show?"); checkbox->toggle(); this->resize(200,200); edit=new QLineEdit(this); edit->resize(190,20); label=new QLabel(this); label->move(0,30);//注意label的位置不能和CheckBox的位置冲突 //否则会导致无法选中CheckBox label->resize(190,20);//以上为初始化窗口控件 QTimer *timer=new QTimer(); timer->start(0);//为了持续更新,使用QTimer计时0ms //即循环不断执行槽函数。 connect(timer,&QTimer::timeout,this,&MainWindow::changeText);//连接信号和槽 } MainWindow::~MainWindow() { } void MainWindow::changeText() { if(checkbox->isChecked())//判断是否选择同步展示 label->setText(edit->displayText());//是就更新文字为LineEdit里的文字 else label->setText(tr(""));//否则设置为空串,即可达到不显示的效果。 } ``` **效果图**: **同步**显示: ![8lTXaF.png](https://s1.ax1x.com/2020/03/14/8lTXaF.png) **不同步**显示: ![8l7EIe.png](https://s1.ax1x.com/2020/03/14/8l7EIe.png) ------- ### Part 12 一个简单的项目实例 ------- 既然了解了这么多,何不**写个小程序**练练手? 这里就带着读者编写一个**简单的计时器**。 首先需要考虑,一个计时器它**需要些什么**? 你需要让它在时间到的时候**制造一些动静**, 例如弹出一个窗口。 你需要让它在窗口**显示剩余时间**, 你还需要让它能够**设置时间**。 这样大致的思路就比较清晰了: 利用菜单栏来**激活**时间的**设置**, 而设置时间应该**弹出一个新的窗口**。 这个窗口应该是**模态**窗口, 因为用户要么设置一个时间, 要么就取消, **不能同时进行其它操作**。 开始计时也可以利用菜单栏来实现。 要持续更新时间, 就要用到 `QTimer` 来实现**持续调用**更新函数, 在时间到的时候需要**弹出一个窗口**提示, 这个窗口应该**能够发出提示音**, 如 `QMessageBox::information` 。 事不宜迟, 现在就开始行动吧! 下面是**完整代码**: \Header\mainwindow.h ```cpp #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QLabel> #include <QTimer> #include <QAction> #include <QMenuBar> #include <QLineEdit> #include <QMessageBox> #include <QPushButton> #include <QByteArray> #include <QDialog> #include <QWidget> #include <QFont> #include <cstring> #include <cstdio> #include <ctime>//各种头文件(大部分都用了吧……) class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private://还是建议把要用的变量定义在这里 QLabel *TimeLabel; QDialog *SetTimeDialog; QLineEdit *InputTimeEdit; QWidget *SetTimeWidget; QPushButton *ButtonYes,*ButtonNo; QFont TimeFont,OtherFont; QAction *BeginAction,*SetAction; QTimer *timer; void Unpdate_Time(); void Begin_Timing(); void Set_Time(); void Get_Time(); }; #endif // MAINWINDOW_H ``` \Sources\main.cpp ```cpp #include "mainwindow.h" #include <QApplication> //其实main.cpp自始至终都没有改变 //因为它的作用就是创建窗口,显示窗口,获取返回值。 int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); } ``` \Source\mainwindow.cpp ```cpp //为了方便理解,窝还特意把代码的层次划分得十分明显 #include "mainwindow.h" bool f=0; int Last_Time=0,Remaining_Time=0,STime=0;//存储有关时间的变量 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { this->setWindowTitle(tr("Timer"));//设置窗口标题 this->resize(1375,760);//设置窗口大小 TimeFont.setPixelSize(400);//设置字体大小 OtherFont.setPixelSize(20); TimeLabel=new QLabel(this);//初始化 TimeLabel->setText(tr("00:00"));//设置初始显示字体,未设定默认为0 TimeLabel->setAlignment(Qt::AlignCenter);//设置文字居中对齐 TimeLabel->resize(1375,760);//设置大小,可以根据电脑分辨率适当调整 TimeLabel->setFont(TimeFont);//设置字体 BeginAction=new QAction(QIcon(),tr("Start timing"),this);//开始计时的事件 BeginAction->setShortcut(tr("Ctrl+B")); BeginAction->setStatusTip(tr("Start timing.")); connect(BeginAction,&QAction::triggered,this,&MainWindow::Begin_Timing); //连接槽函数 SetAction=new QAction(QIcon(),tr("Set Time"),this);//设置时间的事件 SetAction->setShortcut(tr("Ctrl+S")); SetAction->setStatusTip(tr("Set Time.")); connect(SetAction,&QAction::triggered,this,&MainWindow::Set_Time);//连接槽函数 QMenu *menu=menuBar()->addMenu(tr("Start")); menu->addAction(BeginAction); menu=menuBar()->addMenu(tr("Set")); menu->addAction(SetAction); //这里把两种操作分在不同的菜单下,当然你也可以为了方便分在一起 timer=new QTimer(); timer->start(0);//持续更新 connect(timer,&QTimer::timeout,this,&MainWindow::Unpdate_Time);//连接 statusBar();//设置好StatusTip } MainWindow::~MainWindow() { } void MainWindow::Begin_Timing() { f=1;//更改为正在计时状态 } void MainWindow::Set_Time() { SetTimeDialog=new QDialog(this); SetTimeWidget=new QWidget(SetTimeDialog); SetTimeDialog->resize(230,100); SetTimeWidget->resize(230,100);//设置窗口大小,注意Dialg要和Widget相同 SetTimeDialog->setWindowTitle(tr("Plaese set time"));//设置窗口标题 InputTimeEdit=new QLineEdit(SetTimeWidget);//初始化输入框 InputTimeEdit->resize(150,30);//设置大小 InputTimeEdit->move(10,10);//移动位置 InputTimeEdit->setFont(OtherFont);//设置输入框中的字体 QLabel *Tip=new QLabel(SetTimeWidget);//显示单位用 Tip->setFont(OtherFont); Tip->setText(tr("(s)")); Tip->move(175,10); Tip->resize(50,30); ButtonNo=new QPushButton(SetTimeWidget); ButtonYes=new QPushButton(SetTimeWidget);//初始化两个按钮 ButtonNo->resize(80,30); ButtonYes->resize(80,30);//设置两个按钮的大小,为了和谐美,它们应该尽量相同 ButtonNo->move(110,50); ButtonYes->move(10,50);//移动到各自位置,一般确定在前 ButtonNo->setText(tr("Cancel")); ButtonYes->setText(tr("OK"));//设置文字 ButtonNo->setFont(OtherFont); ButtonYes->setFont(OtherFont);//设置字体,PushButton默认居中对齐 connect(ButtonNo,&QPushButton::clicked,SetTimeDialog,&QDialog::close); //取消就直接关闭 connect(ButtonYes,&QPushButton::clicked,this,&MainWindow::Get_Time); //确定就获取信息 connect(ButtonYes,&QPushButton::clicked,SetTimeDialog,&QDialog::close); //当然也要关闭 connect(InputTimeEdit,&QLineEdit::returnPressed,this,&MainWindow::Get_Time); connect(InputTimeEdit,&QLineEdit::returnPressed,SetTimeDialog,&QDialog::close); //习惯是按回车结束输入,为了方便,这里也让按下回车作为确定的标志 SetTimeDialog->show(); } void MainWindow::Unpdate_Time() { if(Remaining_Time<0)//到了0之后再减一秒,为了防止弹窗不受控制地弹出(初始值为0嘛!) { Remaining_Time=STime; QMessageBox::information(this,tr("Time out!"),tr("!!!Time out!!!"));//提示 f=0;//调回初始状态 } int m,s; char str[10]; m=Remaining_Time/60; s=Remaining_Time%60; sprintf(str,"%02d:%02d",m,s);//格式化写入字符串,Dev也可以用 //写入的效果类似printf输出的效果 TimeLabel->setText(str);//修改文字 if(!f) return; if(Last_Time==0)//等于零说明刚开始,等待到下一个整秒保证精度 { Last_Time=time(NULL); return; } if(time(NULL)!=Last_Time) { Remaining_Time--;//剩余时间减一 Last_Time=time(NULL);//调整为新时间 } } void MainWindow::Get_Time() { char *str; QByteArray ba=InputTimeEdit->displayText().toLatin1(); //利用QByteArray作为中转,把QString转换到char*,方便处理 str=ba.data(); Remaining_Time=0; for(int i=0;i<strlen(str);i++) { Remaining_Time=Remaining_Time*10+str[i]-'0';//就像快读里的一样,识别数字 //但是这里没有判断,你也可以自己加上判断语句 //过滤无用信息,防止乱码 } STime=Remaining_Time;//复制一遍到这个变量,防止忘记~~ //(其实是之后要用,你总不希望每次都重复地设置相同的时间吧) } ``` **效果图**: ![881OGq.png](https://s1.ax1x.com/2020/03/15/881OGq.png) 设置: ![881jzV.png](https://s1.ax1x.com/2020/03/15/881jzV.png) 计时中: ![881xMT.png](https://s1.ax1x.com/2020/03/15/881xMT.png) 结束提示: ![883hk9.png](https://s1.ax1x.com/2020/03/15/883hk9.png) 要是你也成功了的话, 那恭喜你,你已经成功**完成了 Qt 的入门**! ~~庆祝一下吧~~ ------- ### 写在最后的话 ------- 其实 Qt 中预置的类**远不止**这么一点儿, 它们都有**各自的用**处,读者可以自己了解, 这篇文章也只能帮助你**了解 Qt** , 并学到**一点点**的用法 这些确实只是 **Qt 表面**的一些东西, 如果你想用 Qt 来**开发游戏**, 那确实很**困难**, 因为你还需要详细地学习 **Qt 的绘制系统**, 虽然不得不承认它的绘制系统算法很好, 据说可以同时处理百万对象还不卡顿…… 如同 Part 2 所提到的那样, Qt 是**由一个个组件构成**的, 不会某个组件**不会影响**你的制作, 学习它就像是在学习如何使用一个个组件, 而这些组件太多了…… 但是利用 Qt 做一些**简单的界面**还是**比 Win32 简单**很多的。 相信你也可以做出属于自己的窗口应用程序! 小提示:显示**中文可能出现乱码**(如果你**乱动了设置**的话,~~叫你别乱调~~), 只需要进行如下修改 **工具→选项→文本编辑器→文件编码→默认编码:UTF-8, UTF-8 BOM:目前存在了则保留** 就可以了 ------- ### 完结撒花 *★,°*:.☆( ̄▽ ̄)☆.:*°★,* 。 -------