Есть ли что-нибудь вроде ICommand в Qt / QML? - PullRequest
1 голос
/ 10 июля 2020

Чтобы достичь той же функциональности, что и в моем приложении WPF, здесь я создал Q_INVOKABLE с именем isNewPlotValid и вызываю его всякий раз, когда изменяется содержимое моих полей:

ColumnLayout{
    LineField{
        label: "Name"
        text: addContext.newPlot.name
        onTextChanged: {
            addContext.newPlot.name = text
            button.isEnabled = addContext.isNewPlotValid()
        }
    }
    AreaField{
        label: "Address"
        Layout.fillHeight: true
        text: addContext.newPlot.description
        onTextChanged: {
            addContext.newPlot.description = text
            button.isEnabled = addContext.isNewPlotValid()
        }
    }
    PathButton{
        id: button
        pathData: C.addIcon
        onClicked: addContext.addNewPlot()
    }
}

It делает то, что я хотел, НО я должен иметь button.isEnabled = addContext.isNewPlotValid() в каждом поле! Есть ли какой-либо ярлык?

EDIT

В папке Model моего проекта у меня есть только файлы заголовков, которые определяют структуру данных (Q_PROPERTY) следующим образом:

class Plot : public QObject{
    Q_OBJECT
    Property(int, id)
    Property(QString, name)
    Property(QString, description)
};

ничего больше для любого типа Model, кроме enum, а макрос Property создает все необходимые Q-наполнители для Model следующим образом:

#define Property(QType, name) \
    Q_PROPERTY(QType name READ name WRITE set##name NOTIFY name##Changed) \
    public: \
        QType& name() {return m_##name;} \
        void set##name(QType value){if(m_##name != value){m_##name = value; emit name##Changed();}} \
        Q_SIGNAL void name##Changed(); \
    private: \
        QType m_##name;

All мои ViewModels находятся в отдельной папке под названием ViewModel. У меня есть одна ViewModel с именем AddVM, и это Property типа Plot*:

class AddVM : public QObject
{
    ...
    Property(Plot*, newPlot)
    ...
};

Теперь это будет уведомлять пользовательский интерфейс только при изменении указателя. Итак, мне нужно создать еще один slot, который делает это:

void AddVM::onPlotChanged()
{
    emit newPlotChanged();
}

и соединить сигналы nameChanged и descriptionChanged newPlot с этим слотом onPlotChanged? Я пробовал соединить оба способа таким образом:

QObject::connect(m_newPlot, &AddVM::m_newPlot->nameChanged, this, &AddVM::onPlotChanged);
QObject::connect(m_newPlot, &AddVM::m_newPlot->descriptionChanged, this, &AddVM::onPlotChanged);

НО второй аргумент, &AddVM::m_newPlot->nameChanged и &AddVM::m_newPlot->descriptionChanged, в обеих строках говорит:

не может создать non -константный указатель на функцию-член.

EDIT

Таким образом, решение состоит в том, чтобы иметь их в AddVM:

class AddVM : public QObject
{
    Property(Plot*, newPlot)
    Property(bool, isNewPlotValid)

private:
    bool hasMatch(QString& text);
    void hookupSignalAndSlots();

private slots:
    void onPlotChanged();
}; 

они в cpp:

bool AddVM::hasMatch(QString &text)
{
    foreach(auto x, mvm->plots())
        if(x->name() == text) return true;
    return false;
}

void AddVM::hookupSignalAndSlots()
{
    QObject::connect(m_newPlot, &Plot::nameChanged, this, &AddVM::onPlotChanged);
    QObject::connect(m_newPlot, &Plot::descriptionChanged, this, &AddVM::onPlotChanged);
}

void AddVM::onPlotChanged()
{
    if(!newPlot()->name().isEmpty() && !newPlot()->description().isEmpty() && !hasMatch(newPlot()->name()))
        setisNewPlotValid(true);
    else setisNewPlotValid(false);
}

, а эти в qml:

ColumnLayout{
    LineField{
        label: "Name"
        text: addContext.newPlot.name
        onTextChanged: addContext.newPlot.name = text
    }
    AreaField{
        label: "Address"
        Layout.fillHeight: true
        text: addContext.newPlot.description
        onTextChanged: addContext.newPlot.description = text
    }
    PathButton{
        pathData: C.addIcon
        toolTip: "insert into database"
        onClicked: addContext.addNewPlot()
        isEnabled: addContext.isNewPlotValid
    }
}

работают должным образом.

1 Ответ

0 голосов
/ 10 июля 2020

Вы должны преобразовать этот Q_INVOKABLE в свойство, которое может NOTIFY, чтобы QML Engine знал, что свойство изменено, затем вы можете привязать это свойство к свойству enabled либо каждой кнопки по отдельности, либо для всего ColumnLayout сразу.

Добавьте в свой класс «addContext»:

Q_PROPERTY(bool isNewPlotValid READ isNewPlotValid NOTIFY isNewPlotValidChanged)

//add signal:
signals:
    void isNewPlotValidChanged();

Сложная часть может заключаться в том, чтобы сигнализировать в правильных местах, но это неясно из предоставленного кода. Если у вас много условий, вы можете добавить несколько вызовов emit isNewPlotValidChanged(); в тех местах, где эти условия меняются. Механизм QML всегда будет перечитывать значение, вызывая isNewPlotValid() и применяя новое значение (которое, таким образом, может оставаться таким же).

Затем вам нужно настроить свой QML на:

ColumnLayout{
    //enabled: addContext.isNewPlotValid

    LineField{
        label: "Name"
        text: addContext.newPlot.name
        onTextChanged: {
            addContext.newPlot.name = text
        }
    }
    AreaField{
        label: "Address"
        Layout.fillHeight: true
        text: addContext.newPlot.description
        onTextChanged: {
            addContext.newPlot.description = text
        }
    }
    PathButton{
        id: button
        pathData: C.addIcon
        enabled: addContext.isNewPlotValid
        onClicked: addContext.addNewPlot()
    }
}

Включение всего ColumnLayout имеет то преимущество, что поле ввода также отключается, но это зависит от вашей ситуации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...