Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Need QML advice

Need QML advice

Scheduled Pinned Locked Moved Solved General and Desktop
17 Posts 2 Posters 1.9k 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.
  • M Offline
    M Offline
    MScottM
    wrote on last edited by
    #1

    So I've created a GUI based on a QML ApplicationWindow, that has several pages of gauges for monitoring data (pages are loaded with Loaders). On my main page, I've created some space using a Rectangle that hides (zero width) until a condition is set. I want to use the space inside the rectangle for showing warnings, like if a gauge has gone out of a 'normal' range.

    I'm stumbling on how to implement sending the relevant information into the Rectangle. I've been thinking that I could define normal ranges inside each CircularGauge, and if the value of the gauge goes outside the range, I could set a flag, but then how to dynamically send that information to one or more (depending on how many gauges are out of range) TextFields, or other Items in the warning Rectangle?

    I would love to figure out how to show a copy of the gauge that is out of range.

    The data for the gauges will eventually come from a C++ backend, but right now I'm using a QML page with various animations to 'fake' the gauge data. I was wondering if I could use the C++ side to instantiate items inside the rectangle, on the fly?

    As always, I'm willing to do the legwork and studying, if someone can get me started in the right direction.

    M 1 Reply Last reply
    0
    • M MScottM

      So I've created a GUI based on a QML ApplicationWindow, that has several pages of gauges for monitoring data (pages are loaded with Loaders). On my main page, I've created some space using a Rectangle that hides (zero width) until a condition is set. I want to use the space inside the rectangle for showing warnings, like if a gauge has gone out of a 'normal' range.

      I'm stumbling on how to implement sending the relevant information into the Rectangle. I've been thinking that I could define normal ranges inside each CircularGauge, and if the value of the gauge goes outside the range, I could set a flag, but then how to dynamically send that information to one or more (depending on how many gauges are out of range) TextFields, or other Items in the warning Rectangle?

      I would love to figure out how to show a copy of the gauge that is out of range.

      The data for the gauges will eventually come from a C++ backend, but right now I'm using a QML page with various animations to 'fake' the gauge data. I was wondering if I could use the C++ side to instantiate items inside the rectangle, on the fly?

      As always, I'm willing to do the legwork and studying, if someone can get me started in the right direction.

      M Offline
      M Offline
      MScottM
      wrote on last edited by
      #2

      Okay, here is a more specific question:

      How can I create a signal that is emitted when the value goes out of a range I define:

      CircularGauge {
                  id: Pressure
      value: valueSource.Pressure
      
                  property real normalRangeHi: 200
                  property real normalRangeLo: 20
                  
      
                  Layout.preferredHeight: 150
                  Layout.preferredWidth: 150
                  Layout.fillHeight: true
                  Layout.fillWidth: true
                  Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
      
                  maximumValue: 100
                  
                          }
              
      
      1 Reply Last reply
      0
      • sierdzioS Offline
        sierdzioS Offline
        sierdzio
        Moderators
        wrote on last edited by
        #3
        CircularGauge {
          // [...]
        
          signal valueOutOfRange()
        
          onValueChanged: {
            if (value > maximumValue) {
              valueOutOfRange()
            }
          }
        }
        

        Is that what you want to achieve?

        (Z(:^

        1 Reply Last reply
        0
        • M Offline
          M Offline
          MScottM
          wrote on last edited by
          #4

          @sierdzio
          Yes, I think that will be half of it (thank you!), but because the 'receiver' is on another page (separate qml file), not an object of the circular gauge, it isn't aware of the signal. How do I make the signal available like that? And is it possible to also pass a bit of information with the signal? Like this:

          signal valueOutOfRange()
          
            onValueChanged: {
              if (value > maximumValue) {
                outOfRangeValue = value
                valueOutOfRange(outOfRangeValue)
              }
            }
          
          sierdzioS 1 Reply Last reply
          0
          • M MScottM

            @sierdzio
            Yes, I think that will be half of it (thank you!), but because the 'receiver' is on another page (separate qml file), not an object of the circular gauge, it isn't aware of the signal. How do I make the signal available like that? And is it possible to also pass a bit of information with the signal? Like this:

            signal valueOutOfRange()
            
              onValueChanged: {
                if (value > maximumValue) {
                  outOfRangeValue = value
                  valueOutOfRange(outOfRangeValue)
                }
              }
            
            sierdzioS Offline
            sierdzioS Offline
            sierdzio
            Moderators
            wrote on last edited by
            #5

            @MScottM said in Need QML advice:

            @sierdzio
            Yes, I think that will be half of it (thank you!), but because the 'receiver' is on another page (separate qml file), not an object of the circular gauge, it isn't aware of the signal. How do I make the signal available like that?

            There are at least two ways to do it:

            • add same signal to all components above your gauge up until you reach a common root element with your receiver - then add signals down that branch of parent-child relation. In each such "proxy" element you only need to emit the signal when child object emits it:
            // SomeComponent.qml
            Item {
              signal valueOutOfRange()
            
              CircularGauge {
                onValueOutOfRange: parent.valueOutOfRange()
              }
            }
            
            • use some global context property / context object. Preferably some QObject you define in C++ and attach to your QML via QQmlEngine->rootContext()->setContextProperty() (writing names from memory, actual may be different)

            And is it possible to also pass a bit of information with the signal?

            Yes, you can pass any variable through a signal. This is described fully in the documentation.

            (Z(:^

            1 Reply Last reply
            4
            • M Offline
              M Offline
              MScottM
              wrote on last edited by
              #6

              @sierdzio
              First - thank you for your advice so far.

              I'm having a hard time getting it working right now, but with what you've given me above, I know I'll figure it out before too long.

              I wanted to ask about best practice. Sending a signal up and down a chain of parented items seems...inelegant, but in the documentation about interacting between C++ and QML is this statement:

              "Warning: Although it is possible to access QML objects from C++ and manipulate them, it is not the recommended approach, except for testing and prototyping purposes. One of the strengths of QML and C++ integration is the ability to implement UIs in QML separate from the C++ logic and dataset backend, and this fails if the C++ side starts manipulating QML directly. Such an approach also makes changing the QML UI difficult without affecting its C++ counterpart."

              So maybe the first method is considered the best way?

              1 Reply Last reply
              0
              • sierdzioS Offline
                sierdzioS Offline
                sierdzio
                Moderators
                wrote on last edited by
                #7

                The second way I described is not about accessing QML objects from C++, it is about sending a signal from QML to C++, and then connecting to the re-emitted signal in another place. There is no tight integration here and no mixing of logic with UI. At least in my view.

                (Z(:^

                1 Reply Last reply
                1
                • M Offline
                  M Offline
                  MScottM
                  wrote on last edited by
                  #8

                  @sierdzio

                  Okay - still having a hard time (trying to learn!). All of the examples show declaring a QQmlEngine and creating a component

                  QQmlEngine engine;
                  QQmlComponent component(&engine, "MyItem.qml");
                  QObject *object = component.create();
                  

                  which I've already done in my main.cpp - or they create a QGuiApplication

                  int main(int argc, char *argv[]) {
                      QGuiApplication app(argc, argv);
                  
                      QQuickView view(QUrl::fromLocalFile("MyItem.qml"));
                      QObject *item = view.rootObject();
                  
                      MyClass myClass;
                      QObject::connect(item, SIGNAL(qmlSignal(QString)),
                                       &myClass, SLOT(cppSlot(QString)));
                  

                  Is it possible to create a class that is aware of the signals without having to create views or add to the main.cpp file?

                  sierdzioS 1 Reply Last reply
                  0
                  • M MScottM

                    @sierdzio

                    Okay - still having a hard time (trying to learn!). All of the examples show declaring a QQmlEngine and creating a component

                    QQmlEngine engine;
                    QQmlComponent component(&engine, "MyItem.qml");
                    QObject *object = component.create();
                    

                    which I've already done in my main.cpp - or they create a QGuiApplication

                    int main(int argc, char *argv[]) {
                        QGuiApplication app(argc, argv);
                    
                        QQuickView view(QUrl::fromLocalFile("MyItem.qml"));
                        QObject *item = view.rootObject();
                    
                        MyClass myClass;
                        QObject::connect(item, SIGNAL(qmlSignal(QString)),
                                         &myClass, SLOT(cppSlot(QString)));
                    

                    Is it possible to create a class that is aware of the signals without having to create views or add to the main.cpp file?

                    sierdzioS Offline
                    sierdzioS Offline
                    sierdzio
                    Moderators
                    wrote on last edited by
                    #9

                    @MScottM said in Need QML advice:

                    Is it possible to create a class that is aware of the signals without having to create views or add to the main.cpp file?

                    Yes. Consider this:

                    // someclass.h
                    class SomeClass : public QObject
                    {
                      Q_OBJECT
                      // blah blah blah...
                    public signals:
                      void someCppSignal() const;
                    
                    public slots:
                      void someCppSlot();
                    };
                    
                    // main.cpp
                    SomeClass someclass;
                    QQmlApplicationEngine engine;
                    engine.rootContext()->setContextProperty("SomeClass", &someclass);
                    
                    // QML code:
                    Item {
                      id: sender
                      signal valueOutOfRange()
                    
                      onValueOutOfRange: {
                        // If you want to call a slot in C++
                        // Let's assume that someCppSlot() emitts someCppSignal()
                        SomeClass.someCppSlot()
                      }
                    }
                    
                    // Other QML file ("receiver")
                    Item {
                      id: receiver
                      Connections {
                        target: SomeClass
                        onSomeCppSignal: console.log("Oh look! A signal from C++!")
                      } 
                    }
                    

                    Does that help? I can explain more if necessary.

                    (Z(:^

                    1 Reply Last reply
                    1
                    • M Offline
                      M Offline
                      MScottM
                      wrote on last edited by
                      #10

                      @sierdzio

                      Thank you! I'm pretty sure I understand how your example is working. I'll spend this weekend seeing if I can implement it in my code.

                      1 Reply Last reply
                      0
                      • M Offline
                        M Offline
                        MScottM
                        wrote on last edited by MScottM
                        #11

                        @sierdzio
                        Okay...my goal is to pass two pieces of information if a value goes out of range - the name of the gauge and its value. Here the code that I've tried so far.
                        In the QML 'sender' (snipped to keep it short):

                        CircularGauge {
                                    id: Temp
                                    objectName: Temp
                                    value: valueSource.coolantTemp
                        
                                    property real normalRangeHi: 200
                                    property real normalRangeLo: 20
                        
                                    signal valueOutOfRange(var messageObject)
                        
                                      onValueChanged: {
                                        if (value > normalRangeHi) {
                                          valueOutOfRange("Coolant Temp", Temp.value)
                                        } else if (value < normalRangeLo) {
                                            valueOutOfRange("Coolant Temp", Temp.value)
                                        }
                                        onValueOutOfRange:
                                            MessageRelay.setValueMsg(valueOutOfRange(messageObject))
                                      }
                        }
                        

                        and my MessageRelay.h file:

                        #ifndef MESSAGERELAY_H
                        #define MESSAGERELAY_H
                        
                        #include <QObject>
                        #include <QQmlProperty>
                        #include <QVariant>
                        #include <QDebug>
                        
                        class MessageRelay : public QObject
                        {
                            Q_OBJECT
                            Q_PROPERTY(QVariant valueMsg READ valueMsg WRITE setValueMsg NOTIFY valueMsgChanged)
                        
                        private:
                        
                            QVariant m_messageToRelay;
                        
                        public:
                        
                            QVariant valueMsg() const {
                               return m_messageToRelay;
                        }
                        
                        signals:
                        
                            void valueMsgChanged(const QVariant relayMessage);
                        
                        public slots:
                        
                            void setValueMsg(QVariant relayMessage){
                                m_messageToRelay = relayMessage;
                                emit valueMsgChanged(m_messageToRelay);
                              }
                        };
                        
                        #endif // MESSAGERELAY_H
                        

                        I can't get past this error from moc:

                        error: no match for call to '(QVariant) ()'
                        case 0: reinterpret_cast< QVariant>(_v) = _t->valueMsg(); break;

                        I haven't put any code in the 'receiver' yet.

                        1 Reply Last reply
                        0
                        • M Offline
                          M Offline
                          MScottM
                          wrote on last edited by
                          #12

                          Okay - got a little further (edited above code to what I have now). My QML file with the gauge is saying that "Reference error: MessageRelay is not defined" and then "Reference error: messageObject is not defined".

                          Is that the purpose of the 'setContextProperty' statement in main.cpp? Or, I am probably not bundling the variables correctly into the messageObject - looking at that now.

                          1 Reply Last reply
                          0
                          • sierdzioS Offline
                            sierdzioS Offline
                            sierdzio
                            Moderators
                            wrote on last edited by
                            #13

                            @MScottM said in Need QML advice:

                            void setValueMsg(QVariant relayMessage){

                            Qt classes, especially implicitly shared ones, are best passed by const reference const QVariant &relayMessage.

                            id: Temp
                            objectName: Temp

                            Not sure if using a capital letter at the start of an id is a good idea. If it works - fine. But the convention is that capital letters are for QML components only.

                            Object name should be a string.

                            Now, the actual reply:

                            MessageRelay.setValueMsg(valueOutOfRange(messageObject))

                            Here you are calling a method on MessageRelay so there is no need to use valueOutOfRange here.

                            Also, this code does not belong inside onValueChanged because it is inside another slot. Here is something that should work:

                            onValueChanged: {
                              if (value > normalRangeHi) {
                                valueOutOfRange("Coolant Temp", Temp.value)
                              } else if (value < normalRangeLo) {
                                valueOutOfRange("Coolant Temp", Temp.value)
                              }
                            }
                            
                            onValueOutOfRange: MessageRelay.setValueMsg(messageObject)
                            

                            Note: since you declared valueMsg as a Q_PROPERTY, the last line can also be:

                            onValueOutOfRange: MessageRelay.valueMsg = messageObject
                            

                            There might be one more problem ahead: you declare valueOutOfRange signal with one argument (messageObject) but then you pass two in onValueChanged. That is likely going to fail, but I may be wrong.

                            (Z(:^

                            1 Reply Last reply
                            4
                            • M Offline
                              M Offline
                              MScottM
                              wrote on last edited by
                              #14

                              @sierdzio
                              Okay - here is updated code from my gauge:

                              CircularGauge { //portCoolantTemp
                                          id: temp
                              
                                          property real normalRangeHi: 200
                                          property real normalRangeLo: 20
                              
                                          signal valueOutOfRange(var msgGauge, var msgValue)
                              
                                            onValueChanged: {
                                              if (value > normalRangeHi) {
                                                valueOutOfRange("Coolant Temp", temp.value)
                                              } else if (value < normalRangeLo) {
                                                  valueOutOfRange("Coolant Temp", temp.value)
                                              }
                                            }
                                            onValueOutOfRange:
                                                MessageRelay.setValueMsg(msgGauge, msgValue)
                              

                              and the change to the MessageRelay.h code:

                                  void setValueMsg(const QVariant &relayMessage){
                                      m_messageToRelay = relayMessage;
                                      emit valueMsgChanged(m_messageToRelay);
                                      qDebug()<<m_messageToRelay;
                                    }
                              };
                              
                              #endif // MESSAGERELAY_H
                              

                              my qDebug is in the slot is printing 'QVariant(invalid)', so you are probably right that I need to separate the values to pass them - trying to figure that out now. This is progress to me! Thanks!

                              1 Reply Last reply
                              0
                              • sierdzioS Offline
                                sierdzioS Offline
                                sierdzio
                                Moderators
                                wrote on last edited by
                                #15

                                OK, good progress. Now you need to decide on the nature of your MessageRelay class:

                                • if you want to keep valueMsg property, you need it to have only one argument (a property can only have one value). Here what you can do is create a JavaScript array and use it in your valueOutOfRange signal - on C++ side that will be translated to QVariantList
                                • if you don't need the valueMsg property, you can remove it and use the setValueMsg slot (you'll need to declare it as slot or Q_INVOKABLE). Or, if you really want to only relay the messages without remembering them, you should be able to emit the valueMsgChanged signal directly from QML, like this: onValueOutOfRange: MessageRelay.valueMsgChanged(messageObject)

                                (Z(:^

                                1 Reply Last reply
                                0
                                • M Offline
                                  M Offline
                                  MScottM
                                  wrote on last edited by
                                  #16

                                  @sierdzio
                                  Okay!! I have messages passing from QML to C++ now!

                                  Here is a snip from MessageRelay.h:

                                  public:
                                  
                                      qint16 m_valueToRelay = 0;
                                      QString m_nameToRelay = "";
                                  
                                  signals:
                                  
                                      void valueMsgChanged(const qint16 relayValue);
                                      void valueNameChanged(const QString relayName);
                                  
                                  public slots:    
                                  
                                      Q_INVOKABLE void setValueMsg(const qint16 &relayValue){
                                          if (m_valueToRelay != relayValue) {
                                              m_valueToRelay = relayValue;
                                              emit valueMsgChanged(relayValue);
                                              qDebug() << "relayMessage: " << relayValue;
                                          }
                                      }
                                  
                                      Q_INVOKABLE void setNameMessage(const QString &relayName){
                                          if (m_nameToRelay != relayName){
                                              m_nameToRelay = relayName;
                                              emit valueNameChanged(relayName);
                                              qDebug() << "relayName: " << relayName;
                                          }
                                      }
                                  
                                  };
                                  

                                  And here is the QML code from the CircularGauge:

                                  CircularGauge { //CoolantTemp
                                              id: temp
                                  
                                          //  SIGNALING
                                              property real normalRangeHi: 200
                                              property real normalRangeLo: 20
                                  
                                              signal valueOutOfRange(var messageValue)
                                              signal nameOutOfRange(var messageName)
                                  
                                                onValueChanged: {
                                                  if (temp.value > normalRangeHi) {
                                                    valueOutOfRange(temp.value)
                                                    nameOutOfRange("CoolantTemp")
                                                  } else if (temp.value < normalRangeLo) {
                                                      valueOutOfRange(temp.value)
                                                      nameOutOfRange("CoolantTemp")
                                                  }
                                                }
                                  
                                                onValueOutOfRange:
                                                    MessageRelay.setValueMsg(messageValue)
                                                onNameOutOfRange:
                                                    MessageRelay.setNameMessage(messageName)
                                  

                                  and this code gets this output in the console when the gauge goes low:

                                  relayMessage: 20
                                  relayName: "CoolantTemp"
                                  relayMessage: 19
                                  relayMessage: 18
                                  relayMessage: 17
                                  relayMessage: 16
                                  relayMessage: 15

                                  But now I think I have a problem. If more than one gauge goes out of range at the same time, which is very possible when Bad Stuff happens, I won't be able to associate the value being sent with the name.

                                  I've tried several ways to bundle the two variables into one message, but nothing seems to work. The closest I was able to get was some code that compiled and ran, but the QML side never seemed to trigger the signal.

                                  If there is a way to bundle the variables into one message so that I can tell where the messages are coming from on the receiver side, I would really appreciate the advice - and by the way, I really appreciate the help so far.

                                  1 Reply Last reply
                                  0
                                  • M Offline
                                    M Offline
                                    MScottM
                                    wrote on last edited by MScottM
                                    #17

                                    Here is the code I finally got working:

                                    QML Gauge:

                                    CircularGauge { //temp
                                                id: temp
                                    
                                            //  SIGNALING
                                                property real normalRangeHi: 180
                                                property real normalRangeLo: 50
                                    
                                                signal messageObject(var messageName, var messageValue)
                                    
                                                  onValueChanged: {
                                                    if (temp.value > normalRangeHi) {
                                                        messageObject("Coolant Temp HI", temp.value)
                                                        } else if (portCoolantTemp.value < normalRangeLo) {
                                                        messageObject("Coolant Temp LO", temp.value)
                                                        } 
                                                    }
                                                  onMessageObject: MessageRelay.setValueMsg(messageName, messageValue)
                                    
                                    

                                    MessageRelay.h:

                                    class MessageRelay : public QObject
                                    {
                                        Q_OBJECT
                                    
                                    
                                    private:
                                    
                                    
                                    public:
                                    
                                        qint16 m_valueToRelay = 0;
                                        QString m_nameToRelay = "";
                                        bool relayAlarm = false;
                                    
                                    signals:
                                    
                                        void valueMsgChanged(const QString relayName, const qint16 relayValue, const bool relayAlarm);
                                    
                                    public slots:    
                                    
                                        Q_INVOKABLE void setValueMsg(const QString &relayName, const qint16 &relayValue){
                                            if(relayValue != 0){
                                                //qDebug()<< m_nameToRelay;
                                                m_nameToRelay = relayName;
                                                m_valueToRelay = relayValue;
                                                relayAlarm = true;
                                                emit valueMsgChanged(m_nameToRelay, m_valueToRelay, relayAlarm);
                                                }
                                            else {emit valueMsgChanged("", 0, false);
                                            }
                                        }
                                    
                                    };
                                    

                                    And the receiver QML code:

                                    Connections {
                                            target: MessageRelay
                                            onValueMsgChanged: {cnsl(relayName, relayValue, relayAlarm)}
                                            function cnsl(relayName, relayValue, relayAlarm) {
                                                if (relayValue > 0){
                                                    alarmState = relayAlarm
                                                    txt.text =  relayName + ": " + relayValue
                                                } else {
                                                    alarmState = false                
                                                }
                                                //console.log(alarmState)
                                            }
                                        }
                                    

                                    EDIT
                                    And the main.cpp code registering the class:

                                    int main(int argc, char *argv[])
                                    {
                                        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
                                    
                                        QGuiApplication app(argc, argv);
                                    
                                        MessageRelay messageRelay;
                                        QQmlApplicationEngine engine;    
                                        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
                                    
                                        engine.rootContext()->setContextProperty("MessageRelay", &messageRelay);
                                        
                                        if (engine.rootObjects().isEmpty())
                                            return -1;
                                    
                                        return app.exec();
                                    
                                    }
                                    
                                    
                                    1 Reply Last reply
                                    1

                                    • Login

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