如何声明多个 QML 属性而不单独处理每个属性?
Posted
技术标签:
【中文标题】如何声明多个 QML 属性而不单独处理每个属性?【英文标题】:How to declare multiple QML properties without handling each one individually? 【发布时间】:2016-07-07 17:17:47 【问题描述】:我有一个玩具 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(),并且基于调用中的“地址”参数,四个 muteN QML 属性之一发生更改.而且,每当 mute4 属性更改时,文本区域中的文本都会更新以反映这一点。
就目前而言,这一切都很好,但在某些时候我需要扩大规模;特别是我将需要跟踪成百上千个值,而不仅仅是四个,并且必须为每个值手动声明一个单独的、手工命名的 QML 属性很快就会变得很困难并且难以维护;如果必须手动 if/elseif 遍历每个属性,onControlPointValueUpdated() 的实现将变得非常低效。
所以我的问题是,在 QML 中是否有某种方法可以让我声明一个属性数组(或者更好的是一个字典/映射),以便我可以一次声明大量 QML 属性?即这样的事情:
property bool mutes[256]: false // would declare mutes[0] through mutes[255], all default to false
...如果没有,是否有其他推荐的方法来在 QML 文档中保存大量可观察状态?我喜欢将我的 GUI 小部件绑定到 QML 属性以在必要时自动更新的能力,但似乎 QML 属性可能不打算用于大规模?
【问题讨论】:
这取决于您打算如何呈现所有变量。目前,您已经有了“极简主义的示例 GUI”——实际的 GUI 会是什么样子?会是显示这些值的Text
项目列表吗?
现在,也许我不明白,但你不能只创建一个 javascript 对象吗?然后你从像text: parent.obj.mute4 ? "MUTED" : "unmuted"
这样的文本访问它,你认为undefined
是false
?还有更新obj[property] = value
?
@Mitch 可能类似于一个大的按钮网格,当静音处于活动状态时会变成红色,或类似的东西。但我正在寻找一种能够很好地工作(即高效,并且 QML 编码复杂度最低)的机制,用于我将来可能提出的任何 GUI。
@rpadovani 可能;我对 QML 或 Javascript 还不是很熟悉,所以我不清楚这是否可行。特别是,JavaScript 对象的成员是否充当 QML 属性? (即设置 obj[property]=value 会导致 Text 小部件自行更新吗?)
你确定你不想要一个数据驱动的设计吗? (例如某个模型上的中继器)
【参考方案1】:
一旦您获得大量需要显示的数据(尤其是如果它们都共享一种通用格式),您就应该考虑使用QAbstractItemModel
派生数据。
另外需要指出的是,从 C++ 调用用户界面的 QML 并不是一个好主意,因为它往往会限制您在 QML 中可以做的事情,并将 QML 与 C++ 联系起来。
例如,如果您要在列表中显示数据:
视图中的项目是按需创建的,并且在任何时候分配的数量都是有限的,这在您拥有大量数据和潜在的复杂委托时会有所帮助。
下面是带有自定义角色的简单自定义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
例如,您可以轻松地将 ListView
替换为 GridView
。最重要的是,C++ 对 QML 一无所知。
您可以阅读更多关于在 Qt Quick 中使用 C++ 模型的信息here。
查看您在 cmets 中发布的图片,可以通过多种方式制作这种类型的 UI。您可以使用上述方法,但在委托中使用 Loader
。 Loader
可以根据模型中的一些数据来确定要显示的component 的类型。然而,看起来你的 UI 上显示的数据量虽然很大,但如果没有视图确实可能会更好。
您仍然可以使用模型(例如,使用 @peppe 提到的 Repeater
),但您也可以以类似的方式列出每个“面板”(我不知道它们叫什么)来逃避你现在是怎么做的。看起来一个面板中的大部分数据都是相同的,所以每个都可以是自己的类(下面是未经测试的代码):
#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
【讨论】:
我认为这种方法的一个问题是,在 ListView(或 GridView 等)中一起显示所有值对于我的 GUI 来说是不够的;而是 GUI 需要为与其关联的每种类型的数据项提供适当类型的控件(即用于静音的切换按钮、用于电平控制的推子、用于平移控制的 panpots 等)。此外,它还需要能够以各种 QML 文件定义的方式布置这些控件,我认为 View 不允许这样做。 那么这是您应该在问题中提及的内容。 :) 毕竟,我们在这里谈论的是 GUI 工具包。 但我很难相信除了视图之外的任何东西都是“数百或数千个值”的正确选择。我会用一些可能有帮助的额外信息来更新我的答案。 这是一个(随机选择的)GUI 示例,可能在某些时候需要:wheatstone.com/images/NAB2016%20PR%20IMAGES/… 用 Licecap 制作它们非常容易,而且它们让枯燥的旧文字墙变得更加有趣。 :)【参考方案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"
【讨论】:
酷!我什至不知道那门课!以上是关于如何声明多个 QML 属性而不单独处理每个属性?的主要内容,如果未能解决你的问题,请参考以下文章