dh1tw
dh1tw

Reputation: 1491

Different delegates for QML ListView

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!

Upvotes: 36

Views: 30167

Answers (7)

Stephen Quan
Stephen Quan

Reputation: 25956

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!

Upvotes: 0

Snibbor
Snibbor

Reputation: 161

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
}

Upvotes: 15

Aur&#233;lien  Lambert
Aur&#233;lien Lambert

Reputation: 730

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.

Upvotes: 27

S.M.Mousavi
S.M.Mousavi

Reputation: 5226

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

Upvotes: 10

Andrii
Andrii

Reputation: 1906

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

Upvotes: 16

skypjack
skypjack

Reputation: 50540

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.

Upvotes: 0

folibis
folibis

Reputation: 12864

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
}

Upvotes: 0

Related Questions