文章目录
本文是我在学习QT的GUI界面设计过程当中的心得和学习笔记,在学习时已经有C, C++,Python的基础。文章附上了学习的代码,仅供大家参考。如果有问题,有错误欢迎大家留言。此外,博主还有另外几篇文章,分别关于 Python基础知识、 Python的具体应用、 C语言指针结构体的难点、 C++入门和进阶知识点和 C++高阶知识点,大家点击即可翻阅。
一、Qt简介和下载安装
Qt是一个1991年由Qt Company开发的跨平台C++图形用户界面(Graphic User Interface, GUI)应用程序开发框架。QT包括但不仅限于GUI的开发,也包含了诸如系统调用、网络编程、数据库编程,2D/3D图形处理等等。QT具有强大的跨平台运行的性能,几乎囊括了所有的操作系统,例如Linux、Windows、Mac OS、Android、IOS。我们所熟知的金山WPS、Google Earth谷歌地图、SKype网络电话就是用Qt开发的。
博主使用的版本是Qt 5.14.2, 下载、安装 参照B站视频。
二、Qt入门
2.1 创建第一个项目
第一步,选择new->Application->Qt Widgets Application->Choose:
第二步,修改项目名称和项目路径,点击下一步。
第三步,修改类名称,其中基类有三种,分别是QMainWindow(菜单类), QWidget, QDialog(对话框类),表示创建的类继承的基类,例如,图中所示mywidget类的父类就是QWidget。QDialog和QMainWindow是QWidget的子类。QMainWindow是菜单类,左上角有一些菜单选项,右上角有最小化最大化按钮。QDialog是对话框类,下图所示就是一个对话框类。
第四步,选择MinGW 64-bit 编译器,32位和64位的区别在于32位能在64位的机器上跑,64位不能在32位的机器上跑,初始项目选择任意一个就可以,点击下一步,然后在点击完成,就可以产生一个名为Qt_test的项目,项目底下有一个Qt_test.pro的项目文件。
之后的步骤默认就可以,一直点下一步,然后编译运行,出现一个空白窗口,创建完毕。
编译成功后,会在项目目录底下生成build文件,然后点击debug文件,里面有生成的.exe可执行文件,点击即可运行,结果就是一个空白图窗。博主在运行.exe的时候碰到了错误弹窗,显示程序“无法找到入口”,添加了环境变量之后还需要将环境变量上移,具体解决参考解决Qt生成exe错误:无法定位程序输入点。
# QT_hello.pro文件
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
# 版本大于4以上的添加widgets模块
CONFIG += c++11 # 用C++11标准来解释代码
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
mywidget.cpp
HEADERS += \
mywidget.h
TARGET = UAV # 生成的.exe文件名称
TEMPLATE = app # 应用程序模板 Application
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
// mywidget.h文件
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QWidget>
class mywidget : public QWidget // 公共继承
{
Q_OBJECT // Q_OBJECT宏,允许类中使用信号和槽的机制
public:
mywidget(QWidget *parent = nullptr); // 构造函数
~mywidget(); // 析构函数
};
#endif // MYWIDGET_H
// main.cpp文件
# include <QApplication>
# include <QtWidgets>
# include <QDebug>
// main程序入口 argc命令行变量数量, argv命令行变量的数组
int main(int argc, char *argv[])
{
QApplication a(argc, argv); // 应用程序对象, 在QT中,应用程序对象有且仅有一个
mywidget w; // 窗口对象 mywidget是Qwidget的子类
w.show(); // show方法, 窗口对象默认不会显示
qDebug()<<"hello world"; // 在控制台输出, 用于调试
return a.exec(); // 让应用程序对象进入消息循环
}
// mywidget.cpp文件
#include "mywidget.h"
mywidget::mywidget(QWidget *parent)
: QWidget(parent)
{
}
mywidget::~mywidget()
{
}
2.2 快捷键和命名规范
QT如下,可以提高编码效率:
/* 快捷键
* 运行代码: Ctrl + r
* 编译: Ctrl + b
* 注释: Ctrl + /
* 缩放字体:Ctrl + 滚轮
* 查找/替换字体: Ctrl + f
* 整行移动代码: Ctrl + Shift + 上/下键
* 自动对齐:Ctrl + i
* 在同名文件和源文件之间切换: F4
* 快速添加函数定义:鼠标移到声明的那一行,按Alt + Enter
* 修改变量名,并应用到所有:Ctrl + Shift + r
* 快捷打开输出窗口: Alt + number(1-8)
* 书签功能: 快速跳到代码
* Ctrl + M 添加/删除标签
* Ctrl + . 查找并移动到下一个标签处
* 查看帮助文档:
* 第一种:Qt Creator 查看 F1
* 第二种:独立的帮助文档程序查看
*/
/*
类名: 首字母大写,单词和单词之间首字母大写
函数名和变量名称: 首字母小写,单词和单词之间首字母大写
*/
Qt设计师创建的文件后缀为.ui, 无法直接运用到C++中,因此引入一个uic工具,可以将.ui文件转换为.c文件。rcc moc同样是这样类型的工具,将一些qt文件转换成C++语法格式的文件。
2.3 Qt项目和VS2022项目相互转换
博主最近需要使用QT和VS 2022联合编程,大家有需要也可以参考视频 VS项目和QT项目相互转换。
使用VS2022创建的QT项目,输出为.pro文件,利用Qt createor打开,需要在.pro文件中加载模块(添加如下代码),因为VS2022是在项目配置的时候加载的。如下图所示。
# ----------------------------------------------------
# This file is generated by the Qt Visual Studio Tools.
# ------------------------------------------------------
QT += core gui widgets
# 模块加载, 使用VS2022创建的项目,输出为.pro文件,利用Qt createor打开,需要在.pro文件中加载模块
# 因为VS2022是在项目配置的时候加载的。
TEMPLATE = app
TARGET = QtWidgetsTest
##########################################
# 以下代码可以不要
DESTDIR = ../x64/Debug
CONFIG += debug
LIBS += -L"."
DEPENDPATH += .
MOC_DIR += GeneratedFiles/$(ConfigurationName)
OBJECTS_DIR += debug
UI_DIR += GeneratedFiles
RCC_DIR += GeneratedFiles
###########################################
HEADERS += ./qtwidgetstest.h
SOURCES += ./qtwidgetstest.cpp \
./main.cpp
FORMS += ./qtwidgetstest.ui
RESOURCES += qtwidgetstest.qrc
w.setWindowTitle(u8"VS2022 QT 窗口"); // 不乱码
/*
产生乱码, 英文不会有乱码,英文编码格式都是同意的ASCII,QT中文编码格式是UTF-8,
windows中文编码格式是GB2312,u8为转换成UTF-8,QT就可以识别了
*/
三、Qt基础
3.1 Qt对象树和窗口坐标系概念
当创建的对象在堆区的时候,如果指定的父亲是QObject派生下来的类或者QObject子类派生下来的类,可以不用管理释放操作,QT会对象会放入到对象树中,会自动释放内存,一定程度上简化了内存回收机制。这也是QT的优点之一,因此,我们在构造时候就指定parent对象,就不需要操心内存释放问题。
Qt窗口的坐标系:以左上角顶点为原点(0, 0),X向右增加,Y向下增加。对于嵌套窗口,其坐标系是相对于父窗口而言。
3.2 QPushButton
在编写这部分代码时,博主的编辑器竟然没有代码补全功能,于是又在网上找了解决办法,这里给出链接。
在学习QT的各种类的过程中,最重要的是 学会如何查找帮助文档以及看懂帮助文档。例如QPushButton类,帮助文档中给出详细解释:添加头文件,同时要在.pro文件中加入widgets模块,其父类是QAbstractButton,其子类是QCommandLinkButton等等信息。
3.3 信号和槽(signals and slots)
3.3.1 pushbutton关闭窗口
信号和槽是学习Qt的一个非常重要知识点,在信号和槽当中,我们引入一个连接函数connect( ),将信号发送者和信号接收者链接起来。connect( )一共有四个参数:
- 参数1:信号发送者;
- 参数2:发送的信号(函数地址);
- 参数3:信号接收者;
- 参数4:处理的槽函数(函数地址)。
在空白项目的基础上改写mywidget.cpp函数,实现点击按钮,关闭窗口案例:
# include "mywidget.h"
# include <QPushButton>
# include <QDebug>
mywidget::mywidget(QWidget *parent)
: QWidget(parent)
{
qDebug() << "hello world"; // 调试信息
// 创建一个按钮
QPushButton * btn = new QPushButton;
//btn->show(); // show以顶层(新窗口)的方式弹出窗口控件
btn->setParent(this); // 让btn对象依赖在mywidget窗口中
btn->setText("第一个按钮");
QPushButton *btn2 = new QPushButton("第二个按钮",this);
btn2->move(100,100); // 移动btn2按钮
resize(600,400); // 重置窗口大小
btn2->resize(50,50); // 设置btn2按钮大小
setWindowTitle("第一个窗口应用"); // 设置窗口名称
connect(btn,&QPushButton::clicked, this, &mywidget::close);
}
mywidget::~mywidget()
{
}
3.3.2 自定义信号和槽
创建一个下课,老师饿了,学生请客的案例。添加自定义的老师类和学生类,选择C++类,这个两个类不是窗口类,直接继承QObject类。分别产生了teacher和student的.cpp .h文件。
头文件中定义,变量名称和函数声明,在.cpp文件中写实现。
// mywidget.h
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QWidget>
# include "teacher.h"
# include "student.h"
class mywidget : public QWidget // 公共继承
{
Q_OBJECT // Q_OBJECT宏,允许类中使用信号和槽的机制
public:
mywidget(QWidget *parent = nullptr); // 构造函数
~mywidget(); // 析构函数
Teacher *t; // 在头文件中声明变量和函数
Student *s;
void ClassIsOver();
};
#endif // MYWIDGET_H
// student.h
#ifndef STUDENT_H
#define STUDENT_H
#include <QObject>
class Student : public QObject
{
Q_OBJECT
public:
explicit Student(QObject *parent = nullptr);
signals:
public slots:
/* 早期的QT版本必须要写到,public slots,高级版本可以写到public或全局下
* 返回值void,需要声明,也需要实现
* 可以有参数,可以发生重载
*/
void treat();
void treat(QString foodName);
};
#endif // STUDENT_H
// teacher.h
#ifndef TEACHER_H
#define TEACHER_H
#include <QObject>
class Teacher : public QObject
{
Q_OBJECT
public:
explicit Teacher(QObject *parent = nullptr);
signals:
/* 自定义信号类,没有返回值
* 只需要声明,不需要实现
* 可以有参数,可以重载
*/
void hungry();
void hungry(QString foodName);
public slots:
/* 早期的QT版本必须要写到,public slots,高级版本可以写到public或全局下
* 返回值void,需要声明,也需要实现
* 可以有参数,可以发生重载
*/
};
#endif // TEACHER_H
// mywidget.cpp
# include "mywidget.h"
# include <QPushButton>
# include <QDebug>
mywidget::mywidget(QWidget *parent)
: QWidget(parent)
{
// 创建老师和学生对象
this->t = new Teacher(this);
this->s = new Student(this);
// 无参
// //ClassIsOver(); // 调用下课函数 没有链接,没有任何响应
// connect(t,&Teacher::hungry, s, &Student::treat); // 先链接后触发信号,才能响应
// ClassIsOver(); // 调用下课函数
// 链接代参数的函数,
void (Teacher:: *f1)(QString) = &Teacher::hungry;
void (Student:: *f2)(QString) = &Student::treat;
// connect(t,f1, s, f2); // 因为发生了函数重载,不能简单的用取地址符,编译器判断不了是哪个函数,用函数指针
ClassIsOver(); // 调用下课函数
// 点击一个按钮, 触发下课
QPushButton *btn = new QPushButton("下课",this);
this->resize(800,600); // 重置窗口大小
connect(btn,&QPushButton::clicked,this, &mywidget::ClassIsOver);
// disconnect(btn,&QPushButton::clicked,this, &mywidget::ClassIsOver); // 断开链接
/* 1、信号可以链接信号
* 2、一个信号可以链接到多个槽函数
* 3、多个信号可以链接到一个槽函数
* 4、信号和槽函数的参数必须类型一一对应(槽函数要接收信号的参数)
* 5、信号参数个数可以多于槽函数参数个数,但是类型也要一一对应
*/
// QT5 6 向下兼容 QT4版本以前的信号和槽的链接方式
connect(t,SIGNAL(hungry()), s, SLOT(treat(QString))); // 优点,直观,缺点,类型不做检测
}
mywidget::~mywidget()
{
}
void mywidget::ClassIsOver()
{
// emit t->hungry();
emit t->hungry("宫保鸡丁");
}
// student.cpp
#include "student.h"
# include <QDebug>
Student::Student(QObject *parent) : QObject(parent)
{
}
void Student::treat()
{
qDebug() << "请老师吃饭";
}
void Student::treat(QString foodName)
{
// qDebug() << "请老师吃 :" << foodName; // 带引号,用toUtf8()先将它转成QbyteArray类型,然后用data()在转成char *类型
qDebug() << "请老师吃 :" << foodName.toUtf8().data();
}
main.cpp和teacher.cpp默认即可。信号和槽使用时必须先链接后触发信号,才能响应。
3.4 Lambda表达式
Lambda表达式是C++11中用来定义并创建匿名的函数对象。实际上是一个匿名方法,用来声明一个只在此次使用的匿名函数。
[函数对象参数](操作符重载函数参数)mutable->返回值(函数体)
- 1、函数对象参数:[],标识一个lambda的开始,这部分不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构迨函数。函数对象参数只能使用那些到定义Lambda 为止时Lambda所在作用范围内可见的局部变量(包括Lambda所在类的this)。函数对象参数有以下几种形式:
参数 | 作用 |
---|---|
空 | 没有使用任何函数对象参数 |
= | 函数体可以使用lambda所在作用范围内所有可见的局部变量 |
this | 函数体可以使用lambda所在类中的成员变量 |
a | 将a按值进行传递 |
&a | 将a按引用进行传递 |
a, &b | 将a按值传递,b按引用传递 |
=, &a, &b | 除a b按引用传递外,其余值按值进行传递 |
&, a, b | 除a b按值进行传递外,其余值按引用进行传递 |
其中=传递了包括Lambda所在类的this,并且是引用传递方式,相当于编译器自动为我们引用传递了所有局部变量。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
- 2、操作符重载函数参数:标识函数重载的()参数,没有参数时,可以省略。参数可以用过按值传递和按引用两种方式进行传递。
- 3、可修改标识符:mutable声明,这部分可以省略。按值传递函数对象参数时,加上mutable修饰符,可以修改按值传递进来的拷贝(注意仅仅是能修改拷贝, 而不是修改值本身)
- 4、函数返回值:->返回值类型, 标识函数返回值的类型,当返回值为void,或者函数体中只有溢出return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。
- 5、函数体: {},标识函数的实现,这部分不能省略,但函数体可以为空。
[capture](parameters)mutable->return-type
{
statement;
}
在空项目的基础之上改变mywidget.cpp函数:
#include "mywidget.h"
#include <QPushButton>
mywidget::mywidget(QWidget *parent)
: QWidget(parent)
{
// 利用lambda表达式,点击按钮,关闭窗口
QPushButton *btn = new QPushButton("关闭", this);
btn->move(100,100);
connect(btn,&QPushButton::clicked,this, [=](){this->close();});
}
mywidget::~mywidget()
{
}
3.5 菜单栏工具栏的创建
在空项目的基础之上改变mywidget.cpp函数:
#include "mainwindow.h"
#include <QPushButton>
#include <QMenuBar>
#include <QToolBar>
#include <QStatusBar>
#include <QDockWidget>
#include <QLabel>
#include <QTextEdit>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
resize(1400,900); // 重置窗口大小
/********** 菜单栏创建 **********/
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("aaa",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::BottomDockWidgetArea | Qt::TopDockWidgetArea); // 设置停靠区域,只允许上下
/********** 设置中心部件,只能有一个 **********/
QTextEdit *edit = new QTextEdit(this);
setCentralWidget(edit);
/*总结: 使用set加入窗口的部件智能有一个,而add加入的能有多个*/
}
MainWindow::~MainWindow()
{
}
3.6 资源文件添加和UI界面使用
在新建空白项目的第三步点击generate form,生成项目后就会产生一个.ui文件。UI界面可以直接拖拽控件,输入文本,我们开发窗口应用就变得很方便。
在此界面的基础上,创建文件,编辑,工具,帮助等菜单,菜单的一级目录是无法键入中文的,只能输入英文,然后在创建好的对象中将文本改成中文,建立完成后的文件如下。点击项目添加文件,add new file -> Qt -> Qt resource file -> choose,然后更改文件名称,一般设置为res,然后会在Resources底下生成一个res.qrc的文件。
将图片复制到项目目录底下的Image文件(所有图片文件都放进去),以资源编辑器的方式打开res.qrc,添加前缀(可以直接使用默认或者“/”),添加文件,使用“ : + 前缀名 + 文件名称” 。mainwindow.cpp文件如下所示:
// mainwindow.cpp文件
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// ui->actionnew->setIcon(QIcon("C:/Users/19080/Pictures/Camera Roll/文件图标.JPEG") ); // 绝对路径
// 使用添加Qt资源文件
ui->actionnew->setIcon(QIcon(":Image\\fileIcon.JPEG") );
ui->actionopen->setIcon(QIcon(":Image\\Luffy.jpg") );
}
MainWindow::~MainWindow()
{
delete ui;
}
3.7 对话框
3.7.1 模态和非模态
mainwindow.cpp文件如下所示:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDialog>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 点击新建按钮 弹出一个对话框
connect(ui->actionnew,&QAction::triggered,[=](){
/* 对话框 分类
* 模态对话框(不可以对其他窗口进行操作) 非模态对话框则相反
*/
/* 模态创建 阻塞 */
// QDialog dig(this);
// dig.resize(200,100);
// dig.exec();
// qDebug() <<"模态对话框弹出";
/* 非模态创建 */
QDialog *dig2 = new QDialog(this);
dig2->resize(200,100);
dig2->show();
dig2->setAttribute(Qt::WA_DeleteOnClose); // 55号属性
qDebug() << "非模态对话框弹出";
});
}
MainWindow::~MainWindow()
{
delete ui;
}
3.7.2 消息对话框
目前Qt内置对话框有:
名称 | 作用 |
---|---|
QColorDialog | 选择颜色 |
QFileDialog | 选择文件或者目录 |
QFontDialog | 选择字体 |
QInputDialog | 允许用户输入一个值,并将值返回 |
QMessageBox | 模态对话框,用于显示信息、询问信息等等 |
QPageSetupDialog | 为打印机提供纸张相关的选项 |
QPrintDialog | 打印机配置 |
QPrintPreviewDialog | 打印预览 |
QProgressDialog | 显示操作过程 |
mainwindow.cpp文件如下所示,其中QMessageBox::question的返回值是QMessageBox::StandardButton类型,我们就可以利用if语句去判断返回值是否为QMessageBox::Save,从而进一步做其他操作。
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDialog>
#include <QMessageBox>
#include <QDebug>
#include <QColorDialog>
#include <QFileDialog>
#include <QFontDialog>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
connect(ui->actionnew,&QAction::triggered,[=](){
// 错误对话框
// QMessageBox::critical(this, "critical", "错误");
// 信息对话框
// QMessageBox::information(this, "info", "信息");
// 提问对话框 参数1:父类,参数2:title, 参数3:提示信息, 参数4:按键选项, 参数5: 默认选项(关联回车选项)
// if(QMessageBox::Save == QMessageBox::question(this, "ques", "提问",QMessageBox::Save|QMessageBox::Cancel,QMessageBox::Save))
// {
// 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\\19080\\Desktop", "(*.txt)");
// qDebug() << str;
// 字体对话框
bool flag;
QFont font = QFontDialog::getFont(&flag, QFont("华文云彩", 36) );
qDebug() <<" 字体: "<< font.family() <<" 大小:"<< font.pointSize()<< "是否加粗:"<< font.bold() << "是否倾斜:"<< font.italic();
});
}
MainWindow::~MainWindow()
{
delete ui;
}
3.8 设计登录界面
首先我们现在UI界面创建如下控件,用户名和密码用Label控件,输入框用Line Edit控件,登录和退出用PushButton控件。
然后在左侧工具栏Containers中选择Widget控件,将用户名、密码和输入框拖入Widget中选择,在上方工具栏中选择栅格布局(适用于多行多列的,如果是单行或单列可以选择水平布局或垂直布局),布局之后就变得更整齐。登录和退出就选择水平布局。为了在窗口缩放是保持各个空间的相对位置不变,可以加入Spacers控件(也可以不加),其效果于弹簧。
登录界面一般开发时就确定大小,我们找到MainWindow->sizePolicy->水平和垂直策略都选择Fixed,然后将minnumSize和maxiumSize都选择固定的尺寸(具体数值任意,大小合适即可)。操作完毕后窗口大小就固定下来。
最后修改窗口名称,选中密码对应的编辑框,QLineEdit->echoMode->Password(输入密码的编程一个个黑圈圈),到目前为止我们将登录窗口的UI界面设计完毕,但是具体的功能还需要底层代码才能实现。
3.9 各类控件
3.9.1 按钮组
Qt的UI设计界面的按钮组有:PushButton,ToolButton, Radio Button, Check Box等等。
ToolButton建立后,可以添加图片和修改文本,选择Icon->选择资源文件(前面部分有介绍),然后选择QToolButton->autoRaise,其效果是当光标移动到该按钮时,按钮自动高光亮起。
依次创建4个Radio Button,分别命名为男 女, 已婚, 未婚。然后添加Group Box, 将男女添加今一个Group Box, 修改文本为性别。同理,已婚和未婚这两个Radio Button为另外一组。
创建4个Check Box依次修改文本,放入Group Box中,设置垂直布局。然后在添加一个ListWidget,在预览图中显示为白色框。
在mainwindow.cpp中输入如下代码,形成代码和界面的联动:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 设置单选按钮 默认男选中
ui->radioButton->setChecked(true);
// 选中女后打印信息
connect(ui->radioButton_2, &QRadioButton::clicked,[=](){
qDebug() << "选中了女性按钮";
});
// 多选按钮 2:选中 1:半选中 0:未选中
// 信号为stateChanged, 槽函数为lambda表达式, 信号的参数会自动传给槽函数
connect(ui->checkBox_4, &QCheckBox::stateChanged,[=](int state){
qDebug() << state;
});
// 利用listWidget写诗
QListWidgetItem *item = new QListWidgetItem("窗前明月光");
// 将一行诗放到listWidget控件中
ui->listWidget->addItem(item);
item->setTextAlignment(Qt::AlignHCenter); // 设置为水平居中
// QStringList QList<QString>
QStringList list;
list << "疑是地上霜"<< "举头望明月"<<"低头思故乡"; // 将这几句诗加入链表类中
ui->listWidget->addItems(list); // 这种方法无法设置对齐格式
}
MainWindow::~MainWindow()
{
delete ui;
}
3.9.2 QTreeWidget和QTableWidget控件
QTreeWidget控件代码如下:
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// treeWidget树控件使用
// 设置水平头
// 从下面两行代码可以看出,str相当于一个string类型的列表(例如,vector<string>) <<操作符相当于 append()函数的作用
// QStringList str = QStringList()<<"英雄"<<"英雄介绍";
// qDebug() << str;
ui->treeWidget->setHeaderLabels (QStringList()<<"卡牌类型"<<"卡牌介绍");
QTreeWidgetItem *monster = new QTreeWidgetItem(QStringList()<< " 怪兽");
QTreeWidgetItem *magic = new QTreeWidgetItem(QStringList()<< " 魔法");
QTreeWidgetItem *trap = new QTreeWidgetItem(QStringList()<< " 陷阱");
// 加载顶层节点
ui->treeWidget->addTopLevelItem(monster);
ui->treeWidget->addTopLevelItem(magic);
ui->treeWidget->addTopLevelItem(trap);
// 加载子节点
QStringList m1 = QStringList()<< "增殖的G"<< "效果怪兽,每次对方对怪兽的特殊召唤成功,自己从卡组抽1张";
QTreeWidgetItem *monster1 = new QTreeWidgetItem(m1);
monster->addChild(monster1);
QStringList m2 = QStringList()<< "效果遮蒙者"<< "效果怪兽,以对方场上1只效果怪兽为对象,其效果直到回合结束时无效。";
QTreeWidgetItem *monster2 = new QTreeWidgetItem(m2);
monster->addChild(monster2);
QStringList ma1 = QStringList()<< "强欲之壶"<< "通常魔法,从卡组抽两张牌";
QTreeWidgetItem *magic1 = new QTreeWidgetItem(ma1);
magic->addChild(magic1);
QStringList ma2 = QStringList()<< "天使的施舍"<< "通常魔法,从卡组抽三张,然后丢弃两张手牌";
QTreeWidgetItem *magic2 = new QTreeWidgetItem(ma2);
magic->addChild(magic2);
QStringList t1 = QStringList()<< "技能抽取"<< "永续陷阱,能够使场上表侧表示的怪兽卡效果无效";
QTreeWidgetItem *trap1 = new QTreeWidgetItem(t1);
trap->addChild(trap1);
QStringList t2 = QStringList()<< "王宫的敕命"<< "永续陷阱,能够使场上的魔法卡效果无效";
QTreeWidgetItem *trap2 = new QTreeWidgetItem(t2);
trap->addChild(trap2);
}
Widget::~Widget()
{
delete ui;
}
效果图:
QTableWidget控件
#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 = QStringList()<<"李大"<<"柳二"<<"张三"<<"刘四"<<"王五";
QStringList sexList = QStringList()<<"男"<<"男"<<"女"<<"女"<<"女";
for(int i=0;i<5;i++)
{
int col = 0;
ui->tableWidget->setItem(i,col++,new QTableWidgetItem(nameList[i]));
ui->tableWidget->setItem(i,col++,new QTableWidgetItem(sexList.at(i)));
ui->tableWidget->setItem(i,col++,new QTableWidgetItem(QString::number(i+18)));
}
}
Widget::~Widget()
{
delete ui;
}
效果图:
3.9.3 其他控件
主要包括了scroll area、 tool box、tab widget、stacked widget等控件。其中scroll area 是滚动条控件,toolbox是列表页面(例如QQ的联系人列表),tab widget是类似网页页面的控件。stacked widget是栈控件,可以将以上三个页面全部放到栈控件中,然后实现多个页面的切换。代码中还包括了combo box下拉框,QLabel的简单使用。其中,QLabel可以用作显示图片,播放动态图,视频等等。
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
#include <QMovie>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// stacked Widget栈控件的使用
// 默认定位
ui->stackedWidget->setCurrentIndex(0);
// scroll area 按钮
connect(ui->btn_scroll,&QPushButton::clicked, [=](){
ui->stackedWidget->setCurrentIndex(0);
});
// toolBox 按钮
connect(ui->btn_toolBox,&QPushButton::clicked, [=](){
ui->stackedWidget->setCurrentIndex(1);
});
// tabWidge 按钮
connect(ui->btn_tabWidget,&QPushButton::clicked, [=](){
ui->stackedWidget->setCurrentIndex(2);
});
// combo box 下拉框
ui->comboBox->addItem("奔驰");
ui->comboBox->addItem("宝马");
ui->comboBox->addItem("拖拉机");
// 点击按钮选中拖拉机
connect(ui->pushButton_16,&QPushButton::clicked, [=](){
// ui->comboBox->setCurrentIndex(2);
ui->comboBox->setCurrentText("拖拉机"); // 两句代码效果一样
});
// 利用QLabel显示图片
ui->imaLabel->setPixmap(QPixmap(":/Image/fileIcon.JPEG").scaled(ui->imaLabel->size()));
//利用QLabel显示gif动态图片
QMovie *movie = new QMovie(":/Image/picaqu.gif");
movie->setScaledSize(ui->movieLabel->size());
ui->movieLabel->setMovie(movie);
// 播放动图
movie->start();
}
Widget::~Widget()
{
delete ui;
}
效果图:
3.9.4 自定义控件封装
建立一个自定义的控件,将QSpinBox和QSlider联动起来:QSpinBox移动,QSlider跟着移动,QSlider跟着移动,QSpinBox也跟着移动。
项目文件点击->新建->Qt->设计师界面->widget,然后修改文件名(SmallWidget)。在smallwidget.up界面中添加,QSpinBox和QSlider两个控件:
右键SmallWidget窗口->提升为->添加->提升,成功以后,widget类底下就会包含SmallWidget类。
修改smallwidget.cpp文件:
// smallwidget.cpp文件
#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; // valueChanged有重载版本,因此需要确定输入参数是哪种版本的,这里需要的是int输入
connect(ui->spinBox, spSignal, ui->horizontalSlider, &QSlider::setValue);
// QSlider跟着移动 QSpinBox移动
connect(ui->horizontalSlider, &QSlider::valueChanged, ui->spinBox, &QSpinBox::setValue);
}
SmallWidget::~SmallWidget()
{
delete ui;
}
3.10 鼠标和定时器事件以及事件分发器、过滤器
添加myLabel类->C+±>C++Class,父类选择为QWidget,因为新建项目窗口能够选择父类有限,这里我们就选择QWidget类,项目文件建好后,修改mylabel.h文件中include头文件和父类,mylabel.cpp中构造函数的父类,全部修改为QLabel。
进入UI界面,拖拽一个Label控件,修改为合适大小,文字删除,此时控件消失,为了方便观察,我们设置控件的边框为Box类型,属性页面QFrame->frameShape->Box,如下图所示。
UI界面的设置创建两个label控件,用来显示定时器数字。定时器主要使用到timerEvent(QTimerEvent *ev)函数,多个定时器之间用timeId来区分。
多个事件之间通过bool event(QEvent *ev)来进行事件分发,返回值是bool类型,如果返回值是true代表用户要处理这个事件,不向下分发事件。
// mylabel.h文件
#ifndef MYLABEL_H
#define MYLABEL_H
#include <QLabel>
class myLabel : public QLabel
{
public:
explicit myLabel(QWidget *parent = nullptr);
// 声明
// 鼠标进入事件
void enterEvent(QEvent *event);
// 鼠标离开事件
void leaveEvent(QEvent *);
// 鼠标按下事件
void mousePressEvent(QMouseEvent *event);
// 鼠标释放事件
void mouseReleaseEvent(QMouseEvent *event);
// 鼠标移动事件
void mouseMoveEvent(QMouseEvent *event);
// 通过event事件分发器拦截 鼠标按下事件
bool event(QEvent *e);
signals:
};
#endif // MYLABEL_H
// widget.h文件
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
// 定时器事件
void timerEvent(QTimerEvent *);
int id1; // 定时器1的唯一标识
int id2; // 定时器2的唯一标识
// 重写事件过滤器的时间
bool eventFilter(QObject *obj, QEvent *e);
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
// mylabe.cpp文件
#include "mylabel.h"
#include <QDebug>
#include <QMouseEvent>
myLabel::myLabel(QWidget *parent) : QLabel(parent)
{
// 设置鼠标追踪
setMouseTracking (true); // 原来是点击后鼠标移动才能触发,现在只要鼠标移动就能触发鼠标移动事件。
}
// 鼠标进入事件
void myLabel::enterEvent(QEvent *event)
{
qDebug() << "鼠标进入";
}
// 鼠标离开事件
void myLabel::leaveEvent(QEvent *)
{
qDebug()<< "鼠标离开";
}
// 鼠标按下事件
void myLabel::mousePressEvent(QMouseEvent *event)
{
// 要求:当鼠标左键按下时,打印信息,右键按下不打印
if(event->button() == Qt::LeftButton)
{
QString str = QString("鼠标按下了 x = %1, y = %2, globalx = %3, globaly = %4").arg(event->x()).arg (event->y()).arg (event->globalX()).arg (event->globalY());
qDebug()<< str;
}
}
// 鼠标释放事件
void myLabel::mouseReleaseEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton)
{
qDebug()<< "鼠标释放";
}
}
// 鼠标移动事件
void myLabel::mouseMoveEvent(QMouseEvent *event)
{
qDebug()<< "鼠标移动";
// if(event->buttons() & Qt::LeftButton) // &位与操作,buttons用于同时按下多个按钮,只要按下的按钮中包含左键,执行下面的操作。
// {
// qDebug()<< "鼠标移动";
// }
}
// 通过event事件分发器拦截 鼠标按下事件
bool myLabel::event(QEvent *e)
{
// 如果是鼠标按下,在event中做拦截操作,也就是说在这一层做处理,后面的 鼠标按下 相关代码就不会触发
if(e->type() == QEvent::MouseButtonPress)
{
QMouseEvent *ev = static_cast<QMouseEvent *>(e); // static_cast是C++的强制类型转换,大精度类型转小精度类型,有损
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 <QTimer>
#include <QMouseEvent>
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
id1 = startTimer(1000); // 启动计时器,参数1:间隔,单位毫秒
id2 = startTimer(2000);
// 定时器的第二种方式
QTimer *timer = new QTimer(this);
// 启动定时器
timer->start(500);
connect(timer, &QTimer::timeout, [=](){
static int num3 = 1;
// label4 每隔0.5秒+1
ui->label_4->setText(QString::number(num3++));
});
// 点击按钮暂停定时器,第三个暂停
connect(ui->btn1, &QPushButton::clicked, [=](){
timer->stop();
});
// 给label 安装事件过滤器, 实际上是一个比event拦截器更高级的拦截器
// 第一步
ui->label->installEventFilter(this);
}
// 第二步 重写 eventfiler事件
bool Widget::eventFilter(QObject *obj, QEvent *e)
{
if(obj == ui->label)
{
if(e->type() == QEvent::MouseButtonPress)
{
QMouseEvent *ev = static_cast<QMouseEvent * >(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)
{
static int num1 = 1;
ui->label_2->setText(QString::number(num1++));
}
if(ev->timerId() == id2)
{
static int num2 = 1;
ui->label_3->setText(QString::number(num2++));
}
}
Widget::~Widget()
{
delete ui;
}
3.11 绘画
3.11.1 绘画设置
这里主要介绍的是画类操作,画直线,画圆,画矩形等等,画笔,毛刷等等设置,widget.cpp文件如下,此外,还需要在widget.h文件声明函数void paintEvent(QPaintEvent *event)。
// widget.cpp文件
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 点击移动按钮,移动图片 ,paintEvent函数默认会调用一次,然后就是调用repaint函数,即重新绘画函数
connect (ui->pushButton, &QPushButton::clicked, [=](){
// 手动调用绘图事件函数,实际上是调用repaint函数
PosX += 20; // 需要在widget.h中声明, int PosX = 150;
update();
});
}
// 绘图事件
void Widget::paintEvent(QPaintEvent *event)
{
// // 实例化画家对象, this制定绘图设备
// QPainter painter(this);
// // 设置画笔
// QPen pen(QColor(255,0,0)); // 设置颜色
// pen.setWidth (5); // 设置宽度
// pen.setStyle (Qt::DotLine); // 设置风格
// painter.setPen (pen);
// // 设置画刷
// QBrush brush(QColor(0,255,0));
// brush.setStyle (Qt::Dense1Pattern);
// painter.setBrush (brush);
// // 画了一条线(两个点确定)
// painter.drawLine(QPoint(0,0),QPoint(100,100));
// // 画椭圆圆,圆心和长短轴焦点a,b确定 a=b就是圆
// painter.drawEllipse (QPoint(100,100),50,50);
// // 画矩形
// painter.drawRect (QRect(20,20,50,50));
// // 画文字
// painter.drawText (QRect(10,200,150,50),"好好学习,天天向上");
****高级设置*******/
// QPainter painter(this);
// painter.drawEllipse (QPoint(200,200),100,100);
// // 设置 抗锯齿能力,即画的仔细一点,毛边少一点,但是效率低一点
// painter.setRenderHint (QPainter::Antialiasing);
// painter.drawEllipse (QPoint(400,200),100,100);
// // 画矩形
// painter.drawRect (QRect(20,20,50,50));
// painter.translate (100,0); // 画家从0,0开始作画,变成从100,0开始作画
// // 保存画家状态
// painter.save();
// painter.drawRect (QRect(20,20,50,50));
// // 还原画家保存状态
// painter.restore ();
// painter.drawRect (QRect(20,20,50,50));
/ ******* 利用画家调用图片资源 **** //
QPainter painter(this);
// 如果超过屏幕宽度 ,从0开始
if(PosX > this->width())
{
PosX = 0;
}
painter.drawPixmap(PosX,20,QPixmap(":/Image/Luffy.jpg"));
}
Widget::~Widget()
{
delete ui;
}
3.11.2 绘图设备
主要有QPixmap,QPicture,QImage三种,需要在widget.h文件声明函数void paintEvent(QPaintEvent *event)
#include "widget.h"
#include "ui_widget.h"
#include <QPixmap>
#include <QPainter>
#include <QPicture>
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:\\software\\QT\\QT_Project\\qtDemo10\\pix.png");
// ///
// // QImage 绘图设备 可以对每个像素进行访问
// QImage img(300,300, QImage::Format_RGB32);
// img.fill(Qt::white);
// QPainter painter(&img);
// painter.setPen(QPen(Qt::blue));
// painter.drawEllipse(QPoint(150,150),100,100);
// // 保存
// img.save("D:\\software\\QT\\QT_Project\\qtDemo10\\img.png");
// QPicture 绘图设备,可以记录和重现绘图指令
QPainter painter;
QPicture pic;
painter.begin(&pic); // 开始往pic上画画
painter.setPen(QPen(Qt::blue));
painter.drawEllipse (QPoint(150,150),100,100);
painter.end(); // 结束画画
// 保存到磁盘
pic.save("D:\\software\\QT\\QT_Project\\qtDemo10\\pic.hyf");
// hyf是博主的姓名缩写,在文件资源管理器中是无法打开这个图片的
// 我们在绘图事件中使用load函数可以打开,准确来说是重新绘制,因此pic保存的不是图片本身而是绘制图片的指令
}
// 绘图事件
void Widget::paintEvent(QPaintEvent *event)
{
// // 利用QImage 对像素进行修改
// QPainter painter(this);
// QImage img;
// img.load(":/Image/fileIcon.JPEG");
// // 修改像素点
// 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(0,0,img);
// 重现QPicture绘图指令
QPainter painter(this);
QPicture pic;
pic.load("D:\\software\\QT\\QT_Project\\qtDemo10\\pic.hyf");
painter.drawPicture(0,0,pic);
}
Widget::~Widget()
{
delete ui;
}
3.12 文件读取
文件读取是主要注意打开文件,也要关闭文件,此外,QFile默认是UTF-8格式类型。widget.cpp文件如下所示:
#include "widget.h"
#include "ui_widget.h"
#include <QFileDialog>
#include <QFile>
#include <QTextCodec>
#include <QFileInfo>
#include <QDebug>
#include <QDateTime>
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/19080/Desktop");
// 将路径放到lineEdit中
ui->lineEdit->setText(path);
// 编码格式类
QTextCodec *codec = QTextCodec::codecForName("gbk");
// 读取内容,放入到textEdit中
// QFile默认支持的格式是UTF-8
QFile file(path); // 参数就是file path
file.open(QIODevice::ReadOnly); // 设置打开方式
//QByteArray array = file.readAll();
QByteArray array;
while( !file.atEnd() )
{
array += file.readLine();
}
// 将读取的数据放入到text Edit中
ui->textEdit->setText(array);
//ui->textEdit->setText(codec->toUnicode(array));
file.close(); // 对文件对象进行关闭
文件写入操作
file.open(QIODevice::Append); // 追加的方式写入
file.write("123456789");
file.close();
QFileInfo ///
// QFileInfo 文件信息类
QFileInfo info(path);
qDebug() << "大小(字节):" << info.size()<< "后缀名:"<< info.suffix()<< "文件名称:"<< info.fileName() <<"文件路径:"<< info.filePath();
qDebug() << "文件创建日期: "<< info.created().toString ("yyyy/MM/dd hh:mm:ss"); // 按格式输出 yyyy/MM/dd hh:mm:ss
qDebug() << "文件创建日期: "<< info.lastModified().toString ("yyyy/MM/dd hh:mm:ss");
});
}
Widget::~Widget()
{
delete ui;
}
四、翻金币小游戏
4.1 出现的问题
如果没有素材可以从网上找,图片资源都可以根据实际变化,不是唯一的。资源网站这里推荐阿里的图标库。
博主在写这个代码的过程中出现了一些错误,第一个错误博主没有解决,将第二个错误解决后,第一个就没有出现了。第二个和第三个错误在头文件前面加上Q_OBJECT成员变量,加在public前面,问题解决参考自博客No Q_OBJECT in the class with the signal错误解决办法,然后在.pro文件末尾加上空格重新编译。
error: 'QtPrivate::QFunctorSlotObject<Func, N, Args, R>::QFunctorSlotObject(Func) [with Func = MainScene::MainScene(QWidget*)::<lambda()>; int N = 0; Args = QtPrivate::List<>; R = void]', declared using local type 'MainScene::MainScene(QWidget*)::<lambda()>', is used but never defined [-fpermissive]
explicit QFunctorSlotObject(Func f) : QSlotObjectBase(&impl), function(std::move(f)) {}
^~~~~~~~~~~~~~~~~~
error: static assertion failed: No Q_OBJECT in the class with the signal
# define Q_STATIC_ASSERT_X(Condition, Message) static_assert(bool(Condition), Message)
^
error: undefined reference to `vtable for
4.2 源码下载
这里直接给大家分享一下成品,用百度网盘给出,步骤博主就不在一一介绍。
链接:https://pan.baidu.com/s/1QGdOGuyTYMGfSgi81O71vw?pwd=zajg
提取码:zajg
4.3 NSIS打包程序
当我们写好程序后将编译运行按钮中的debug输出改为release输出,然后得到一个发布版本的.exe程序,如图所示:
我们将他单独拎出来放到桌面的release文件夹(自己命名的空文件夹都可以)中,然后找到对应编译器的命令行窗口,如下图所示,博主这里有两个编译器,博主的是MinGW 64-bit的。然后输入windeployqt.exe CoinFlip.exe,按回车键,程序打包成功,之前的release文件就多了一些生成的打包文件。
windeployqt.exe CoinFlip.exe
windeployqt.exe文件实际上是Qt编译器提供的打包成window程序的可执行文件,可以在对应编译器的bin文件中找到。
这里需要注意不能使用普通的命令行窗口执行这个命令,会出现无法找到入口的问题。
然后我们使用HM NIS Edit软件进行Setup.exe文件的打包,需要配合NSIS软件一起使用。
NSIS是"Nullsoft 脚本安装系统"(Nullsoft scriptable Installation System)的缩写,它是是一个免费的win32安装、卸载系统,可以很方便的打包windows应用程序。它的特点:脚本简洁高效;系统开销少;支持安装、卸载、系统设置、解压文件等功能。这里博主直接给出NSIS下载地址和HM NIS Edit下载地址,嫌麻烦的也可以用博主的百度网盘地址下载,链接:https://pan.baidu.com/s/1FrLENkVtB-B2lGslqw33bw?pwd=wybc
提取码:wybc。
参考博客 手把手教NIS Edit安装向导的使用。当顺着教程做到这一步的时候,点击树形图,选择release文件,将release文件中的所有文件添加进来。其他部分按照教程或者默认即可。最终会在项目目录中生成Setup.exe文件。将Setup.exe安装之后,就会在桌面生成快捷方式,点击即可进行游戏。