如何使用QML模型?

时间:2021-08-21 02:35:59

I have a GUI written in qml and c++. There are 2 comboboxes (qt control 5.1). Second combobox has to update at runtime whenever the value of the first one is changed.

我有一个用qml和c++编写的GUI。有两个组合盒(qt控制5.1)。每当第一个combobox的值发生变化时,第二个combobox必须在运行时进行更新。

maincontext->setContextProperty("typemodel", QVariant::fromValue(m_typemodel));

maincontext->setContextProperty("unitmodel", QVariant::fromValue(m_unitmodel));

These are 2 models that I give to qml from c++.

这是我从c++中给qml的两个模型。

ComboBox {
    id: typebox

    anchors.left: text1.right
    anchors.leftMargin: 5
    signal changed(string newtext)

    width: 70
    height: 23
    anchors.top: parent.top
    anchors.topMargin: 37
    model: typemodel

    onCurrentTextChanged: {

        mainwin.unitGenerator(typebox.currentText);

    }

This is the first combobox. As you see, the c++ model of second combobox is updated every time the value of the first is changed (mainwin.unitGenerator(typebox.currentText)). But it does not seem to update the combobox's model.

这是第一个组合框。正如您所看到的,每次修改第一个combobox的值(mainwin.unitGenerator(typebox.currentText))时,都会更新第二个combobox的c++模型。但它似乎并没有更新combobox的模型。

How can I update qml's model on runtime?

如何在运行时更新qml的模型?

2 个解决方案

#1


30  

To even begin to address your issue, we'd need to see what the unitGenerator method does. If you're using a custom model, it's almost certain that you're not correctly implementing the notifications. My bet at the moment would be that you're not signaling the model reset.

为了开始解决您的问题,我们需要了解unitGenerator方法的作用。如果您正在使用自定义模型,几乎可以肯定您没有正确地实现通知。我敢打赌,你并不是在发出信号,表明模型正在重置。

Below is a complete code example that shows how you can tie a QStringListModel to an editable ListView and to ComboBoxes. The second ComboBox's model is regenerated based on the selection from the first one. This presumably approximates your desired functionality.

下面是一个完整的代码示例,它展示了如何将QStringListModel绑定到可编辑的ListView和ComboBoxes。第二个ComboBox的模型是根据第一个的选择重新生成的。这大概是您想要的功能。

Note the specific handling of roles done by the QStringListModel. The model treats the display and edit roles almost the same: they both are mapped to the string value in the list. Yet when you update a particular role's data, the dataChanged signal carries only the role that you've changed. This can be used to break a binding loop that might be otherwise present in the model editor item (TextInput). When you use a custom model, you may need to implement similar functionality.

注意QStringListModel对角色的特定处理。模型对显示和编辑角色的处理几乎相同:它们都映射到列表中的字符串值。然而,当您更新特定角色的数据时,dataChanged信号只携带您已更改的角色。这可以用来中断绑定循环,否则可能会出现在模型编辑器项(TextInput)中。当您使用自定义模型时,您可能需要实现类似的功能。

The display role is used to bind the combo boxes to the model. The edit role is used to pre-populate the editor objects. The editor's onTextChanged signal handler is updating the display role, and this doesn't cause a binding loop to itself. If the handler was updating the edit role, it would cause a binding loop via the text property.

显示角色用于将组合框绑定到模型。编辑角色用于预填充编辑器对象。编辑器的onTextChanged信号处理程序正在更新显示角色,这不会导致对自身的绑定循环。如果处理程序正在更新编辑角色,它将通过文本属性导致绑定循环。

On Models in QML

There are various kinds of "models" in QML. Internally, QML will wrap almost "anything" in a model. Anything that is internally not a QObject yet can still be a model (say a QVariant), won't be notifying anyone about anything.

QML中有各种各样的“模型”。在内部,QML将在模型中包装几乎“任何东西”。任何内部不是QObject的东西仍然可以是一个模型(比如一个q变体),它不会通知任何人任何事情。

For example, a "model" based on QVariant that wraps an int will not issue notifications, because QVariant is not a QObject that could signal changes.

例如,基于包装int的QVariant的“模型”将不会发出通知,因为QVariant不是可以通知更改的QObject。

Similarly, if your "model" is tied to a property value of a class derived from QObject, but you fail to emit the property change notification signal, it also won't work.

类似地,如果您的“模型”与从QObject派生的类的属性值绑定,但是您无法发出属性更改通知信号,那么它也不会工作。

Without knowing what your model types are, it's impossible to tell.

如果不知道模型类型是什么,就不可能知道。

main.qml

main.qml

import QtQuick 2.0
import QtQuick.Controls 1.0

ApplicationWindow {
    width: 300; height: 300
    ListView {
        id: view
        width: parent.width
        anchors.top: parent.top
        anchors.bottom: column.top
        model: model1
        spacing: 2
        delegate: Component {
            Rectangle {
                width: view.width
                implicitHeight: edit.implicitHeight + 10
                color: "transparent"
                border.color: "red"
                border.width: 2
                radius: 5
                TextInput {
                    id: edit
                    anchors.margins: 1.5 * parent.border.width
                    anchors.fill: parent
                    text: edit // "edit" role of the model, to break the binding loop
                    onTextChanged: model.display = text
                }
            }
        }
    }
    Column {
        id: column;
        anchors.bottom: parent.bottom
        Text { text: "Type";  }
        ComboBox {
            id: box1
            model: model1
            textRole: "display"
            onCurrentTextChanged: generator.generate(currentText)
        }
        Text { text: "Unit"; }
        ComboBox {
            id: box2
            model: model2
            textRole: "display"
        }
    }
}

main.cpp

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickWindow>
#include <QStringListModel>
#include <QQmlContext>

class Generator : public QObject
{
    Q_OBJECT
    QStringListModel * m_model;
public:
    Generator(QStringListModel * model) : m_model(model) {}
    Q_INVOKABLE void generate(const QVariant & val) {
        QStringList list;
        for (int i = 1; i <= 3; ++i) {
            list << QString("%1:%2").arg(val.toString()).arg(i);
        }
        m_model->setStringList(list);
    }
};

int main(int argc, char *argv[])
{
    QStringListModel model1, model2;
    Generator generator(&model2);
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;

    QStringList list;
    list << "one" << "two" << "three" << "four";
    model1.setStringList(list);

    engine.rootContext()->setContextProperty("model1", &model1);
    engine.rootContext()->setContextProperty("model2", &model2);
    engine.rootContext()->setContextProperty("generator", &generator);

    engine.load(QUrl("qrc:/main.qml"));
    QObject *topLevel = engine.rootObjects().value(0);
    QQuickWindow *window = qobject_cast<QQuickWindow *>(topLevel);
    window->show();
    return app.exec();
}

#include "main.moc"

#2


3  

This is actually more of an answer/comment to the answer of @KubaOber.

这实际上更像是对@KubaOber的回答的回答。

I found that it is actually not necessary to do any special tricks using multiple roles if you bind to the correct event:

我发现,如果您绑定到正确的事件,实际上不需要使用多个角色进行任何特殊的技巧:

onAccepted: model.edit = text

works just fine and does not create any update loop (as it is only called on "human"/input modification).

工作正常,不创建任何更新循环(因为只调用“human”/input修改)。

#1


30  

To even begin to address your issue, we'd need to see what the unitGenerator method does. If you're using a custom model, it's almost certain that you're not correctly implementing the notifications. My bet at the moment would be that you're not signaling the model reset.

为了开始解决您的问题,我们需要了解unitGenerator方法的作用。如果您正在使用自定义模型,几乎可以肯定您没有正确地实现通知。我敢打赌,你并不是在发出信号,表明模型正在重置。

Below is a complete code example that shows how you can tie a QStringListModel to an editable ListView and to ComboBoxes. The second ComboBox's model is regenerated based on the selection from the first one. This presumably approximates your desired functionality.

下面是一个完整的代码示例,它展示了如何将QStringListModel绑定到可编辑的ListView和ComboBoxes。第二个ComboBox的模型是根据第一个的选择重新生成的。这大概是您想要的功能。

Note the specific handling of roles done by the QStringListModel. The model treats the display and edit roles almost the same: they both are mapped to the string value in the list. Yet when you update a particular role's data, the dataChanged signal carries only the role that you've changed. This can be used to break a binding loop that might be otherwise present in the model editor item (TextInput). When you use a custom model, you may need to implement similar functionality.

注意QStringListModel对角色的特定处理。模型对显示和编辑角色的处理几乎相同:它们都映射到列表中的字符串值。然而,当您更新特定角色的数据时,dataChanged信号只携带您已更改的角色。这可以用来中断绑定循环,否则可能会出现在模型编辑器项(TextInput)中。当您使用自定义模型时,您可能需要实现类似的功能。

The display role is used to bind the combo boxes to the model. The edit role is used to pre-populate the editor objects. The editor's onTextChanged signal handler is updating the display role, and this doesn't cause a binding loop to itself. If the handler was updating the edit role, it would cause a binding loop via the text property.

显示角色用于将组合框绑定到模型。编辑角色用于预填充编辑器对象。编辑器的onTextChanged信号处理程序正在更新显示角色,这不会导致对自身的绑定循环。如果处理程序正在更新编辑角色,它将通过文本属性导致绑定循环。

On Models in QML

There are various kinds of "models" in QML. Internally, QML will wrap almost "anything" in a model. Anything that is internally not a QObject yet can still be a model (say a QVariant), won't be notifying anyone about anything.

QML中有各种各样的“模型”。在内部,QML将在模型中包装几乎“任何东西”。任何内部不是QObject的东西仍然可以是一个模型(比如一个q变体),它不会通知任何人任何事情。

For example, a "model" based on QVariant that wraps an int will not issue notifications, because QVariant is not a QObject that could signal changes.

例如,基于包装int的QVariant的“模型”将不会发出通知,因为QVariant不是可以通知更改的QObject。

Similarly, if your "model" is tied to a property value of a class derived from QObject, but you fail to emit the property change notification signal, it also won't work.

类似地,如果您的“模型”与从QObject派生的类的属性值绑定,但是您无法发出属性更改通知信号,那么它也不会工作。

Without knowing what your model types are, it's impossible to tell.

如果不知道模型类型是什么,就不可能知道。

main.qml

main.qml

import QtQuick 2.0
import QtQuick.Controls 1.0

ApplicationWindow {
    width: 300; height: 300
    ListView {
        id: view
        width: parent.width
        anchors.top: parent.top
        anchors.bottom: column.top
        model: model1
        spacing: 2
        delegate: Component {
            Rectangle {
                width: view.width
                implicitHeight: edit.implicitHeight + 10
                color: "transparent"
                border.color: "red"
                border.width: 2
                radius: 5
                TextInput {
                    id: edit
                    anchors.margins: 1.5 * parent.border.width
                    anchors.fill: parent
                    text: edit // "edit" role of the model, to break the binding loop
                    onTextChanged: model.display = text
                }
            }
        }
    }
    Column {
        id: column;
        anchors.bottom: parent.bottom
        Text { text: "Type";  }
        ComboBox {
            id: box1
            model: model1
            textRole: "display"
            onCurrentTextChanged: generator.generate(currentText)
        }
        Text { text: "Unit"; }
        ComboBox {
            id: box2
            model: model2
            textRole: "display"
        }
    }
}

main.cpp

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickWindow>
#include <QStringListModel>
#include <QQmlContext>

class Generator : public QObject
{
    Q_OBJECT
    QStringListModel * m_model;
public:
    Generator(QStringListModel * model) : m_model(model) {}
    Q_INVOKABLE void generate(const QVariant & val) {
        QStringList list;
        for (int i = 1; i <= 3; ++i) {
            list << QString("%1:%2").arg(val.toString()).arg(i);
        }
        m_model->setStringList(list);
    }
};

int main(int argc, char *argv[])
{
    QStringListModel model1, model2;
    Generator generator(&model2);
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;

    QStringList list;
    list << "one" << "two" << "three" << "four";
    model1.setStringList(list);

    engine.rootContext()->setContextProperty("model1", &model1);
    engine.rootContext()->setContextProperty("model2", &model2);
    engine.rootContext()->setContextProperty("generator", &generator);

    engine.load(QUrl("qrc:/main.qml"));
    QObject *topLevel = engine.rootObjects().value(0);
    QQuickWindow *window = qobject_cast<QQuickWindow *>(topLevel);
    window->show();
    return app.exec();
}

#include "main.moc"

#2


3  

This is actually more of an answer/comment to the answer of @KubaOber.

这实际上更像是对@KubaOber的回答的回答。

I found that it is actually not necessary to do any special tricks using multiple roles if you bind to the correct event:

我发现,如果您绑定到正确的事件,实际上不需要使用多个角色进行任何特殊的技巧:

onAccepted: model.edit = text

works just fine and does not create any update loop (as it is only called on "human"/input modification).

工作正常,不创建任何更新循环(因为只调用“human”/input修改)。