在Qt框架下搭起来的桌面程序,要是把数据库连接地址、窗口相关的参数、串口通讯的设置,还有用户自己调整过的选项一股脑儿都硬写在代码里,到了后期再去维护就会非常痛苦,因为每改动一个小配置都得重新编译、重新打包,不光麻烦还容易出错。所以,我们很自然地就会碰到两个实际的问题:一是在Qt里面怎么把这些配置妥善地保存下来,二是一旦保存之后,怎么才能在修改配置的同时让它立刻在程序里生效,而不用每次都重启。平时处理这类事情,大家用得最多的就是QSettings这个现成的工具类,它既能直接生成INI文件,也能写进Windows注册表,还可以对接各个平台自带的配置存储机制,只靠简单的键值对就能把数据存起来,对桌面程序和工具类应用来说已经绰绰有余了。
一、Qt怎么保存配置文件
在正式把配置写进文件之前,一般要先选定一种存放的格式,对于那种需要在不同操作系统之间搬来搬去的项目,多数人会选择INI格式,因为它生成的是一个普通文本文件,可以随时用记事本打开核对,部署的时候复制起来也相当利索。
1、建好一个QSettings对象
我们先要在程序的某个位置创建一个QSettings实例,并且把将来要用的配置文件路径和格式提前交代明白。比如可以这样写:QSettings settings("config.ini",QSettings::IniFormat);这段代码的意思就是告诉程序,接下来所有跟配置有关的读写操作,都冲着那个名叫config.ini的文件去,并且格式是INI。如果这里偷懒不指定一个明确的存储位置,Qt就有可能按照它自己的默认规则,把文件丢到系统深处某个目录里,等哪天想起来再去找,路径反而让人摸不着头脑。
2、把具体的配置项逐一写进去
有了settings这个对象以后,就可以直接用它的setValue方法,像往字典里填内容一样,把需要记住的信息一条一条地存起来。比如用来保存数据库信息的代码可以这样敲:settings.setValue("database/ip","127.0.0.1");settings.setValue("database/port",3306);这里斜杠前面的database相当于一个分组标签,你完全不用去手工拼那些带方括号的文本节点,因为Qt自己就能根据你写的键名,把对应的节和值的位置都给安排好,非常省事。
3、记得把数据及时同步到磁盘
当你调用完setValue之后,这些改动其实一开始还逗留在程序的内存缓存里,并没有真正落到硬盘上,如果这个时候软件突然崩溃了,刚才改的配置也就跟着一块儿丢了。所以比较稳妥的做法是,在关键的地方补上一句settings.sync();,它能够强制把缓存里的内容立刻刷新到文件中,后面无论程序正常退出还是碰到意外,至少文件里的数据都是最新的。
4、在需要的位置把配置值读回来
等到要用这些设置的时候,我们再去用value方法把它们取出来,比如要拿到之前存进去的IP地址,就可以写成:QString ip=settings.value("database/ip").toString();更有意思的是,你还可以顺手给一些还没有存过的键提供一个默认值,比如写成settings.value("theme","dark");,这样一来,就算是软件第一次在用户电脑上跑起来,也不会因为读不到某个配置项就在界面上露出空白或者直接报错,等于提前打好了预防针。
二、Qt配置文件修改后怎么立即生效
不少项目会遇到一个让人很别扭的现象:明明已经把配置保存到了文件里,可界面上看到的却还是老样子,非得老老实实退出去再重新打开一次,设置才会变过来。背后的真正原因,一般并不是QSettings没把文件写成功,而是正在运行着的程序一直在用自己记忆里的那套旧数据,根本没去文件里再读一遍。
1、保存完毕之后马上安排重新读取
因此,当我们把新的配置写入文件以后,紧跟着就应该让与这些设置相关的那些代码再跑一遍加载流程。比如,一旦数据库的IP或者端口号被改掉了,就要在保存动作之后立刻主动重建数据库的连接;如果仅仅是把主题颜色换了一下,那就应该马上重新刷一遍整个窗口的样式。绝对不能只满足于把文件内容改完,却让内存里还在运转的那些对象继续抓着修改前的旧值不放。
2、先用sync把文件的内容确定下来
在配置更新的逻辑里,最好先把settings.sync();调在前面,心里也更有底——至少我们能保证写入磁盘的操作已经真真切切地完成了。特别是当程序里的好几个功能模块,都在差不多的时间里去读写同一份配置文件的时候,这样一个同步动作就能避免许多因为缓冲顺序不对而引发的奇怪现象。
3、留神那些被悄悄缓存起来的变量
很常见的一种写法是,软件刚启动的时候,会把QSettings里的配置一股脑儿读出来,填进某个全局的单例对象或者静态变量里,之后全程序都在用那一份副本,再也没有回头看原来的配置文件一眼。这样一来,哪怕你把文件改出花来,界面和功能也不会跟着动一下。所以当发现设置不生效时,就要先翻一翻代码,看看是不是还有这样的旧缓存在拦着。
三、Qt配置文件不生效怎么排查
有时候配置明明已经成功保存了,可程序的表现就是纹丝不动,这个时候一般不用先去怀疑QSettings本身有什么毛病,更大的可能性是文件保存的实际路径跟自己以为的不一样、读写文件的权限受到了限制,或者加载配置的时机没有找准。
1、把程序真正在读写的路径打印出来
最先要做的事情,就是想办法在程序里输出一下settings.fileName(),用它来确认当前的QSettings对象到底在操作硬盘上的哪一个文件。因为在开发时跑集成环境、给用户安装到不同目录下,或者以不同权限启动的时候,程序默认的存放位置很可能完全是两个地方,一旦路径对不上,那边改了一个文件,这边却还在读另一个文件,自然就不可能生效。
2、注意INI文件的编码和格式
要是你喜欢手动打开INI文件去编辑,一定要小心不要无意间弄乱了它的整体结构,比如把节名改掉、不小心加进去了非法字符,或者让同一个节出现了两次,这些都会导致读取动作失败。键名哪怕只拼错了一个字母,程序也只能当成另一个不相关的配置来看待,结果自然就是读不到想要的值。
3、小心多线程同时写入的风险
在一个多线程的应用程序里,如果有好几个线程在互相不知道对方动作的情况下,争着往同一个配置文件里写内容,就特别容易出现值被覆盖,或者前后写入动作不同步的问题。所以对于那种比较核心的配置项,最好只让一个集中的模块去负责所有的读写工作,其他部分都通过它来间接访问,这样就不容易乱了。
总结
总的来看,在Qt项目里管理程序设置的思路还挺清晰的:先靠QSettings搭配INI格式把配置项用setValue存起来,再用sync把它们安全地同步到磁盘;读取的时候可以顺手给个默认值,让第一次启动也不会出错。想在修改配置后立刻见到效果,核心的一条就是主动去重新加载那些受影响的模块,而不是光改掉文件里的内容;监听配置文件的改动也能让程序反应更快。