Qt快速上手指南
Transparent
2020-03-13 20:04:09
-------
### 写在前面的话
-------
你是否想过要让你的程序**图形化**?
你是否想过要让你的程序像其他程序一样**拥有界面**,
拥有**多个对话框**,可以让别人方便地使用?
除了再控制台上做手脚,
你还可以试试 **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:目前存在了则保留** 就可以了
-------
### 完结撒花 *★,°*:.☆( ̄▽ ̄)☆.:*°★,* 。
-------