Proper way of defining and composing custom elements in QML - qt

Lets say I want to create a field element in QML which has its "field" part and could have icon on the right, left side or not show it at all. Basic qml code would look like this (without functionality to change sides):
// TestField.qml
Item
{
Image
{
anchors.right: field.left
anchors.top: field.top
source: "qrc:/res/settings/logOptions.svg"
height: 40
width: 40
}
Rectangle
{
id: field
anchors.fill: parent
color: "blue"
}
}
Then I will use it like this:
// Main.qml
TestField
{
x: 100
y: 100
height: 100
width: 100
}
Or use it in some grid layout, anchor it to another element, etc. I used hardcoded coordinates and sizes just to keep the question simpler.
The problem with this is that I specified sizes for the element (100x100) and my field rectangle would fill the whole space, leaving icon "hanging" out of bounds of this element. In this case QML will treat this element as of the size 100x100, but since the icon is out of bounds the actual size of the whole element with the icon is bigger (140x100) and could overlap with other such elements when placed into layout.
Worth noting that for such elements I want to be able to change position of the icon to be to the left or right of the rectangle (it will be some sort of property with enum value, that I will define in code, it won't change dynamically throughout application work) so some rectangles will have icon from different sides.
One solution I see is to rework TestField so its elements will be tied to parents width or height, for example:
Item
{
Image
{
x: 0
y: 0
width: parent.width / 2
height: parent.width / 2
}
Rectangle
{
id: field
x: parent.width / 2
y: 0
width: parent.width / 2
height: parent.height
}
}
The question is - is this a good solutions or there are proper ways to tackle such issue? My main concern is that element size should be actually the size that I could query with element.width or element.height and none of the internal part are out of bounds possibly overlapping with other elements. Maybe I'm missing some basic concept that will allow me to make elements that always keep their parts inside its bounds?

The problem with your sizing is that your Rectangle takes up the whole size of its parent. You want to shrink that to account for the size of the Image. I also added a way to switch the image between left and right side.
Item
{
property bool imageOnLeft: true
Image
{
id: img
anchors.left: imageOnLeft ? parent.left : field.right
source: "qrc:/res/settings/logOptions.svg"
height: 40
width: 40
}
Rectangle
{
id: field
anchors.left: imageOnLeft ? img.right : parent.left
width: parent.width - img.width
height: parent.height
color: "blue"
}
}

Related

How to align items in a ListView in QtQuick

I have the following horizontal list:
BulletsListDelegate.qml
import QtQuick 2.0
Rectangle {
width: 8
height: 8
color: "#808080"
radius: width * 0.5
}
main.qml
import QtQuick 2.0
Item {
width: 256
height: 256
ListModel {
id: bulletsListModel
ListElement {
a: 'example'
}
ListElement {
a: 'example'
}
...
}
ListView {
id: bulletsList
spacing: 8
orientation: ListView.Horizontal
delegate: BulletsListDelegate {}
model: bulletsListModel
anchors.bottom: parent.bottom
width: parent.width
}
}
And the elements are shown like this (they are the grey bullets)
I want them to show in the horizontal center of the black box above them (its parent).
Is there any form of centralize or justify the items of the list?
So, if I understand your question correctly, you want to have alignment for instances of your items in a ListView. Using a ListView, that is not so easy to achieve. If you have an uneven number of items, you can do it by using preferredHighlightBegin and preferredHighlightEnd to have a 1-item sized region in the center of your ListView, and then setting hightlightRangeMode to ListView.StrictlyEnforceRange. You can set the currentIndex to point to index so that the middle item will be considered current. That puts it within the range you defined, and thus in the center. That does not work when you have an even number of items though, so it's a hack with limited value.
My question is: do the items have to be positioned using a ListView? It looks like you don't actually need much of the functionality of the ListView at all? If you don't need the other features from ListView (like scrolling), you can just use a Repeater instead. That allows you to simply put the items in a Row positioner, which you can width of count*(delegateWidth+spacing)-spacing and a height equal to your delegate height. Then, you can use acnhors to position the Row centered to whatever you like.
André's answer suggested me to use Repeater and Rows instead of ListView, and it completely solved my problem. But... didn't find a way to align an actual ListView yet.
Row {
spacing: 8
Repeater {
id: bulletsRepeater
model: 5
BulletsDelegate { }
}
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
}

QML: What does the "x" property exactly do and what's the difference with anchors.leftMargin/rightMargin in a ListView?

I tried to add a margin to a ListView element using anchors.leftMargin expecting it would create a margin between the screen border and the beginning of the list, but it didn't work at all. Using x instead solved the problem. Here is the code:
ListView {
id: list
width: parent.width - sideMargin
x: sideMargin //works
anchors.leftMargin: sideMargin //doesn't work
orientation: ListView.Horizontal
focus: true
spacing: 16
//...
}
So now I am wondering when to use these properties, because reading the docs is not enough for me to understand when to use one or the other.
Items in QML/QtQuick can be positioned and sized in 3 different ways:
Freely using the x and y properties for position and the width and height properties for the size.
See: https://doc.qt.io/qt-5/qml-qtquick-item.html#x-prop for more information.
ListView {
id: list
width: parent.width - sideMargin
x: sideMargin
y: topMargin
}
Using Layouts, or more generally containers, such as ColumnLayout. All the work is done by the layout and you don't have to do anything, but you can still provide tips to the container.
Using anchors. This is done through the anchors properties of each Item. This works by attaching (anchoring) Items against each other.
ListView {
id: list
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: sideMargin
}
Note: Do not mix these 3 ways on a single Item as you can create conflicts or have unexpected results. In your case you are using method 3 for setting the margin without setting a position and method 1 for setting the width and thus it does not work.

How to make ListView items height dynamic based on inner content?

In following listview's items, length of text can be different (10, or 1000 characters), so I want make each list view item height fit text height.
(Like css height: auto).
Component {
id: sysNotificationsDelegate
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
height: childrenRect.height
color: "#eee"
Text {
text: model.body // <-- TEXT SIZE CAN BE DIFFERENT
wrapMode: Text.WordWrap
}
}
}
ListView {
anchors.fill: parent
spacing: 10
model: ListModel {
id: listModel
}
delegate: sysNotificationsDelegate
}
What is proper and most performant way to achieve this? (taking into account that I will have a lot of such elements and I've read that property bindings in qml have some additional performance cost)
(Qt 5.10)
For a Text to be able to wrap (or elide), it needs to have a width set, otherwise it will expand with no limit.
Text has 3 important width properties:
width: consider it the max width of a line of your text, it should always be set unless you don't want to limit the width of your screen (you can scroll or pan horizontally). Setting wrapMode or elide with no width will have no effect.
implicitWidth: the width the text would need to occupy to fit in a single line. Includes the left and right padding. Does not depend on the explicit width. Not sure when to use it ¯\_(ツ)_/¯ any ideas in the comments?
contentWidth: the width the text is actually occupying taking in account the wrapping and eliding. Does not include the left and right padding. Depends on the explicit width. Use this to query the width of your text, like when you want to draw a box around some text (a chat bubble for example).
The same corresponding properties exist for height too. maximumLineCount can limit the height of a text in addition to an explicit height
That means that in your case you want to do:
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
height: text.contentHeight // contentHeight, height and implicitHeight are all the same here since there's no padding, no maximumLineCount and height isn't explicitely set
color: "#eee"
Text {
id: text
text: model.body // <-- TEXT SIZE CAN BE DIFFERENT
width: parent.width // remember, width = max width for Text
wrapMode: Text.WordWrap
}
}

Culling items that are outside the visible area

From the docs:
The default renderer does not do any CPU-side viewport clipping nor occlusion detection. If something is not supposed to be visible, it should not be shown. Use Item::visible: false for items that should not be drawn. The primary reason for not adding such logic is that it adds additional cost which would also hurt applications that took care in behaving well.
So is there a trick to do it easily, without implementing it myself?
Note that in my case the items that are outside the visible area are there because they are in a ScrollView and they are not scrolled-to.
The reason I want culling is to reduce CPU usage for full-scene redraws.
Here is a trivial example you can extend upon:
Window {
visible: true
width: 640
height: 480
Rectangle {
anchors.centerIn: parent
width: 200
height: 200
color: "yellow"
Flickable {
id: view
anchors.fill: parent
contentWidth: 200
contentHeight: col.height
property real span : contentY + height
Column {
id: col
x: 90
spacing: 2
Repeater {
model: 50
delegate: Rectangle {
width: 10
height: 10
color: inView ? "blue" : "red"
property bool inView: y > view.contentY && y < view.span
}
}
}
}
}
}
Obviously, a full-proof solution would also include the item's height in the calculation. You can also do the check in the x axis if necessary.
To add to dtech's answer, I just learned that there are QML components, such as GridView and ListView, that do culling automatically.

How to correctly change the row height of a TableView?

Window {
id: uninstallWindow
width: 640
height: 480
property variant pluginData;
TableView {
id:_pluginTable
anchors.right: parent.right
anchors.rightMargin: 0
anchors.left: parent.left
anchors.leftMargin: 0
anchors.bottom: parent.bottom
anchors.bottomMargin: 43
anchors.top: parent.top
anchors.topMargin: 0
model: pluginData
itemDelegate: Text {
text: modelData
font.pixelSize: 24
}
TableViewColumn {
}
}
}
It's taken me hours just to get this far, and I feel like this should be a relatively simple operation, so why is it so hard? As you can see I change the font size of the items in the table because they were too small by default. This simply caused them to get clipped by the non-changing row size. I've tried
Setting a rowDelegate object (but this causes loss of all other styling info that is there by default like background, selection color, etc and I don't know how to specify it otherwise)
Setting a custom model object based on QAbstractListModel / QAbstractTableModel (for some reason only known to Qt, the "data" function was never ever called...)
Setting a custom item delegate (it seems that the height is no longer controlled from this object though)
What hoops do I need to jump through to get the rows to change their size?
As the Asker already wrote, custom row height can be achieved using the rowDelegate, but this discards the default style. The default style can be restored using the SystemPalette.
rowDelegate: Rectangle {
height: 30
SystemPalette {
id: myPalette;
colorGroup: SystemPalette.Active
}
color: {
var baseColor = styleData.alternate?myPalette.alternateBase:myPalette.base
return styleData.selected?myPalette.highlight:baseColor
}
}
This restores the default background color of the rows (including alternating the row colors when desired) and the color of the selected rows, which seems to be all that is needed.
The following just worked like a charm for me in Qt 5.10:
rowDelegate: Item { height: 30 }
I do the actual styling (fonts/colors) in itemDelegate (and in headerDelegate) and provide content by TableViewColumns (with and without delegates of their own).
To change the row height you need to use rowDelegate. For example:
rowDelegate: Rectangle{
width: childrenRect.width
height: 40
}
To change tableview height you can use Layout.preferredHeight. For example:
Layout.preferredHeight: 300

Resources