how to replace or redefine parent class' inline-component - qt

I would like to know if it is possible to replace/redefine a parent's inline component. There are ways to do so with a 'classic' component; for example, I can assign the component to a property and re-define the property:
Super.qml
import QtQuick 2.0
import QtQuick.Controls 2.12
Rectangle {
property Component classicComponent: Text { text: "Hello" }
width: 60
height: 30
border.color: "blue"
border.width: 2
Loader {
id: loader
sourceComponent: classicComponent
}
}
main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window {
width: 80
height: 90
visible: true
title: qsTr("Hello World")
Column {
x: 10; y: 10
spacing: 10
Super { }
Super {
classicComponent: Label {
text: "World"
background: Rectangle { color: "#30DD0000" }
}
}
}
}
I would like to know if it is possible to do something conceptually similar, but with an inline component. I have not found any way to combine an inline component with a property, and if I try something like:
InlineParent.qml
Rectangle {
component Foo: Text { text: "Hello" }
Foo {}
}
and
Child.qml
InlineParent {
component Foo: Label {
text: "World"
background: Rectangle { color: "#30DD0000" }
}
}
Both will print "Hello". Obviously, there are better ways to add a red background to an Item (such as Text); the concept I am interested in is in having the subclass redefine a component declared in the superclass/parent class.

After getting some sleep, I found that it IS possible to place an inline component in a property, allowing a subclass to redefine it or expand it.
Super.qml
import QtQuick 2.0
import QtQuick.Controls 2.12
Rectangle {
property Component inlineComponent: Foo {}
component Foo: Text { text: "Hello" }
width: 60
height: 30
border.color: "blue"
border.width: 2
Loader {
id: loader
sourceComponent: inlineComponent
}
}
main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window {
width: 80
height: 130
visible: true
title: qsTr("Hello World")
Column {
x: 10; y: 10
spacing: 10
Super { }
Super {
inlineComponent: Label {
text: "World"
background: Rectangle { color: "#30dd0000" }
}
}
Super {
inlineComponent: Super.Foo {
text: "World"
Rectangle {
color: "#30dd0000"
anchors.fill: parent
}
}
}
}
}
It solved my problem, allowing me to replace or extend Foo.

Related

Why does my Custom QML Tooltip have no background?

I have created a custom tooltip style using this guide: https://doc.qt.io/qt-6/qtquickcontrols2-customize.html#creating-a-custom-style
here is ToolTip.qml within my style:
import QtQuick.Templates 2.0 as T
import QtQuick
T.ToolTip {
id: ctrl
contentItem: Text{
color: "red"
text: ctrl.text
}
background: Rectangle {
color: "blue"
border.color: "yellow"
}
}
And here is how I use it:
import QtQuick.Controls 2.15
import QtQml 2.15
import QtQuick.Layouts 2.15
import QtQuick
ApplicationWindow {
id: mainWindow
visible: true
width: 800
height: 600
visibility: Window.Maximized
Button {
anchors.centerIn: parent
id: button
text: "Click me"
onClicked: {
console.log("Clicked")
}
ToolTip.visible: hovered
ToolTip.text: "hello world"
}
}
The text colour works, but the background doesn't.
Why does it not show the blue background rectangle?
I am using Qt6 with PySide6.
*edit:
I have tried using TextMetrics to give the background a width and height. Is this the idiomatic way to do it? It feels like I shouldn't need text metrics. This also leaves the Label uncentered in the background, which looks bad.
import QtQuick.Templates 2.0 as T
import QtQuick
import QtQuick.Controls
T.ToolTip {
id: ctrl
contentItem: Label {
color: "red"
text: ctrl.text
}
background: Rectangle {
id: bg
color: "yellow"
width: tmet.width + 5
height: tmet.height + 5
}
TextMetrics {
id: tmet
font: ctrl.font
text: ctrl.text
}
}
Everytime I need to create custom controls I'll look into the QtQuick Basic style. On my machine I can find it here Qt/6.4.0/gcc_64/qml/QtQuick/Controls/Basic/ToolTip.qml
Templates are non-visual implementations of controls' logic and
behavior.
You need to set a size on the ToolTip. Look how they are setting the implicitWidth and implicitHeight of the ToolTip.
import QtQuick
import QtQuick.Controls.impl
import QtQuick.Templates as T
T.ToolTip {
id: control
x: parent ? (parent.width - implicitWidth) / 2 : 0
y: -implicitHeight - 3
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
contentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
contentHeight + topPadding + bottomPadding)
margins: 6
padding: 6
closePolicy: T.Popup.CloseOnEscape | T.Popup.CloseOnPressOutsideParent | T.Popup.CloseOnReleaseOutsideParent
contentItem: Text {
text: control.text
font: control.font
wrapMode: Text.Wrap
color: control.palette.toolTipText
}
background: Rectangle {
border.color: control.palette.dark
color: control.palette.toolTipBase
}
}
To set the toolTip text color, it is not necessary to define a custom contentItem Label component. You can change the tooltip color by setting palette.toolTipText.
I can see your use case is to try to apply a custom ToolTip style so that all your components will inherit.
[EDIT: Change the direction of my answer to a custom Button instead of changing the default ToolTip style]
Alternatively, consider implementing custom components that have a custom ToolTip style. e.g. instead of Button, consider having MyButton. This way, we leave the default Button with the default style.
import QtQuick
import QtQuick.Controls
Page {
MyButton {
anchors.centerIn: parent
id: button
text: "Click me"
onClicked: {
console.log("Clicked")
}
ToolTip.visible: hovered
ToolTip.text: "hello world"
}
}
// MyButton.qml
import QtQuick
import QtQuick.Controls
Button {
ToolTip {
visible: parent.hovered
text: parent.ToolTip.text
palette.toolTipText: "red"
background: Rectangle {
color: "lightsteelblue"
border.color: "yellow"
}
}
}
You can Try it Online!

Is it possible to put a QML widget in a child of an imported QML file?

Say I have the following code in a file called NestedCircle.qml:
import QtQuick 2.12
Rectangle {
color: "blue"
radius: Math.min(width, height)
property Rectangle innerCircle: Rectangle {
radius: Math.min(width, height)
x: 10
y: 10
width: parent.width - 20
height: parent.height - 20
color: "yellow"
}
}
Then in main.qml:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window {
title: "Three circles"
NestedCircle {
// the line below fails with a syntax error, Expected token `:'
property NestedCircle innerCircle.innerCircle: NestedCircle {
color: "red"
}
}
}
So assuming I need to use NestedCircle.qml and can't edit it (this is a minimal example, not the real application), is there a way to put something inside innerCircle wihtout re-making it?
No need for dynamic object creation here, you can do that purely declaratively. In fact here are 4 ways of doing it, dependings on your needs and the API you want:
// NestedCircle.qml
import QtQuick 2.12
Rectangle {
color: "blue"
radius: Math.min(width, height)
default property alias innerCircleData: innerCircle.data
property alias innerCircle: innerCircle
Rectangle {
id: innerCircle
radius: Math.min(width, height)
anchors.centerIn: parent
width: parent.width - 20
height: parent.height - 20
color: "yellow"
}
}
// main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window {
visible: true
width: 800
height: 640
title: "Three circles"
Row {
anchors.centerIn: parent
spacing: 30
NestedCircle { // setting parent in the nested circle
id: rootCircle
width: 100
height: 100
NestedCircle {
parent: rootCircle.innerCircle
width: 50
height: 50
anchors.centerIn: parent
}
}
NestedCircle { // assigning to the inner circle's children in the outer circle
width: 100
height: 100
innerCircle.children: NestedCircle {
width: 50
height: 50
anchors.centerIn: parent
}
}
NestedCircle { // using an alias to the innerCircle.data
width: 100
height: 100
innerCircleData: NestedCircle {
width: 50
height: 50
anchors.centerIn: parent
}
}
NestedCircle { // using an alias to the innerCircle.data as a default property
width: 100
height: 100
NestedCircle {
width: 50
height: 50
anchors.centerIn: parent
}
}
}
}
If you want to add an item that is inside another, a possible solution is for the first item to be a child of the second.
NestedCircle.qml
import QtQuick 2.12
Rectangle {
color: "blue"
radius: Math.min(width, height)
property Rectangle innerCircle: inner
Rectangle {
id: inner
radius: Math.min(width, height)
x: 10
y: 10
width: parent.width - 20
height: parent.height - 20
color: "yellow"
}
}
main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window {
title: "Three circles"
Component{
id: provider
NestedCircle{
}
}
NestedCircle {
width: 100
height: 100
Component.onCompleted: {
var obj = provider.createObject(innerCircle, {width: 50, height: 50})
obj.anchors.centerIn = innerCircle
}
}
}

how to read custom property of parent ListView from external delegate?

I have a ListView and its delegate defined in other qml-file.
main.qml:
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
visible: true
width: 640
height: 480
ListModel {
id: listModel
ListElement {
name: "Bill Smith"
}
ListElement {
name: "John Brown"
}
ListElement {
name: "Sam Wise"
}
}
ListView {
width: 180; height: 200
property color delegateColor: "green"
model: listModel
delegate: ExternalDelegate {}
}
}
ExternalDelegate.qml:
import QtQuick 2.12
import QtQuick.Controls 2.12
ItemDelegate {
background: Rectangle {
color: ListView.view.delegateColor
}
Text {
text: model.name
}
}
Parent ListView has a custom property delegateColor. I need to read this property from the delegate. But if I try to access it by attached property ListView.view it does not work. And I see messages in console:
qrc:/ExternalDelegate.qml:7: TypeError: Cannot read property 'delegateColor' of null
How to read custom property of ListView from an external delegate?
I need to set this property in ListView (not in delegate) because I also want to access this property from header, footer and section delegates.
In this case it is better that the components do not know what they are used for but rather to expose through properties of the root element, for example an alias of the color of the rectangle can be created, and in the case of the text the ItemDelegate component already has that property.
import QtQuick 2.12
import QtQuick.Controls 2.12
ItemDelegate {
id: root
property alias color: bg.color
background: Rectangle {
id: bg
}
contentItem: Text {
text: root.text
}
}
delegate: ExternalDelegate {
text: model.name
color: ListView.view.delegateColor
}
Another solution is to only change the alias for a new property:
import QtQuick 2.12
import QtQuick.Controls 2.12
ItemDelegate {
id: root
property color color : "white"
background: Rectangle {
color: root.color
}
contentItem: Text {
text: root.text
}
}

QML RowLayout does not dynamically resize based on content

I have some experience with Qt Widgets, but only recently started to use QML.
The problem I face is that I'd like some layouts defined in QML to automatically adjust to fit their contents. This works, but not dynamically, i.e. if the content changes the layout does not adapt. With the old-style (non-QML) Layout/Widget approach, this happened automatically.
Here is an example (my code looks different and consists of different files, but I pasted this MWE together to demonstrate the problem):
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.0
import QtQuick.Layouts 1.2
Window {
id: root
visible: true
width: 640
height: 480
property var nbx: 3
Column {
RowLayout {
Repeater {
model: 3
Rectangle {
width: childrenRect.width
height: childrenRect.height
color: "green"
ColumnLayout {
Rectangle {
height: 10
}
RowLayout {
Repeater {
model: root.nbx
Rectangle {
width: 20
height: 20
color: "orange"
}
}
}
}
}
}
}
Button {
text: "5 boxes"
onClicked: root.nbx= 5;
}
Button {
text: "2 boxes"
onClicked: root.nbx = 2;
}
}
}
How can I achieve the same with QML?
You can make it work by setting the implicit size of the green Rectangle to the implicit size of the child ColumnLayout. I'm not exactly sure why, it seems the childrenRect properties are not propertly updated.
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.0
import QtQuick.Layouts 1.2
Window {
id: root
visible: true
width: 640
height: 480
property var nbx: 3
ColumnLayout {
RowLayout {
Repeater {
model: 3
Rectangle {
implicitHeight: col1.implicitHeight // <--- here is the change
implicitWidth: col1.implicitWidth
color: "green"
ColumnLayout {
id: col1
Rectangle {
height: 10
}
RowLayout {
Repeater {
model: root.nbx
Rectangle {
width: 20
height: 20
color: "orange"
}
}
}
}
}
}
}
Button {
text: "5 boxes"
onClicked: root.nbx= 5;
}
Button {
text: "2 boxes"
onClicked: root.nbx = 2;
}
}
}

Extra space at the bottom of Flow

I have a QML code like this:
MyItem.qml:
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.3
Item {
id: root
width: parent.width
height: grid.height
Rectangle {
anchors.fill: root
color: "blue"
z: -1
}
Flow {
id: grid
width: parent.width
spacing: 5
Button {
text: qsTr("Button 1")
}
Button {
text: qsTr("Button 2")
}
Button {
text: qsTr("Button 3")
}
}
}
main.qml:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.3
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ColumnLayout {
anchors.fill: parent
Button {
Layout.fillWidth: true
Layout.fillHeight: true
text: "hello"
}
MyItem {
Layout.fillWidth: true
}
}
}
If the Flow is wide enough for all three buttons to be at the same line (as with RowLayout) there is an extra empty space at the bottom of the Flow (approximately Button.height * 2). Looks like the Flow height is always calculated as the sum of all its element heights.
What is the logic behind this behavior? How to make the Flow fit its content height?
EDIT1: It is not Flow, but 'root' item has the wrong height.
EDIT2: Download the sample app
The problem with your code is that the root element the expressions:
anchors.fill: parent
height: grid.height
are competing, in the first expression you indicate that the dimensions of the root will take the size of the window and this implies the height but in the next expression you are indicating that the height will no longer be from the window but from the grid, so that generates an indefinite behavior. The only solution is to establish that the width of the root item is that of the window.
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Item {
id: root
height: grid.height
width: parent.width
Rectangle {
anchors.fill: root
color: "blue"
}
Flow {
id: grid
width: parent.width
spacing: 5
Button {
text: qsTr("Button 1")
}
Button {
text: qsTr("Button 2")
}
Button {
text: qsTr("Button 3")
}
}
}
}
Update:
It seems that you do not know how they work (read https://doc.qt.io/qt-5/qml-qtquick-layouts-layout.html#details), by default the height that is taken is the implicitHeight.
Also if you use layout you should not set anchors in the items that are directly affected by the layouts, in your case the CommandsTab is affected by the Layout so you should not use width: parent.width, is unnecesary.
CommandsTab.qml
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.3
Item {
id: root
implicitHeight: grid.height
Rectangle {
anchors.fill: root
color: "blue"
z: -1
}
Flow {
id: grid
width: parent.width
spacing: 5
Button {
text: qsTr("Button 1")
}
Button {
text: qsTr("Button 2")
}
Button {
text: qsTr("Button 3")
}
}
}
main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.3
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ColumnLayout {
anchors.fill: parent
Button {
Layout.fillWidth: true
Layout.fillHeight: true
text: "hello"
}
CommandsTab {
Layout.fillWidth: true
}
}
}

Resources