凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

Qt Quick Local Storage で設定をお手軽に保存する

はじめに

Qt Quick では Local Storage という SQLite ベースの DB を利用して設定の保存が可能です。

ただ生で SQL を扱うのは面倒なので key-value ベースでデータを保存したいと思い、下記 Nokia の Wiki を参考にやってみました。

出来るようになること

例えばスライダの初期値のセットと保存は QML で以下のように記述できます。

Slider {
    value          : storage.get('Setting 1') || 0
    onValueChanged : storage.set('Setting 1', value)
}

コード

(2013/09/23) ルート要素を Item から QtObject へ変更しました。

Storage.qml
import QtQuick 2.1
import QtQuick.LocalStorage 2.0

QtObject {
    property string name        : 'UnnamedDB'
    property string version     : '1.0'
    property string description : 'No description'
    property int estimatedSize  : 100000

    function getDB() {
        return LocalStorage.openDatabaseSync(name, version, description, estimatedSize);
    }

    Component.onCompleted: {
        var db = getDB();
        db.transaction(function(tx) {
            tx.executeSql('CREATE TABLE IF NOT EXISTS settings(key TEXT UNIQUE, value TEXT)');
        });
    }

    function set(key, value) {
        var db = getDB();
        var success = false;
        db.transaction(function(tx) {
            var rs = tx.executeSql('INSERT OR REPLACE INTO settings VALUES (?,?);', [key, value]);
            success = (rs.rowsAffected > 0);
        });
        return success;
    }

    function get(key) {
        var db = getDB();
        var value = null;
        db.readTransaction(function(tx) {
            var rs = tx.executeSql('SELECT value FROM settings WHERE key=?;', [key]);
            if (rs.rows.length > 0) {
                value = rs.rows.item(0).value;
            }
        });
        return value;
    }
}

QtQuick.LocalStorage 2.0 を import します。要素の生成時にテーブルがない場合は追加をし、以降は key-value ベースのアクセスで SQL とデータのやり取りを出来るようにしています。
これを利用した簡単な設定画面を作ってみます。

画面


QML
import QtQuick 2.1
import QtQuick.Controls 1.0
import QtQuick.Layouts 1.0
import '.'

ApplicationWindow {
    width  : 360
    height : 240

    Storage {
        id   : storage
        name : 'TestDB'
    }

    ScrollView {
        id              : view
        anchors.fill    : parent
        anchors.margins : 10

        ColumnLayout {
            spacing: 10

            Text {
                text      : 'Settings'
                font.bold : true
            }

            GroupBox {
                Layout.preferredWidth: view.width
                RowLayout {
                    spacing: 20
                    Text { text: 'Setting 1' }
                    Slider {
                        value          : storage.get('Setting 1') || 0
                        onValueChanged : storage.set('Setting 1', value)
                    }
                }
            }

            GroupBox {
                Layout.preferredWidth: view.width

                RowLayout {
                    spacing: 20
                    Text { text: 'Setting 2' }
                    Slider {
                        value          : storage.get('Setting 2') || 0
                        onValueChanged : storage.set('Setting 2', value)
                    }
                }
            }

            GroupBox {
                Layout.preferredWidth: view.width
                RowLayout {
                    spacing: 20
                    Text { text: 'Setting 3' }
                    Slider {
                        value          : storage.get('Setting 3') || 0
                        onValueChanged : storage.set('Setting 3', value)
                    }
                }
            }

            GroupBox {
                Layout.preferredWidth: view.width
                RowLayout {
                    spacing: 20
                    Text { text: 'Setting 4' }
                    Slider {
                        value          : storage.get('Setting 4') || 0
                        onValueChanged : storage.set('Setting 4', value)
                    }
                }
            }

            GroupBox {
                Layout.preferredWidth: view.width
                RowLayout {
                    spacing: 20
                    Text { text: 'Setting 5' }
                    Slider {
                        value          : storage.get('Setting 5') || 0
                        onValueChanged : storage.set('Setting 5', value)
                    }
                }
            }
        }
    }
}

注目して頂きたいところは冒頭にも書いたように Slider の中身です。

Slider {
    value          : storage.get('Setting 1') || 0
    onValueChanged : storage.set('Setting 1', value)
}

プロパティバインディングにより値の取得とセットがこれだけ簡潔に記述できています。また、初期値を与えるのも楽です(※ 先ほどの Storage.qml で get でデータが取得できない場合には null を返すようにしているのでこんな風にかけます)。
これでスライダをいじって終了し、再起動した場合でもそのスライダの値が保持された状態になります。
GroupBox を別ファイルに分けてまとめてしまえば、ラベル名、get の引数、set の引数を一箇所にまとめられて更に楽になると思います。

保存先について

保存先は QQmlEngine::offlineStoragePath() で調べることが出来ます。

QQuickView viewer;
std::cout << viewer.engine()->offlineStoragePath().toStdString() << std::endl;

Mac の場合は以下の場所に保存されていました。

/Users/(ユーザ名)/Library/Application Support/(アプリ名)/QML/OfflineStorage

おわりに

SQL 部分をラップしてしまえば後はブラウザの LocalStorage の様に使えてお手軽です。なのでブラウザと同じように複雑なデータ構造も JSON.stringify して保存、JSON.parse して復元出来ます。