LINUX.ORG.RU

QT C++ и QML общие настройки

 , ,


0

3

Осваиваю тут QT 5 и сразу проблема, как хранить настройки приложения. Приложение гибридное логика на C++, интерфейс на QML. Для самой QT есть QSettings, для QML есть Settings. Все это здорово конечно, но хотелось бы все настройки иметь в одном месте, а не в двух. Так же хотелось бы чтобы если изменяют некий чекбокс в qml к примеру, то он менял соответствующую настройку и я мог ее прочитать на C++ и наоборот.

Этого можно добиться, простым путем: обернуть QSettings в свой класс и выставить его в QML через rootContext()->setContextProperty или вообще не оборачивать, а выставить как есть - но это уже детали.

Но при таком подходе, QML настройки из него конечно прочитает, но не узнает, если вдруг они изменились, можно подписать на сигнал изменения конечно, но это опять куча ручной работы. Также на каждое изменение настройки в QML придется писать обработчики из серии:

onXChanged: { settings.setValue("main.x", main.x); }

QML-й Settings умеет автоматически сохранять подцепленные к нему свойства, т.е. как в примере из доки:

import QtQuick.Window 2.1
import Qt.labs.settings 1.0

Window {
    id: window

    width: 800
    height: 600

    Settings {
        property alias x: window.x
        property alias y: window.y
        property alias width: window.width
        property alias height: window.height
    }
}
 

В QML прописали что хотим сохранять и все само сохраняется при изменениях.

В связи с этим посмотрел исходники QML-го Settings, не вся реализация там есть, но основная идея вроде понятна.

Набросал тестовый пример: settings.h

#ifndef SETTINGS_H
#define SETTINGS_H

#include <QObject>
#include <QVariant>
#include <QDebug>

class Settings: public QObject
{
    Q_OBJECT
    Q_PROPERTY(QVariant test READ getTest WRITE setTest NOTIFY TestChanged)

public:
    Settings(QObject *parent = 0):
        QObject(parent), m_test(100) {
    }

public:
    QVariant getTest() {
        return m_test;
    }

    void setTest(const QVariant &a_) {
        m_test = a_;

        emit TestChanged(a_);

        qDebug() << "setTest";
    }

signals:
  void TestChanged(QVariant a_);

private:
  QVariant m_test;
};

#endif // SETTINGS_H

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "settings.h"

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    Settings settings;
    QQmlApplicationEngine engine;

    engine.rootContext()->setContextProperty("settings", &settings);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    return app.exec();
}

main.qml

import QtQuick 2.3
import QtQuick.Window 2.2

Window {
    visible: true
    x: settings.test
    height: 320
    width: 240

    onXChanged: {
        title = "x=" + x + ":test=" + settings.test;
    }

    MouseArea {
        anchors.fill: parent
        onClicked: {
          settings.test += 50;
        }
    }
}


 

В сторону QML работает, при любом изменении св-ва test тут же меняется и связанное свойство в QML. А вот наоборот нифига. Причем у QML-го Settings это работает, вопрос собственно простой: Что я делаю не так ?

И в догонку, а вообще как кто решает проблему хранения и отображения настроек в интерфейсе ?

В сторону QML работает, при любом изменении св-ва test тут же меняется и связанное свойство в QML. А вот наоборот нифига.

Поподробнее, пожалуйста. Не вызывается setTest в onClicked, что ли?

И в догонку, а вообще как кто решает проблему хранения и отображения настроек в интерфейсе ?

Создаю класс-обертку над QSettings со свойствами и сигналами для настроек. Только я вместо setContextProperty регистрирую QML-тип и создаю объект на стороне QML.

Кстати, если свойство test - целое число, то определять его лучше как int, а не QVariant.

equeim ★★ ()
Ответ на: комментарий от equeim

Поподробнее, пожалуйста. Не вызывается setTest в onClicked, что ли?

Если написать, то вызовется - куда он денется. В Settings QML-ном ведь не надо ничего писать - я собственно также хочу. Чтоб указал свойство и при его изменении (не важно кем) оно само дергало setValue.

Создаю класс-обертку над QSettings со свойствами и сигналами для настроек. Только я вместо setContextProperty регистрирую QML-тип и создаю объект на стороне QML.

Этот вариант понятен, но ведь куча ручной работы по написанию onClicked и т.п. - неудобно.

Создание на стороне QML на мой взгляд как то нарушает целостность. Задача QML отображать и от пользователя данные получать, настройки он должен получать и уметь их отдать и все.

QVariant это для универсальности, понятно что int в данном конкретном случае. Я пример сильно упростил, чтоб проще было разобраться.

dleather ()
Ответ на: комментарий от equeim

А отображение настроек на стороне QML тогда типо того:

Component.onCompleted: { MyCheckbox.checked = settings.value("woptions", "MyCheckbox", 1) }
И так для каждого QML-элемента в окне настроек.

А когда тыкнули в чекбокс:

onClicked: {
settings.setValue("woptions", "MyCheckbox", MyCheckbox.checked ); 
}
И так для каждого... Потом захотел поменять интерфейс и все это в корзину.

dleather ()
Ответ на: комментарий от dleather

Собственно вставил в main.qml Settings к нему в property alias измененные значения свойства окна (window) попадают, а в мой settings пока явно не напишешь нет.

import QtQuick 2.3
import QtQuick.Window 2.2
import Qt.labs.settings 1.0

Window {
    id: window
    visible: true
    x: settings.test
    height: 320
    width: 240

    onXChanged: {
       // settings.test = window.x; // если раскоментить, то все заработает
        title = "x=" + x + ":test=" + settings.test + ":settings.x=" + s.x
    }

    MouseArea {
        anchors.fill: parent
        onClicked: {
          settings.test += 50;
        }
    }
    Settings {
        id: s
        property alias x: window.x
        property alias y: window.y
        property alias width: window.width
        property alias height: window.height
    }

}
dleather ()

Сделай QObject с настройками в виде Q_PROPERTY, вытягивая значение из QSettings и устанавливая значение туда же. Можно сделать макрос для описания свойства, геттера, сеттера и сигнала. Будет приятно доступно и в С++ и QML, и с сигналами.

dib2 ★★★★★ ()
Последнее исправление: dib2 (всего исправлений: 1)
Ответ на: комментарий от dib2

Сделай QObject с настройками в виде Q_PROPERTY, вытягивая значение из QSettings и устанавливая значение туда же. Можно сделать макрос для описания свойства, геттера, сеттера и сигнала. Будет приятно доступно и С++ и QML, и с сигналами.

Я так и хочу собственно (в приведенном примере так и делается), проблема состоит в том, что при изменении свойства в QML пользователем его значение не меняется в C++ части, в обратном направлении без проблем. Только если принудительно из QML его выставлять.

Сейчас копаю в сторону получить у сигнала на изменение у свойства С++ части список подписчиков и автоматом подписываться на их сигнал изменения. Докопал до QObjectPrivate, даже доступ к нему из своего наследника получил. Уже не сильно нравиться дергать функции из приватной части, но в паблик части нету таких функции. Максимум что есть

 isSignalConnected(TestChanged)

Он возвращает true, что к сигналу есть подключение. Осталось понять как вытащить кто конкретно к нему подключен и уже дальше подключиться к сигналу изменения этого объекта.

dleather ()
Ответ на: комментарий от dleather

проблема состоит в том, что при изменении свойства в QML пользователем его значение не меняется в C++

ну так сделай кеширующий прокси, что-то типа

#define MYPROP(pType, pName, pDefault) \
    public: void pName(const pType& pVal) { m_##pName = QVariant::fromValue(pVal); settings.setValue("pName", m_##pName); emit pName##Changed(); }\
    public: pType pName() const { if (!m_##pName.isValid()) m_##pName = settings.value("pName", pDefault); return m##_pName.value<pType>(); } \
    private: QVariant m_##pName; \
    Q_SIGNAL void pName##Changed(); \
    Q_PROPERTY(pType pName READ pName WRITE pName NOTIFY pName##Changed) \
    private:

class MySettings : public QObject {
    Q_OBJECT

    MYPROP(QString, testProp1, "")
    MYPROP(int, testProp2, "")
    QSettings settings;
};

зы. не компилил, может быть с ошибками. просто для примера

dib2 ★★★★★ ()
Последнее исправление: dib2 (всего исправлений: 1)
Ответ на: комментарий от dib2

Да проблема не как сохранять загружать объявленные свойства...

Проблема в том, что в QML есть допустим CheckBox его свойство checked я привязываю к своему свойству из Settings. Когда я в C++ меняю свойство «галка» у CheckBox меняется автоматически, а когда «галку» у CheckBox меняет пользователь в свойство из Settings значение checked не приходит.

dleather ()
Ответ на: комментарий от dleather

И не должно. Допустим, есть такой код:

CheckBox {
    id: checkBox
    checked: settings.checked
}
Здесь ты устанавливаешь начальное значение checkBox.checked из settings.checked и при каждом изменении settings.checked (т.е. когда вызывается соответствующий NOTIFY сигнал) соответственно изменяется checkBox.ckecked. И ничего более. Почитай документацию: http://doc.qt.io/qt-5/qtqml-syntax-propertybinding.html

Если хочешь, чтобы при изменении checkBox.checked изменялся settings.checked, то есть несколько вариантов:

CheckBox {
    id: checkBox
    checked: settings.checked
    onCheckedChanged: settings.checked = checked
}
CheckBox {
    id: checkBox
    checked: settings.checked
}

Binding {
    target: settings
    property: "checked"
    value: checkBox.checked
}

equeim ★★ ()
Ответ на: комментарий от equeim

Эти варианты сводятся к написанию кучи кода, а когда меняешь интерфейс вся эта куча летит в помойку.

Весь прикол в том, что если в QML добавить Settings и связать с ним тот же checked у ComboBox - прекрасно работает в обе стороны. Вопрос как.

Удобно ведь будет, вместо прописывания кучи onChanged с установкой значения в settings просто приравнять свойства друг другу и все само будет обновляться и сохраняться.

dleather ()
Ответ на: комментарий от dleather

Одна строчка - это куча кода? Ок.

Если Settings создается при запуске программы, а CheckBox при создании окна настроек, например (а в программе сложнее hello world еще и в другом файле), то это не сработает. Signal handlers и нужны для того, чтобы ими пользовались.

equeim ★★ ()
Ответ на: комментарий от dleather

Проблема в том, что

ну так я тебе и предложил вариант решения этого вопроса.

dib2 ★★★★★ ()

Этого можно добиться, простым путем: использовать QWidget

trex6 ★★★★★ ()
Ответ на: комментарий от equeim

Очень даже будет работать. Checkbox при создании окна настройки укажет, что его свойство CurrentIndex связано с MySettings.myCheckBoxIndex.

dleather ()
Ответ на: комментарий от dleather

Собственно в Settings стандартном это работает, но им не очень удобно пользоваться, поскольку надо объявлять кучу property alias. По моей же задумке у MySettings будет список MyProp, а уже у каждого MyProp будет Q_Property которое и свяжется с конкретным св-вом на стороне QML. В одноу сторону (C++ -> QML) это работает, в обратную не работает. Стандартный QML Settings работает так как я хочу.

dleather ()

QT 5
QT

Да б..., откуда же вы берётесь?

akk ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.