Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. Exposing C++ Object list to Qml - objects not displayed when modeling list using Repeater
QtWS25 Last Chance

Exposing C++ Object list to Qml - objects not displayed when modeling list using Repeater

Scheduled Pinned Locked Moved Solved QML and Qt Quick
qmlqml componentsc++ to qmlc++
10 Posts 2 Posters 986 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • B Offline
    B Offline
    bwylegly
    wrote on last edited by bwylegly
    #1

    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.

    1 Reply Last reply
    0
    • B bwylegly

      Hi @fcarney,
      im not sure if I understood that mechanism correctly, but I thought that I provided model for repeater this way:

      1. 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)
      
      1. 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
      
      1. 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.

      fcarneyF Offline
      fcarneyF Offline
      fcarney
      wrote on last edited by
      #8

      @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.

      C++ is a perfectly valid school of magic.

      1 Reply Last reply
      2
      • B Offline
        B Offline
        bwylegly
        wrote on last edited by
        #2

        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?

        1 Reply Last reply
        0
        • fcarneyF Offline
          fcarneyF Offline
          fcarney
          wrote on last edited by
          #3

          Your repeater has no model.
          https://doc.qt.io/qt-5/qml-qtquick-repeater.html#model-prop

          C++ is a perfectly valid school of magic.

          1 Reply Last reply
          0
          • B Offline
            B Offline
            bwylegly
            wrote on last edited by bwylegly
            #4

            Hi @fcarney,
            im not sure if I understood that mechanism correctly, but I thought that I provided model for repeater this way:

            1. 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)
            
            1. 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
            
            1. 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.

            fcarneyF 1 Reply Last reply
            0
            • fcarneyF Offline
              fcarneyF Offline
              fcarney
              wrote on last edited by
              #5

              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?

              C++ is a perfectly valid school of magic.

              1 Reply Last reply
              0
              • B Offline
                B Offline
                bwylegly
                wrote on last edited by
                #6

                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);
                }
                
                1 Reply Last reply
                0
                • B Offline
                  B Offline
                  bwylegly
                  wrote on last edited by bwylegly
                  #7

                  @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.

                  1 Reply Last reply
                  0
                  • B bwylegly

                    Hi @fcarney,
                    im not sure if I understood that mechanism correctly, but I thought that I provided model for repeater this way:

                    1. 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)
                    
                    1. 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
                    
                    1. 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.

                    fcarneyF Offline
                    fcarneyF Offline
                    fcarney
                    wrote on last edited by
                    #8

                    @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.

                    C++ is a perfectly valid school of magic.

                    1 Reply Last reply
                    2
                    • B Offline
                      B Offline
                      bwylegly
                      wrote on last edited by
                      #9

                      I simply cannot belive that, 2 days taken out of life because of that!
                      Now it works as intended. Thank you every much @fcarney !

                      I will leave this post here in case somebody looks for similar approach to implement buttons.

                      1 Reply Last reply
                      0
                      • fcarneyF Offline
                        fcarneyF Offline
                        fcarney
                        wrote on last edited by
                        #10

                        Don't feel bad. I spent 3 weeks working on a solution that I eventually had to scrap and do a different way. I was really mad about it, but it was a learning experience. So in this case just check assumptions. I always have to remind myself of this.

                        C++ is a perfectly valid school of magic.

                        1 Reply Last reply
                        1
                        • B bluesanta referenced this topic on

                        • Login

                        • Login or register to search.
                        • First post
                          Last post
                        0
                        • Categories
                        • Recent
                        • Tags
                        • Popular
                        • Users
                        • Groups
                        • Search
                        • Get Qt Extensions
                        • Unsolved