嵌入式学习-QT-Day07
七、文件IO
1、QFileDialog文件对话框
2、QFileInfo文件信息类
3、QFile文件读写类(重点)
4、UI与耗时操作
5、QThread线程类
5.1 复现程序未响应
5.2 创建并启动一个子线程
5.3 异步刷新
5.4 线程停止
6、数据持久化
七、文件IO
1、QFileDialog文件对话框
与QMessageBox一样,QFileDialog也继承QDialog类,直接使用静态成员函数弹窗,弹窗的结果(选择的文件路径)通过函数返回值获取。
// 获取一个打开或保存的文件路径
// 参数1:父对象
// 参数2:即windowTitle属性(标题)
// 参数3:在那个目录中打开,默认值表示项目的工作目录
// 参数4:文件格式过滤器
// 返回值:选择的文件路径,如果选择失败,返回空字符
QString QFileDialog::getOpenFileName(
QWidget * parent = 0, const QString & caption = QString(), const QString & dir = QString(), const QString & filter = QString())[static]QString QFileDialog::getSaveFileName(
QWidget * parent = 0, const QString & caption = QString(), const QString & dir = QString(), const QString & filter = QString(),
)[static]
需要注意的是,QFileDialog只是一个窗口类,本身不具备任何IO的能力。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QFileDialog>
#include <QMessageBox>namespace Ui {
class Dialog;
}class Dialog : public QDialog
{
Q_OBJECTpublic:
explicit Dialog(QWidget *parent = 0);
~Dialog();private:
Ui::Dialog *ui;
QString readPath;
QString writePath;private slots:
void btnClickedSlot();
};#endif // DIALOG_H
dilaog.cpp
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
connect(ui->pushButtonOpen,SIGNAL(clicked()),
this,SLOT(btnClickedSlot())); connect(ui->pushButtonSave,SIGNAL(clicked()),
this,SLOT(btnClickedSlot()));}Dialog::~Dialog()
{
delete ui;
}void Dialog::btnClickedSlot()
{
if(ui->pushButtonOpen == sender())
{
QString filter = "所有文件(*.*);;Qt(*.cpp *.h *.ui *.pro)";
QString path = QFileDialog::getOpenFileName(this,"打开","D:/",filter);
if(path != "")
{
ui->textBrowserOpen->append(path);
readPath = path;
}
else if(readPath == "")
{
// 弹窗
QMessageBox::warning(this,"警告","请选择要打开的文件");
} }
else if(ui->pushButtonSave == sender())
{
QString filter = "所有文件(*.*);;Qt(*.cpp *.h *.ui *.pro)";
QString path = QFileDialog::getSaveFileName(this,"保存","D:/",filter);
if(path != "")
{
ui->textBrowserSave->append(path);
writePath = path;
}
else if(writePath == "")
{
// 弹窗
QMessageBox::warning(this,"警告","请选择要保存的文件");
}
}
else
{ }
}
2、QFileInfo文件信息类
只需要创建出对象后,通过各种成员函数直接获取文件信息。
// 构造函数
// 参数为文件路径,如果文件非法,仍然可以创建出QFileInfo对象
QFileInfo::QFileInfo(const QString & file)
// 判断文件是否存在
// 如果存在返回ture,不存在返回false
bool QFileInfo::exists() const
// 返回基础文件名,不包含后缀
QString QFileInfo::baseName() const
// 返回文件可读性,true可读,false不可读
bool QFileInfo::isReadable() const
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QFileDialog>
#include <QMessageBox>
#include <QFileInfo>namespace Ui {
class Dialog;
}class Dialog : public QDialog
{
Q_OBJECTpublic:
explicit Dialog(QWidget *parent = 0);
~Dialog();
void printFileInfo();private:
Ui::Dialog *ui;
QString readPath;
QString writePath;private slots:
void btnClickedSlot();
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
connect(ui->pushButtonOpen,SIGNAL(clicked()),
this,SLOT(btnClickedSlot())); connect(ui->pushButtonSave,SIGNAL(clicked()),
this,SLOT(btnClickedSlot()));
}Dialog::~Dialog()
{
delete ui;
}void Dialog::printFileInfo()
{
// 创建文件信息对象
QFileInfo fileInfo(readPath);
if(!fileInfo.exists())
{
return;
} // 获取文件大小
qint64 fileSize = fileInfo.size();
QString text = QString::number(fileSize);
text.prepend("文件大小:").append("字节");
ui->textBrowserOpen->append(text); // 获取文件名称
text = fileInfo.baseName();
text.prepend("文件名称:");
ui->textBrowserOpen->append(text); // 获取文件可读性
bool result = fileInfo.isReadable();
if(result)
{
ui->textBrowserOpen->append("文件可读!!");
}
else
{
ui->textBrowserOpen->append("文件不可读!!");
}
}void Dialog::btnClickedSlot()
{
if(ui->pushButtonOpen == sender())
{
QString filter = "所有文件(*.*);;Qt(*.cpp *.h *.ui *.pro)";
QString path = QFileDialog::getOpenFileName(this,"打开","D:/",filter);
if(path != "")
{
ui->textBrowserOpen->append(path);
readPath = path;
printFileInfo();
}
else if(readPath == "")
{
// 弹窗
QMessageBox::warning(this,"警告","请选择要打开的文件");
} }
else if(ui->pushButtonSave == sender())
{
QString filter = "所有文件(*.*);;Qt(*.cpp *.h *.ui *.pro)";
QString path = QFileDialog::getSaveFileName(this,"保存","D:/",filter);
if(path != "")
{
ui->textBrowserSave->append(path);
writePath = path;
}
else if(writePath == "")
{
// 弹窗
QMessageBox::warning(this,"警告","请选择要保存的文件");
}
}
else
{ }
}
3、QFile文件读写类(重点)
在Qt中所有IO都继承自QIODevice类,QIODevice类中规定了最基础的IO相关的接口。这些接口虽然在不同的派生类中可能实现有所区别,但是调用方式一致。
// 构造函数
// 参数为文件路径,如果是非法路径,也能创建出QFile对象,但是不能正常IO输出输入操作。
QFile::QFile(const QString & name)
// 判断QFile对应的文件是否存在
bool QFile::exists() const
// 打开数据流
// 参数为打开的模式、只读模式、只写模式、读写模式等等。
// 返回值为打开的结果
bool QIODevice::open(OpenMode mode)[virtual]
// 是否读到文件尾部
bool QIODevice::atEnd() const
// 读取最大长度为maxSize个的字节到返回值中
QByteArray QIODevice::read(qint64 maxSize)
// 构造函数,QByteArray是Qt中常用的数组
// 构造一个空字节数组
QByteArray::QByteArray()
// 写出数据
// 参数为写出的内容
// 返回值为实际的数据写出的字节数,出错返回-1
qint64 QIODevice::write(const QByteArray & byteArray)
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QFileDialog>
#include <QMessageBox>
#include <QFileInfo>
#include <QFile>namespace Ui {
class Dialog;
}class Dialog : public QDialog
{
Q_OBJECTpublic:
explicit Dialog(QWidget *parent = 0);
~Dialog();
void printFileInfo();
void copy();private:
Ui::Dialog *ui;
QString readPath;
QString writePath;private slots:
void btnClickedSlot();
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
connect(ui->pushButtonOpen,SIGNAL(clicked()),
this,SLOT(btnClickedSlot())); connect(ui->pushButtonSave,SIGNAL(clicked()),
this,SLOT(btnClickedSlot())); connect(ui->pushButtonCopy,SIGNAL(clicked()),
this,SLOT(btnClickedSlot()));
}Dialog::~Dialog()
{
delete ui;
}void Dialog::printFileInfo()
{
// 创建文件信息对象
QFileInfo fileInfo(readPath);
if(!fileInfo.exists())
{
return;
} // 获取文件大小
qint64 fileSize = fileInfo.size();
QString text = QString::number(fileSize);
text.prepend("文件大小:").append("字节");
ui->textBrowserOpen->append(text); // 获取文件名称
text = fileInfo.baseName();
text.prepend("文件名称:");
ui->textBrowserOpen->append(text); // 获取文件可读性
bool result = fileInfo.isReadable();
if(result)
{
ui->textBrowserOpen->append("文件可读!!");
}
else
{
ui->textBrowserOpen->append("文件不可读!!");
}
}void Dialog::copy()
{
if(readPath == "")
{
QMessageBox::warning(this,"警告","请选择要读取的文件");
return;
} if(writePath == "")
{
QMessageBox::warning(this,"警告","请选择要写入的文件路径");
return;
}
// 正式拷贝时屏蔽拷贝按钮
ui->pushButtonCopy->setEnabled(false); // 创建QFile对象
QFile readFile(readPath);
QFile writeFile(writePath); // 打开文件流
readFile.open(QIODevice::ReadOnly); // 只读模式
writeFile.open(QIODevice::WriteOnly); // 只写模式 // 添加进度条效果
qint64 totalSize = readFile.size(); // 获取文件总大小
qint64 hasRead = 0; // 已经读写的大小 QByteArray array; // 字节数组类对象
while(!readFile.atEnd()) // 判断是否读到文件尾部
{
array = readFile.read(2048); // 每次读取2kb
qint64 writeRet = writeFile.write(array); // 写出数据,返回值为本次写出的大小
if(writeRet == -1) // 如果写出函数的返回值为-1,表示写出失败
{
QMessageBox::critical(this,"错误","文件拷贝失败"); // 清空缓冲区
writeFile.flush(); // 关闭数据流
readFile.close();
writeFile.close();
// 拷贝失败时,解除按钮的屏蔽效果
ui->pushButtonCopy->setEnabled(true);
return;
}
// 将写出的数据设置给进度条
hasRead += writeRet;
int per = hasRead*100 / totalSize; // 计算百分比
ui->progressBar->setValue(per); // 设置给进度条
}
// 清空缓冲区
writeFile.flush(); // 关闭数据流
readFile.close();
writeFile.close();
// 拷贝完成时,解除按钮屏蔽效果
ui->pushButtonCopy->setEnabled(true);
// 拷贝完成提示框
QMessageBox::information(this,"提示","拷贝完成");
}void Dialog::btnClickedSlot()
{
if(ui->pushButtonOpen == sender())
{
QString filter = "所有文件(*.*);;Qt(*.cpp *.h *.ui *.pro)";
QString path = QFileDialog::getOpenFileName(this,"打开","D:/",filter);
if(path != "")
{
ui->textBrowserOpen->append(path);
readPath = path;
printFileInfo();
}
else if(readPath == "")
{
// 弹窗
QMessageBox::warning(this,"警告","请选择要打开的文件");
} }
else if(ui->pushButtonSave == sender())
{
QString filter = "所有文件(*.*);;Qt(*.cpp *.h *.ui *.pro)";
QString path = QFileDialog::getSaveFileName(this,"保存","D:/",filter);
if(path != "")
{
ui->textBrowserSave->append(path);
writePath = path;
}
else if(writePath == "")
{
// 弹窗
QMessageBox::warning(this,"警告","请选择要保存的文件");
}
}
else if(ui->pushButtonCopy == sender())
{
copy();
}
else
{ }
}
【思考】上面的代码有没有问题?
当拷贝大文件时,会出现程序卡顿,如果尝试关闭,则触发:
4、UI与耗时操作
在默认情况下,Qt的项目是单线程的,这个自带的线程用于处理程序的主要任务和UI交互,也被称为主线程或者UI线程。
如果在主线程中执行耗时操作(IO或者复杂算法)会导致主线程原本执行的操作被阻塞,设置无法关闭,形成“假死”现象。
当操作系统发现某个进程无法正常关闭时,会弹出程序未响应窗口引导用于是否选择强制关闭当前进程。
解决方式是使用多线程。
5、QThread线程类
5.1 复现程序未响应
QThread类是Qt的线程类,可以使用下面的函数模拟耗时操作。
// 强制线程休眠msecs个毫秒
void QThread::msleep(unsigned long msecs)[static]
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QThread>
#include <QDebug>namespace Ui {
class Dialog;
}class Dialog : public QDialog
{
Q_OBJECTpublic:
explicit Dialog(QWidget *parent = 0);
~Dialog();private:
Ui::Dialog *ui;private slots:
void btnSleepClickedSlot();
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
connect(ui->pushButtonSleep,SIGNAL(clicked()),
this,SLOT(btnSleepClickedSlot())); connect(ui->pushButtonClose,SIGNAL(clicked()),
this,SLOT(close()));
}Dialog::~Dialog()
{
delete ui;
}void Dialog::btnSleepClickedSlot()
{
qDebug() << "睡眠开始";
QThread::msleep(10000);
qDebug() << "睡眠结束";
}
5.2 创建并启动一个子线程
主线程以外的线程都是子线程,子线程不能执行主线程的ui操作,只能执行耗时操作。
下面是创建并启动一个自定义线程的步骤:
在Qt Creator中选中项目名称,鼠标右键,点击“添加新文件”。
在弹出的窗口中,先设置类名,然后在选择基类名称QObject,最后点击“下一步”。
在项目管理界面, 直接点击完成过,可以看到线程类的文件已经创建。
选择新建的头文件,把继承的QObject更改为QThread
选择新建的.CPP文件,把透传构造的QObject更改为QThread
在自定义线程类中,覆盖基类QThread的run函数。
// 此函数是子线程的执行的起始点,也是子线程的结束点。
void QThread::run()[virtual]
- 在run函数的函数体中编写子线程要执行的耗时操作
- 创建自定义子线程对象,并调用start函数启动子线程。
// 启动子线程,启动后会执行run
// 参数:线程调度优先级,默认是与父线程相同优先级
void QThread::start(Priority priority = InheritPriority)
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QThread>
#include <QDebug>
#include "mythread.h"namespace Ui {
class Dialog;
}class Dialog : public QDialog
{
Q_OBJECTpublic:
explicit Dialog(QWidget *parent = 0);
~Dialog();private:
Ui::Dialog *ui;private slots:
void btnSleepClickedSlot();
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
connect(ui->pushButtonSleep,SIGNAL(clicked()),
this,SLOT(btnSleepClickedSlot())); connect(ui->pushButtonClose,SIGNAL(clicked()),
this,SLOT(close()));
}Dialog::~Dialog()
{
delete ui;
}void Dialog::btnSleepClickedSlot()
{
// 创建子线程并启动
MyThread *mt = new MyThread(this);
mt->start();
}
mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H#include <QThread>
#include <QDebug>class MyThread : public QThread
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = 0);
~MyThread(); void run();
signals:public slots:
};#endif // MYTHREAD_H
mythread.cpp
#include "mythread.h"MyThread::MyThread(QObject *parent) : QThread(parent)
{}MyThread::~MyThread()
{}void MyThread::run()
{
qDebug() << "睡眠开始";
QThread::msleep(10000);
qDebug() << "睡眠结束";
}
5.3 异步刷新
在实际开发中,两个线程不可能毫无关系的前提下各干各的 ,最常见的情况是主线程分配一个耗时的任务给子线程,子线程需要把耗时任务的执行情况反馈给主线程。主线程刷新子线程的耗时操作,并展示对应的UI效果。
【例如】:子线程执行文件拷贝,主线程显示拷贝的进度。
通常子线程是主线程对象的子对象,因此异步刷新就是对象通信的问题,使用信号槽解决。
咱们写一个简单的例子,一个伪拷贝的案例,使用for循环加睡眠,模拟文件拷贝的功能。
今天晚上的作业:实现真正的拷贝功能。
Dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QMessageBox>
#include "mythread.h"namespace Ui {
class Dialog;
}class Dialog : public QDialog
{
Q_OBJECTpublic:
explicit Dialog(QWidget *parent = 0);
~Dialog();private:
Ui::Dialog *ui;private slots:
void btnClickedSlot();
void valueSlot(int);
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this); connect(ui->pushButton,SIGNAL(clicked()),
this,SLOT(btnClickedSlot()));
}Dialog::~Dialog()
{
delete ui;
}void Dialog::btnClickedSlot()
{
ui->pushButton->setEnabled(false);
MyThread *mt = new MyThread(this); connect(mt,SIGNAL(valueSignal(int)),
this,SLOT(valueSlot(int)));
// 启动子线程
mt->start();
}void Dialog::valueSlot(int value)
{
ui->progressBar->setValue(value);
if(value == 100)
{
ui->pushButton->setEnabled(true);
QMessageBox::information(this,"提示","拷贝完成!!");
}
}
mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H#include <QThread>class MyThread : public QThread
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = 0);
~MyThread(); void run();signals:
void valueSignal(int);public slots:
};#endif // MYTHREAD_H
myThread.cpp
#include "mythread.h"MyThread::MyThread(QObject *parent) : QThread(parent)
{}MyThread::~MyThread()
{}void MyThread::run()
{
for(int i = 0; i <= 100; i++)
{
QThread::msleep(100);
emit valueSignal(i);
}
}
进度条到100时,输出提示框。(问题:频繁抖动窗口,会出现焦点抢夺的问题,导致卡死,解决使用hide函数,来隐藏主窗口)
5.4 线程停止
子线程往往执行耗时操作,耗时任务往往又伴随着循环,因此并不建议使用粗暴的方式直接停止线程,因为强行停止线程会导致耗时资源无法回收等问题。
可以通过给循环设置标志位的方式使线程停止。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QMessageBox>
#include "mythread.h"namespace Ui {
class Dialog;
}class Dialog : public QDialog
{
Q_OBJECTpublic:
explicit Dialog(QWidget *parent = 0);
~Dialog();private:
Ui::Dialog *ui;
MyThread *mt;private slots:
void btnClickedSlot();
void valueSlot(int);
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this); connect(ui->pushButton,SIGNAL(clicked()),
this,SLOT(btnClickedSlot()));
}Dialog::~Dialog()
{
delete ui;
}void Dialog::btnClickedSlot()
{
if(ui->pushButton->text() == "开始拷贝")
{
mt = new MyThread(this); connect(mt,SIGNAL(valueSignal(int)),
this,SLOT(valueSlot(int)));
// 启动子线程
mt->start(); ui->pushButton->setText("停止拷贝");
}
else if(ui->pushButton->text() == "停止拷贝")
{
ui->pushButton->setText("开始拷贝");
mt->setRunningState(false);
}}void Dialog::valueSlot(int value)
{
ui->progressBar->setValue(value);
if(value == 100)
{
this->hide(); // 隐藏主窗口,只是看不到
this->show(); // 显示主窗口
QMessageBox::information(this,"提示","拷贝完成!!");
}
}
myThread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H#include <QThread>
#include <QDebug>class MyThread : public QThread
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = 0);
~MyThread(); void run(); bool getRunningState() const;
void setRunningState(bool value);private:
bool runningState; // 状态标志signals:
void valueSignal(int);public slots:
};#endif // MYTHREAD_H
myThread.cpp
#include "mythread.h"MyThread::MyThread(QObject *parent) : QThread(parent)
{
runningState = true;
}MyThread::~MyThread()
{}void MyThread::run()
{
for(int i = 0; i <= 100 && runningState; i++)
{
QThread::msleep(100);
emit valueSignal(i);
} qDebug() << "资源释放成功";
}bool MyThread::getRunningState() const
{
return runningState;
}void MyThread::setRunningState(bool value)
{
runningState = value;
}
6、数据持久化
数据持久化:是将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中的数据模型的统称。
之前说过的数据库就是一种数据持久化的方式,虽然嵌入式使用的SQLite数据库已经是轻量级数据库了,但是相对于其他技术,还是略显冗余。
Qt中提供比数据库更轻巧的数据持久化的方式——QSettings
// 构造函数
// 参数1:存储文件的名称,默认为构建目录
// 参数2:存储格式(.ini)
// 参数3:父对象
QSettings::QSettings(const QString & fileName, Format format, QObject * parent = 0)
// 设置INI文件的编码,建议使用UTF-8
// 编码字符串
void QSettings::setIniCodec(const char * codecName)
// 开始存储,同类型建议使用此函数,以数组的方式进行存储
// 参数为数组的名称
void QSettings::beginWriteArray(const QString & prefix, int size = -1)
// 以 “组”的方式进行存储,不同类型建议使用此函数(相同类型也可以,但是性能不如上面好)
// 参数:“组”的名称
void QSettings::beginGroup(const QString & prefix)
// 在“组”中添加键值对(组和数组都用这个函数)
// 参数1:键
// 参数2:值
void QSettings::setValue(const QString & key, const QVariant & value)
// 结束数组的存储
void QSettings::endArray()
// 结束组的存储
void QSettings::endGroup()
// 根据键获取值
// 参数1:键
// 参数2:如果取出失败的默认值
// 返回值:取出的值
QVariant QSettings::value(const QString & key, const QVariant & defaultValue = QVariant()) const
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <QSettings>namespace Ui {
class Dialog;
}class Dialog : public QDialog
{
Q_OBJECTpublic:
explicit Dialog(QWidget *parent = 0);
~Dialog();private:
Ui::Dialog *ui; void load();private slots:
void save();
};#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this); load();
connect(ui->pushButton,SIGNAL(clicked()),
this,SLOT(save()));
}Dialog::~Dialog()
{
delete ui;
}// 读取
void Dialog::load()
{
// 创建一个临时使用的QSettings对象
QSettings setting("config.ini",QSettings::IniFormat); // 设置文件编码UTF-8
setting.setIniCodec("UTF-8"); // 开始以组的方式读取数据
setting.beginGroup("24081"); // 开始读数据
QString name = setting.value("name").toString();
ui->lineEdit->setText(name);
QString text = setting.value("gender").toString();
ui->comboBox->setCurrentText(text);
int age = setting.value("age").toInt();
ui->spinBox->setValue(age); // 结束组
setting.endGroup();
}// 存储
void Dialog::save()
{
// 创建一个临时使用的QSettings对象
QSettings setting("config.ini",QSettings::IniFormat); // 设置文件编码 UTF-8
setting.setIniCodec("UTF-8"); // 开始以组的方式存储数据
setting.beginGroup("24081"); // 开始写数据
setting.setValue("name",ui->lineEdit->text());
setting.setValue("gender",ui->comboBox->currentText());
setting.setValue("age",ui->spinBox->value()); // 结束组
setting.endGroup();
}