Receiving 'Cannot anchor to a null item' when using States - qml

I have a rectangle with a fixed size that should always stay attached to the bottom of the vertical center of my window. If the window height gets small enough for the rectangle to touch the border I want to change the anchors of the rectangle for it to stick to the bottom of the window instead.
I achieved this using States:
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
id: window
width: 200
height: 480
minimumHeight: maxSize
visible: true
property int maxSize: 150
property bool centerTop: maxSize < (height / 2)
Rectangle {
id: rect
states: [
State {
name: "Centered"
when: centerTop
PropertyChanges {
target: rect
anchors.top: parent.verticalCenter
anchors.bottom: undefined
}
},
State {
name: "Bottom"
when: !centerTop
PropertyChanges {
target: rect
anchors.top: undefined
anchors.bottom: parent.bottom
}
}
]
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 10
anchors.rightMargin: 10
height: maxSize
color: "red"
}
}
This is what the result looks like:
This works perfectly fine, but whenever I decreased the window size to reach the state Bottom and increase it again to reach the state Centered I get the error message
qrc:/main.qml:14:5: QML Rectangle: Cannot anchor to a null item.
Is there a solution or a better approach to my problem?

You want to make sure you use an AnchorChanges instead of PropertyChanges when dealing with anchors. The docs don't give much of an explanation other than to say:
PropertyChanges can be used to change anchor margins, but not other anchor values; use AnchorChanges for this instead.
states: [
State {
name: "Centered"
when: centerTop
AnchorChanges {
target: rect
anchors.top: parent.verticalCenter
anchors.bottom: undefined
}
},
State {
name: "Bottom"
when: !centerTop
AnchorChanges {
target: rect
anchors.top: undefined
anchors.bottom: parent.bottom
}
}
]

Related

QML - How do I make the TabButtons in TabBar visible?

I'm using qtcreator 4.4.1 with qt 5.9.2-1 on linux
I'm trying to create a tabbar with a stackview so that I can switch between the different tabs. But the tabbuttons in the tabbar never show up, and they aren't functional either if I click the area where they should have been.
I've tried adding all sorts of colored rectangles to see if I could somehow bring it to the surface, but it never shows... And I also added visible: true on most of the components. Also I tried to make sure everything has a width and height. But nonetheless, I still am unable to see it.
This is what it looks like
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Extras 1.4
import QtQuick.Layouts 1.3
import QtQuick.Templates 2.2
ApplicationWindow {
id: root
flags: Qt.FramelessWindowHint
visible: true
width: 382
height: 748
Column {
id: column1
width: parent.width
height: parent.height
visible: true
TabBar {
id: bar
width: parent.width
height: 50
visible: true
TabButton {
visible: true
text: qsTr("Fruit")
width: parent.width
height: parent.height
Rectangle {
anchors.fill: parent
color: "#ff0000"
visible: true
}
}
TabButton {
visible: true
text: qsTr("Vegetables")
width: parent.width
height: parent.height
Rectangle {
anchors.fill: parent
color: "#00ff00"
visible: true
}
}
TabButton {
text: qsTr("Demons")
width: parent.width
height: parent.height
Rectangle {
anchors.fill: parent
color: "#0000ff"
visible: true
}
}
}
StackLayout {
width: parent.width
height: parent.height
visible: true
currentIndex: bar.currentIndex
Item {
id: fruitTab
Rectangle {
anchors.fill: parent
color: "#ff0000"
visible: true
}
}
Item {
id: vegetableTab
Rectangle {
anchors.fill: parent
color: "#00ff00"
visible: true
}
}
Item {
id: demonTab
Rectangle {
anchors.fill: parent
color: "#0000ff"
visible: true
}
}
}
}
}
I also tried the simple example given by the qt docs: https://doc.qt.io/qt-5/qml-qtquick-controls2-tabbar.html#details but that didn't work either.
It looks like this
In addition to what #derM said (I would just leave out the width and height assignments altogether), the last import is a problem:
import QtQuick.Templates 2.2
Since the templates and controls have a one-to-one mapping of type names, this will cause the controls types to be shadowed by the ones from templates (since the templates import comes last).
You should always import the templates into their own namespace if you're also importing the controls:
import QtQuick.Templates 2.2 as T
http://doc.qt.io/qt-5/qtqml-syntax-imports.html#import-types explains this in detail:
This import allows multiple modules which provide conflicting type names to be imported at the same time, however since each usage of a type provided by a module which was imported into a qualified namespace must be preceded by the qualifier, the conflict is able to be resolved unambiguously by the QML engine.
In your example it looks like you're not using the templates at all, so you can just remove the import.
Try to remove the width in your TabButtons.
The problem seems to be, the dynamic sizing of the buttons.
You set them to be of the same width as the tab bar. So each button would fill the whole bar on its own.
When it tries to layout this, it obviously fails.
The same goes, if you set all of them, e.g. to width = parent.width / 2 as the parent's width is determined by the width of the children.
You need to either set the width of the buttons in relation to the TabBars width, by using myTabBarsId.width or you can just leave it out and let it be sized dynamically.
TabBar {
id: bar
width: parent.width
height: 50
visible: true
TabButton {
width: bar.width / 2 // Define width based on the `TabBar` width
text: qsTr("Fruit")
height: parent.height
}
TabButton {
text: qsTr("Vegetables")
height: parent.height
}
TabButton {
text: qsTr("Demons")
height: parent.height
}
}

QML: Center variable text and image

I need to horizonally center a variable-length text (red box) and an image (yellow box) in a big box (green box). The text shall wrap if it does not fit the box.
Existing code:
Item {
id: bigBox
x: 255
y: 0
width: 800
height: 100
Image {
id: imageBox
source: "image.png"
width: 52
height: 46
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Image.AlignLeft
verticalAlignment: Image.AlignVCenter
fillMode: Image.Pad
}
Text {
id: textBox
anchors.left: symbol.right
anchors.leftMargin: 12
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
text: qsTr("heading text")
font.pixelSize: 36
font.bold: true
horizontalAlignment: Text.AlignCenter
}
}
Update:
Actual running code and a real screenshot showing the problem:
import QtQuick 2.0
Rectangle {
id: mask
x: 0
y: 0
width: 800
height: 430
color: "#FFFFFF"
property int pageState: 0
Rectangle {
x: 0
y: 0
width: 111
height: 100
color: "#0000FF"
}
Item {
id: whitespace
x: 117
y: 0
width: 800-x
height: 100
Row {
anchors.centerIn: parent
Image {
id: symbol
source: "../img/pepper.png"
width: 52
height: 46
anchors.verticalCenter: parent.verticalCenter
fillMode: Image.Pad
}
Text {
id: heading
property var texts: ["Active Blabla","Active Blaaaaaaah Blaaaah ","Active Blabla and Blaaaaaaah Blaaaah"]
anchors.verticalCenter: parent.verticalCenter
color: "#333191"
text: texts[pageState]
font.family: "Liberation Sans"
font.pixelSize: 36
font.bold: true
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
textFormat: Text.PlainText
width: Math.min(150,contentWidth)
}
}
}
Rectangle {
id: stage
x: 0
y: 106
width: parent.width
height: parent.height-y
color: "#FFFF00"
}
Timer {
interval: 1000 // milliseconds
triggeredOnStart: true
repeat: true
running: true
onTriggered: {
pageState=(pageState+1)%3;
}
}
}
The white area top right is the green box in the first picture. The timer simply runs through the three texts, like the real application would do.
Changing the first parameter of Math.min() to 600 does not change anything.
My set of hacks to solve that problem:
Item {
// ...
Text {
function escapeHTML(text)
{
return text.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");
}
function toHTML(imageUrl,text)
{
var textlines=escapeHTML(text).replace(/\r?\n/,"\n").split("\n");
var retval="<center>";
retval=retval+'<table><tr>';
if (imageUrl!="") {
retval=retval+'<td rowspan="'+(textlines.length)+'"><img src="'+imageUrl+'"></td>';
retval=retval+'<td rowspan="'+(textlines.length)+'"> </td>';
}
for (var i=0; i<textlines.length; i++) {
if (i>0) {
retval=retval+"<tr>";
}
retval=retval+'<td>'+textlines[i]+'</td></tr>';
}
retval=retval+'</table>';
retval=retval+"</center>";
return retval;
}
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
textFormat: Text.RichText
text: toHtml("../img/pepper.png",qsTr("heading"))
}
}
<center> centers the text in the available space, but not the image
(it stays left - bug!).
<table> can be centered by <center>, and it can contain the
image.
Wrapping does not work as expected, so the translated text returned
by qsTr() has to contain linebreaks at the right positions.
toHTML() splits at the linebreaks and generates table rows from that.
The image needs a table cell with rowspan, or else the image is placed too high relative to the text.
And finally, verticalAlignment: Text.AlignVCenter places all of that vertically centered -- except for the table borders that I luckily don't need. (If you are bored, add border=1 to the <table> tag.)
And no, the HTML and CSS subset supported by Text.RichText does not support vertical alignment. The rendering engine behaves like ancient browsers, you have to stack hacks and workarounds as if it was still 1996.
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.2
Item {
id: bigBox
width: 800
height: 100
Row {
anchors.centerIn: parent
Image {
anchors.verticalCenter: parent.verticalCenter
source: "blue.png"
}
Text {
anchors.verticalCenter: parent.verticalCenter
// maximum width of the text
width: Math.min(150, contentWidth)
text: qsTr("heading")
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
}
}

Image size different after second click on it

I have following minimal working example, taken from my current project:
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.4
Window {
visible: true
width: Screen.width/2
height: Screen.height/2
property real ueMinOpacity: 0.00
property real ueMaxOpacity: 1.00
Rectangle {
anchors.fill: parent
anchors.margins: 8
border.color: "#4682b4"
radius: 16
clip: true
gradient: Gradient {
GradientStop {
position: 0
color: "#ffffff"
} // GradientStop
GradientStop {
position: 1
color: "#303030"
} // GradientStop
} // Gradient
Rectangle {
anchors.fill: parent
antialiasing: true
border.color: "#4682b4"
border.width: 1
radius: 16
clip: true
gradient: Gradient {
GradientStop {
position: 0
color: "#ffffff"
} // GradientStop
GradientStop {
position: 1
color: "#000000"
} // GradientStop
} // Gradient
RowLayout {
spacing: 8
anchors.fill: parent
TextField {
id: ueProductSearchTextField
antialiasing: true
Layout.fillWidth: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignLeft|Qt.AlignVCenter
Layout.margins: 8
placeholderText: qsTr("Enter product info")
} // TextField
Rectangle {
id: ueImageWrapper
Layout.fillWidth: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignRight|Qt.AlignVCenter
Layout.margins: 8
antialiasing: true
border.color: "#4682b4"
border.width: 1
radius: 16
clip: true
visible: ueProductSearchTextField.length > 0
gradient: Gradient {
GradientStop {
position: 0
color: "#636363"
} // GradientStop
GradientStop {
position: 1
color: "#303030"
} // GradientStop
} // Gradient
Image {
anchors.fill: parent
source: "http://www.clipartbest.com/cliparts/9iR/gEX/9iRgEXXxT.png"
antialiasing: true
clip: true
smooth: true
fillMode: Image.PreserveAspectFit
horizontalAlignment: Image.AlignHCenter
verticalAlignment: Image.AlignVCenter
sourceSize.width: 96
sourceSize.height: 96
} // Image
MouseArea {
anchors.fill: parent
enabled: ueImageWrapper.visible
onClicked: {
ueProductSearchTextField.text="";
} // onClicked
} // MouseArea
onWidthChanged: {
print("ueImageWrapper.width:"+ueImageWrapper.width);
} // onWidthChanged
onHeightChanged: {
print("ueImageWrapper.height:"+ueImageWrapper.height);
} // onHeightChanged
} // Rectangle
} // RowLayout
} // Rectangle
} // Rectangle
} // Window
Now, the purpose of this Item/Rectangle is to filter database records according to TextField's entered value, which works perfectly. However, once TextField's text is not empty anymore (when user enters some string), on the right side of Layout Image for clearing text is shown via OpacityAnimator. Once the app is launched, I get following screenshot - clear text icon is hidden since there is not text in TextField:
Then, I enter some text into TextField and clear text icon pops up:
Then, for instance, I clear text by clicking on clear text icon and it (icon) is hidden again, which is ok:
And finally, I reenter text into TextField, clear text icon is visible again, but it has different size:
Why? I did not change the code. It must be some problem with Layouts, but I simply do not see it! Here is also a debug output from onWidthChanged and onHeightChanged handlers:
qml: ueImageWrapper.width:37.56521739130435
qml: ueImageWrapper.height:480
qml: ueImageWrapper.width:132.92307692307693
qml: ueImageWrapper.width:133.83783783783784
BaCaRoZzo's suggestion works, but I'm also a bit unsure about why it behaves the way it does. If you take a simpler example:
import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Layouts 1.0
import QtQuick.Controls 1.0
Window {
visible: true
width: 800
height: 800
Shortcut {
sequence: "Ctrl+Q"
onActivated: Qt.quit()
}
Item {
id: boundary
width: 400
height: 400
anchors.centerIn: parent
RowLayout {
anchors.fill: parent
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: "steelblue"
}
Rectangle {
id: rect
Layout.fillWidth: true
Layout.fillHeight: true
color: "salmon"
visible: false
}
}
}
Rectangle {
anchors.fill: boundary
color: "transparent"
border.color: "black"
}
Button {
text: "Toggle visibility"
onClicked: rect.visible = !rect.visible
}
}
The second rectangle starts off being invisible, and is then shown/hidden by clicking the button. However, when it starts off as invisible, it never gets a size once shown. On the other hand, if it starts off visible, then it gets half the width of the layout.
If you read the documentation carefully, it doesn't say that it's necessary to set a preferredWidth/preferredHeight if you just want to make an item fill the available space. For that reason, it seems like a bug in how layouts handle initial visibility of their items. I'd suggest filing a bug report.

How to properly bind values to 'from' and 'to', inside a NumberAnimation?

I want to create a horizontal scrolling text animation (enter at the right side, go through the screen, exit at the left side, repeat).
import QtQuick 2.4
import QtQuick.Window 2.2
Window {
id: root
visible: true
Rectangle {
id: scrollLine
anchors.fill: parent
color: "black"
Text {
id: scrollText
color: "white"
text: "This is a test"
font.pixelSize: parent.height * 0.5
anchors.verticalCenter: parent.verticalCenter
x: scrollLine.width
NumberAnimation on x {
id: scrollAnimation
from: scrollLine.width; to: -scrollText.width
duration: 5000
loops: Animation.Infinite
running: true
}
}
}
}
The problem is, that my text acts weird. Appears left side, scrolls left with two characters, repeat... Something is wrong at the binding
from: scrollLine.width; to: -scrollText.width,
but I have no idea what.
Ah, this is weird! :)
The first thing I can see is that this
x: scrollLine.width
does nothing. The NumberAnimation runs immediately, causing the x value of the Text to be set, so we can remove that code to make it easier to find the problem.
The next thing to do is to print out the widths of the items:
import QtQuick 2.4
import QtQuick.Window 2.2
Window {
id: root
visible: true
Rectangle {
id: scrollLine
anchors.fill: parent
color: "black"
onHeightChanged: print("rectangle height", height)
Text {
id: scrollText
color: "white"
text: "This is a test"
font.pixelSize: parent.height * 0.5
anchors.verticalCenter: parent.verticalCenter
onWidthChanged: print("text width", width)
NumberAnimation on x {
id: scrollAnimation
from: scrollLine.width
to: -scrollText.width
duration: 5000
loops: Animation.Infinite
running: true
}
}
}
}
That gives us:
qml: text width 72.078125
qml: rectangle height 160
qml: text width 443.734375
Ok, it's weird that the text size changes width, but... it indirectly depends on the size of the window, right? We set its font.pixelSize to parent.height * 0.5. It just so happens that the window size is determined after the Text gets its initial size. However, being a declarative language, you'd think this should work.
Let's check the from and to values of the animation:
onFromChanged: print("from", from)
onToChanged: print("to", to)
Now we get:
qml: from 0
qml: to 0
qml: text width 72.078125
qml: to -72.078125
qml: from 160
qml: rectangle height 160
qml: text width 443.734375
qml: to -443.734375
They are initially incorrect, sure, but they do eventually become correct. This smells like a bug. Let's double check by printing out the x position of the Text:
qml: x -0.576625
...
qml: x -71.4654609375
That's not right. It seems like a bug. I thought it was, too, but then I checked the documentation:
If the NumberAnimation is defined within a Transition or Behavior, this value defaults to the value defined in the starting state of the Transition, or the current value of the property at the moment the Behavior is triggered.
You're not using a Behavior, although the syntax looks very similar. A bit more searching reveals the documentation for the on keyword:
The animation starts as soon as the rectangle is loaded, and will automatically be applied to its x and y values.
So, it's not a bug. You'll have to give the animation sensible from and to values somehow. One solution is to hard-code the values:
import QtQuick 2.4
import QtQuick.Window 2.2
Window {
id: root
width: 250
height: 250
visible: true
Rectangle {
id: scrollLine
anchors.fill: parent
color: "black"
Text {
id: scrollText
color: "white"
text: "This is a test"
font.pixelSize: parent.height * 0.5
anchors.verticalCenter: parent.verticalCenter
NumberAnimation on x {
id: scrollAnimation
from: root.width
to: -1000
duration: 5000
loops: Animation.Infinite
running: true
}
}
}
}
The best solution would probably be not to rely on the window's height for the font size, though. The default font size chosen by Qt is legible on all platforms that provide sensible DPI information, so you would be better off multiplying that by some factor:
import QtQuick 2.4
import QtQuick.Window 2.2
Window {
id: root
width: 250
height: 250
visible: true
Rectangle {
id: scrollLine
anchors.fill: parent
color: "black"
FontMetrics {
id: fontMetrics
}
Text {
id: scrollText
color: "white"
text: "This is a test"
font.pixelSize: fontMetrics.font.pixelSize * 8
anchors.verticalCenter: parent.verticalCenter
NumberAnimation on x {
id: scrollAnimation
from: root.width
to: -1000
duration: 5000
loops: Animation.Infinite
running: true
}
}
}
}
You code works as expected with a little modification. Instead of using font.pixelSize: parent.height*0.5, I used a fixed size point. Try this
import QtQuick 2.4
import QtQuick.Window 2.2
Window {
id: root
visible: true
Rectangle {
id: scrollLine
anchors.fill: parent
color: "black"
Text {
id: scrollText
color: "white"
text: "This is a test"
font.pixelSize: 150; //////// Changed this
anchors.verticalCenter: parent.verticalCenter
x: scrollLine.width
NumberAnimation on x {
id: scrollAnimation
from: scrollText.width; to: -scrollText.width
duration: 5000
loops: Animation.Infinite
running: true
}
}
}
}

QML Row element refuses to work

I know I should be using Row, Column etc. rather than items anchored by ID to make my code simpler and easier to read. But they refuse to work most of the time. For example, in this case:
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Layouts 1.1
ListView {
id: listView
anchors.fill: parent
topMargin: spacing
anchors.leftMargin: spacing
anchors.rightMargin: spacing
clip: true
spacing: 0.5 * pxPermm
model: SqlQueryModel {}
delegate: Rectangle {
id: delegateItem
color: "white"
height: 14 * pxPermm
width: listView.width
clip: true
Row {
id: row
anchors.fill: delegateItem
spacing: pxPermm
Image {
height: row.height
width: height
source: "qrc:/resources/ryba.jpg"
fillMode: Image.PreserveAspectCrop
}
Item {
id: textItem
height: row.height
Label {
anchors.left: textItem.left
anchors.top: textItem.top
text: nazov
font.bold: true
}
Label {
anchors.left: textItem.left
anchors.bottom: textItem.bottom
text: cas
}
}
}
}
}
This shows two Labels on the top of an Image in the delegate of list view. Not two labels to the right of the image, as you would expect. However, this code works:
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Layouts 1.1
ListView {
id: listView
anchors.fill: parent
topMargin: spacing
anchors.leftMargin: spacing
anchors.rightMargin: spacing
clip: true
spacing: 0.5 * pxPermm
model: SqlQueryModel {}
delegate: Rectangle {
id: delegateItem
color: "white"
height: 14 * pxPermm
width: listView.width
clip: true
Row {
id: row
anchors.fill: delegateItem
spacing: pxPermm
Image {
height: row.height
width: height
source: "qrc:/resources/ryba.jpg"
fillMode: Image.PreserveAspectCrop
}
Label {
text: nazov
font.bold: true
}
}
}
}
Of course I need to show more than one label in the delegate. What am I missing here?
It turns out that Item has zero width by default. The code works properly after the width is set:
Item {
id: textItem
height: row.height
width: childrenRect.width
// labels etc
}