How to disable the scalability of QML objects [duplicate] - qt

This question already has answers here:
How can I avoid creating a property binding on initialization in QML?
(5 answers)
Closed 4 years ago.
How to disable a the resizeability of QML objects? The height and width property of the object that I created are based on a scalable object, but i would not like for the object to be resized when the parent object is resized. How do I achieve this?
Window {
id: window
visible: true
title: qsTr("Stack")
Component.onCompleted: {
window.showMaximized()
}
Rectangle {
width:parent.width/2
height:parent.height/2
}
}
With the code above the rectangle will be created with 1/2 the size of the parent, and I would it to stay that way. When I resize the window the rectangle resizes too, I would like for it to be fixed after it is created. How do I achieve this? Thank you.

Remove your property bindings.
Window {
id: window
visible: true
title: qsTr("Stack")
Component.onCompleted: {
window.showMaximized()
rect.width = parent.width / 2; // use these
rect.height = parent.height / 2;
}
Rectangle {
id: rect
// width: parent.width/2 // no property bindings
// height: parent.height/2
}
}
By using property bindings on rect.width and rect.height, you're making them subject to dynamic change. You don't want that. Instead, you may call static assignments from Javascript.
If you place the JS code in Component.onCompleted, it will only run once (not updating every time the window resizes). These assignments won't create property bindings, fixing rect.width and rect.height.

Related

In Qt Quick, how to ensure a ListView's delegate's width to equal the view's width?

Here's my QML view:
// Imports ommitted
Item {
id: paymentMethods
required property PaymentMethodsModel model
ColumnLayout {
anchors.fill: parent;
Text {
text: "Payment methods";
}
ListView {
Layout.fillHeight: true
Layout.fillWidth: true
model: paymentMethods.model
delegate: PaymentMethods.Item { }
}
ToolBar { }
}
}
The problem is, it looks like this:
I think it's because the delegate doesn't specify width, because if I do this:
delegate: PaymentMethods.Item {
width: parent.width
onPmSaved: {
ListView.view.model.rename(index, newName)
}
}
It looks much better:
The problem is, when I do edits that reorder the items, I get this error:
qrc:/PaymentMethods.qml:32: TypeError: Cannot read property 'width' of null
Is there a good way to set a QML ListView's delegate's width to full parent's width?
From the ListView documentation:
Delegates are instantiated as needed and may be destroyed at any time. As such, state should never be stored in a delegate. Delegates are usually parented to ListView's contentItem, but typically depending on whether it's visible in the view or not, the parent can change, and sometimes be null. Because of that, binding to the parent's properties from within the delegate is not recommended. If you want the delegate to fill out the width of the ListView, consider using one of the following approaches instead:
ListView {
id: listView
// ...
delegate: Item {
// Incorrect.
width: parent.width
// Correct.
width: listView.width
width: ListView.view.width
// ...
}
}
In the transition of the reordering, the item does not have a parent, so the error indicates it, a possible solution is to set the width of the item depending on whether it has a parent or not.
width: parent ? parent.width : 40 // default value

What is the correct understanding about ScrollBar syntax in QtQuick/QML?

Recently, I used a Scrollbar with a TableView. I referred to
QML documentation for ScrollBar and I can see an example:
Flickable {
focus: true
Keys.onUpPressed: scrollBar.decrease()
Keys.onDownPressed: scrollBar.increase()
ScrollBar.vertical: ScrollBar { id: scrollBar }
}
I thought that ScrollBar.vertical is a bool variant, but why there is an object ScrollBar { id: scrollBar } after colon?
Is there any documentation about this syntax?
What is the difference between using
ScrollBar.vertical: ScrollBar { id: scrollBar }
and
ScrollBar { id: scrollBar; orientation: Qt.Vertical }
The same confusion came to me with the code below:
Flickable {
anchors.fill: parent
contentWidth: parent.width * 2
contentHeight: parent.height * 2
ScrollBar.horizontal: ScrollBar { id: hbar; active: vbar.active }
ScrollBar.vertical: ScrollBar { id: vbar; active: hbar.active }
}
On the line anchors.fill: parent, anchors is lower-case.
I thought that ScrollBar.vertical is a bool variant, but why there is an object ScrollBar { id: scrollBar } after the colon?
The answer is simply because ScrollBar.vertical is neither a bool nor a variant but has a type of ScrollBar. This is stated in the documentation.
ScrollBar.vertical : ScrollBar
This property attaches a vertical scroll bar to a Flickable.
Flickable {
contentHeight: 2000
ScrollBar.vertical: ScrollBar { }
}
Note the subheader tells us the type after the colon: ScrollBar.
Is there any documentation about this syntax?
Yes there is. I copied the above from this page.
What is the difference between using [...]
I'll walk through each confusing line of code and label each one with its name.
// Attached Property
ScrollBar.vertical: ScrollBar { id: scrollBar }
// Child Object
ScrollBar { id: scrollBar; orientation: Qt.Vertical }
// Grouped Property
anchors.fill: parent
Let's go through these one-by-one.
Attached Properties
Attached properties [...] are mechanisms that enable objects to be annotated with extra properties or signal handlers that are otherwise unavailable to the object. In particular, they allow objects to access properties or signals that are specifically relevant to the individual object.
References to attached properties [...] take the following syntax form:
<AttachingType>.<propertyName>
For example, the ListView type has an attached property ListView.isCurrentItem that is available to each delegate object in a ListView. This can be used by each individual delegate object to determine whether it is the currently selected item in the view:
import QtQuick 2.0
ListView {
width: 240; height: 320
model: 3
delegate: Rectangle {
width: 100; height: 30
color: ListView.isCurrentItem ? "red" : "yellow"
}
}
In this case, the name of the attaching type is ListView and the property in question is isCurrentItem, hence the attached property is referred to as ListView.isCurrentItem.
(source)
In our particular case, ScrollBar is the attaching type and vertical is the property.
Keep in mind that there are several differences between ListView.isCurrentItem and ScrollBar.vertical. The former is of type bool while the latter is of type ScrollBar. Additionally, the former is a read-only property, meaning that we can't assign or change it. On the other hand, you can assign to ScrollBar.vertical.
If ListView.isCurrentItem wasn't read-only, we could've assigned it like we did with ScrollBar.vertical.
delegate: Rectangle {
ListView.isCurrentItem: true
}
But since it is read-only, this raises an error.
Child Objects
This is QML basics right here. Here's an example:
ApplicationWindow {
visible: true
width: 800; height: 600
// child object of ApplicationWindow
Rectangle {
width: 200; height: 200
color: "red"
// child object of Rectangle
Text { text: "Hello World" }
}
// child object of ApplicationWindow
Rectangle {
x: 400
width: 200; height: 200
color: "blue"
}
}
Looking back at ScrollBar:
Flickable {
ScrollBar { id: scrollBar; orientation: Qt.Vertical }
}
This will instantiate a child object ScrollBar but that's it. No added functionality.
Grouped Properties
In some cases properties contain a logical group of sub-property attributes. These sub-property attributes can be assigned to using either the dot notation or group notation.
For example, the Text type has a font group property. Below, the first Text object initializes its font values using dot notation, while the second uses group notation:
Text {
// dot notation
font.pixelSize: 12
font.b: true
}
Text {
// group notation
font { pixelSize: 12; b: true }
}
(source)
Another common example of a grouped property is anchors (as you may have noted).
Don't let the dot notation confuse you. Try to spot a generic difference between the two properties below:
anchors.top
ScrollBar.vertical
The important distinction to make is that properties must begin with a lower-case letter whereas QML types begin with an upper-case letter. With this in mind, we can see that anchors is clearly a property while ScrollBar is a type.
With those out of the way, I think we can try to address one more issue.
Why use attached properties instead of defining ScrollBar as a child object?
Because of better automation. From documentation:
When ScrollBar is attached vertically or horizontally to a Flickable, its geometry and the following properties are automatically set and updated as appropriate:
orientation
position
size
active
An attached ScrollBar re-parents itself to the target Flickable. A vertically attached ScrollBar resizes itself to the height of the Flickable, and positions itself to either side of it based on the layout direction. A horizontally attached ScrollBar resizes itself to the width of the Flickable, and positions itself to the bottom.
(source)
This allows you to focus on other things, instead of worrying about the position of the scrollbar.
But sure, instantiating ScrollBar as a child object (non-attached) also has it merits.
It is possible to create an instance of ScrollBar without using the attached property API. This is useful when the behavior of the attached scroll bar is not sufficient or a Flickable is not in use. [...]
When using a non-attached ScrollBar, the following must be done manually:
Layout the scroll bar (with the x and y or anchor properties, for example).
Set the size and position properties to determine the size and position of the scroll bar in relation to the scrolled item.
Set the active property to determine when the scroll bar will be visible.
(source)

How to achieve better caching control for ListView?

I tried to play with cacheBuffer, but it's only help me to increase count of cached delegates, when I want to disable caching at all.
Now with zero caching buffer my example (only one item stretched on all ListView) behaves like this:
At the start ListView creates two delegates: currently visible and
next one.
When I scrolling list forward it creates and keep up to 4 delegates without beginning destroying them.
When I start scrolling list backward it begin immediately destroying delegates without looking on cacheBuffer.
If you replace "height: root.height" to "height: listView.height", it will create delegates for all model items at the start.
Is this behaviour normal? Can I change it some way?
You can tried it yourself:
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtQuick.Window 2.11
import Qt.labs.calendar 1.0
Window {
id: root
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ListView {
id: listView
anchors.fill: parent
snapMode: ListView.SnapOneItem
cacheBuffer: 0
model: 10
delegate: Rectangle {
width: parent.width
height: root.height
// height: listView.height
border.color: "black"
Text {
anchors.centerIn: parent
text: modelData
}
Component.onCompleted: {
console.log("Delegate completed")
}
Component.onDestruction: {
console.log("Delegate destruction")
}
}
}
}
Replace
delegate: MyVeryComplexDelegate {
}
by
delegate: Loader {
width: expectedDelegateWidth
height: expectedDelegateHeight // Otherwise you might create all...
sourceComponent: MyVeryComplexDelegate {
}
active: someCriteriaYouFeelGoodAbout()
}
Now you will only have simple Loaders in your cache and you can decide which ones of those in the cache are active.
Probably better: Have parts of the MyVeryComplexDelegate loaded as the ListView wants, and just hide the most complex parts behind a Loader that turns active only if you really need the full complexity.
On your strange findings as far as I can explain them:
Regarding the difference between root.height and listView.height, the explanation is an issue that is subject to many questions:
While root.height references the property height of the window, which you have explicitly set, listView.height is determined by anchors.fill: parent, which results in setting the height to root.contentItem.height - and that is initially 0. Therefore the delegates, initially all have a height of 0, all of them would fit in the view and therefor have to be created, even if you load as lazy as possible. Later they will resize together with the root.contentItem and some will be destroyed again.
You can see that, when monitoring the height changes of your delegates and your ListView
The next thing is, that even if the delegate really fills the ListView from the beginning, a second delegate is instantiated. The reason for that is, the condition used by the ListView, when to create new delegates. For that the sum of heights - the displacement of the first has to be larger than the ListView. That is not fulfilled when it is equal to the height.
Increase the height of your delegate by a fraction of a pixel, and you are good.
height: root.height + 0.0001

QML: referencing root window by parent reference is unreliable

Qt/QML question. Using Qt 5.7.
Take the following simple QML program that displays a red rectangle and a blue rectangle aligned vertically. Click handlers for both rectangles attempt to change the color of the parent host window. But with a subtle difference. The red rectangle references the host window directly by it's id (rootWindow). The blue click handler changes color via a parent reference.
The former case works fine. The latter case does not work. It seems like the root window is treated specially and isn't directly part of the parent/child hierarchy, even if the Rectangles are logically nested in the code that way.
Can someone explain the rule around this?
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
id: rootWindow
color: "#ffffee"
Rectangle {
id: rect1; width: 50; height: 50; color:"red"
MouseArea {
anchors.fill: parent;
onClicked: {
print("rect1 clicked");
rootWindow.color = "green"; // works fine
}
}
}
Rectangle {
id: rect2; width: 50; height: 50; color:"blue"
anchors.top: rect1.bottom
MouseArea {
anchors.fill: parent;
onClicked: {
print("rect2 clicked");
rect2.parent.color = "pink"; // does not work
}
}
}
}
If you add the following line to the onClicked handler, you'll see that its parent isn't the Window:
print(rect2.parent)
Output:
qml: QQuickRootItem(0x18b18147bc0)
This is explained not-so-visibly in the documentation for Window:
If you assign an Item to the data list, it becomes a child of the Window's contentItem, so that it appears inside the window. The item's parent will be the window's contentItem, which is the root of the Item ownership tree within that Window.
The window itself isn't an item, so it uses contentItem instead so that child items can have a parent.
However, in Qt 5.7, Window got an attached property that can be used to access the window of an item:
rect2.Window.window.color = "pink";
Whichever item comes before the Window.window part will be the item that the attached property is used on. You could use it on any item in this scene (e.g. the MouseArea), as they all belong to the same window.
Note that attached properties create a QObject-derived object for each unique item they're used on, so be mindful of how you use them, especially in items that are created in very large numbers.

How to define a "template" with child placeholders in QML?

I really like QML. I like how I can define components (comparable to classes) and their properties, and instantiate them from somewhere else (comparable to objects).
I can define, let's say, a button, having some look and feel, and a label text on it. This could be done, for example, using this component definition (Button.qml):
Item {
id: button
property string label
anchors.fill: parent
Rectangle {
anchors.fill: parent
radius: 10
color: "gray"
Text {
anchors.centerIn: parent
font.pixelSize: 20
text: button.label
color: "white"
}
}
}
and instanciated in this main file (main.qml):
Rectangle {
width: 300
height: 200
Button {
anchors.centerIn: parent
anchors.margins: 50
label: "Hello button!"
}
}
But I see the following restriction: I can only define a button template with some properties, not with some placeholder. All children defined in the instance will be direct children, at least per default, and I want to change this behavior.
Let's say I want to place an item (let's say an image, but I don't want to tell the definition of Button that it will be an image) in the button. I imagine something like this:
Item {
id: button
property Item contents <-- the client can set the placeholder content here
anchors.fill: parent
Rectangle {
anchors.fill: parent
radius: 10
color: "gray"
Item {
id: placeholder <-- where the placeholder should be inserted
}
}
Component.onCompleted: {
// move the contents into the placeholder...
}
}
How can I achieve this? I don't know if using Component.onCompleted is the correct way. Note that, however, that in my case the contents will never change afterwards (at least in my current design of the application...).
Also, I want anchoring to work within the placeholder. For example, if I define the contents to be a Text element, being centered in its parent (which will first be the template itself). Then my code moves this Text instance into the placeholder and the parent anchors should then be those of the placeholder item, not the template item.
I found a much nicer answer to this question, suggested in a presentation of the Qt Developer Days 2011 "Qt Quick Best Practices and Design Patterns".
They use default property alias ... to alias the child items to any property of any item. If you don't want to alias the children but give the alias property a name, just remove default. (Literal children are per QML definition the value of the default property.)
Item {
id: button
default property alias contents: placeholder.children
anchors.fill: parent
Rectangle {
anchors.fill: parent
radius: 10
color: "gray"
Item {
id: placeholder <-- where the placeholder should be inserted
}
}
}
Necro answering in case someone else end up here as I did.
In Qt5 somewhere along the line the default property became "data" and not "children".
This makes it possible to add other object types than "Item".
e.g. Connections can be added as well (to answer my own question above)
So in Qt5 you should do:
Item {
id: button
default property alias contents: placeholder.data
anchors.fill: parent
Rectangle {
anchors.fill: parent
radius: 10
color: "gray"
Item {
id: placeholder <-- where the placeholder should be inserted
}
}
}
Note the:
placeholder.data instead of placeholder.children
Also please note that you don't have to use the alias name contents - this can be anything you like. An example:
Item {
id: button
default property alias foo: placeholder.data
...
}
Actually, the correct answer from what I've heard is to use a QML Loader to accomplish what you want.
[that being said; I haven't actually tried it yet but it's on my near-term to-try list and looks fairly straight forward]
Also, search stackoverflow for other "QML Loader" questions as there are a number that will help you get started.
You can move the item(s) (if you want to support multiple items within the placeholder) using this piece of code:
property list<Item> contents
Component.onCompleted: {
var contentItems = [];
for(var i = 0; i < contents.length; ++i)
contentItems.push(contents[i]);
placeholder.children = contentItems;
}
Note that you do not have to provide a list of Items for the contents property, as single values will be accepted by the QML engine also for list properties.
In short (to show the idea):
import QtQuick 1.1
Item {
width: 200
height: 100
//<-- the client can set the placeholder content here
property Item contents: Rectangle {
anchors.fill: parent
anchors.margins: 25
color: "red"
}
Rectangle {
id: container
anchors.fill: parent
radius: 10
color: "gray"
//<-- where the placeholder should be inserted
}
Component.onCompleted: {
contents.parent = container
}
}
Somewhat longer version (supporting contents reassignment):
import QtQuick 1.1
Item {
width: 200
height: 100
//<-- the client can set the placeholder content here
property Item contents: Rectangle {
//anchors can be "presupplied", or set within the insertion code
//anchors.fill: parent
//anchors.margins: 25
color: "red"
}
Rectangle {
id: container
anchors.fill: parent
radius: 10
color: "gray"
//<-- where the placeholder should be inserted
//Item {
// id: placeholder
//}
}
//"__" means private by QML convention
function __insertContents() {
// move the contents into the placeholder...
contents.parent = container
contents.anchors.fill = container
contents.anchors.margins = 25
}
onContentsChanged: {
if (contents !== null)
__insertContents()
}
Component.onCompleted: {
__insertContents()
}
}
Hope this helps :)

Resources