Exposing C++ Object list to Qml - objects not displayed when modeling list using Repeater
-
Hi
I have just started to learn Qt Qml, by following Nicholas Sheriffs' book: Build modern, responsive cross-platform desktop applications with Qt, C++, and QML from which the whole idea come.
Following authors' idea, I'm trying to implement context sensitive buttons that would be displayed in different views.
In order to do that, I have implemented Command class that represents single button:
#ifndef COMMAND_H #define COMMAND_H #include <functional> #include <QObject> #include <QScopedPointer> #include <QString> #include <cms-lib_global.h> namespace cms { namespace framework { class CMSLIBSHARED_EXPORT Command : public QObject { Q_OBJECT Q_PROPERTY(QString ui_iconCharacter READ iconCharacter CONSTANT) Q_PROPERTY(QString ui_description READ description CONSTANT) Q_PROPERTY(bool ui_canExecute READ canExecute NOTIFY canExecuteChanged) public: explicit Command(QObject* parent = nullptr, const QString& iconCharacter = "", const QString& description = "", std::function<bool()> canExecute = [](){ return true; }); ~Command(); const QString& iconCharacter() const; const QString& description() const; bool canExecute() const; signals: void canExecuteChanged(); void executed(); private: class Implementation; QScopedPointer<Implementation> implementation; }; } } #endif // COMMAND_H //command.cpp #include "command.h" namespace cms { namespace framework { class Command::Implementation{ public: QString iconCharacter; QString description; std::function<bool()> canExecute; Implementation(const QString& _iconCharacter, const QString& _description, std::function<bool()> _canExecute) : iconCharacter(_iconCharacter), description(_description), canExecute(_canExecute) {} }; Command::Command(QObject* parent, const QString& iconCharacter, const QString& description, std::function<bool()> canExecute) : QObject(parent){ implementation.reset(new Implementation(iconCharacter, description, canExecute)); } Command::~Command() { } const QString& Command::iconCharacter() const { return implementation->iconCharacter; } const QString& Command::description() const { return implementation->description; } bool Command::canExecute() const { return implementation->canExecute(); } } }
Command Class is handled withing CommandController class:
#ifndef COMMANDCONTROLLER_H #define COMMANDCONTROLLER_H #include <QObject> #include <QtQml/QQmlListProperty> #include <cms-lib_global.h> #include <framework/command.h> namespace cms { namespace controllers { class CMSLIBSHARED_EXPORT CommandController : public QObject{ Q_OBJECT Q_PROPERTY(QQmlListProperty<cms::framework::Command> ui_createPatientViewContextCommands READ ui_createPatientViewContextCommands CONSTANT) public: explicit CommandController(QObject* _parent = nullptr); ~CommandController(); QQmlListProperty<cms::framework::Command> ui_createPatientViewContextCommands(); public slots: void onCreateClientSaveExecuted(); private: class Implementation; QScopedPointer<Implementation> implementation; }; } } #endif // COMMANDCONTROLLER_H //commandcontroller.cpp #include "commandcontroller.h" #include <QList> #include <QDebug> using namespace cms::framework; namespace cms{ namespace controllers{ class CommandController::Implementation{ public: //Controller CommandController* commandController {nullptr}; //Commands QList<Command*> createClientViewContextCommands{}; Implementation(CommandController* _commandController) : commandController(_commandController) { Command* createClientSaveCommand = new Command(commandController, QChar( 0xf0c7 ), "Save"); QObject::connect(createClientSaveCommand, &Command::executed, commandController, &CommandController::onCreateClientSaveExecuted); createClientViewContextCommands.append(createClientSaveCommand); } }; CommandController::CommandController(QObject* parent) : QObject(parent){ implementation.reset(new Implementation(this)); } CommandController::~CommandController(){ } QQmlListProperty<Command> CommandController::ui_createPatientViewContextCommands(){ return QQmlListProperty<Command>(this, implementation->createClientViewContextCommands); } void CommandController::onCreateClientSaveExecuted(){ qDebug() << "You executed the Save command!"; } } }
With that in place, i registered both created types within Qml inside main.cpp :
qmlRegisterType<cms::framework::Command>("CMS", 1, 0, "Command"); qmlRegisterType<cms::controllers::CommandController>("CMS", 1, 0, "CommandController");
I created 2 component Qml files and successfully (atleast i think so) exported them in my components module:
//CommandBar.qml import QtQuick 2.12 import assets 1.0 Item { property alias commandList: commandRepeater.model anchors { left: parent.left bottom: parent.bottom right: parent.right } height: Style.heightCommandBar Rectangle { anchors.fill: parent color: Style.colorCommandBarBackground Row { anchors { top: parent.top bottom: parent.bottom right: parent.right } Repeater { id: commandRepeater delegate: CommandButton { command: modelData } } } } } //CommandButton.qml import QtQuick 2.12 import CMS 1.0 import assets 1.0 Item { property Command command width: Style.widthCommandButton height: Style.heightCommandButton Rectangle { id: background anchors.fill: parent color: Style.colorCommandBarBackground //Command button icon Text { id: textIcon anchors { centerIn: parent verticalCenterOffset: -10 } font { family: Style.fontAwesomeSolid pixelSize: Style.pixelSizeCommandBarIcon } color: command.ui_canExecute ? Style.colorCommandBarFont : Style.colorCommandBarFontDisabled text: command.ui_iconCharacter horizontalAlignment: Text.AlignHCenter } //Command button description Text { id: textDescription anchors { top: textIcon.bottom bottom: parent.bottom left: parent.left right: parent.right } font.pixelSize: Style.pixelSizeNavigationBarText color: command.ui_canExecute ? Style.colorCommandBarFont : Style.colorCommandBarFontDisabled text: command.ui_description horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor hoverEnabled: true onEntered: background.state = "hover" onExited: background.state = "" onClicked: if(command.ui_canExecute) { command.executed(); } } states: [ State { name: "hover" PropertyChanges { target: background color: Qt.darker(Style.colorCommandBarBackground) } } ] } }
I used created components inside CreatePatientView.qml
import QtQuick 2.12 import assets 1.0 import components 1.0 Item { Rectangle { anchors.fill: parent color: Style.colorBackground Text { anchors.centerIn: parent text: "Create patient view" } } CommandBar { commandList: masterController.ui_commandController.ui_createClientViewContextCommands } }
And used the CreatePatientView inside MasterView.qml
For the beginning, im trying to display single "save" button (icon + description) onto my CreatePatientView, before I implement more buttons.
Sadly, the icon + description command button is not displayed, mouse hover area isn't present neither. Im new to Qml and UI, so I cannot figure out what might be the problem.
Sorry for lots of code, but I simply do not know where should I look for source of problem.
Best regards.
-
@bwylegly said in Exposing C++ Object list to Qml - objects not displayed when modeling list using Repeater:
ui_createClientViewContextCommands
Should this be ui_createPatientViewContextCommands? I don't see ui_createClientViewContextCommands in any of the code you posted. That is where I had trouble. I could not see where those 3 items were defined in the code.
-
After doing some digging, I have found the following:
-
if the CommandButton is declared explicitly outside of CommandBar, and initialized using aliases, the button is populated and displayed correctly - therefore, I think that my problem is connected to populating CommandButton's using Repeater OR displaying it within row in CommandBar.qml
-
I have placed simple onCompleted console logs inside CommandBar and CommandButton root Item's - if i DO NOT use any explicit CommandButtons but those retrievied from Command metaData, the CommandBar qml is being read, while CommandButton is not.
Can somebody point out what am I doing wrong?
-
-
Your repeater has no model.
https://doc.qt.io/qt-5/qml-qtquick-repeater.html#model-prop -
Hi @fcarney,
im not sure if I understood that mechanism correctly, but I thought that I provided model for repeater this way:- CommandController provides getter:
QQmlListProperty<cms::framework::Command> ui_createPatientViewContextCommands();
which is registered as Q_PROPERTY:
Q_PROPERTY(QQmlListProperty<cms::framework::Command> ui_createPatientViewContextCommands READ ui_createPatientViewContextCommands)
- CommandBar.qml contains Repeater inside it's hierarchy (as posted in original post)
Repeater { id: commandRepeater delegate: CommandButton { command: modelData } }
With that in place, i use alias inside CommandBar.qml:
property alias commandList: commandRepeater.model
- Inside CreatePatientView.qml I include CommandBar component, populating it using getter from CommandController:
CommandBar { commandList: masterController.ui_commandController .ui_createClientViewContextCommands }
What's wrong in that approach?
Thanks in advance.
-
Okay, I see what you are doing there with the alias. I do not see how the value you set is defined:
CommandBar { commandList: masterController.ui_commandController.ui_createClientViewContextCommands Component.onCompleted: { console.log(masterController) console.log(masterController.ui_commandController) console.log(masterController.ui_commandController.ui_createClientViewContextCommands) } }
That will tell you if those are valid objects. Where are those defined?
-
For now, as its only button for testing purposes i want to keep it as simple as possible, so it's defined inside constructor for CommandController (also mentioned in original post):
Implementation(CommandController* _commandController) : commandController(_commandController) { Command* createClientSaveCommand = new Command(commandController, QChar( 0xf0c7 ), "Save"); QObject::connect(createClientSaveCommand, &Command::executed, commandController, &CommandController::onCreateClientSaveExecuted); createClientViewContextCommands.append(createClientSaveCommand); }
createClientViewContextCommands is defined as:
QList<Command*> createClientViewContextCommands{};
which is used inside getter I have mentioned before:
QQmlListProperty<Command> CommandController::ui_createPatientViewContextCommands(){ return QQmlListProperty<Command>(this, implementation->createClientViewContextCommands); }
-
@fcarney following your suggestion, after placing console logs i got the following output:
qml: cms::controllers::MasterController(0x70fe18) qml: cms::controllers::CommandController(0x8e00d0) qml: undefined
I see what's the problem, but could u provide any tips where should I look for the reason of this?
I can't find any errors in my code. -
@bwylegly said in Exposing C++ Object list to Qml - objects not displayed when modeling list using Repeater:
ui_createClientViewContextCommands
Should this be ui_createPatientViewContextCommands? I don't see ui_createClientViewContextCommands in any of the code you posted. That is where I had trouble. I could not see where those 3 items were defined in the code.
-
-