前言
Qt连载:
1.Qt开发初步
2.Qt常用控件
3.Qt嵌入式编程
QT下载:
http://download.qt.io
我边学习边总结的,很多内容来自于网上的好文,有错误的地方望指正
比较入门的知识,熟练使用还是少不了一本书,不管是深入学习还是查阅
我下载的源码版本:https://download.qt.io/archive/qt/4.8/4.8.4/qt-everywhere-opensource-src-4.8.4.tar.gz
我下载的qt creater:https://mirrors.tuna.tsinghua.edu.cn/qt/archive/qtcreator/2.5/qt-creator-linux-x86_64-opensource-2.5.2.bin
附上多本Qt书的网盘链接:
https://blog.csdn.net/weixin_44705391/article/details/113863535
1.特点
1.可以看成是 “图形界面库” + “idl” 的组合。qt creater作为ide,结合qt源码来编译工程。
2.qt源码可以通过各种复杂配置来选择自己想要的功能,但是需要安装各种库比如opengl、zlib、tslib
3.注意版本,5以上需要c++11标准,注意自己的编译器是否满足
4.可通过 “交叉编译后的qmake” + “编译工具链” + “自己的工程” 生成可在嵌入式设备上执行的程序
5.qt creater安装时默认使用gcc,其qmake也默认使用gcc编译的qmake
6.Linux、Windows、MAC OS、ios、ARM等平台都可用
2.QT creator使用
1.选择合适的基类 在新建工程时需要选择基类,我们先选精简版的窗口
父类 Qwidget 精简版窗口
子类 QMainWindow 带菜单栏的窗口
子类 QDialog 对话框
2.QT助手是最靠谱的参考文档 (但是Qt的assistant帮助文档会更好用,在开始栏Qt文件夹中可找到)点击索引即可查找需要的控件,前提是记住控件名称
3.工程
1.pro工程文件
一般不需要改
2.按钮示例
(代码中的解释文字重要 )可选择添加ui或者不添加ui文件,以下为不添加ui示例:
main.cpp:
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
//QApplication应用程序类,初始化我们的应用程序
QApplication a(argc, argv);
//创建一个主窗口控件
Widget w;
//显示主窗口,hide隐藏窗口(窗口默认是隐藏的,所以创建了之后一定要调用show)
w.show();
//a.exec()主事件循环(带阻塞 等待用户操作界面)widget.cpp
return a.exec();
}
widget.cpp:
#include "widget.h"
#include <QPushButton>
//界面的设计在这,窗口控件的构造函数中
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
//this代表当前主窗口,如果不设置title,则窗口名字和类名相同
this->setWindowTitle("第一个窗口");
//固定窗口(默认是窗口大小可以拉的)
//this->setFixedSize(800,600); //窗口不可以拖动
//设置窗口大小
this->resize(400,300); //窗口可以拖动
//在窗口上放一个按钮,包含头文件,qmake中增加模块QT += widgets (默认是添加了的),(QT助手)
//parent父对象(和父类的概念不同,表示button将来希望被谁接管)this表示button放到当前窗口,将来负责析构button,
//所以button下面是不需要显式调用delete的,也不需要调用~QPushButton析构函数
QPushButton *button1 = new QPushButton("跟屁虫",this);
//用new的好处:下面的对象树会讲,用局部变量,栈来存储会有什么坏处
//如下分开设置
QPushButton *button2 = new QPushButton;
button2->setParent(this);
button2->setText("Richard");
//默认控件会显示到主窗口左上方,会覆盖button1
//移动,move函数在QPushButton的爷爷类中
button2->move(400,300); //刚好被挡住,拖动窗口显示按钮
}
Widget::~Widget()
{
}
3.对象数
解释了为什么分配在堆上比分配在栈上好
一句话概括就是子对象会被交由父对象接管,关闭程序时父对象会负责清理子对象留下的垃圾。如上述代码。
在Qt中创建对象的时候会提供一个Parent对象指针,下面来解释这个parent到底是干什么的。
QObject是以对象树的形式组织起来的。
当你创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。
这相当于,在创建QObject对象时,可以提供一个其父对象,我们创建的这个QObject对象会自动添加到其父对象的children()列表。
当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类!)
这种机制在 GUI 程序设计中相当有用。例如,一个按钮有一个QShortcut(快捷键)对象作为其子对象。当我们删除按钮的时候,这个快捷键理应被删除。这是合理的。
QWidget是能够在屏幕上显示的一切组件的父类。
QWidget继承自QObject,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件。因此,它会显示在父组件的坐标系统中,被父组件的边界剪裁。例如,当用户关闭一个对话框的时候,应用程序将其删除,那么,我们希望属于这个对话框的按钮、图标等应该一起被删除。事实就是如此,因为这些都是对话框的子组件。
当然,我们也可以自己删除子对象,它们会自动从其父对象列表中删除。比如,当我们删除了一个工具栏时,其所在的主窗口会自动将该工具栏从其子对象列表中删除,并且自动调整屏幕显示。
Qt 引入对象树的概念,在一定程度上解决了内存问题。
当一个QObject对象在堆上创建的时候,Qt 会同时为其创建一个对象树。不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。
任何对象树中的 QObject对象 delete 的时候,如果这个对象有 parent,则自动将其从 parent 的children()列表中删除;如果有孩子,则自动 delete 每一个孩子。Qt 保证没有QObject会被 delete 两次,这是由析构顺序决定的。
如果QObject在栈上创建,Qt 保持同样的行为。正常情况下,这也不会发生什么问题。来看下下面的代码片段:
{
QWidget window;
QPushButton quit("Quit", &window);
}
作为父组件的 window 和作为子组件的 quit 都是QObject的子类(事实上,它们都是QWidget的子类,而QWidget是QObject的子类)。这段代码是正确的,quit 的析构函数不会被调用两次,因为标准 C++要求,局部对象的析构顺序应该按照其创建顺序的相反过程。因此,这段代码在超出作用域时,会先调用 quit 的析构函数,将其从父对象 window 的子对象列表中删除,然后才会再调用 window 的析构函数。
但是,如果我们使用下面的代码:
{
QPushButton quit("Quit");
QWidget window;
quit.setParent(&window);
}
情况又有所不同,析构顺序就有了问题。我们看到,在上面的代码中,作为父对象的 window 会首先被析构,因为它是最后一个创建的对象。在析构过程中,它会调用子对象列表中每一个对象的析构函数,也就是说, quit 此时就被析构了。然后,代码继续执行,在 window 析构之后,quit 也会被析构,因为 quit 也是一个局部变量,在超出作用域的时候当然也需要析构。但是,这时候已经是第二次调用 quit 的析构函数了,C++ 不允许调用两次析构函数,因此,程序崩溃了。
由此我们看到,Qt 的对象树机制虽然帮助我们在一定程度上解决了内存问题,但是也引入了一些值得注意的事情。这些细节在今后的开发过程中很可能时不时跳出来烦扰一下,所以,我们最好从开始就养成良好习惯,在 Qt 中,尽量在构造的时候就指定 parent 对象,并且大胆在堆上创建。
4.信号槽
(关于信号槽百度上有很多解释,在这就不多做解释了)
类似于中断绑定:
其中的关键就是connect函数:
connect(sender, signal, receiver, slot);
参数:
sender:发出信号的对象
signal:发送对象发出的信号
receiver:接收信号的对象
slot:接收对象在接收到信号之后所需要调用的函数
//点击按钮关闭窗口
QPushButton *p = new QPushButton("关闭",this);
//设置连接,点击按钮产生信号,会调用窗口的close函数
connect(p,&QPushButton::clicked,this,&QWidget::close);
自定义一个print槽函数:
首先需要在widget类添加槽成员函数print:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
public slots: //添加槽成员函数
void print(); //
};
#endif // WIDGET_H
实现槽函数:
注意如果信号没有参数,槽函数也不能有参数,如果信号有参数,槽函数可以有也可以没有
#include "widget.h"
#include <QPushButton>
#include <iostream>
#include <QDebug>
using namespace std;
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
QPushButton *p = new QPushButton("点击打印",this);
//如果信号没有参数,槽函数也不能有参数,如果信号有参数,槽函数可以有也可以没有
connect(p,&QPushButton::pressed,this,&Widget::print);
}
void Widget::print()
{
cout << "2021 Richard Stewart" << endl;
//qDebug() << "2021 Richard Stewart"; //和上面代码效果一样
}
Widget::~Widget()
{
}
自定义信号:
考虑这样一个问题:新建两个窗口,每个窗口实现一个按钮,按钮功能为隐藏本窗口,显示对方窗口。
1.我们可以在父窗口中创建子窗口,因为有子窗口的地址,那么可以用父.hide();子->show();来实现。
2.但是子窗口中没有父窗口地址,不能用指针来实现,那么在子窗口定义一个信号,在父窗口设置接收到信号时回调一个槽函数,用槽函数来完成隐藏子窗口,显示父窗口。
方法一:将子窗口设为父窗口的成员
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
QWidget *sonwind;
public slots:
void func_father();
void func_son();
};
#endif // WIDGET_H
#include "widget.h"
#include <QPushButton>
#include <iostream>
#include <QDebug>
using namespace std;
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
this->resize(300,300);
QPushButton *button1 = new QPushButton("点击父窗口",this);
connect(button1,&QPushButton::clicked,this,&Widget::func_father);
sonwind = new QWidget;
sonwind->resize(300,300);
sonwind->show();
QPushButton *button2 = new QPushButton("点击子窗口",sonwind);
connect(button2,&QPushButton::clicked,this,&Widget::func_son);
button2->move(50,50);
}
void Widget::func_father()
{
this->hide();
this->sonwind->show();
}
void Widget::func_son()
{
this->show();
this->sonwind->hide();
}
Widget::~Widget()
{
}
方法二:新建一个子窗口文件
右击工程->添加新文件->c++ class->输入名称SONwidget->选择基类为QWidget->完成,生成两个文件:sonwindow.hpp、sonwindow.cpp
sonwindow.hpp:
#ifndef SONWIDGET_H
#define SONWIDGET_H
#include <QWidget>
#include <QPushButton>
class SONwidget : public QWidget
{
Q_OBJECT
public:
explicit SONwidget(QWidget *parent = nullptr);
QPushButton *button2;
signals:
//信号没有返回值,可以有参数,参数会传给与之绑定的槽函数。信号函数不需要定义,只需要声明
void show_hide_signal(int a);
public slots:
void emit_signal();
};
#endif // SONWIDGET_H
sonwindow.cpp:
#include "sonwidget.h"
SONwidget::SONwidget(QWidget *parent) : QWidget(parent)
{
this->resize(300,300);
button2 = new QPushButton("点击子窗口",this);
connect(button2,&QPushButton::clicked,this,&SONwidget::emit_signal);
}
void SONwidget::emit_signal()
{
//发送:
emit show_hide_signal(10);
}
widget.hpp:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QPushButton>
#include "sonwidget.h"
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
SONwidget *sonwindow;
QPushButton *button1;
public slots:
void button_set();
void signal_set(int a);
};
#endif // WIDGET_H
widget.cpp:
#include "widget.h"
#include <QPushButton>
#include <iostream>
#include <QDebug>
using namespace std;
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
this->resize(300,300);
button1 = new QPushButton("点击父窗口",this);
this->sonwindow = new SONwidget;
sonwindow->show();
connect(button1,&QPushButton::clicked,this,&Widget::button_set);
connect(sonwindow,&SONwidget::show_hide_signal,this,&Widget::signal_set);
}
void Widget::button_set()
{
this->hide();
this->sonwindow->show();
}
void Widget::signal_set(int a)
{
this->show();
this->sonwindow->hide();
cout << a << endl;
}
Widget::~Widget()
{
}
信号和槽函数可以重载,注意避免二义性问题,其解决方法如下:
方法1:
void (sub::*fun1)() = &sub::mysignal;
void (sub::*fun2)(int,QString) = &sub::mysignal;
connect(s,fun1,this,&QWidget::show);
connect(s,fun2,this,&QWidget::show);
方法2,使用Qt4的connect
局限性:SIGNAL、SLOT将变量变为字符串,导致不做类型判断
槽函数前面必须在类中加slots关键字
connect(s,SIGNAL(mysignal()),this,SLOT(dealSub()));
connect(s,SIGNAL(mysignal(int,QString)),this,SLOT(dealSlot()));
方法3,lambda表达式(仅限c++11,注意自己的编译器是否支持c++11)
connect(b4,&QPushButton::clicked,
// = :把外部所有局部变量,类中所有成员以值传递方式(只读)
// this :类中所有成员以值传递方式
// & :把外部所有局部变量引用传递方式(不太安全)
[=]()
{
b4->setText("123");
qDebug() << "asfas";
}
//加mutable使得值可变
[=](bool isCheck)mutable
{
qDebug() << isCheck;
}
);
关于信号和槽的总结:
1.槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响;
2.发送者和接收者都需要是 QObject 的子类(当然,槽函数是全局函数、Lambda 表达式等无需接收者的时候除外);
3.任何成员函数、static函数、全局函数、lambda表达式都可作为槽函数
4.槽函数的参数可以比信号少,但是顺序要保持一致,这是因为信号和槽的实现都是Qt完成的,使用时要遵循其内部实现原理。
拓展:
1.一个信号可以和多个槽相连
2.多个信号可以连接到一个槽
3.一个信号可以连接到另外的一个信号(当第一个信号发出时,第二个信号被发出。这种信号-信号的形式和信号-槽的形式没有什么区别。)
4.槽可以被取消链接(当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。)
5.信号槽可以断开:使用disconnect函数
6.使用lambda表达式
4.基类
已经讲了Qwidget、接下来讲QMainWindow、QDialog
1.初遇QMainWindow
之前的都是围绕QWidget进行的,接下来是QMainWindow内容。
mainwindow.cpp:
#include "mainwindow.h"
#include <QMenuBar>
#include <QAction>
#include <QToolBar>
#include <QStatusBar>
#include <QLabel>
#include <QDockWidget>
#include <QTextEdit>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
this->resize(500,500);
//取出菜单栏
QMenuBar *menubar1 = this->menuBar();
//往菜单栏添加菜单
QMenu *filemenu = menubar1->addMenu("文件");
QMenu *fileedit = menubar1->addMenu("编辑");
//向菜单添加菜单项
QAction *open = filemenu->addAction("打开");
//菜单项之间添加分割线
filemenu->addSeparator();
QAction *save = filemenu->addAction("保存");
//获取工具栏
QToolBar *toolbar = this->addToolBar("");
//向工具栏添加工具(添加菜单项)
toolbar->addAction(open);
toolbar->addAction(save);
//取出状态栏
QStatusBar *status = this->statusBar();
status->addWidget(new QLabel("状态"));//向状态栏添加控件
//铆接控件(浮动窗口)
//QDockWidget *dockwidget = new QDockWidget("这是一个铆接部件",this);
//this->addDockWidget(Qt::TopDockWidgetArea,dockwidget);
//中心控件
QTextEdit *edit = new QTextEdit("文本编辑器",this);
this->setCentralWidget(edit);
}
MainWindow::~MainWindow()
{
}
资源文件
通俗的讲就是将文件以二进制格式编译进工程,好处是不会像用外部链接一样当链接丢失时错误。
右击工程->添加新文件->qt->Qt Resource File->完成。
左边编辑按钮->添加 前缀->添加 文件->可以将资源改别名,这样以后就可以用这个别名->点击编译按钮即可将文件编译进刚刚新建的资源文件。
右击资源可以复制资源的路径。
QPixmap pic; //定义一个图片对象
pic.load(":/new/prefix1/a.jpg"); //给图片对象加载图片
QPushButton->setIcon(QIcon(pic));
2.对话框 QDialog
概念
1.通常是一个顶层窗口,出现在程序最上层,用于实现短期任务或者简洁的用户交互。
2.分为模态对话框和非模态对话框
模态对话框,就是会阻塞同一应用程序中其它窗口的输入。
非模态对话框,例如查找对话框
标准对话框(Qt实现的对话框)
QColorDialog: 选择颜色;
QFileDialog: 选择文件或者目录;
QFontDialog: 选择字体;
QInputDialog: 允许用户输入一个值,并将其值返回;
QMessageBox: 模态对话框,用于显示信息、询问问题等;
QPageSetupDialog: 为打印机提供纸张相关的选项;
QPrintDialog: 打印机配置;
QPrintPreviewDialog:打印预览;
QProgressDialog: 显示操作过程。
例子:
connect(button,&QPushButton::clicked,this,[](){
//QString str = QFileDialog::getOpenFileName();
//qDebug()<< str;
//返回多个路径:
QStringList strlist = QFileDialog::getOpenFileNames();
qDebug()<<strlist;
});
模态对话框
1.应用程序级别的模态
2.窗口级别的模态
一般默认是应用程序级别的模态。
如下代码调用了exec(),阻塞,这就是模态对话框。
QDialog dialog;
dialog.setWindowTitle(tr("Hello, dialog!"));
dialog.exec();
非模态对话框
将exec()修改为show(),对话框闪退
QDialog dialog(this);
dialog.setWindowTitle(tr("Hello, dialog!"));
dialog.show();
这是因为,show()函数不会阻塞当前线程,对话框会显示出来,然后函数立即返回,代码继续执行。dialog 是建立在栈上的,show()函数返回,MainWindow::open()函数结束,dialog 超出作用域被析构,因此对话框消失了。
将 dialog 改成堆上建立:
QDialog *dialog = new QDialog;
dialog->setWindowTitle(tr("Hello, dialog!"));
dialog->show();
但是这样dialog 存在内存泄漏。dialog 使用 new 在堆上分配空间,却一直没有 delete。解决方案也很简单:将 MainWindow 的指针赋给 dialog 即可,变成父对象。
QDialog *dialog = new QDialog(this);
dialog->setWindowTitle(tr("Hello, dialog!"));
dialog->show();
这样做有一个问题:如果我们的对话框不是在一个界面类中出现呢?由于QWidget的 parent 必须是QWidget指针,那就限制了我们不能将一个普通的 C++ 类指针传给 Qt 对话框。另外,如果对内存占用有严格限制的话,当我们将主窗口作为 parent 时,主窗口不关闭,对话框就不会被销毁,所以会一直占用内存。在这种情景下,我们可以设置 dialog 的WindowAttribute:
QDialog *dialog = new QDialog;
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setWindowTitle(tr("Hello, dialog!"));
dialog->show();
setAttribute()函数设置对话框关闭时,自动销毁对话框。
消息对话框
QMessageBox用于显示消息提示。我们一般会使用其提供的几个 static 函数:
1.显示关于对话框。
void about(QWidget * parent, const QString & title, const QString & text)
这是一个最简单的对话框,其标题是 title,内容是 text,父窗口是 parent。对话框只有一个 OK 按钮。
例子:注意lambda的=,&
connect(button1,&QPushButton::clicked,this,[=](){
QMessageBox::about(this,"标题","内容");
});
2.显示关于 Qt 对话框。该对话框用于显示有关 Qt 的信息。
void aboutQt(QWidget * parent, const QString & title = QString()):
3.显示严重错误对话框。
StandardButton critical(QWidget * parent,
const QString & title,
const QString & text,
StandardButtons buttons = Ok,
StandardButton defaultButton = NoButton):
这个对话框将显示一个红色的错误符号。我们可以通过 buttons 参数指明其显示的按钮。默认情况下只有一个 Ok 按钮,我们可以使用StandardButtons类型指定多种按钮。
与QMessageBox::critical()类似,不同之处在于这个对话框提供一个普通信息图标。
StandardButton information(QWidget * parent,
const QString & title,
const QString & text,
StandardButtons buttons = Ok,
StandardButton defaultButton = NoButton)
与QMessageBox::critical ()类似,不同之处在于这个对话框提供一个问号图标,并且其显示的按钮是“是”和“否”。
StandardButton question(QWidget * parent,
const QString & title,
const QString & text,
StandardButtons buttons = StandardButtons( Yes | No ),
StandardButton defaultButton = NoButton)
与QMessageBox::critical()类似,不同之处在于这个对话框提供一个黄色叹号图标。
StandardButton warning(QWidget * parent,
const QString & title,
const QString & text,
StandardButtons buttons = Ok,
StandardButton defaultButton = NoButton)
我们可以通过下面的代码来演示下如何使用QMessageBox。
connect(button,&QPushButton::clicked,this,[=](){
//QMessageBox::question(this,"标题","你需要吗");后面两个参数可以省略,默认为yes|no
StandardButton ret = QMessageBox::question(this,"标题","你需要吗",QMessageBox::Open|QMessageBox::Save);
if(ret == QMessageBox::Open)
qDebug() << "open";
else
qDebug() << "save";
});
if (QMessageBox::Yes == QMessageBox::question(this,
tr("Question"), tr("Are you OK?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes))
QMessageBox::information(this, tr("Hmmm..."),tr("I'm glad to hear that!"));
else
QMessageBox::information(this, tr("Hmmm..."),tr("I'm sorry!"));
我们使用QMessageBox::question()来询问一个问题。
这个对话框的父窗口是 this。
QMessageBox是QDialog的子类,这意味着它的初始显示位置将会是在 parent 窗口的中央。
第二个参数是对话框的标题。
第三个参数是我们想要显示的内容。
第四个参数是关联的按键类型,我们可以使用或运算符(|)指定对话框应该出现的按钮。比如我们希望是一个 Yes 和一个 No。
最后一个参数指定默认选择的按钮。
这个函数有一个返回值,用于确定用户点击的是哪一个按钮。按照我们的写法,应该很容易的看出,这是一个模态对话框,因此我们可以直接获取其返回值。
QMessageBox类的 static 函数优点是方便使用,缺点也很明显:非常不灵活。我们只能使用简单的几种形式。为了能够定制QMessageBox细节,我们必须使用QMessageBox的属性设置 API。如果我们希望制作一个询问是否保存的对话框,我们可以使用如下的代码:
QMessageBox msgBox;
msgBox.setText(tr("The document has been modified."));
msgBox.setInformativeText(tr("Do you want to save your changes?"));
msgBox.setDetailedText(tr("Differences here..."));
msgBox.setStandardButtons(QMessageBox::Save
| QMessageBox::Discard
| QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Save);
int ret = msgBox.exec();
switch (ret)
{
case QMessageBox::Save:
qDebug() << "Save document!";
break;
case QMessageBox::Discard:
qDebug() << "Discard changes!";
break;
case QMessageBox::Cancel:
qDebug() << "Close document!";
break;
}
msgBox 是一个建立在栈上的QMessageBox实例。我们设置其主要文本信息为“The document has been modified.”,informativeText 则是会在对话框中显示的简单说明文字。下面我们使用了一个detailedText,也就是详细信息,当我们点击了详细信息按钮时,对话框可以自动显示更多信息。我们自己定义的对话框的按钮有三个:保存、丢弃和取消。然后我们使用了exec()是其成为一个模态对话框,根据其返回值进行相应的操作。
标准文件对话框
QFileDialog,也就是文件对话框。在本节中,我们将尝试编写一个简单的文本文件编辑器,我们将使用QFileDialog来打开一个文本文件,并将修改过的文件保存到硬盘。
首先,我们需要创建一个带有文本编辑功能的窗口。
openAction = new QAction(QIcon(":/images/file-open"),tr("&Open..."), this);
openAction->setStatusTip(tr("Open an existing file"));
saveAction = new QAction(QIcon(":/images/file-save"), tr("&Save..."), this);
saveAction->setStatusTip(tr("Save a new file"));
QMenu *file = menuBar()->addMenu(tr("&File"));
file->addAction(openAction);
file->addAction(saveAction);
QToolBar *toolBar = addToolBar(tr("&File"));
toolBar->addAction(openAction);
toolBar->addAction(saveAction);
textEdit = new QTextEdit(this);
setCentralWidget(textEdit);
我们在菜单和工具栏添加了两个动作:打开和保存。接下来是一个QTextEdit类,这个类用于显示富文本文件。也就是说,它不仅仅用于显示文本,还可以显示图片、表格等等。不过,我们现在只用它显示纯文本文件。QMainWindow有一个setCentralWidget()函数,可以将一个组件作为窗口的中心组件,放在窗口中央显示区。显然,在一个文本编辑器中,文本编辑区就是这个中心组件,因此我们将QTextEdit作为这种组件。
我们使用connect()函数,为这两个QAction对象添加响应的动作:
connect(openAction, &QAction::triggered,this, &MainWindow::openFile);
connect(saveAction, &QAction::triggered,this, &MainWindow::saveFile);
下面是最主要的openFile()和saveFile()这两个函数的代码:
//打开文件
void MainWindow::openFile()
{
QString path = QFileDialog::getOpenFileName(this,tr("Open File"), ".", tr("Text Files(*.txt)"));
if(!path.isEmpty())
{
QFile file(path);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
{
QMessageBox::warning(this, tr("Read File"), tr("Cannot open file:\n%1").arg(path));
return;
}
QTextStream in(&file);
textEdit->setText(in.readAll());
file.close();
}
else
QMessageBox::warning(this, tr("Path"), tr("You did not select any file."));
}
//保存文件
void MainWindow::saveFile()
{
QString path = QFileDialog::getSaveFileName(this,tr("Open File"), ".", tr("Text Files(*.txt)"));
if(!path.isEmpty())
{
QFile file(path);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
{
QMessageBox::warning(this, tr("Write File"),tr("Cannot open file:\n%1").arg(path));
return;
}
QTextStream out(&file);
out << textEdit->toPlainText();
file.close();
}
else
QMessageBox::warning(this, tr("Path"),tr("You did not select any file."));
}
在openFile()函数中,我们使用QFileDialog::getOpenFileName()来获取需要打开的文件的路径。这个函数原型如下:
QString getOpenFileName(QWidget * parent = 0,
const QString & caption = QString(),
const QString & dir = QString(),
const QString & filter = QString(),
QString * selectedFilter = 0,
Options options = 0)
不过注意,它的所有参数都是可选的,因此在一定程度上说,这个函数也是简单的。这六个参数分别是:
parent:父窗口。
Qt 的标准对话框提供静态函数,用于返回一个模态对话框;
caption:对话框标题;
dir:对话框打开时的默认目录
“.” 代表程序运行目录
“/” 代表当前盘符的根目录(特指 Windows 平台;Linux 平台当然就是根目录),这个参数也可以是平台相关的,比如“C:\”等;
filter:过滤器。
我们使用文件对话框可以浏览很多类型的文件,但是,很多时候我们仅希望打开特定类型的文件。比如,文本编辑器希望打开文本文件,图片浏览器希望打开图片文件。过滤器就是用于过滤特定的后缀名。如果我们使用“Image Files(.jpg .png)”,则只能显示后缀名是 jpg 或者 png 的文件。如果需要多个过滤器,使用“;;”分割,比如“JPEG Files(.jpg);;PNG Files(.png)”;
selectedFilter:默认选择的过滤器;
options:对话框的一些参数设定
比如只显示文件夹等等,它的取值是enum QFileDialog::Option,每个选项可以使用 | 运算组合起来。
QFileDialog::getOpenFileName()返回值是选择的文件路径。我们将其赋值给 path。通过判断 path 是否为空,可以确定用户是否选择了某一文件。只有当用户选择了一个文件时,我们才执行下面的操作。
在saveFile()中使用的QFileDialog::getSaveFileName()也是类似的。使用这种静态函数,在 Windows、Mac OS 上面都是直接调用本地对话框,但是 Linux 上则是QFileDialog自己的模拟。这暗示了,如果你不使用这些静态函数,而是直接使用QFileDialog进行设置,那么得到的对话框很可能与系统对话框的外观不一致。这一点是需要注意的
5.ui布局管理器
创建工程勾选ui项
widget.cpp中ui_widget.h的来历:系统下有个叫做UIC的工具解析ui文件就生成了.h文件,这个文件在编译之后才会有。
可以给按钮增加图片:右下栏的QAbstractButton->icon->添加图片
之后会在ui文件中看到图片的路径,此路径不是相对ui文件的路径,而是相对makefile的路径。我们在qt下运行是可以的,但是在windows下点击.exe文件运行则图片显示不出了,因为图片的路径不对,不是相对.exe文件的路径。所以之前讲过的资源文件的作用就体现出来了。制作资源文件后在icon栏选择添加资源。
参考文章:
关于对象树的解释来自:https://www.cnblogs.com/gd-luojialin/p/9215707.html
对话框来自https://blog.csdn.net/weixin_45525272/article/details/106858517