如何声明多个QML属性而不单独处理每个属性?

时间:2016-07-07 17:17:47

标签: qt properties qml

我有一个玩具QML应用程序(Qt 5.7),它包含以下QML页面和一些C ++代码,允许QML在音频设备上订阅某个状态并在QML GUI中反映该状态。玩具QML看起来像这样:

import QtQuick 2.6
import QtQuick.Controls 1.5
import QtQuick.Window 2.2

Item {
   id: deviceState

   property bool mute1: false
   property bool mute2: false
   property bool mute3: false
   property bool mute4: false

   Component.onCompleted: {
      // Call out to C++ land, to tell the server what 
      // control points we are interested in tracking the state of
      topLevelWindow.subscribeToControlPoints("Input 1-4 Mute", deviceState);
   }

   // Called by C++ land to tell us the current state of
   // a control point we previously subscribed to.
   function onControlPointValueUpdated(address, value) {
      if (address == "Input 1 Mute") {
         mute1 = value
      }
      else if (address == "Input 2 Mute") {
         mute2 = value
      }
      else if (address == "Input 3 Mute") {
         mute3 = value
      }
      else if (address == "Input 4 Mute") {
         mute4 = value
      }
   }

   // Absurdly minimalist example GUI
   Text {
      text: parent.mute4 ? "MUTED" : "unmuted"
   }
}

当我运行它时,只要我的音频设备上的四个静音状态之一发生变化,我的C ++代码就会调用onControlPointValueUpdated(),并且根据调用中的'address'参数,四个muteN QML属性中的一个被更改。并且,每当更改mute4属性时,“文本”区域中的文本都会更新以反映该内容。

这一切都很好,就目前而言,但在某些时候我需要扩大规模;特别是我需要跟踪数百或数千个值而不是仅仅四个,并且必须为每个值手动声明一个单独的,手工命名的QML属性将很快变得艰难且难以维护;并且onControlPointValueUpdated()的实现将变得非常低效,如果它必须手动if / elseif通过每个属性。

所以我的问题是,在QML中是否有某种方式可以声明属性的数组(或者更好的是字典/映射),这样我就可以一次声明大量的QML属性?就像这样:

property bool mutes[256]: {false}   // would declare mutes[0] through mutes[255], all default to false

......或者如果没有,是否有其他一些建议的方法来保存QML文档中的大量可观察状态?我喜欢让我的GUI小部件绑定到QML属性以根据需要自动更新的能力,但似乎QML属性可能不打算用于 en masse

2 个答案:

答案 0 :(得分:8)

只要您获得大量必须显示的数据(特别是如果它们都共享一种通用格式),您应该考虑使用QAbstractItemModel衍生物。

另一点需要指出的是,从C ++调用用户界面的QML并不是一个好主意,因为它往往会限制你在QML中可以做的事情,并将QML与C ++联系起来

例如,如果您要在列表中显示数据:

listview

视图中的项目是按需创建的,并且在任何时候都分配了有限的数量,这有助于您拥有大量数据和可能复杂的代理。

以下是具有自定义角色的简单自定义QAbstractListModel的外观:

main.cpp

#include <QtGui>
#include <QtQuick>

class UserModel : public QAbstractListModel
{
public:
    UserModel() {
        for (int i = 0; i < 100; ++i) {
            mData.append(qMakePair(QString::fromLatin1("Input %1").arg(i), i % 5 == 0));
        }
    }

    enum Roles
    {
        NameRole = Qt::UserRole,
        MutedRole
    };

    QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE
    {
        if (!index.isValid())
            return QVariant();

        switch (role) {
        case Qt::DisplayRole:
        case NameRole:
            return mData.at(index.row()).first;
        case MutedRole:
            return mData.at(index.row()).second;
        }

        return QVariant();
    }

    int rowCount(const QModelIndex &/*parent*/ = QModelIndex()) const Q_DECL_OVERRIDE
    {
        return mData.size();
    }

    int columnCount(const QModelIndex &/*parent*/ = QModelIndex()) const Q_DECL_OVERRIDE
    {
        return 1;
    }

    virtual QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE
    {
        QHash<int, QByteArray> names;
        names[NameRole] = "name";
        names[MutedRole] = "muted";
        return names;
    }

private:
    QVector<QPair<QString, int> > mData;
};

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

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

    return app.exec();
}

main.qml

import QtQuick 2.3
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2

Window {
    width: 300
    height: 300
    visible: true

    ListView {
        id: listView
        anchors.fill: parent
        anchors.margins: 10
        model: userModel
        spacing: 20

        delegate: ColumnLayout {
            width: ListView.view.width

            Text {
                text: name
                width: parent.width / 2
                font.pixelSize: 14
                horizontalAlignment: Text.AlignHCenter
            }

            RowLayout {
                Rectangle {
                    width: 16
                    height: 16
                    radius: width / 2
                    color: muted ? "red" : "green"
                }

                Text {
                    text: muted ? qsTr("Muted") : qsTr("Unmuted")
                    width: parent.width / 2
                    horizontalAlignment: Text.AlignHCenter
                }
            }
        }
    }
}

例如,您可以轻松地将ListViewGridView交换出来。最重要的是,C ++对QML一无所知。

您可以阅读有关使用Qt Quick here的C ++模型的更多信息。

查看您在评论中发布的图片,可以通过多种方式制作这种类型的用户界面。您可以使用上述方法,但在委托中使用LoaderLoader可以根据模型中的某些数据确定要显示的component类型。但是,看起来UI上显示的数据量虽然数量很大,但如果没有视图,可能会更好。

你仍然可以使用一个模型(例如像{peper提到的那样Repeater),但你也可以通过列出每个&#34;面板&#34; (我不知道他们被称之为什么)的方式与你目前的做法类似。看起来一个面板中的大多数数据是相同的,因此每个数据都可以是它自己的类(未经测试的代码如下):

#include <QtGui>
#include <QtQuick>

class Panel : public QObject
{
    Q_OBJECT
    Q_PROPERTY(bool muted READ muted WRITE setMuted NOTIFY mutedChanged)

    Panel() { /* stuff */ }

    // getters
    // setters

signals:
    // change signals

private:
    // members
};

class WeirdPanel : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int level READ level WRITE setLevel NOTIFY levelChanged)

    WeirdPanel() { /* stuff */ }

    // getters
    // setters

signals:
    // change signals

private:
    // members
};

class Board : public QObject
{
    Q_OBJECT
    Q_PROPERTY(Panel *panel1 READ panel1 CONSTANT)
    Q_PROPERTY(Panel *panel2 READ panel2 CONSTANT)
    Q_PROPERTY(Panel *panel3 READ panel3 CONSTANT)
    // etc.
    Q_PROPERTY(WeirdPanel *weirdPanel READ weirdPanel WRITE setWeirdPanel NOTIFY weirdPanelChanged)

public:

    Board() {
    }

    // getters
    // setters

signals:
    // change signals

private:
    Panel panel1;
    Panel panel2;
    Panel panel3;
    Panel panel4;
    WeirdPanel weirdPanel;
};

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

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

    return app.exec();
}

main.qml

import QtQuick 2.3
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2

Window {
    width: 300
    height: 300
    visible: true

    RowLayout {
        anchors.fill: parent

        Panel {
            id: panel1
            panel: board.panel1
        }

        Panel {
            id: panel2
            panel: board.panel2
        }

        Panel {
            id: panel3
            panel: board.panel3
        }

        WeirdPanel {
            id: weirdPanel
            panel: board.weirdPanel
        }
    }
}

然后,Panel.qml可能只是一列按钮和诸如此类的东西:

import QtQuick 2.0
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.0

Rectangle {
    property var panel

    color: "#444"

    implicitWidth: columnLayout.implicitWidth
    implicitHeight: columnLayout.implicitHeight

    ColumnLayout {
        anchors.fill: parent

        Text {
            text: panel.name
            width: parent.width / 2
            font.pixelSize: 14
            horizontalAlignment: Text.AlignHCenter
        }

        RowLayout {
            Rectangle {
                width: 16
                height: 16
                radius: width / 2
                color: panel.muted ? "red" : "green"
            }

            Text {
                text: panel.muted ? qsTr("Muted") : qsTr("Unmuted")
                width: parent.width / 2
                horizontalAlignment: Text.AlignHCenter
            }
        }
    }
}

答案 1 :(得分:2)

经过一番搜索,看来最适合我当前需求的机制是QQmlPropertyMap类。

特别是,我向QQmlPropertyMap添加QQmlContext,并通过setContextProperty()将其公开给QML世界:

_sessionContext = new QQmlContext(_engine.rootContext());
_propertyMap = new QQmlPropertyMap(_sessionContext);
_sessionContext->setContextProperty("cps", _propertyMap);

...然后当我在C ++代码中收到一个值更新,而不是调用QML函数时,我只是用它来更新QQmlPropertyMap对象:

_propertyMap->insert("input_4_mute", QVariant(true));  // arguments hard-coded here for clarity

...在QML代码中还有待完成的事情是绑定到属性但是我想:

Rectangle {
    width: 0; height: 0
    x:160; y:120
    color: cps.input_4_mute ? "red" : "blue"
}