How to fill a QTableview using QML Tool Button - qml

I need to press my create button under red circle and by pressing this button i am trying to fill first row of my table view. I am a new user in QT Quick please help me. I have wasted lot of time but have no way to do it.
I am providing UI and code as well please have a look.
Thank you !
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.0
import QtQuick.Dialogs 1.0
import "content"
import "images"
ApplicationWindow {
visible: true
title: "Mask Editor: Subsystem"
width: 720
height: 420
minimumHeight: 400
minimumWidth: 720
statusBar: StatusBar {
id: minstatusbar
RowLayout {
id:row1
spacing: 2
width: parent.width
Button {
text:"Unmask"
}
Item { Layout.fillWidth: true }
Button {
text:"OK"
}
Button {
text:"Cancel"
}
Button {
text:"Help"
}
Button {
text:"Apply"
}
}
}
/* ListModel {
id: largeModel
Component.onCompleted: {
for (var i=0 ; i< 10 ; ++i)
largeModel.append({"index":i , "prompt": "pmpt", "variable":i+10, "type" : "Female","tabname":"something"})
}
}*/
Action {
id: openAction
text: "&Open"
shortcut: "Ctrl+O"
iconSource: "images/document-open.png"
onTriggered: fileDialog.open()
tooltip: "Open an Image"
}
TabView {
id:frame
enabled: enabledCheck.checked
tabPosition: controlPage.item ? controlPage.item.tabPosition : Qt.TopEdge
anchors.fill: parent
anchors.margins: Qt.platform.os === "osx" ? 12 : 2
Tab {
id: controlPage
title: "Parameters"
Item {
id:root
anchors.fill: parent
anchors.margins: 8
ColumnLayout {
id: mainLayout
anchors.fill: parent
spacing: 4
GroupBox {
id: gridBox
title: "Grid layout"
Layout.fillWidth: true
ListModel {
id: nestedModel
ListElement{index:"1";prompt:"pmt";variable:10; type: "to be defined";tabname:"something"}
}
GridLayout {
id: gridLayout
anchors.fill: parent
rows: 3
flow: GridLayout.TopToBottom
ToolButton { iconSource: "images/window-new.png"
action:filldata
Accessible.name: "New window"
onClicked: {
nestedmodel.append({"index":i , "prompt": "pmpt", "variable":i+10, "type" : "Female","tabname":"something"})
}
tooltip: "Toggle visibility of the second window" }
ToolButton { iconSource: "images/view-refresh.png"
onClicked: window1.visible = !window1.visible
Accessible.name: "New window"
tooltip: "Toggle visibility of the second window" }
ToolButton { Accessible.name: "Save as"
iconSource: "images/document-save-as#2x.png"
tooltip: "(Pretend to) Save as..." }
TableView{
model: modelcontl
Layout.rowSpan: 3
Layout.fillHeight: true
Layout.fillWidth: true
//anchors.fill: parent
TableViewColumn {
role: "index"
title: "#"
width: 36
resizable: false
movable: false
}
TableViewColumn {
role: "prompt"
title: "Prompt"
width: 120
}
TableViewColumn {
role: "variable"
title: "Variable"
width: 120
}
TableViewColumn {
role: "type"
title: "Type"
width: 200
visible: true
}
TableViewColumn {
role: "tabname"
title: "Tab Name"
Layout.fillWidth: true
visible: true
}
}
}
}
Rectangle{
Layout.fillHeight: true
Layout.fillWidth: true
GridLayout {
//id: gridLayout
rows: 1
flow: GridLayout.TopToBottom
anchors.fill: parent
GridLayout{
id: grid1
columns: 2
anchors.fill: parent
GroupBox {
//id: gridBox
title: "Grid layout"
Layout.fillHeight: true
Layout.fillWidth: true
}
GroupBox {
//id: gridBox
title: "Grid layout"
Layout.fillHeight: true
Layout.fillWidth: true
}
}
}
}
}
}
}
Tab {
title: "Documentation"
Controls { }
}
Tab {
title: "dialog"
MessageDialogs { }
}
}
}

If you want to add a QML control into a cell you just have to define custom delegate. Since you need different delegates for different columns it can be done in a bit "tricky" way with Loader. See my example below:
Component {
id: comboDelegate
ComboBox {
model: ListModel {
ListElement { text: "Banana" }
ListElement { text: "Apple" }
ListElement { text: "Coconut" }
}
}
}
Component {
id: textDelegate
Text {
text: itemText
}
}
TableView {
Layout.fillWidth: true
Layout.fillHeight: true
TableViewColumn{ role: "col1" ; title: "Column1" }
TableViewColumn{ role: "col2" ; title: "Column2" }
TableViewColumn{ role: "col3" ; title: "Column3" }
model: ListModel {
id: myModel
}
itemDelegate: Item {
height: 25
Loader {
property string itemText: styleData.value
sourceComponent: styleData.column === 0 ? comboDelegate : textDelegate
}
}
}

Try this:
TableView {
TableViewColumn{ role: "col1" ; title: "Column1" }
TableViewColumn{ role: "col2" ; title: "Column2" }
TableViewColumn{ role: "col3" ; title: "Column3" }
model: ListModel {
id: myModel
}
}
Button {
text: "Add row"
onClicked: {
myModel.append({col1: "some value",
col2: "Another value",
col3 : "One more value" });
}
}

Related

TableView is invisible

I'm trying to set something up, where there's a a HorizontalHeaderView that spans the entire width of the window, and a TableView that also spans the entire width of the window, along with stretching down to the bottom of the window. I also wanted a TextField and Button side by side, to sit on top of all this.
So far, I've gotten the TextField and Button to sit at the top, and part of the HorizontalHeaderView to be visible, but the TableView is completely invisible. I've tried fiddling around with height, Layout.preferredHeight, etc. but nothing makes it stretch to the bottom and fill the width of the window.
Here's what I've got so far:
import QtQuick 6.0
import QtQuick.Controls.Basic 6.0
import QtQuick.Layouts 6.0
ApplicationWindow {
title: "Portmod"
width: 640
height: 480
visible: true
header: TabBar {
id: mainTabBar
width: parent.width
TabButton {
text: "Manage"
width: implicitWidth
}
TabButton {
text: "Search"
width: implicitWidth
}
}
StackLayout {
id: mainStackLayout
currentIndex: mainTabBar.currentIndex
ColumnLayout {
id: manageTab
RowLayout {
TextField {
placeholderText: "Filter packages..."
}
Button {
text: "Filter"
}
}
HorizontalHeaderView {
id: installedPackagesHeader
syncView: installedPackagesTable
}
TableView {
id: installedPackagesTable
columnSpacing: 1
rowSpacing: 1
clip: true
model: installedPackagesModel
Keys.onUpPressed: installedPackagesTableVerticalScrollBar.decrease()
Keys.onDownPressed: installedPackagesTableVerticalScrollBar.increase()
Keys.onLeftPressed: installedPackagesTableHorizontalScrollBar.decrease()
Keys.onRightPressed: installedPackagesTableHorizontalScrollBar.increase()
ScrollBar.vertical: ScrollBar {
id: installedPackagesTableVerticalScrollBar
parent: installedPackagesTable
}
ScrollBar.horizontal: ScrollBar {
id: installedPackagesTableHorizontalScrollBar
parent: installedPackagesTable
}
delegate: Rectangle {
implicitWidth: 300
implicitHeight: 25
Text {
text: display
anchors.left: parent.left
}
}
}
}
Rectangle {
id: searchTab
width: parent.parent.width
height: parent.parent.height
}
}
}
Here's my custom QSortFilterProxyModel and QAbstractItemModel:
class InstalledPackagesProxyModel(QSortFilterProxyModel):
def __init__(self, data: list[list]) -> None:
super().__init__()
self.realModel = InstalledPackagesModel(data)
self.setSourceModel(self.realModel)
def get_atom(self, index: QModelIndex) -> Atom:
"""
Returns a reference to the source index instead of proxy index, to handle the sorted view.
"""
mapped_index = self.mapToSource(index)
data = [tuple(pkg_data_list) for pkg_data_list in self.realModel._data]
return Atom(f"{data[mapped_index.row()][2]}/{data[mapped_index.row()][0]}")
class InstalledPackagesModel(QAbstractTableModel):
def __init__(self, data: list[list]):
super(InstalledPackagesModel, self).__init__()
self._data = data
self.header_labels = ["Name", "Use Flags", "Category", "Version"]
def data(self, index, role):
if role == Qt.DisplayRole: # type: ignore
value = self._data[index.row()][index.column()]
if isinstance(value, Atom):
return str(value)
return value
def rowCount(self, index):
return len(self._data)
def columnCount(self, index):
try:
return len(self._data[0])
# If there are no installed mods in the prefix
except IndexError:
return 1
def headerData(self, section, orientation, role=Qt.DisplayRole): # type: ignore
if role == Qt.DisplayRole and orientation == Qt.Horizontal: # type: ignore
return self.header_labels[section]
return QAbstractTableModel.headerData(self, section, orientation, role)
This is what it looks like:
You need to use anchors.fill: parent on the StackView so it fills out its parent and has a proper size. Furthermore the HorizontalHeaderView needs to get a implicitHeight and the TableView should set Layout.fillWidth and Layout.fillHeight to true.
You need to use Layout.fillWidth and Layout.fillHeight to span to the full width and height of layouts.
import QtQuick 6.0
import QtQuick.Controls.Basic 6.0
import QtQuick.Layouts 6.0
import Qt.labs.qmlmodels 1.0
ApplicationWindow {
title: "Portmod"
width: 640
height: 480
visible: true
header: TabBar {
id: mainTabBar
width: parent.width
TabButton { text: "Manage" }
TabButton { text: "Search" }
}
StackLayout {
id: mainStackLayout
currentIndex: mainTabBar.currentIndex
anchors.fill: parent
ColumnLayout {
id: manageTab
Layout.fillWidth: true
RowLayout {
Layout.fillWidth: true
TextField {
Layout.fillWidth: true
placeholderText: "Filter packages..."
}
Button {
Layout.fillWidth: true
text: "Filter"
}
}
HorizontalHeaderView {
id: installedPackagesHeader
syncView: tableView
implicitHeight: 30
Layout.fillWidth: true
}
TableView {
id: tableView
Layout.fillWidth: true
Layout.fillHeight: true
columnSpacing: 1
rowSpacing: 1
clip: true
columnWidthProvider: function (column) {
return tableView.model ? tableView.width / tableView.model.columnCount : 0
}
rowHeightProvider: function (row) { return 30 }
onWidthChanged: tableView.forceLayout()
model: TableModel {
TableModelColumn { display: "name" }
TableModelColumn { display: "color" }
rows: [
{ "name": "cat", "color": "black" },
{ "name": "dog", "color": "brown" },
{ "name": "bird", "color": "white" }
]
}
delegate: Rectangle {
Text {
text: display
anchors.left: parent.left
}
}
}
}
Rectangle {
id: searchTab
Layout.fillWidth: true
Layout.fillHeight: true
color: "red"
}
}
}
I figured out that I needed to set the columnWidthProvider to this:
return Window.width / installedPackagesTableView.model.columnCount()
This is my full code for the TableView:
TableView {
id: installedPackagesTableView
property int selectedRow: 0
focus: true
columnSpacing: 1
rowSpacing: 1
clip: true
model: installedPackagesModel
columnWidthProvider: function () {
return Window.width / installedPackagesTableView.model.columnCount()
}
rowHeightProvider: function (row) {
return 30;
}
onWidthChanged: installedPackagesTableView.forceLayout()
Layout.fillWidth: true
Layout.fillHeight: true
Keys.onUpPressed: function () {
if (selectedRow != 0) {
selectedRow -= 1;
}
// Move scrollbar up if the selectedRow is going to be invisible
if (selectedRow == topRow) {
installedPackagesTableVerticalScrollBar.decrease()
}
}
Keys.onDownPressed: function () {
if (selectedRow != installedPackagesModel.rowCount() - 1) {
selectedRow += 1;
// Move scrollbar down if the selectedRow is going to be invisible
if (selectedRow == bottomRow) {
installedPackagesTableVerticalScrollBar.increase()
}
}
}
Keys.onReturnPressed: installedPackagesModel.getAtom(selectedRow)
ScrollBar.vertical: ScrollBar {
id: installedPackagesTableVerticalScrollBar
}
ScrollBar.horizontal: ScrollBar {
id: installedPackagesTableHorizontalScrollBar
}
delegate: ItemDelegate {
highlighted: row == installedPackagesTableView.selectedRow
onClicked: installedPackagesTableView.selectedRow = row
onDoubleClicked: installedPackagesModel.getAtom(row)
text: model.display
}
}

GridLayout Arrangement

Following is my main.qml:
Window {
id: window
visible: true
width: 800
height: 480
title: qsTr("Hello World")
ListModel {
id: _listModel
ListElement {
textData: "E1"
isEnabled: false
}
ListElement {
textData: "E2"
isEnabled: false
}
ListElement {
textData: "E3"
isEnabled: false
}
ListElement {
textData: "E4"
isEnabled: false
}
ListElement {
textData: "E5"
isEnabled: false
}
ListElement {
textData: "E6"
isEnabled: false
}
}
ListView {
id: _listview
model: _listModel
width: 100
height: parent.height
anchors.right: parent.right
delegate: Item {
width: parent.width
height: 50
anchors.right: parent.right
Component.onCompleted:
{
if (isEnabled)
visibleRecs++;
}
RowLayout {
Text {
id: itemText
text: qsTr(textData)
}
CheckBox {
height: 30
width: height
checked: isEnabled
onCheckedChanged: {
isEnabled = checked
}
}
}
}
}
ScrollView {
id: _scrollView
width: parent.width / 2
height: parent.height
clip: true
GridLayout {
id: _gridLayout
anchors.fill: parent
anchors.horizontalCenter: parent.horizontalCenter
columnSpacing: 10
rowSpacing: 10
columns: 2
Repeater {
model: _listModel
Loader {
id: _loader
sourceComponent: isEnabled ? _recComponent : null
onLoaded: {
item.text = textData
}
}
}
}
}
Component {
id: _recComponent
Rectangle {
property alias text : _txt.text
id: _rec
width: 100
height: 50
radius: 5
color: "yellow"
Text {
id: _txt
anchors.centerIn: parent
}
}
}
}
The above code creates following (when all check boxes are ticked):
When all checkboxes are ticked:
When checkbox E3 is unchecked:
I want the items to be rearranged in the gridlayout if any of the item goes invisible.
E.g. in the above case when E3 is unchecked, I want my view to be something like this:
Please let me know, if it is possible to achieve. Thanks in advance.
The problem is that you still instantiate the Loader, you just set the sourceComponent to null. You have to make the item invisible to not use space in the GridLayout (or put width/height to 0)
ScrollView {
id: _scrollView
width: parent.width / 2
height: parent.height
clip: true
GridLayout {
id: _gridLayout
anchors.fill: parent
anchors.horizontalCenter: parent.horizontalCenter
columnSpacing: 10
rowSpacing: 10
columns: 2
Repeater {
model: _listModel
Loader {
id: _loader
visible: isEnabled
sourceComponent: isEnabled ? _recComponent : null
onLoaded: {
item.text = textData
}
}
}
}
}

Changing model doesn't affect ComboBox

Suppose, we have the following code:
import QtQuick 2.4
import QtQuick.Window 2.0
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
Window {
id: win
width: 800
height: 600
ListModel {
id: listModel
ListElement { name: "element1" }
ListElement { name: "element2" }
ListElement { name: "element3" }
}
ColumnLayout {
anchors.centerIn: parent
width: 200
height: 200
ComboBox {
model: listModel
currentIndex: 1
Layout.fillWidth: true
}
ListView {
model: listModel
delegate: Text {
text: name
}
Layout.fillHeight: true
Layout.fillWidth: true
}
Button {
text: "Change model"
onClicked: {
listModel.get(1).name = "changed text";
//listModel.setProperty(1,"name","changed text"); this line not works too
}
}
}
}
So clicking the button have to change model's element with index 1. But changing the model affects only ListView. The ComboBox remains unchanged.
Why that happens? Is it bug or feature? Is there a way to update ComboBox after changing its model?
I had a similar problem, I used a workaround. In onClicked function of button, create copy of model, change it as you want and then assign it again to ListViews model:
ListView {
id: listView
...
}
Button {
onClicked: {
var copy = listView.model;
copy.get(1).name = "changed text";
listView.model = copy; }
}
}

QML StackView: Item Destroyed on pop

Consider the following example:
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
Window {
visible: true
width: 320
height: 320
StackView {
id: stackView
anchors.fill: parent
Component.onCompleted: {
stackView.push( { item: comp1, destroyOnPop:false } )
}
}
Component {
id: comp1
Rectangle {
color: "lightgray"
Text {
id: mtext
anchors.centerIn: parent
text: "First Page"
}
}
}
Component {
id: comp2
Rectangle {
color: "lightgreen"
Text {
id: mtext
anchors.centerIn: parent
text: "Second Page"
}
MouseArea {
id: mouseArea
anchors.fill: parent
onClicked: mtext.text += " Clicked"
}
}
}
Row {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
Button {
id: next
text: "Next"
onClicked: {
stackView.push( { item: comp2, destroyOnPop:false } )
enabled = false
prev.enabled = true
}
}
Button {
id: prev
text: "Prev"
enabled: false
onClicked: {
stackView.pop()
enabled = false
next.enabled = true
}
}
}
}
Here I'm pushing 2 Components into the StackView on the each button clicks, i.e. clicking the "Next" button loads the second page. When I click on it, it displays an updated text("Second Page Clicked"). Then clicking "Prev" loads the first page as expected.
Now if I click "Next" again, it should load the second page with that updated text ("Second Page Clicked"), but it doesn't. It shows the initial text ("Second Page").
So the question is whether the Item is destroyed on pop? If not then shouldn't the second page display the updated text ("Second Page Clicked")?
I have even set the flag destroyOnPop to false. Have I misunderstood the concept of StackView? Any possible way to solve this?
I want to load any page from any point in StackView and that the state of Item be as it is where I left it. Just like QStackWidget, where we use setCurrentIndex().
Documentation is referring to Item object whereas you are using Components. The documentation is pretty clear about the fact that a component is not an Item since it is not derived from Item. Indeed a Component act like a Class which is instanced to a specific object.
Each time you call
stackView.push( { item: comp2, destroyOnPop:false } )
a new "instance" of the component comp2 is generated and such new instance is used instead of the previous one. I strongly suggest to read this article to better understand the correlation between Component objects and generated instances.
Instances can be generated via createObject function. Hence, you can create an array of Items and use such Items instead of the Components. Final code looks like this:
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
Window {
visible: true
width: 320
height: 320
StackView {
id: stackView
anchors.fill: parent
Component.onCompleted: {
push( { item: items[0], destroyOnPop:false })
}
property variant items: [comp1.createObject(), comp2.createObject()] // objects from the components
}
Component {
id: comp1
Rectangle {
color: "lightgray"
Text {
id: mtext
anchors.centerIn: parent
text: "First Page"
}
}
}
Component {
id: comp2
Rectangle {
color: "lightgreen"
Text {
id: mtext
anchors.centerIn: parent
text: "Second Page"
}
MouseArea {
id: mouseArea
anchors.fill: parent
onClicked: mtext.text += " Clicked"
}
}
}
Row {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
Button {
id: next
text: "Next"
onClicked: {
stackView.push({ item: stackView.items[1], destroyOnPop:false })
enabled = false
prev.enabled = true
}
}
Button {
id: prev
text: "Prev"
enabled: false
onClicked: {
stackView.pop()
enabled = false
next.enabled = true
}
}
}
}
The same correct result could be achieved by defining directly the Rectagle objects in the source. In this case we have two Items which are reparented to the Window thanks the destroyOnPop set to false.
Rectangle {
id: comp1
color: "lightgray"
visible: false
Text {
id: mtext
anchors.centerIn: parent
text: "First Page"
}
}
Rectangle {
id: comp2
color: "lightgreen"
visible: false
Text {
id: mtext2
anchors.centerIn: parent
text: "Second Page"
}
MouseArea {
id: mouseArea
anchors.fill: parent
onClicked: mtext2.text += " Clicked"
}
}

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.
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
}
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!