Is there a way to hide a particular item on some event in a ListView?
So far I can do it by setting visible to false and height to zero of a delegate.
But If i have spacing in a listView set to 2 for example it appears that this solution is broken.
I think the proper way is to use a proxy model that filters out the elements that should not be displayed.
You can use a QSortFilterProxyModel or implement your own QAbstractProxyModel.
With that it is even possible to animate the removal and addition of the elements.
Or use SortFilterProxyModel if you don't wanna touch C++ and performance is not a problem
A hack around this could be to set the spacing of the ListView to 0 and implement it in the delegate itself. Something like this:
ListView{
id: listView
spacing: 0
delegate: Item{
id: itemDelegate
width: parent.width; height: spacingRect.height + actualDelegate.height
Item {id: actualDelegate;} // your actual delegate
Rectangle{ id: spacingRect; height: 2; width: parent.width; color: "transparent"; anchors.top: actualDelegate.bottom}
}
}
In this way when you hide the delegate the spacing will also be hidden
Related
The following QML code:
Window {
id: window
width: 450
height: 700
visible: true
StackView {
id: mainStack
property Item itemTest: Item {
id: itemTest
ColumnLayout {
id: mainLayout
width: mainStack.width
ScrollView {
id: scrollview
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ColumnLayout{
id: colLayout
anchors.fill: scrollview
}
}
}
}
initialItem: itemTest
anchors.fill: parent
}
}
outputs "QML ColumnLayout: Cannot anchor to an item that isn't a parent or sibling."
Replacing "anchors.fill: scrollview" by "anchors.fill: parent" in the above code makes this message disappear but then the ColumnLayout does not seem to fill the ScrollView.
Given this behaviour, I come to the conclusion that the ScrollView in this QML file isn't actually the parent of "colLayout", which comes against my first intuition about the way parenting works in QML.Can someone explain to me what is meant exactly by the keyword "parent" in QML ? Many thanks in advance.
The issue is that Controls use the concept of a contentItem. While the ScrollView itself is a Control which itself is in turn an Item, the children are parented to a different Item called contentItem.
More info here:
https://doc.qt.io/qt-5/qml-qtquick-controls2-control.html#contentItem-prop
See also the comment there:
Note: Most controls use the implicit size of the content item to calculate the implicit size of the control itself. If you replace the content item with a custom one, you should also consider providing a sensible implicit size for it (unless it is an item like Text which has its own implicit size).
You don't want to grow your ColumnLayout to match the contentItem, the contentItem will automatically resize to fit the implicit size of the ColumnLayout.
If the effect you are trying to get is to match the size of the ColumnLayout to that of the ScrollView, then use something like:
ColumnLayout{
id: colLayout
implicitWidth: scrollview.width
implicitHeight: scrollview.height
height: implicitHeight
width: implicitWidth
}
But in that case, why bother using a ScrollView? Normally you would allow the ColumnLayout to manage its implicit size normally based on it's children. When the contentItem ends up overflowing the ScrollView, then it starts to automatically scroll.
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
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
}
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.
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