読者です 読者をやめる 読者になる 読者になる

Turn On Blog

ぺーぺーSEの色々メモ

【Qt】QSettingsでアプリケーションの設定を保存する

この記事は、Qt Advent Calendar 2015 16日目の記事です。
昨日は(前回に引き続き)asobotさまの記事でした。
qiita.com
これは、ほんとうに、Qtで開発を始めるときに、
有識者が音頭をとって整理しないといけない部分ですよね。
現在業務でQtを使っておられるお方、今後使う予定のあるお方は、是非!

QSettingsとは?

概要

一言でいうと、アプリケーションの設定を保存するためのクラスです。
たとえば、アプリケーションを開いていた位置やサイズ、
ファイル保存先などのユーザが指定した方法を保持しておき、
次回起動時に復元できる、といったものです。
QSettings Class | Qt Core 5.5
とはいえ、全部自動なわけではなく、書き出しや読み出しは自分で定義する必要があり、(あたりまえですね)
データが実際どのような形式で保存されているかを抽象化し、I/Fを統一してくれる、そんなクラスです。

保存先・フォーマットはレジストリXMLファイル、INIファイルなどから自由に選択可能ですが、今回の例はさまざまなOSで動作が同じとなるINIフォーマットで紹介します。

使い方

基本的な概念

データを識別する文字列である「Key」とデータをセットで保存します。
「Key」は、複数をまとめて1つのグループとして扱うことができ、このグループを「Section」と呼びます。
今回は簡単のためにSectionについては説明を省き、Keyだけを使って設定の読み出し・保存を行っていきます。

読み出し

値の読み出しには、value(QString) 関数を用います。
QSettings Class | Qt 4.8
後述しますが、QSettingsには基本的にどのような型でも書き出せるため、
戻り値はQVariant型となっています。
QVariant::toInt() やQVariant::value()を用いて、意図する型で値を取り出しましょう。

定期的にファイルの自動保存を行う機能を持ったアプリケーションの設定を読みだしてみましょう。
以下の2件の設定があるとします。

  1. ファイル自動保存を行うかどうか(bool)
  2. 自動保存を行う周期のミリ秒(int)
// ファイル名"settings.ini"のファイルからQSettingsインスタンスを生成
QSettings settings("settings.ini", QSettings::IniFormat, this);
// キー"AUTO_SAVE"でbool型のデータを取り出す。
// データ読み出しに失敗した場合、デフォルト値としてtrueを用いる。
// QVariant型のため、toBoolでbool型に変換する。
bool autoSave_ = settings.value("AUTO_SAVE", true).toBool();
// キー"INTERVAL_MSEC"でint型のデータを取り出す。
// デフォルト値は1000とする。
// toIntでint型に変換する。
int intervalMSec = settings.value("INTERVAL_MSEC", 1000).toInt();

どうでしょうか。直感的で簡単ですね。

保存

値の書き出しには、setValue() 関数を用います。
QSettings Class | Qt 4.8
こちらも読み出しと同様に、QVariant型で保存します。
が、QVariantは柔軟なコンストラクタをもつので、Qtで定義されている型の場合、
たいていはそのまま渡して大丈夫です。
(QVariant::fromValueとかやる必要はないです)*1

それではさきほどのアプリケーションについて、今度は保存を行ってみましょう。

// ファイル名"settings.ini"のファイルからQSettingsインスタンスを生成
// (読み出しと同じです)
QSettings settings("settings.ini", QSettings::IniFormat, this);
// メンバ変数autoSave_の値を"AUTO_SAVE"キーの値として保存する。
settings.setValue("AUTO_SAVE", autoSave_);
// intervalMsec_の値を"INTERVAL_MSEC"の値として保存する。
settings.setValue("INTERVAL_MSEC", intervalMsec_);

以上です。
書き込んだ値はsettingsのデストラクタで自動的にファイルに反映されます。
明示的にすぐ反映したいときはsync()を呼び出しますが、通常必要ありません。

エラーチェック

QSettingsに何らかの異常が起きている場合、status()関数で検知することができます。

応用編

QSettingsでなんでも保存できます

QSettignsはQVariantを介して値の読み出し・保存を行いますので、
QVariantに格納できる値ならなんでも読み出し・保存することができます。

これは、int, qreal, QStringなどの基本的な型や、
QtのQRect, QList, QPointなどはもちろん、
独自に定義した型でも保存可能ということです。
データ構造を手軽にそのまま保存できるというのは、非常に便利ですよね。

手順

  1. QDataStream型のストリーム演算子(operator<<)を定義する
  2. QDataStream型のストリーム演算子(operator>>)を定義する
  3. 保存したい型をQ_DECLARE_METATYPEでQMetatype登録する
  4. ストリーム演算子をqRegisterMetaTypeStreamOperators()で登録する

先日書いたこのあたりの記事と同じような話になりますので、
よければこちらもあわせてどうぞ。
taurano.hatenablog.com

1.(2.) : QDataStream型のストリーム演算子を定義

以下のストリーム演算子を定義します。

QDataStream &operator<<(QDataStream &out, Type type);
QDataStream &operator>>(QDataStream & in, Type type);

メンバ変数ではないことにご注意ください。
privateな変数がある場合(Typeがクラスの場合ほぼこれに該当しますが)、
Typeクラス内でfriend関数として宣言してあげましょう。

3. Q_DECLARE_METATYPEでQMetatype登録

Q_DECLARE_METATYPEマクロにより、QMetaTypeに独自の型を登録します。
これにより、手順4のqRegisterMetaTypeStreamOperators()が利用可能となります。
QMetaType Class | Qt Core 5.5

4. qRegisterMetaTypeStreamOperators()で登録する

main()関数冒頭など、定義したストリーム演算子を使うより以前の場所で、
以下のように登録します。

qRegisterMetaTypeStreamOperator<Type>("Type");

使ってみよう

以上の手順を踏むことで、以下のように保存・読み込みが行えるようになります。

// よみこみ
Type t = settings.value("TYPE_VALUE", Type()).value<Type>();
// かきだし
settings.value("TYPE_VALUE", t);

全くべつのシステムやユーザが編集するファイルには不向きかもしれませんが、コードを共有したり、またはそのアプリケーションからのみ読み書きするようなファイルには非常に便利だな~と思います。

1, 2, 3の手順を実践したコードを以下に載せておきます。
生徒クラスStudentをQSettingsで保存するようストリーム演算子を定義しています。

Student.h

#include <QMetaType>
#include <QDataStream>
#include <QString>

class Student
{
private:
    QString name_;
    int age_;
    int japanese_;
    int math_;
public:
    Student(
            QString name = ""
            , int age = 0
            , int japanese = 0
            , int math = 0
            ):
        name_(name)
      , age_(age)
      , japanese_(japanese)
      , math_(math)
    {
    }
    
    // メンバ変数にアクセスするため、friend宣言する。
    friend QDataStream &operator<<(QDataStream &out, Student &student);
    friend QDataStream &operator>>(QDataStream & in, Student &student);
};

Q_DECLARE_METATYPE(Student)

QDataStream &operator<<(QDataStream &out, Student &student)
{
    // 氏名を格納
    out << student.name_;
    // 年齢を格納
    out << student.age_;
    // 得点/国語 を格納
    out << student.japanese_;
    // 得点/数学 を格納
    out << student.math_;
    return out;
}

QDataStream &operator>>(QDataStream &in, Student &student)
{
    // << 演算子で格納した順序と同様の順序で取り出す。
    
    // 氏名を取り出し
    in >> student.name_;
    // 年齢を取り出し
    in >> student.age_;
    // 得点/国語 を取り出し
    in >> student.japanese_;
    // 得点/数学 を取り出し
    in >> student.math_;
    return in;
}

おわりに

お読み頂きありがとうございました。
前回に引き続き初歩的な内容でしたが、どなたかの助けになれば!

QMetaTypeの周辺など、勉強不足で記述の怪しいところがありますので、
何か不備などありましたらご指摘頂けると助かります。

去年は見ているだけだったQt Advent Calendarに参加することが出来てとても楽しかったです!
来年もQtが盛り上がる1年になるといいなあと思っています。
微力ながら盛り上げて行きたいです。

*1:関係ないですが、QAbstractTableModel::data()などで、全部の戻り値がQVariant::fromValue()で囲まれていたりすると暴れたくなります