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. Creating Reusable Components

Creating Reusable Components

Scheduled Pinned Locked Moved Solved QML and Qt Quick
10 Posts 2 Posters 1.2k 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.
  • mzimmersM Offline
    mzimmersM Offline
    mzimmers
    wrote on last edited by
    #1

    Hi all -

    I want to begin creating a project collection of reusable components, so that I can apply the desired customization/styling in one place, and have it take effect project-wide. Something like this:

    import QtQuick
    import QtQuick.Controls
    import QtQuick.Layouts
    import Qt5Compat.GraphicalEffects
    
    Item {
        property real param_fontsize
        property color param_color
        property string caption: "this text should have been replaced."
    
        ColumnLayout {
            Image {
                id: theImage
                source: "qrc:/icons/ecomode.svg"
                visible: false // only show the color overlay
            }
            ColorOverlay {
                height: theImage.height
                width: theImage.width
                source: theImage
                color: param_color
            }
            Text {
                text: caption
                font.pixelSize: param_fontsize
            }
        }
    }
    

    In this manner, I can do all my formatting in one place.

    A potential pitfall to this approach is that some of my components will require sizing and positioning, and will be used in different positioning methods (eg, layouts and anchors). Is there some clean way to create components so that they will work no matter how the parent positions them?

    Thanks...

    1 Reply Last reply
    0
    • johngodJ Offline
      johngodJ Offline
      johngod
      wrote on last edited by
      #4

      Whoever instanciates the item should be responsible for the item size and item position / anchoring. But you have to define the type of behaviour you want for the component taking the size in consideration.

      See the following example of a ButtonBox.qml component which is a group of two buttons Cancel and Ok, aligned side by side. The buttons are instances of another button custom component

      // ButtonBox.qml
      Rectangle {
          width: mainRoot.width
          height: cancelID.height + defaultMargins * 2
      
          color: colors.mainColor
      
          property alias btnOkLabel: okID.text
          property alias btnCancelLabel: cancelID.text
          property bool showOk: true
          property bool showCancel: true
      
          signal btnOk()
          signal btnCancel()
      
          ButtonHybrid {
              id: cancelID
              visible: showCancel
              width: parent.width / 2 - defaultMargins * 2
              text: qsTr("Cancel")
              anchors.bottom: parent.bottom
              anchors.left: parent.left
              anchors.bottomMargin: defaultMargins
              anchors.leftMargin: defaultMargins
              onButtonClick: btnCancel()
          }
      
          ButtonHybrid {
              id: okID
              visible: showOk
              width: parent.width / 2 - defaultMargins * 2
              text: qsTr("Ok")
              anchors.bottom: parent.bottom
              anchors.right: parent.right
              anchors.bottomMargin: defaultMargins
              anchors.rightMargin: defaultMargins
              onButtonClick: btnOk()
          }
      }
      
      //---------------------------------------
      //ButtonHybrid.qml
      
      Rectangle {
          id: root
          width: btnSize
          height: btnSize
          color: isPressed || isSelected ? colorPressed : colorClear
          border.color: "grey"//"white"
          border.width: btnSize * 0.05
          radius: btnSize * 0.4
          //radius: 24
      
          Text {
              id: textItem
              anchors.centerIn: parent
              text: ""
          }
      
          property string iconName: "circle256.png"
          Image {
              id: imageItem
              anchors.fill: parent
              anchors.margins: parent.width * 0.1
              source: "qrc:/images/"+iconName
              visible: (textItem.text === "")? true: false
              mipmap: true
          }
      
          signal buttonClick()
          signal buttonDoubleClick()
          MouseArea {
              id: mouseArea
              anchors.fill: parent
              onClicked: {
                  //changeState()
                  buttonClick()
              }
              onDoubleClicked: {
                  buttonDoubleClick()
              }
              onPressed: isPressed = true
              onReleased: isPressed = false
              onCanceled: isPressed = false
          }
      
      }
      
      

      I deleted some code for simplicity, but the ideia is ButtonHybrid has a custom pre defined size, but it is being overrided by the ButtonBox, which is coded so that the buttons will strech to fill is horizontal size. I did this on porpuse because I wanted this behaviour but you may want the ButtonBox to keep the buttons same size even the buttonBox is streched to anchor is parent. In that case it would make sense to keep the buttons width and anchored them to, for example, bottom right. See, there is no universal answer, you have to figure out the type of behaviour. In your example of a ColumnLayout I think it makes sense to not have a fixed size, and increase to fill the size of whoever is instacinating it. In this case anchor it to fill the parent Item. But only you know the context of your reusable components.

      mzimmersM 1 Reply Last reply
      1
      • johngodJ Offline
        johngodJ Offline
        johngod
        wrote on last edited by
        #2

        You just have to anchor the ColumnLayout to his parent, the Item, or set the dimensions of ComunLayout related to Item. Then whenever you set the component dimensions or anchoring, the ColumnLayout will adjust.
        I do this all the time, it works very well.

        mzimmersM 1 Reply Last reply
        0
        • johngodJ johngod

          You just have to anchor the ColumnLayout to his parent, the Item, or set the dimensions of ComunLayout related to Item. Then whenever you set the component dimensions or anchoring, the ColumnLayout will adjust.
          I do this all the time, it works very well.

          mzimmersM Offline
          mzimmersM Offline
          mzimmers
          wrote on last edited by
          #3

          @johngod OK, but how do I go about sizing the Item itself? Is that best controlled by whoever is instantiating it? I can't put something Layout-based, or anchor-based, in the Item because I don't know what kind of positioning is above it (if that makes sense).

          1 Reply Last reply
          0
          • johngodJ Offline
            johngodJ Offline
            johngod
            wrote on last edited by
            #4

            Whoever instanciates the item should be responsible for the item size and item position / anchoring. But you have to define the type of behaviour you want for the component taking the size in consideration.

            See the following example of a ButtonBox.qml component which is a group of two buttons Cancel and Ok, aligned side by side. The buttons are instances of another button custom component

            // ButtonBox.qml
            Rectangle {
                width: mainRoot.width
                height: cancelID.height + defaultMargins * 2
            
                color: colors.mainColor
            
                property alias btnOkLabel: okID.text
                property alias btnCancelLabel: cancelID.text
                property bool showOk: true
                property bool showCancel: true
            
                signal btnOk()
                signal btnCancel()
            
                ButtonHybrid {
                    id: cancelID
                    visible: showCancel
                    width: parent.width / 2 - defaultMargins * 2
                    text: qsTr("Cancel")
                    anchors.bottom: parent.bottom
                    anchors.left: parent.left
                    anchors.bottomMargin: defaultMargins
                    anchors.leftMargin: defaultMargins
                    onButtonClick: btnCancel()
                }
            
                ButtonHybrid {
                    id: okID
                    visible: showOk
                    width: parent.width / 2 - defaultMargins * 2
                    text: qsTr("Ok")
                    anchors.bottom: parent.bottom
                    anchors.right: parent.right
                    anchors.bottomMargin: defaultMargins
                    anchors.rightMargin: defaultMargins
                    onButtonClick: btnOk()
                }
            }
            
            //---------------------------------------
            //ButtonHybrid.qml
            
            Rectangle {
                id: root
                width: btnSize
                height: btnSize
                color: isPressed || isSelected ? colorPressed : colorClear
                border.color: "grey"//"white"
                border.width: btnSize * 0.05
                radius: btnSize * 0.4
                //radius: 24
            
                Text {
                    id: textItem
                    anchors.centerIn: parent
                    text: ""
                }
            
                property string iconName: "circle256.png"
                Image {
                    id: imageItem
                    anchors.fill: parent
                    anchors.margins: parent.width * 0.1
                    source: "qrc:/images/"+iconName
                    visible: (textItem.text === "")? true: false
                    mipmap: true
                }
            
                signal buttonClick()
                signal buttonDoubleClick()
                MouseArea {
                    id: mouseArea
                    anchors.fill: parent
                    onClicked: {
                        //changeState()
                        buttonClick()
                    }
                    onDoubleClicked: {
                        buttonDoubleClick()
                    }
                    onPressed: isPressed = true
                    onReleased: isPressed = false
                    onCanceled: isPressed = false
                }
            
            }
            
            

            I deleted some code for simplicity, but the ideia is ButtonHybrid has a custom pre defined size, but it is being overrided by the ButtonBox, which is coded so that the buttons will strech to fill is horizontal size. I did this on porpuse because I wanted this behaviour but you may want the ButtonBox to keep the buttons same size even the buttonBox is streched to anchor is parent. In that case it would make sense to keep the buttons width and anchored them to, for example, bottom right. See, there is no universal answer, you have to figure out the type of behaviour. In your example of a ColumnLayout I think it makes sense to not have a fixed size, and increase to fill the size of whoever is instacinating it. In this case anchor it to fill the parent Item. But only you know the context of your reusable components.

            mzimmersM 1 Reply Last reply
            1
            • johngodJ johngod

              Whoever instanciates the item should be responsible for the item size and item position / anchoring. But you have to define the type of behaviour you want for the component taking the size in consideration.

              See the following example of a ButtonBox.qml component which is a group of two buttons Cancel and Ok, aligned side by side. The buttons are instances of another button custom component

              // ButtonBox.qml
              Rectangle {
                  width: mainRoot.width
                  height: cancelID.height + defaultMargins * 2
              
                  color: colors.mainColor
              
                  property alias btnOkLabel: okID.text
                  property alias btnCancelLabel: cancelID.text
                  property bool showOk: true
                  property bool showCancel: true
              
                  signal btnOk()
                  signal btnCancel()
              
                  ButtonHybrid {
                      id: cancelID
                      visible: showCancel
                      width: parent.width / 2 - defaultMargins * 2
                      text: qsTr("Cancel")
                      anchors.bottom: parent.bottom
                      anchors.left: parent.left
                      anchors.bottomMargin: defaultMargins
                      anchors.leftMargin: defaultMargins
                      onButtonClick: btnCancel()
                  }
              
                  ButtonHybrid {
                      id: okID
                      visible: showOk
                      width: parent.width / 2 - defaultMargins * 2
                      text: qsTr("Ok")
                      anchors.bottom: parent.bottom
                      anchors.right: parent.right
                      anchors.bottomMargin: defaultMargins
                      anchors.rightMargin: defaultMargins
                      onButtonClick: btnOk()
                  }
              }
              
              //---------------------------------------
              //ButtonHybrid.qml
              
              Rectangle {
                  id: root
                  width: btnSize
                  height: btnSize
                  color: isPressed || isSelected ? colorPressed : colorClear
                  border.color: "grey"//"white"
                  border.width: btnSize * 0.05
                  radius: btnSize * 0.4
                  //radius: 24
              
                  Text {
                      id: textItem
                      anchors.centerIn: parent
                      text: ""
                  }
              
                  property string iconName: "circle256.png"
                  Image {
                      id: imageItem
                      anchors.fill: parent
                      anchors.margins: parent.width * 0.1
                      source: "qrc:/images/"+iconName
                      visible: (textItem.text === "")? true: false
                      mipmap: true
                  }
              
                  signal buttonClick()
                  signal buttonDoubleClick()
                  MouseArea {
                      id: mouseArea
                      anchors.fill: parent
                      onClicked: {
                          //changeState()
                          buttonClick()
                      }
                      onDoubleClicked: {
                          buttonDoubleClick()
                      }
                      onPressed: isPressed = true
                      onReleased: isPressed = false
                      onCanceled: isPressed = false
                  }
              
              }
              
              

              I deleted some code for simplicity, but the ideia is ButtonHybrid has a custom pre defined size, but it is being overrided by the ButtonBox, which is coded so that the buttons will strech to fill is horizontal size. I did this on porpuse because I wanted this behaviour but you may want the ButtonBox to keep the buttons same size even the buttonBox is streched to anchor is parent. In that case it would make sense to keep the buttons width and anchored them to, for example, bottom right. See, there is no universal answer, you have to figure out the type of behaviour. In your example of a ColumnLayout I think it makes sense to not have a fixed size, and increase to fill the size of whoever is instacinating it. In this case anchor it to fill the parent Item. But only you know the context of your reusable components.

              mzimmersM Offline
              mzimmersM Offline
              mzimmers
              wrote on last edited by
              #5

              @johngod thank you for the very detailed answer. Question: in your ButtonHybrid.qml, where does "btnSize" come from? (I know you said you deleted some code, but this is key to what I'm trying to understand.)

              Thanks again.

              1 Reply Last reply
              0
              • johngodJ Offline
                johngodJ Offline
                johngod
                wrote on last edited by
                #6
                    property real calibrationFactor: 1//0.5
                    property real mm: Screen.pixelDensity * calibrationFactor
                    property real defaultMargins: 2 * mm
                    property real btnSizeSmall: 4 * mm
                    property real btnSize: 7 * mm
                

                It is just a global property defined in main.qml. Some may argue that ButtonHybrid should be self contained and not have global properties, but in the scope of my personal project it makes sense I am the only developer and I am using that value all around.
                Then, tunning the size of all buttons just requires changing that value which is great.

                mzimmersM 1 Reply Last reply
                1
                • johngodJ johngod
                      property real calibrationFactor: 1//0.5
                      property real mm: Screen.pixelDensity * calibrationFactor
                      property real defaultMargins: 2 * mm
                      property real btnSizeSmall: 4 * mm
                      property real btnSize: 7 * mm
                  

                  It is just a global property defined in main.qml. Some may argue that ButtonHybrid should be self contained and not have global properties, but in the scope of my personal project it makes sense I am the only developer and I am using that value all around.
                  Then, tunning the size of all buttons just requires changing that value which is great.

                  mzimmersM Offline
                  mzimmersM Offline
                  mzimmers
                  wrote on last edited by
                  #7

                  @johngod thank you for that. On a related note, do you explicitly define all of your global properties directly in your main.qml file, or do you import a special file for that purpose? I need to make that decision fairly soon myself.

                  johngodJ 1 Reply Last reply
                  0
                  • johngodJ Offline
                    johngodJ Offline
                    johngod
                    wrote on last edited by
                    #8
                    This post is deleted!
                    1 Reply Last reply
                    0
                    • mzimmersM mzimmers

                      @johngod thank you for that. On a related note, do you explicitly define all of your global properties directly in your main.qml file, or do you import a special file for that purpose? I need to make that decision fairly soon myself.

                      johngodJ Offline
                      johngodJ Offline
                      johngod
                      wrote on last edited by
                      #9

                      @mzimmers Well if you have a lot of properties, I would create a new file, if few, maybe use the main, up to you

                      1 Reply Last reply
                      0
                      • johngodJ Offline
                        johngodJ Offline
                        johngod
                        wrote on last edited by
                        #10

                        Here is another example, I have a project were I draw types of entites like lines, circles, rectangles, ... and I needed a properties editor, for each. The number of properties and their names change for each type, so I would have a lot of work to make several properties editor for each type. Instead I created a custom PropertiesListEditor.qml that is just a custom ListView,
                        Listmodel and delegate. I exposed some properties as alias to access this component, then it becomes just one line of code for adding a new property to the editor

                        // property editor for circles
                        	PropertiesListEditor {
                                id: lineEd
                                anchors.left: parent.left
                                anchors.right: parent.right
                                anchors.top: parent.top
                                //anchors.topMargin: defaultMargins
                                anchors.bottom: btnBox.top
                                //anchors.margins: defaultMargins
                        
                                Component.onCompleted: {
                                    setModelCenterRadius()
                                }
                            }
                        	
                        	function setModelCenterRadius() {
                                lineEd.myModel.clear()
                        
                                lineEd.myModel.append({"propertyName": "Circle center", "propertyValue": "", "propertyIsTitle": true})
                                lineEd.myModel.append({"propertyName": "center.x", "propertyValue": String(circleCenter.x), "propertyIsTitle": false})
                                lineEd.myModel.append({"propertyName": "center.y", "propertyValue": String(circleCenter.y), "propertyIsTitle": false})
                                lineEd.myModel.append({"propertyName": "center.z", "propertyValue": String(circleCenter.z), "propertyIsTitle": false})
                        
                                lineEd.myModel.append({"propertyName": "Circle radius", "propertyValue": "", "propertyIsTitle": true})
                                lineEd.myModel.append({"propertyName": "radius", "propertyValue": String(circleRadius), "propertyIsTitle": false})
                            }
                        
                        // property editor for Lines
                        	PropertiesListEditor {
                                id: lineEd
                                anchors.left: parent.left
                                anchors.right: parent.right
                                anchors.top: column.bottom
                                anchors.topMargin: -defaultMargins
                                anchors.bottom: btnBox.top
                                //anchors.margins: defaultMargins
                        
                                Component.onCompleted: {
                                    setModelP1P2()
                                }
                            }
                        	
                        	function setModelP1P2() {
                                lineEd.myModel.clear()
                        
                                lineEd.myModel.append({"propertyName": "Rectangle p0", "propertyValue": "", "propertyIsTitle": true})
                                lineEd.myModel.append({"propertyName": "p0.x", "propertyValue": String(p0.x), "propertyIsTitle": false})
                                lineEd.myModel.append({"propertyName": "p0.y", "propertyValue": String(p0.y), "propertyIsTitle": false})
                                lineEd.myModel.append({"propertyName": "p0.z", "propertyValue": String(p0.z), "propertyIsTitle": false})
                        
                                lineEd.myModel.append({"propertyName": "Rectangle p2", "propertyValue": "", "propertyIsTitle": true})
                                lineEd.myModel.append({"propertyName": "p2.x", "propertyValue": String(p2.x), "propertyIsTitle": false})
                                lineEd.myModel.append({"propertyName": "p2.y", "propertyValue": String(p2.y), "propertyIsTitle": false})
                                lineEd.myModel.append({"propertyName": "p2.z", "propertyValue": String(p2.z), "propertyIsTitle": false})
                            }
                        

                        PropertiesListEditor.qml

                        import QtQuick 2.9
                        import QtQuick.Controls 2.2
                        
                        Item {
                        
                            property alias myModel: _model
                            property alias myView: _view
                        
                            ListView {
                                id: _view
                                anchors.left: parent.left
                                anchors.right: parent.right
                                anchors.top: parent.top
                                anchors.bottom: parent.bottom
                                anchors.margins: defaultMargins
                                delegate: myDelegate
                                model: _model
                            }
                        
                            Component {
                                id: myDelegate
                                Row {
                                    property bool isTitle: propertyIsTitle
                                    Rectangle {
                                        id: delegateName
                                        width: isTitle ? _view.width : _view.width * 0.5
                                        height: 3 * mm //btnSize * 0.7
                                        border.color: "black"
                                        color: isTitle ? "lightgrey" : "white"
                        
                                        Text {
                                            anchors.top: parent.top
                                            anchors.bottom: parent.bottom
                                            anchors.left: parent.left
                                            anchors.leftMargin: defaultMargins
                                            anchors.right: parent.right
                                            verticalAlignment: Text.AlignVCenter
                                            text: propertyName
                                            font.bold: isTitle
                                        }
                                    }
                        
                                    TextInputHy {
                                        width: isTitle ? 0 : _view.width * 0.5
                                        height: delegateName.height
                                        radius: 0
                                        text: propertyValue
                                        onTextEdited: function(tex) {
                                            //console.log("tex: "+tex)
                                            var aux = parseFloat(tex)
                                            //console.log("aux: "+aux)
                                            //_model.setProperty(index, "propertyValue", parseFloat(tex))
                                            //_model.get(index).propertyValue = parseFloat(tex)
                                            _model.get(index).propertyValue = tex
                        
                                            //lineEd.myModel.append({"propertyName": "point p1", "propertyValue": 0, "propertyIsTitle": true})
                        
                                        }
                                    }
                                }//Row
                            }//Component
                        
                            ListModel {
                                id: _model
                                //dynamicRoles: true
                            }
                        
                        }
                        
                        
                        1 Reply Last reply
                        0

                        • Login

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