QML Listview space items to fill width - qt

I have a ListView (horizontal orientation) in my qml containing some fixed-size elements. I want items to be spaced out to fill the entiew width of ListView element. So if there are less elements I want them to be spaced out more. Basically what I need is exactly like Layout.fillWidth = true property of RowLayout but for ListView.
I can count how many items I have, then subtract total items width from ListView width, divide by items count and assign the spacing but it seems too silly to do.
Is there a way to do this automatically like in RowLayout?
Or maybe I need to use something different from ListView for this? Something like RowLayout but that I can assign my list data model to?

You can accomplish what you want with a ListView, you just need to adjust the spacing dynamically based on how many delegates you have. This example will break down if your delegates are differently-sized (as this is based only on the width of the first delegate), or if the delegates cumulatively exceed the width of the ListView.
ListView {
width: 500
orientation: Qt.Horizontal
model: 6
spacing: {
if (count > 0) {
return (width - (itemAtIndex(0).width * count))/(count - 1)
} else {
return 0
}
}
delegate: Rectangle {
implicitHeight: 50
implicitWidth: 50
color: "red"
border.width: 1
}
}
ListView may not be the most appropriate container for this task. I say this because it has a built in ScrollView and other behaviors that it sounds like you don't need. If all you need is a simple row of a few identically-sized delegates, I agree with scopchanov and believe that a Repeater inside a RowLayout would be the best option. Here is a simple example:
RowLayout {
width: 500
Repeater {
model: 6
delegate: Rectangle {
implicitHeight: 50
implicitWidth: 50
color: "tomato"
border.width: 1
Layout.alignment: Qt.AlignHCenter // must be set to align the rectangles within their empty space
}
}
}
You may notice that this introduces gaps to the left and right, if these gaps are unacceptable, you may need to set the spacing on the RowLayout in the same manner as the ListView example instead.

Related

Proper way of defining and composing custom elements in QML

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"
}
}

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: flat non-scrollable list view

I want to have two list views followed by one another in a big ScrollView, say because they have slightly different delegates. So a layout is like this:
Unfortunately ListView type is also a flickable, so it doesn't present all its content in a flat list suitable for having inside a scroll view.
So how do I do this with Qt Quick views?
I've tried a trick: I can resize list views like this:
ListView {
id: list1
height: contentHeight + spacing * count
model: superModel
delegate: delegate1
}
Unfortunately, aside from being a dirty hack and leaving an unneccesary flickable grabbing my clicks, it doesn't really work: content just doesn't fit as there are still top and bottom margins I don't know the value of.
You should use a ColumnLayout with two Repeater's in a ScrollView (or Flickable if you like)
ScrollView {
contentWidth: width //maybe you don't need this
ColumnLayout {
width: parent.width //maybe you don't need this
Repeater {
model: superModel1
delegate: delegate1
}
Repeater {
model: superModel2
delegate: delegate2
}
}
}
Since you didn't show the delegate, you might need minor tweaking of implicitHeight and/or implicitWidth.

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 position items in Gridlayout

I am adding Image objects dynamically to a GridLayout in a QML file in qt. I want to position the image items so that each new image item comes to the right side of the previous one and if there is no place in that row, it is placed directly to the next row. It is exactly the way how I typed this question, i.e. letters are my image items and text box is the layout.
However the output is following:
The layout leaves space between items as seen in the screenshot. I've read through the properties of gridlayout and tried many combinations out but didn't come up with the right combination. These are the properties I set so far:
GridLayout {
id: myLayout
anchors.fill: parent
columnSpacing: preferredColumnSpacing
rowSpacing: preferredRowSpacing
Component {
id: myImageComponent
Image {
visible: true
Layout.alignment: (Qt.AlignLeft | Qt.AlignTop)
Layout.preferredWidth: preferredImageWidth
Layout.preferredHeight: preferredImageHeight
}
}
}
Which modification gives me the expected behaviour?
Edit: in the screenshot above, the layout has 9 columns, 4 rows but 17 image items.

Resources