Rajeev Sahu
Rajeev Sahu

Reputation: 1732

Sorting listbox items as per the input provided by User in Qt/QML

I am trying to sort a list of items in the QML listview, as per the input provided by user. I have written the logic for sorting but unable to set the sorted model. It seems the sorted model is not assigned tp the actual model used for the original ListView. Please have a look to the code and let me know if I am doing something wrong.

//main.qml

import QtQuick 1.1
Rectangle {
     id: page
     width: 500; height: 400
     color: "#edecec"
     ModifiedForSorting {
         id: search; focus: true
     }
 }

//ModifiedForSorting.qml

import QtQuick 1.1

FocusScope {
    id: focusScope
    width: 250; height: 28
    Text {
        id: typeSomething
        anchors.fill: parent; anchors.leftMargin: 8
        verticalAlignment: Text.AlignVCenter
        text: "Type here..."
        color: "gray"
        font.italic: true
    }

    MouseArea {
        anchors.fill: parent
        onClicked: { focusScope.focus = true; textInput.openSoftwareInputPanel(); }
    }

    TextInput {
        id: textInput
        anchors { left: parent.left; leftMargin: 8;rightMargin: 8; verticalCenter: parent.verticalCenter }
        focus: true
        selectByMouse: true

        onTextChanged: {
            //update list as per input from user
            container.getSortedItems(textInput.text);
            color = "red"
        }
    }
    states: State {
        name: "hasText"; when: textInput.text != ''
        PropertyChanges { target: typeSomething; opacity: 0 }
    }

    transitions: [
        Transition {
            from: ""; to: "hasText"
            NumberAnimation { exclude: typeSomething; properties: "opacity" }
        },
        Transition {
            from: "hasText"; to: ""
            NumberAnimation { properties: "opacity" }
        }
    ]

    Rectangle {
        id: container
        width: 500; height: 400
        color: "#343434"
        anchors.top: textInput.bottom
        ListModel {
            id: namesModel

            ListElement {
                title: "Mumbai"
            }
            ListElement {
                title: "Pune"
            }
            ListElement {
                title: "Bangalore"
            }
            ListElement {
                title: "Kolkata"
            }
            ListElement {
                title: "Hyderabad"
            }
            ListElement {
                title: "Nagpur"
            }
            ListElement {
                title: "Thane"
            }
        }

        // The delegate for each item in the model:
        Component {
            id: listDelegate

            Item {
                id: delegateItem
                width: listView.width; height: 55
                clip: true

                Row {
                    anchors.verticalCenter: parent.verticalCenter
                    spacing: 10
                    Column {
                        anchors.verticalCenter: parent.verticalCenter

                        Text {
                            text: title
                            font.pixelSize: 15
                            color: "white"
                        }
                    }
                }
            }
        }

        function showAll() {
            var filteredItems = "";
            for (var i = 0; i < namesModel.count; i++) {

                filteredItems = filteredItems + namesModel.get(i).title;
            }
            listView.model = filteredItems;
            //namesModel = filteredItems;
        }

        function getSortedItems(searchTerm) {
            var filteredItems = "";
            if (searchTerm === "") {
                showAll();
                return;
            }

            for (var i = 0; i < namesModel.count; i++) {
                if (namesModel.get(i).title.indexOf(searchTerm) === 0) {
                    filteredItems = filteredItems + namesModel.get(i).title;
                }
            }
            listView.model = filteredItems;
            //namesModel = filteredItems;
        }

        // The ListView:
        ListView {
            id: listView
            anchors.fill: parent; anchors.margins: 20
            model: namesModel
            delegate: listDelegate
        }
    }
}

As shown above I guess the sorted model is not reassigned to the ListView. I am not sure. Please help me out. Thanks.

Upvotes: 4

Views: 1625

Answers (2)

Stephen Quan
Stephen Quan

Reputation: 25981

You can now do this with far less code using Array filter(). Below is an example written using Qt6.x syntax:

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
     background: Rectangle { color: "#edecec" }
     property var cities: [
         { title: "Mumbai" },
         { title: "Pune" },
         { title: "Bangalore" },
         { title: "Kolkata" },
         { title: "Hyderabad" },
         { title: "Nagpur" },
         { title: "Thane" }
     ]
     ColumnLayout {
         anchors.fill: parent
         TextField {
             id: textField
             Layout.fillWidth: true
             placeholderText: qsTr("Type something here")
         }
         ListView {
             Layout.fillWidth: true
             Layout.fillHeight: true
             model: cities.filter( city => !textField.text || city.title.toLowerCase().indexOf(textField.text.toLowerCase()) !== -1 )
             delegate: ItemDelegate { text: modelData.title }
         }
     }
}

You can Try it Online!

Alternatively, you can consider using a ListModel combined with a DelegateGroup. The advantage of this is you do not need to repopulate a ListModel everytime the filter changes. Instead, you update the visibility of the item in the DelegateGroup:

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQml.Models
Page {
    background: Rectangle { color: "#edecec" }
    ListModel {
        id: cities
        ListElement { title: "Mumbai" }
        ListElement { title: "Pune" }
        ListElement { title: "Bangalore" }
        ListElement { title: "Kolkata" }
        ListElement { title: "Hyderabad" }
        ListElement { title: "Nagpur" }
        ListElement { title: "Thane" }
    }
    ColumnLayout {
        anchors.fill: parent
        TextField {
            id: textField
            Layout.fillWidth: true
            placeholderText: qsTr("Type something here")
            onTextChanged: delegateModel.refresh()
        }
        ListView {
            Layout.fillWidth: true
            Layout.fillHeight: true
            model: DelegateModel {
                id: delegateModel
                model: cities
                groups: [
                    DelegateModelGroup {
                        id: allItems
                        name: "all"
                        includeByDefault: true
                    },
                    DelegateModelGroup {
                        id: visibleItems
                        name: "visible"
                    }
                ]
                filterOnGroup: "visible"
                delegate: ItemDelegate { text: title }
                function refresh() {
                    allItems.setGroups(0, allItems.count, ["all"]);
                    for (let i = 0; i < allItems.count; i++) {
                        let v = true;
                        let item = allItems.get(i);
                        if (textField.text) {
                            v = item.model.title.toLowerCase().indexOf(textField.text.toLowerCase()) !== -1;
                        }
                        if (v) allItems.setGroups(i, ["all", "visible"] );
                    }
                }
            }
        }
    }
    Component.onCompleted: delegateModel.refresh()
}

You can Try it Online!

Upvotes: 0

TheHuge_
TheHuge_

Reputation: 1039

Taking a look at your code I guess you mean filtering items, and not sorting.

In your functions you try to assign a string value filteredItems to the listView.model property. You're probably getting an explicit error for that in your application log.

To solve this error you can use an auxiliary ListModel and populate it with just the items that fits your filter. Try to replace all your code below the listDelegate Component with this:

    ListModel{ id: filteredModel }

    function getSortedItems(searchTerm) {

        // Clear the aux model
        filteredModel.clear();

        // Add fitting items to the aux model
        for (var i = 0; i < namesModel.count; i++) {
            if (searchTerm === "" || namesModel.get(i).title.indexOf(searchTerm) === 0) {
                filteredModel.append(namesModel.get(i));
            }
        }

    }

    // The ListView:
    ListView {
        id: listView
        anchors.fill: parent; anchors.margins: 20
        model: filteredModel
        delegate: listDelegate
    }

Upvotes: 3

Related Questions