Как добавить параметр поиска в ListView в Qml

#qt #qml

#qt #qml

Вопрос:

У меня есть один Listview и одно TextFileld. Мы вводим значения в TextFiled, элементы, соответствующие этим значениям, отображаются в Listview.

Комментарии:

1. Каков источник данных для вашего ListView, или, другими словами, какое значение имеет свойство ‘model’?

Ответ №1:

Если ваша модель достаточно мала, вы также можете выбрать фильтр DelegateModel, который находится здесь.

Пожалуйста, обратите внимание, вам необходимо скопировать SortFilterModel.файл qml с этого сайта в вашем проекте, прежде чем вы сможете его использовать. Пример использования следующий:

 SortFilterModel {
    id: delegateModel

    filterAcceptsItem: function(item) {
        return item.modelData % 2 == view.remainder;
    }

    model: <your original model>
    delegate: Text {
        id: item

        text: qsTr("This is item #%1").arg(modelData)
    }
}

ListView {
    model: delegateModel
    //delegate doesn't have to be provided here, it's already in the DelegateModel
}
  

В этом примере входными данными было целое число, приводящее к модели с номером от 0 до этого числа, которое затем фильтруется по четным числам. В вашем случае вы, вероятно, можете (трудно сказать без фактического кода) сделать что-то вроде этого:

 filterAcceptsItem: function(item) {
    return item.model.title.includes(textBoxId.text)
}
  

Ответ №2:

Если вы используете QAbstractListModel для вашего ListView, то я рекомендую QSortFilterProxyModel для отображения результатов поиска. Для простых вариантов использования вы указываете ему, какую роль в исходной модели следует сравнивать с заданной вами строкой регулярных выражений / подстановочных знаков. Для более сложной фильтрации вы можете подклассировать ее и переопределить filterAcceptsRow функцию.

Если ваша модель является QML ListModel, то я рекомендую SortFilterProxyModel. Он делает то же самое, но декларативным способом.

Комментарии:

1. Обратите внимание, что вы можете использовать связанную библиотеку с QAbstractListModel . Это просто QSortFilterProxyModel, созданный для создания экземпляров и легко используемый из QML. И ListModel под капотом тоже есть QAbstractListModel.

2. @GrecKo спасибо за добавленную информацию. Я много раз рекомендовал вашу SortFilterProxyModel. Она заслуживает включения в стандартные библиотеки Qt.

Ответ №3:

Вы обдумывали DelegateModel ? Это позволяет вам создавать «представления» на вашем, ListModel чтобы вы могли управлять тем, что вы хотите отобразить с помощью filterOnGroup свойства.

Это довольно трудно понять, но в следующем примере у меня есть ListModel список стран. Когда вы начинаете вводить поиск, TextField он обновляет filterText свойство my DelegateModel , и это приводит к изменению списка стран в зависимости от того, что вы вводите.

 import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

Page {
    ColumnLayout {
        anchors.fill: parent
        TextField {
            id: search
            Layout.fillWidth: true
        }
        Frame {
            Layout.fillWidth: true
            Layout.fillHeight: true
            padding: 0
            ListView {
                anchors.fill: parent
                clip: true
                model: SortFilterDelegateModel {
                    id: sortFilterDelegateModel
                    model: Countries { }
                    filterRole: "country"
                    filterText: search.text
                    //filterRegExp: search.text ? new RegExp(search.text, "i") : null
                    sortRole: "country"
                    sortOrder: ascending.checked ? Qt.AscendingOrder : Qt.DescendingOrder
                    delegate: Frame {
                        width: ListView.view.width - 20
                        property int visibleIndex: DelegateModel.visibleIndex
                        background: Rectangle {
                            color: visibleIndex amp; 1 ? "#eee" : "#ccc"
                            border.color: "#aaa"
                        }
                        RowLayout {
                            width: parent.width
                            Text {
                                text: visibleIndex   1
                                color: "grey"
                            }
                            Text {
                                Layout.fillWidth: true
                                text: country
                            }
                        }
                    }
                }
                ScrollBar.vertical: ScrollBar {
                    width: 20
                    policy: ScrollBar.AlwaysOn
                }
            }
        }
        Label { text: qsTr("%1 countries found").arg(sortFilterDelegateModel.count) }
        CheckBox { id: ascending; text: qsTr("Ascending"); checked: true }
    }
}

// SortFilterDelegateModel.qml
import QtQuick
import QtQml.Models

DelegateModel {
    id: delegateModel
    property string filterRole: ""
    property var filterText: null
    property var filterRegExp: null
    property string sortRole: ""
    property var sortRoles: null
    property int sortOrder: Qt.AscendingOrder
    property var sortOrders: null
    property int sortCaseSensitivity: Qt.CaseInsensitive
    property int updateIndex: 0
    onFilterRoleChanged: Qt.callLater(sort, 0)
    onFilterTextChanged: Qt.callLater(sort, 0)
    onFilterRegExpChanged: Qt.callLater(sort, 0)
    onSortRoleChanged: Qt.callLater(sort, 0)
    onSortRolesChanged: Qt.callLater(sort, 0)
    onSortOrderChanged: Qt.callLater(sort, 0)
    onSortOrdersChanged: Qt.callLater(sort, 0)
    onSortCaseSensitivityChanged: Qt.callLater(sort, 0)
    groups: [
        DelegateModelGroup {
            id: allItems
            name: "all"
            includeByDefault: true
            onCountChanged: {
                Qt.callLater(sort, ...(allItems.count === 0 ? [0] : []));
            }
        },
        DelegateModelGroup {
            id: visibleItems
            name: "visible"
        }
    ]
    filterOnGroup: "visible"

    function compareItems(a, b) {
        if (!sortRole amp;amp; !sortRoles) return 0;
        let _sortRoles = sortRoles ?? [ sortRole ];
        let cmp = 0;
        for (let index = 0; index < _sortRoles.length; index  ) {
            let _sortRole = _sortRoles[index];
            let _sortOrder = sortOrders ? sortOrders[index] : sortOrder;
            let valueA = a[_sortRole];
            let valueB = b[_sortRole];
            if (typeof(valueA) === 'number' amp;amp; typeof(valueB) === 'number') {
                cmp = valueA - valueB;
            } else {
                valueA = valueA.toString();
                valueB = valueB.toString();
                cmp = sortCaseSensitivity === Qt.CaseInsensitive
                      ? valueA.toLowerCase().localeCompare(valueB.toLowerCase())
                      : valueA.localeCompare(valueB);
            }
            if (_sortOrder === Qt.DescendingOrder) cmp = -cmp;
            if (cmp) return cmp;
        }
        return 0;
    }

    function findInsertIndex(item, head, tail) {
        if (head >= count) return head;
        let cmp = compareItems(item, visibleItems.get(head).model);
        if (cmp <= 0) return head;
        cmp = compareItems(item, visibleItems.get(tail).model);
        if (cmp === 0) return tail;
        if (cmp > 0) return tail   1;
        while (head   1 < tail) {
            let mid = (head   tail) >> 1;
            cmp = compareItems(item, visibleItems.get(mid).model);
            if (cmp === 0) return mid;
            if (cmp > 0) head = mid; else tail = mid;
        }
        return tail;
    }

    function sort(index) {
        if (index !== undefined) updateIndex = index;
        if (updateIndex === 0) allItems.setGroups(0, allItems.count, [ "all" ] );
        for (let ts = Date.now(); updateIndex < allItems.count amp;amp; Date.now() < ts   500; updateIndex  ) {
            let visible = true;
            if (filterRole) {
                let item = allItems.get(updateIndex).model;
                let val = item[filterRole];
                if (val amp;amp; filterText) {
                    visible = visible amp;amp; (val.toLowerCase().indexOf(filterText.toLowerCase()) !== -1);
                }
                if (val amp;amp; filterRegExp) {
                    visible = visible amp;amp; val.match(filterRegExp) !== null;
                }
            }
            if (!visible) continue;
            allItems.setGroups(updateIndex, 1, [ "all", "visible" ]);
            if (!sortRole amp;amp; !sortRoles) continue;
            let visibleIndex = visibleItems.count - 1;
            if (visibleIndex <= 0) continue;
            let newIndex = findInsertIndex(visibleItems.get(visibleIndex).model, 0, visibleIndex - 1);
            if (newIndex === visibleIndex) continue;
            visibleItems.move(visibleIndex, newIndex, 1);
        }
        if (updateIndex < allItems.count) Qt.callLater(sort)
    }

    Component.onCompleted: Qt.callLater(sort, 0)
}

// Countries.qml
import QtQuick

ListModel {
    property string countries: `Afghanistan
Albania
Algeria
Andorra
Angola
Antigua amp; Deps
Argentina
Armenia
Australia
Austria
Azerbaijan
Bahamas
Bahrain
Bangladesh
Barbados
Belarus
Belgium
Belize
Benin
Bhutan
Bolivia
Bosnia Herzegovina
Botswana
Brazil
Brunei
Bulgaria
Burkina
Burundi
Cambodia
Cameroon
Canada
Cape Verde
Central African Rep
Chad
Chile
China
Colombia
Comoros
Congo
Congo {Democratic Rep}
Costa Rica
Croatia
Cuba
Cyprus
Czech Republic
Denmark
Djibouti
Dominica
Dominican Republic
East Timor
Ecuador
Egypt
El Salvador
Equatorial Guinea
Eritrea
Estonia
Ethiopia
Fiji
Finland
France
Gabon
Gambia
Georgia
Germany
Ghana
Greece
Grenada
Guatemala
Guinea
Guinea-Bissau
Guyana
Haiti
Honduras
Hungary
Iceland
India
Indonesia
Iran
Iraq
Ireland {Republic}
Israel
Italy
Ivory Coast
Jamaica
Japan
Jordan
Kazakhstan
Kenya
Kiribati
Korea North
Korea South
Kosovo
Kuwait
Kyrgyzstan
Laos
Latvia
Lebanon
Lesotho
Liberia
Libya
Liechtenstein
Lithuania
Luxembourg
Macedonia
Madagascar
Malawi
Malaysia
Maldives
Mali
Malta
Marshall Islands
Mauritania
Mauritius
Mexico
Micronesia
Moldova
Monaco
Mongolia
Montenegro
Morocco
Mozambique
Myanmar, {Burma}
Namibia
Nauru
Nepal
Netherlands
New Zealand
Nicaragua
Niger
Nigeria
Norway
Oman
Pakistan
Palau
Panama
Papua New Guinea
Paraguay
Peru
Philippines
Poland
Portugal
Qatar
Romania
Russian Federation
Rwanda
St Kitts amp; Nevis
St Lucia
Saint Vincent amp; the Grenadines
Samoa
San Marino
Sao Tome amp; Principe
Saudi Arabia
Senegal
Serbia
Seychelles
Sierra Leone
Singapore
Slovakia
Slovenia
Solomon Islands
Somalia
South Africa
South Sudan
Spain
Sri Lanka
Sudan
Suriname
Swaziland
Sweden
Switzerland
Syria
Taiwan
Tajikistan
Tanzania
Thailand
Togo
Tonga
Trinidad amp; Tobago
Tunisia
Turkey
Turkmenistan
Tuvalu
Uganda
Ukraine
United Arab Emirates
United Kingdom
United States
Uruguay
Uzbekistan
Vanuatu
Vatican City
Venezuela
Vietnam
Yemen
Zambia
Zimbabwe`
    function reload() {
        clear();
        for (let country of countries.split(/n/)) {
            append({country});
        }
    }
    Component.onCompleted: reload()
}
  

Вы можете попробовать это онлайн!

Я переработал вышесказанное в FilterDelegateModel повторно используемый компонент. Не стесняйтесь проверить это: