前言
之前在 Android 开发的时候,也有用到用户偏好保存,当时用的是 SharedPreferences
。
数据最终会被存储为 xml文件
,里面存放的是键值对。Qt 也有类似的类,即 QSettings
。
QSettings
建议先阅读一遍文档 :Qt文档 - QSettings
根据文档所述,QSettings
是一个 平台无关类。之前在 Qt - 跨平台程序打包发布 中提到过Qt跨平台的实现。不了解这个概念的小伙伴可以去看一看。
创建
//最简,NativeFormat
QSettings settings("MySoft", "Star Runner");
//ini文件
QSettings settings("/home/petra/misc/myapp.ini",
QSettings::IniFormat);
//plist文件
QSettings settings("/Users/petra/misc/myapp.plist",
QSettings::NativeFormat);
QSettings对象可以在 堆栈
或 堆
上创建(即使用 new
)。构造和销毁QSettings对象`非常快。
存储格式
QSettings
存储的文件格式有:
-
NativeFormat
使用最适合平台的存储格式存储设置。
在Windows上,指的是
系统注册表
。在macOS和iOS上,这意味着
CFPreferences API
。在Unix上,即
INI格式的文本配置文件
。 -
Registry32Format
仅限Windows:显式访问
32位系统注册表
。在32位Windows上或从64位Windows上的32位应用程序中,其作用与指定NativeFormat相同。
-
Registry64Format
仅限Windows:显式访问
64位系统注册表
。在32位Windows上或在64位Windows上的64位应用程序中,此操作与指定NativeFormat相同。
-
IniFormat
将设置存储在
INI文件
中。注意:从INI文件读取设置时 不会保留类型信息。所有值将作为
QString
返回。 -
InvalidFormat
自定义类型;registerFormat()
返回的特殊值。
小结:用户偏好信息,在Windows中此信息通常存储在 系统注册表
中,在macOS和iOS上则存在 属性列表文件 .qlist
中。在Unix系统上,在没有标准的情况下,许多应用程序(包括KDE应用程序)都使用 INI文本文件
。
QSettings 的 API 基于QVariant(可戳),我们可以使用大多数的类型,例如QString,QRect和QImage。
QSettings存储文件都由一个 QString
和一个 QVariant
组成,该 QString 指定 设置的名称(键),QVariant 存储 与键相关的数据。存储时调用 setValue()
即可。
保存&读取
保存使用的是 setValue()
关于键名,Windows注册表和INI文件使用不区分大小写的键,而macOS和iOS上的CFPreferences API 使用区分大小写的键。
为避免可移植性问题,Qt团队建议遵循以下规则:
-
始终使用相同的大小写引用相同的键。
-
请避免使用相同的键名(不区分大小写)。
-
不要在键名中使用斜杠(“ /”和“ \”);反斜杠字符用于分隔子键。在Windows上,QSettings将“ \”转换为“ /”,从而使它们相同。
我们可以使用’/'字符作为分隔符来形成分层键,类似于 Unix文件路径
settings.setValue(("mainwindow/size", win->size());());
如果要保存或恢复许多具有相同前缀的设置,可以采用 组机制
。用 beginGroup()
指定前缀,结束时调用 endGroup()
。
注意:
- 如果已经存在具有相同键的设置,则现有值将被新值覆盖。
- 为了提高效率,更改由事件循环按固定间隔自动保存至永久存储,所以可能不会立即更新到永久存储中。 若想立刻更新,可调用sync()手动提交更改。
读取使用的是 value()
当使用 value()找不到键值时,将返回一个空的QVariant
;当然我们也可以在使用value()时传入第二个参数作为 默认值
。
void MainWindow::writeSettings()
{
QSettings settings("MySoft", "Star Runner");
settings.beginGroup("MainWindows");
settings.setValue("size",size());
settings.setValue("pos",pos());
settings.endGroup();
}
void MainWindow::readSettings()
{
QSettings settings("MySoft", "Star Runner");
settings.beginGroup("MainWindows");
//参数2:默认值
resize(settings.value("size",QSize(400,400)).toSize());
move(settings.value("pos",QPoint(200,200)).toPoint());
settings.endGroup();
}
要测试给定键是否存在,可调用 contains()
。
注意:如果使用 beginGroup()
设置了一个组,则将键视为相对于该组的键。
要删除与键关联的设置,可调用 remove()
。
QSettings settings;
settings.setValue("ape");
settings.setValue("monkey", 1);
settings.setValue("monkey/sea", 2);
settings.setValue("monkey/doe", 4);
settings.remove("monkey");
QStringList keys = settings.allKeys();
// keys: ["ape"]
如果在组中使用,且 key
为空,则将删除该组所以键值对:
QSettings settings;
settings.setValue("ape");
settings.setValue("monkey", 1);
settings.setValue("monkey/sea", 2);
settings.setValue("monkey/doe", 4);
settings.beginGroup("monkey");
settings.remove("");
settings.endGroup();
QStringList keys = settings.allKeys();
// keys: ["ape"]
要获取所有键的列表,可调用 allKeys()
。
要删除所有键,可调用 clear()
。
删除与此QSettings对象关联的 主要位置 中的所有条目。
后备位置 的条目不会被删除。
存储位置
QSettings 有一个 备用机制,每次查找键值对的时候它会按顺序查找以下四个位置:
- User, Application
- User, Organization
- System, Application
- System, Organization
这四个位置是Qt的抽象,使我们无须指定文件路径或注册表路径。
前面说到,claer()
只会删除主位置的所以条目,备用位置不受影响。
若想关闭该机制可调用 setFallbacksEnabled(false)
。
接下来看一个例子:
QSettings obj1(("MySoft", "Star Runner");
QSettings obj2(("MySoft");
QSettings obj3((QSettings::SystemScope, "MySoft", "Star Runner");
QSettings obj4((QSettings::SystemScope, "MySoft");
下表列举了以上QSettings的主要位置及备用位置:
注意:主要位置可用于读写,而后备位置仅作为读取时的备胎。
那么这些位置具体指哪里呢?
由于比较琐屑,可自行移步 QSettings平台特定说明
跨平台注意事项
-
Windows系统注册表具有以下限制:子项不能超过255个字符,条目的值不能超过16,383个字符,并且键的所有值都不能超过65535个字符。 要解决这些限制的一种方法是使用存储的设置
IniFormat
而不是NativeFormat
。 -
在Windows上,当使用Windows系统注册表时,QSettings不会保留该值的原始类型。 因此,设置新值时,值的类型可能会更改。
-
在macOS和iOS上,allKeys()将针对适用于所有应用程序的全局设置返回一些额外的键。这些键可以使用value()读取,但不能更改,只能被显示。 调用
setFallbacksEnabled(false)
将隐藏这些全局设置。 -
在macOS和iOS上,QSettings使用的CFPreferences API需要Internet域名而不是组织名称。 为了提供统一的API,QSettings从组织名称中派生了一个假域名,该算法在公司名称后附加“ .com”,并用连字符替换空格和其他非法字符。如果要指定其他域名,可使用以下函数:
QCoreApplication::setOrganizationDomain() QCoreApplication::setOrganizationName() QCoreApplication::setApplicationName() QSettings settings;
另一种解决方案是使用预处理器指令:
#ifdef Q_OS_MAC Q_OS_MAC QSettings settings(("grenoullelogique.fr", "Squash"); #else QSettings settings(("Grenoulle Logique", "Squash"); #endif
-
在macOS上,访问权限问题 。
自定义存储格式
该函数用于注册自定义存储格式。成功后,返回一个特殊的Format值,然后我们可将其传递给QSettings构造函数。若创建失败,则返回 InvalidFormat
。
readFunc
和 writeFunc
参数是指向读取和写入一组键/值对的功能。始终以二进制模式打开读取和写入功能的QIODevice参数。
caseSensitivity
参数指定是否键区分大小写,默认值区分大小写。
默认情况下,如果使用了与组织名称和应用程序名称有关的构造函数,则使用的文件系统位置与IniFormat相同。当然也可使用setPath()指定其他位置。
bool readXmlFile(QIODevice &device, QSettings::SettingsMap &map);
bool writeXmlFile(QIODevice &device, const QSettings::SettingsMap &map);
int main(int argc, char *argv[])
{
const QSettings::Format XmlFormat =
QSettings::registerFormat("xml", readXmlFile, writeXmlFile);
QSettings settings(XmlFormat, QSettings::UserScope, "MySoft",
"Star Runner");
}
Demo
下面是一个程序退出前自动保存用户设置的demo:
//mainwindow.h
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
void writeSettings();
void readSettings();
private:
Ui::MainWindow *ui;
protected:
virtual void closeEvent(QCloseEvent* event)override;
};
//mainwindow.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//下次程序进入时自动加载设置 - 主界面构造函数中读取
readSettings();
}
void MainWindow::writeSettings()
{
QSettings settings("MySoft", "Star Runner");
settings.beginGroup("MainWindows");
settings.setValue("size",size());
settings.setValue("pos",pos());
settings.endGroup();
}
void MainWindow::readSettings()
{
QSettings settings("MySoft", "Star Runner");
settings.beginGroup("MainWindows");
//参数2:默认值
resize(settings.value("size",QSize(400,400)).toSize());
move(settings.value("pos",QPoint(200,200)).toPoint());
settings.endGroup();
}
//实现程序退出前保存用户设置 - 重写closeEvent()
void MainWindow::closeEvent(QCloseEvent *event)
{
writeSettings();
}