35

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!

BaCaRoZzo
  • 7,502
  • 6
  • 51
  • 82
dh1tw
  • 1,411
  • 2
  • 23
  • 29
  • 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. – BaCaRoZzo Aug 13 '15 at 12:24
  • Why don't you simply define a `delegate` containing both the `Rectangle`s and set their `visible` field on a per `position` bases? – skypjack Aug 13 '15 at 14:53

7 Answers7

27

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.

Mohammad Kanan
  • 4,452
  • 10
  • 23
  • 47
Aurélien Lambert
  • 712
  • 1
  • 8
  • 12
15

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 {  }
        }
    }
}
Andrii
  • 1,788
  • 11
  • 20
  • Is there the risk to incur in performance issues if the number of items is high? I know that `Loader`s are not blocking components, but he will have a lot of them working under the hood. Am I wrong? – skypjack Aug 13 '15 at 17:47
  • It depends on complexity of delegates, but not on the Loader. – Andrii Aug 14 '15 at 06:46
  • @BaCaRoZzo 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. ;-) – skypjack Aug 14 '15 at 08:50
  • @skypjack did you see the linked video? It should provide the answer to your question, sort of. – BaCaRoZzo Aug 14 '15 at 09:54
  • 1
    I'm on the beach, I will see it once on a WiFi, sorry... :-) – skypjack Aug 14 '15 at 09:56
  • shouldn't "sourceDelegate" be "sourcecomponent"? – refro Sep 08 '15 at 12:28
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

S.M.Mousavi
  • 5,013
  • 7
  • 44
  • 59
  • 1
    I did some research about performance. It seems that performance is very good and there is no need to any optimization. – S.M.Mousavi Feb 16 '16 at 17:30
  • 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? – Luis Ayuso Dec 07 '21 at 16:00
  • Yeah you can. I used it because in this way it is more error-prone and possible to bind its properties easily. – S.M.Mousavi Dec 08 '21 at 05:34
11

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
}
Snibbor
  • 121
  • 2
  • 5
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
}
folibis
  • 12,048
  • 6
  • 54
  • 97
  • 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. – skypjack Aug 13 '15 at 14:50
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.

skypjack
  • 49,335
  • 19
  • 95
  • 187
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!

Stephen Quan
  • 21,481
  • 4
  • 88
  • 75