Q_PROPERTY Система не работает должным образом

У меня есть этот класс, который я намеревался использовать в контексте движка qml, поэтому, чтобы использовать привязку свойств, я установил эти макросы Q_PROPERY. Я хочу использовать ключевое слово MEMBER и получать сигнал уведомления автоматически.

class InterfaceBackend : public QObject
{
    Q_OBJECT
    Q_PROPERTY(quint8 current_view MEMBER m_current_view NOTIFY sCurrentViewChanged)
    Q_PROPERTY(quint8 future_view MEMBER m_future_view NOTIFY sFutureViewChanged)

public:
    explicit InterfaceBackend(QObject *parent = 0);
    ~InterfaceBackend();

    quint8 getCurrentView() { return this->m_current_view; }
    quint8 getFutureView() { return this->m_future_view; }

private:
    quint8 m_current_view;
    quint8 m_future_view;
    QByteArray m_selected_language;

public slots:
    void onLanguageSelected(QByteArray language);

private slots:


signals:
    void sCurrentViewChanged(quint8 current_view);
    void sFutureViewChanged(quint8 future_view);

};


InterfaceBackend::InterfaceBackend(QObject *parent) : QObject(parent)
{
    this->setObjectName("backend");
    QObject::connect(this, &InterfaceBackend::sFutureViewChanged, []() {qDebug() << "sFutureViewChanged";});
    this->m_current_view=1;
    this->m_future_view=1;
}

InterfaceBackend::~InterfaceBackend()
{

}

void InterfaceBackend::onLanguageSelected(QByteArray language)
{
    this->m_selected_language=language;
    this->m_future_view=2;
}

Документы qt говорят:

Сигнал NOTIFY не является обязательным. Если он определен, он должен указывать один существующий сигнал в этом классе, который излучается всякий раз, когда значение свойства изменяется. Сигналы NOTIFY для переменных MEMBER должны принимать ноль или один параметр, который должен быть того же типа, что и свойство. Параметр примет новое значение свойства. Сигнал NOTIFY должен выдаваться только тогда, когда свойство действительно было изменено, чтобы избежать, например, ненужной переоценки привязок в QML. Qt автоматически генерирует этот сигнал, когда это необходимо для свойств MEMBER, у которых нет явного установщика

Но всякий раз, когда я вызываю слот, сигналы никогда не вызываются, а свойство не обновляется в модели qml, что не так !?

попробуйте удалить геттеры. Ключевое слово MEMBER автоматически создает геттер и сеттер для свойства

Andrey Semenov 20.06.2018 15:03

закомментированные геттеры (которые не включены в макросы) все еще не работают, но спасибо

Valentino Miori 20.06.2018 15:11
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
1 190
4

Ответы 4

Как вы можете видеть в статье это:

New keyword in Q_PROPERTY: MEMBER let you bind a property to a class member without requiring to have a getter or a setter.

Итак, вы должны удалить свои геттеры, и ваш результирующий код будет выглядеть следующим образом

class InterfaceBackend : public QObject
{
    Q_OBJECT
    Q_PROPERTY(quint8 current_view MEMBER m_current_view NOTIFY sCurrentViewChanged)
    Q_PROPERTY(quint8 future_view MEMBER m_future_view NOTIFY sFutureViewChanged)

public:
    explicit InterfaceBackend(QObject *parent = 0)
        : QObject(parent)
    {
        this->setObjectName("backend");
        QObject::connect(this, &InterfaceBackend::sFutureViewChanged, []() { qDebug() << "sFutureViewChanged";});
        this->m_current_view=1;
        emit sCurrentViewChanged();
        this->m_future_view=1;
        emit sFutureViewChanged();
    }
    ~InterfaceBackend() = default;

private:
    quint8 m_current_view;
    quint8 m_future_view;
    QByteArray m_selected_language;

public slots:
    void onLanguageSelected(QByteArray language) {
        this->m_selected_language=language;
        this->m_future_view=2;
        emit sFutureViewChanged();
    }

signals:
    void sCurrentViewChanged();
    void sFutureViewChanged();

};

Удаление геттеров не будет иметь никакого значения, поскольку они не упоминаются в макросах с READ или WRITE, также это НЕ то, что я хочу, испускание сигнала ручного изменения работает хорошо (я пробовал перед публикацией), НО, как я понимаю (если я не неправильно) использование MEMBER автоматически подаст сигнал, ФАКТ НЕ БУДЕТ, и проблема здесь

Valentino Miori 20.06.2018 15:19

Если вы измените свойство из qml, он автоматически подаст сигнал. Но если вы измените значение свойства на стороне С ++, он не будет излучать никакого сигнала.

Andrey Semenov 20.06.2018 16:35

Хорошо, я понял, что мне нужно использовать setProperty () для автоматической генерации сигнала в C++, но если я изменю его с помощью qml, он не излучает сигнал, почему?

Valentino Miori 20.06.2018 16:41

Нет, извините, ОН излучает сигнал, но он НЕ обновляет переменную-член, мне совсем не понятно поведение этой системы свойств, даже если я внимательно прочитал документацию ...

Valentino Miori 20.06.2018 16:51

ОК. Если я правильно понял: 1) вы используете `` setProperty ("current_view", 1) `` `вместо` `this-> m_current_view = 1; испустить sCurrentViewChanged (); `` в C++ 2) вы перемещаете экземпляр своего объекта в qml и используете его в qml как возможное решение: попробуйте унаследовать свой класс от QQuickItem, зарегистрируйте его и импортируйте в qml. После этого создайте экземпляр в qml и попробуйте использовать это свойство

Andrey Semenov 20.06.2018 17:04

Каждый Q_PROPERTY должен иметь общедоступный метод READ и общедоступный слот WRITE, также сигнал никогда не будет генерироваться автоматически, вы должны испускать его при изменении MEMBER.

class InterfaceBackend : public QObject
{
    Q_OBJECT
    Q_PROPERTY(quint8 current_view MEMBER m_current_view READ currentView WRITE setCurrentView NOTIFY sCurrentViewChanged)
    Q_PROPERTY(quint8 future_view MEMBER m_future_view READ futureView WRITE setFutureView NOTIFY sFutureViewChanged)

public:
    explicit InterfaceBackend(QObject *parent = 0);
    ~InterfaceBackend();

    quint8 currentView() const
    {
        return m_current_view;
    }
    quint8 futureView() const
    {
        return m_future_view;
    }

private:
    quint8 m_current_view;
    quint8 m_future_view;
    QByteArray m_selected_language;

public slots:
    void onLanguageSelected(QByteArray language);

    void setCurrentView(quint8 current_view)
    {
        if (m_current_view == current_view)
            return;

        m_current_view = current_view;
        emit sCurrentViewChanged(m_current_view);
    }

    void setFutureView(quint8 future_view)
    {
        if (m_future_view == future_view)
            return;

        m_future_view = future_view;
        emit sFutureViewChanged(m_future_view);
    }

private slots:


signals:
    void sCurrentViewChanged(quint8 current_view);
    void sFutureViewChanged(quint8 future_view);

};


InterfaceBackend::InterfaceBackend(QObject *parent) : QObject(parent)
{
    this->setObjectName("backend");
    QObject::connect(this, &InterfaceBackend::sFutureViewChanged, []() {qDebug() << "sFutureViewChanged";});
    this->m_current_view=1;
    this->m_future_view=1;
}

InterfaceBackend::~InterfaceBackend()
{

}

void InterfaceBackend::onLanguageSelected(QByteArray language)
{
    this->m_selected_language=language;
    this->m_future_view=2;
}

Чтобы дать более точный технически ответ:

MEMBER в Q_PROPERTY сообщит moc (компилятору метаобъектов), что при доступе к свойству через мета-объект он должен использовать член напрямую вместо метода получения или установки. Таким образом, moc будет генерировать метод установки внутри себя, который устанавливает член а также, излучающий сигнал - в основном он просто выполняет работу по написанию геттеров / сеттеров за вас. Поскольку при изменении члена необходимо подавать сигнал изменения, это происходит автоматически, когда свойство записывается из мета-объектная система. Итак, звоню:

backend->setProperty("future_view", future_view);

будет правильно излучать измененный сигнал. Это единственная гарантия, которая дается при использовании MEMBER. Изменения, которые выполняются через свойство meta, вызовут сигнал изменения. Это означает, что если вы установите future_view из QML напрямую, без метода onLanguageSelected, он действительно будет работать.

Однако в вашем примере вы напрямую записываете значение в член внутри специального метода - это не будет запускать сигнал автоматически! (Я имею в виду, откуда Qt вообще знать, что вы это сделали). Итак, что вам нужно делать, так это всякий раз, когда вы меняете значение своего члена, вам нужно самостоятельно излучать сигнал изменения:

void onLanguageSelected(QByteArray language)
{
    this->m_selected_language=language;
    this->m_future_view=2;
    emit sFutureViewChanged();
}

Редактировать: Если вы пытались предотвратить запись свойств непосредственно из QML, использование MEMBER не сработает! Вместо этого используйте геттер и зарегистрируйте его только в свойстве. Используйте тот же код, что и выше, для записи и изменения свойств:

    Q_PROPERTY(quint8 future_view READ futureView NOTIFY sFutureViewChanged)

это действительно проясняет, скажем, сторона C++ в порядке, но в QML, если я привяжу свойство current_view, например: "property int current_view: backend.current_view", и QML редактирует это свойство, обратно в C++ я получаю только сигнал onCurrentViewChanged, но переменная делает не обновляйся !!

Valentino Miori 20.06.2018 18:17

QML-привязки однонаправлены. Изменения в backend.current_view обновят свойство QML, но не наоборот.

Felix 20.06.2018 19:24

Потратив некоторое время, я заставил его работать так, как мне нужно, хотя это ЧРЕЗВЫЧАЙНО УЖЕ и БЕЗ ДЕКЛАРАТИВНЫХ СРЕДСТВ Я думаю, что это единственный способ добиться двойной привязки, и, возможно, это будет полезно для других людей, которые подходят к проблеме. Обратите внимание, что это не принятый ответ, я все еще надеюсь, что кто-нибудь где-нибудь, возможно, в будущем, если и когда Qt будет обновлен, сможет предложить гораздо более элегантное решение.

Основные недостатки: более длинный и подробный код, везде приходится использовать сеттеры и (постарайтесь не забыть)

В конце концов, система свойств ничего не делает автоматически, а только указывает, какие свойства выставляются, а какие являются их геттерами / сеттерами и сигналами изменения.

C++

class InterfaceController : public QObject
{
    Q_OBJECT
    Q_PROPERTY(quint8 current_view READ getCurrentView WRITE setCurrentView NOTIFY sCurrentViewChanged)
    Q_PROPERTY(quint8 future_view READ getFutureView WRITE setFutureView NOTIFY sFutureViewChanged)

public:
    explicit InterfaceController(QObject *parent = 0);

    //getters
    quint8 getCurrentView() { return this->m_current_view; }
    quint8 getFutureView() { return this->m_future_view; }

    //setters
    Q_INVOKABLE void setCurrentView(quint8 current_view) { if (this->m_current_view!=current_view) {this->m_current_view=current_view; emit sCurrentViewChanged(this->m_current_view);} }
    Q_INVOKABLE void setFutureView(quint8 future_view) { if (this->m_future_view!=future_view) {this->m_future_view=future_view; emit sFutureViewChanged(this->m_future_view);} }

private:
    quint8 m_current_view;
    quint8 m_future_view;

    QByteArray m_selected_language;

public slots:
    void onLanguageSelected(QByteArray language);

private slots:


signals:
    void sCurrentViewChanged(quint8 current_view);
    void sFutureViewChanged(quint8 future_view);

};

InterfaceController::InterfaceController(QObject *parent) : QObject(parent)
{
    this->m_current_view=1;
    this->m_future_view=1;
}

void InterfaceController::onLanguageSelected(QByteArray language)
{
    this->m_selected_language=language;
    this->setFutureView(2);
}

QML

id: root
property int current_view: 1
property int future_view: 1

Connections {
    target: root
    onCurrent_viewChanged: { backend.setCurrentView(current_view); }
    onFuture_viewChanged: { backend.setFutureView(future_view); }
}

Connections {
    target: backend
    onSCurrentViewChanged: { if (root.current_view!=current_view) {root.current_view=current_view;} }
    onSFutureViewChanged: { if (root.future_view!=future_view) {root.future_view=future_view;} }
}

Другие вопросы по теме