Different delegates for QML ListView
Asked Answered
P

7

36

I would like to know if it's possible to use (several) different delegates for a QML ListView.

Depending on the individual object in the ListView model, I would like to visualize the objects with different delegates.

This piece of code explains what I want to achieve:

main.qml

import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2

ApplicationWindow {
    title: qsTr("Hello World")
    width: 640
    height: 480
    visible: true

    ListModel {
        id: contactsModel
        ListElement {
            name: "Bill Smith"
            position: "Engineer"
        }
        ListElement {
            name: "John Brown"
            position: "Engineer"
        }
        ListElement {
            name: "Sam Wise"
            position: "Manager"
        }
    }

    ListView {
        id: contactsView
        anchors.left: parent.left
        anchors.top: parent.top
        width: parent.width
        height: parent.height
        orientation: Qt.Vertical
        spacing: 10
        model: contactsModel
        delegate: {
            if (position == "Engineer") return Employee;  //<--- depending on condition, load Contact{}
            else if (position == "Manager") return Manager; //<--- depending on condition, load Person{}
        }
    }
}

Employee.qml (One possible Component which I would like to use as a delegate)

import QtQuick 2.4

Rectangle{
    width: 200
    height: 50
    color: ListView.isCurrentItem ? "#003366" : "#585858"
    border.color: "gray"
    border.width: 1

    Text{
        anchors.centerIn: parent
        color: "white"
        text: name
    }
}

Manager.qml (other Component I would like to use as a delegate)

import QtQuick 2.4

Rectangle{
    width: 200
    height: 50
    color: "red"
    border.color: "blue"
    border.width: 1

    Text{
        anchors.centerIn: parent
        color: "white"
        text: name
    }
}

I would appreciate any advice! Thanks!

Prototype answered 13/8, 2015 at 10:43 Comment(2)
In this case you can also consider the creation of a unique delegate which uses position in bindings to change its aspect. Otherwise you can use a Loader but you have to forward some information to the inner item, i.e. the real delegates. Also Folibis solution can work, but I don't think it would in this case since position is itself a role.Viguerie
Why don't you simply define a delegate containing both the Rectangles and set their visible field on a per position bases?Abysmal
I
28

I've had the same problem, the Qt documentation is providing a pretty good answer: http://doc.qt.io/qt-5/qml-qtquick-loader.html#using-a-loader-within-a-view-delegate

The easiest solution is an inline Component with a Loader to set a source file:

ListView {
    id: contactsView
    anchors.left: parent.left
    anchors.top: parent.top
    width: parent.width
    height: parent.height
    orientation: Qt.Vertical
    spacing: 10
    model: contactsModel
    delegate: Component {
        Loader {
            source: switch(position) {
                case "Engineer": return "Employee.qml"
                case "Manager": return "Manager.qml"
            }
        }
    }
}

Any attempt to use Loader.srcComponent will result in missing any variable from the model (including index). The only way for the variables to be present is the children Component to be inside the main Component, but then only one can be present, so it is useless.

Imam answered 18/3, 2016 at 13:24 Comment(0)
K
16

I believe it would be better to implement one base delegate for all kind of position which loads concrete implementation depending on position or any other data properties using Loader

BaseDelegate {
    property var position

    Loader {
        sourceComponent: {
            switch(position) {
                case "Engineer": return engineerDelegate
            }
        }
    }

    Component {
        id: engineerDelegate
        Rectangle {
             Text {  }
        }
    }
}
Knighthood answered 13/8, 2015 at 15:30 Comment(6)
Is there the risk to incur in performance issues if the number of items is high? I know that Loaders are not blocking components, but he will have a lot of them working under the hood. Am I wrong?Abysmal
It depends on complexity of delegates, but not on the Loader.Knighthood
@Viguerie yeah, I love them too, but I've never used them this way, so I was curious if there are performance issues with this solution. ;-)Abysmal
@Abysmal did you see the linked video? It should provide the answer to your question, sort of.Viguerie
I'm on the beach, I will see it once on a WiFi, sorry... :-)Abysmal
shouldn't "sourceDelegate" be "sourcecomponent"?Janes
M
13

The simplest way to do this now is using DelegateChooser. This also allows you to edit the properties of the delegates, which is something that is more difficult to do with Loader!

Example inspired from the docs:

import QtQuick 2.14
import QtQuick.Controls 2.14
import Qt.labs.qmlmodels 1.0

ListView {
    width: 640; height: 480

    ListModel {
        id: contactsModel
    ListElement {
        name: "Bill Smith"
        position: "Engineer"
    }
    ListElement {
        name: "John Brown"
        position: "Engineer"
    }
    ListElement {
        name: "Sam Wise"
        position: "Manager"
    }
   }

    DelegateChooser {
        id: chooser
        role: "position"
        DelegateChoice { roleValue: "Manager"; Manager { ... } }
        DelegateChoice { roleValue: "Employee"; Employee { ... } }
    }

    model: contractsModel
    delegate: chooser
}
Monoploid answered 27/4, 2020 at 21:53 Comment(2)
This should be the right answer now since a default Qt componentLandgrabber
Best answer: no additional .qml files.Googol
J
11

I implemented it as follow:

ListView {
    id: iranCitiesList
    model: sampleModel
    delegate: Loader {
        height: childrenRect.height
        width: parent.width
        sourceComponent: {
            switch(itemType) {
            case "image" :
                return imageDel;
            case "video":
                return videoDel;
            }
        }
    }
    ImageDelegate { id: imageDel }
    VideoDelegate { id: videoDel }
}


ImageDelegate.qml

Component {
    Image { /*...*/ }
}


VideoDelegate.qml

Component {
    Item { /*....*/ }
}

Last note, check width and height of delegates. In my case, I had to set width and height of my delegate in Loader again.
Good luck - Mousavi

Joyance answered 16/2, 2016 at 17:4 Comment(3)
I did some research about performance. It seems that performance is very good and there is no need to any optimization.Joyance
What is the reason to write Component syntax once you declare the object in a different qml file? can't we just use source: in the loader?Javier
Yeah you can. I used it because in this way it is more error-prone and possible to bind its properties easily.Joyance
I
0

Sure, it's possible. ListView.delegate is a kind of pointer to a Component which will draw the items so you can change it.

For example:

Employee { id: delegateEmployee }
Manager { id: delegateManager}
...
ListView {  
    property string position   
    delegate: position == "Engineer" ? delegateEmployee : delegateManager
}
Idiographic answered 13/8, 2015 at 12:7 Comment(1)
This doesn't solve the problem, for you have to define the position before the list is populated, thus the delegate will be of a type and won't change depending on the content of the item.Abysmal
A
0

As far as you have only two types, the following code is as easy to maintain as easy to understand:

delegate: Item {
    Employee { visible = position === "Engineer" }
    Manager { visible = position === "Manager" }
}

In case the number of types will grow, it is not a suitable solution for it easily leads to an hell of if statement.

Abysmal answered 13/8, 2015 at 14:59 Comment(0)
B
0

Because position is either "Manager" or "Engineer" and the delegates are saved in Manager.qml or Engineer.qml we can use a clever expression for Loader.source:

Loader {
   source: position + ".qml"
}

Here's the full source:

import QtQuick
import QtQuick.Controls
Page {
    ListModel {
        id: contactsModel
        ListElement { name: "Bill Smith"; position: "Engineer" }
        ListElement { name: "John Brown"; position: "Engineer" }
        ListElement { name: "Sam Wise"; position: "Manager" }
    }
    ListView {
        id: listView
        anchors.fill: parent
        model: contactsModel
        delegate: Loader {
            width: ListView.view.width
            source: position + ".qml"
        }
    }
}

//Engineer.qml
import QtQuick
import QtQuick.Controls
Rectangle {
    property bool isCurrentItem: listView.currentIndex === index
    height: 50
    color: isCurrentItem ? "#0033cc" : "#585858"
    border.color: "gray"
    border.width: 1
    Text {
        anchors.centerIn: parent
        color: "white"
        text: name
    }
}

//Manager.qml
import QtQuick
import QtQuick.Controls
Rectangle {
    property bool isCurrentItem: listView.currentIndex === index
    height: 50
    color: isCurrentItem ? "#cc3300" : "#661100"
    border.color: "blue"
    border.width: 1
    Text {
        anchors.centerIn: parent
        color: "white"
        text: name
    }
}

You can Try it Online!

Bename answered 27/10, 2022 at 7:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.