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