Reputation: 5650
I try to combine a QML view with a QSortFilterProxyModel within PyQt5. Unfortunately I can't get it to work in any way.
My main problem right now seems to be to pass the items back from QML. But even if this would be possible it seems that it does not work, I get TypeError: QSortFilterProxyModel.setSourceModel(QAbstractItemModel): argument 1 has unexpected type 'PyCapsule'
if I set the model directly in python.
Currently I have:
class SortFilterProxyModel(QSortFilterProxyModel):
@pyqtProperty(QQmlListReference)
def source (self):
return self._source
@source.setter
def source (self, source):
setSourceModel(source)
self._source = source
class MyItem(QObject):
nameChanged = pyqtSignal()
def __init__(self, name, parent=None):
QObject.__init__(self, parent)
self._name = name
@pyqtProperty('QString', notify=nameChanged)
def name(self):
return self._name
class MyModel(QObject):
itemsChanged = pyqtSignal()
def __init__(self, parent=None):
QObject.__init__(self, parent)
self._items = [MyItem('one'), MyItem('two'), MyItem('three')]
@pyqtProperty(QQmlListProperty, notify=itemsChanged)
def items(self):
print('Query for items')
return QQmlListProperty(MyItem, self, self._items)
@pyqtSlot()
def new_item(self):
print('Append new item')
self._items.append(MyItem('new'))
self.itemsChanged.emit()
and
import QtQuick 2.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.2
import QtQuick.Dialogs 1.2
import MyModel 1.0
import MyItem 1.0
import SortFilterProxyModel 1.0
ApplicationWindow {
function getCurrentIndex(list, element) {
console.log('getCurrentIndex')
if (list && element) {
for (var i = 0; i < list.length; i++) {
if (list[i].name === element.name) {
console.log('Found item at pos: ' + i)
return i
}
}
}
return -1
}
id: mainWindow
width: 800; height: 600
color: "gray"
MyModel {
id: mymodel
}
SortFilterProxyModel {
id: proxyModel
source: mymodel.items
}
TableView {
anchors.fill: parent
//model: mymodel.items
model: proxyModel
TableViewColumn {
role: "name"
title: "Name"
}
}
}
full source here:
https://github.com/sturmf/python_samples/tree/master/pyqt_qsortfilterproxymodel
Upvotes: 1
Views: 1658
Reputation: 5650
I now also implemented another way as described here: http://blog.qt.io/blog/2014/04/16/qt-weekly-6-sorting-and-filtering-a-tableview/
The implementation in the SortFilterProxyModel is a bit longer but the QML source gets imho nicer. This version also includes a filter implementation which makes it longer too.
class MyItem(QObject):
nameChanged = pyqtSignal()
def __init__(self, name, parent=None):
QObject.__init__(self, parent)
self._name = name
@pyqtProperty('QString', notify=nameChanged)
def name(self):
return self._name
class MyModel(QAbstractListModel):
NameRole = Qt.UserRole + 1
_roles = {NameRole: "name"}
def __init__(self, parent=None):
super().__init__(parent)
self._items = [MyItem('one'), MyItem('two'), MyItem('three')]
self._column_count = 1
def roleNames(self):
return self._roles
def rowCount(self, parent=QModelIndex()):
return len(self._items)
def data(self, index, role=Qt.DisplayRole):
try:
item = self._items[index.row()]
except IndexError:
return QVariant()
if role == self.NameRole:
return item.name
return QVariant()
and a SortFilterProxyModel which I can use from QML
class SortFilterProxyModel(QSortFilterProxyModel):
class FilterSyntax:
RegExp, Wildcard, FixedString = range(3)
Q_ENUMS(FilterSyntax)
def __init__(self, parent):
super().__init__(parent)
@pyqtProperty(QAbstractItemModel)
def source(self):
return super().sourceModel()
@source.setter
def source(self, source):
self.setSourceModel(source)
@pyqtProperty(int)
def sortOrder(self):
return self._order
@sortOrder.setter
def sortOrder(self, order):
self._order = order
super().sort(0, order)
@pyqtProperty(QByteArray)
def sortRole(self):
return self._roleNames().get(super().sortRole())
@sortRole.setter
def sortRole(self, role):
super().setSortRole(self._roleKey(role))
@pyqtProperty(QByteArray)
def filterRole(self):
return self._roleNames().get(super().filterRole())
@filterRole.setter
def filterRole(self, role):
super().setFilterRole(self._roleKey(role))
@pyqtProperty(str)
def filterString(self):
return super().filterRegExp().pattern()
@filterString.setter
def filterString(self, filter):
super().setFilterRegExp(QRegExp(filter, super().filterCaseSensitivity(), self.filterSyntax))
@pyqtProperty(int)
def filterSyntax(self):
return super().filterRegExp().patternSyntax()
@filterSyntax.setter
def filterSyntax(self, syntax):
super().setFilterRegExp(QRegExp(self.filterString, super().filterCaseSensitivity(), syntax))
def filterAcceptsRow(self, sourceRow, sourceParent):
rx = super().filterRegExp()
if not rx or rx.isEmpty():
return True
model = super().sourceModel()
sourceIndex = model.index(sourceRow, 0, sourceParent)
# skip invalid indexes
if not sourceIndex.isValid():
return True
# If no filterRole is set, iterate through all keys
if not self.filterRole or self.filterRole == "":
roles = self._roleNames()
for key, value in roles.items():
data = model.data(sourceIndex, key)
if rx.indexIn(data) != -1:
return True
return False
# Here we have a filterRole set so only search in that
data = model.data(sourceIndex, self._roleKey(self.filterRole))
return rx.indexIn(data) != -1
def _roleKey(self, role):
roles = self.roleNames()
for key, value in roles.items():
if value == role:
return key
return -1
def _roleNames(self):
source = super().sourceModel()
if source:
return source.roleNames()
return {}
Now I can do the following in QML
MyModel {
id: mymodel
}
SortFilterProxyModel {
id: proxyModel
source: mymodel
sortOrder: tableView.sortIndicatorOrder
sortCaseSensitivity: Qt.CaseInsensitive
sortRole: tableView.getColumn(tableView.sortIndicatorColumn).role
filterString: "*" + searchBox.text + "*"
filterSyntax: SortFilterProxyModel.Wildcard
filterCaseSensitivity: Qt.CaseInsensitive
filterRole: tableView.getColumn(tableView.sortIndicatorColumn).role
}
TableView {
id: tableView
anchors.fill: parent
model: proxyModel
sortIndicatorVisible: true
TableViewColumn {
role: "name"
title: "Name"
}
}
Upvotes: 1
Reputation: 5650
The only way to get it working was to switch to a QAbstractListModel as was suggested by Frank. Here is the important part of the code:
class MyItem(QObject):
nameChanged = pyqtSignal()
def __init__(self, name, parent=None):
QObject.__init__(self, parent)
self._name = name
@pyqtProperty('QString', notify=nameChanged)
def name(self):
return self._name
class MyModel(QAbstractListModel):
NameRole = Qt.UserRole + 1
_roles = {NameRole: "name"}
def __init__(self, parent=None):
print("constructing")
super().__init__(parent)
self._items = [MyItem('one'), MyItem('two'), MyItem('three')]
self._column_count = 1
def roleNames(self):
print("roleNames")
return self._roles
def rowCount(self, parent=QModelIndex()):
print("rowCount", len(self._items))
return len(self._items)
def data(self, index, role=Qt.DisplayRole):
print("in data")
try:
item = self._items[index.row()]
except IndexError:
return QVariant()
if role == self.NameRole:
return item.name
return QVariant()
and a SortFilterProxyModel which I can use from QML
class SortFilterProxyModel(QSortFilterProxyModel):
def __init__(self, parent):
super().__init__(parent)
@pyqtProperty(QAbstractItemModel)
def source (self):
return self._source
@source.setter
def source (self, source):
self.setSourceModel(source)
self._source = source
def roleKey(self, role):
roles = self.roleNames()
for key, value in roles.items():
if value == role:
return key
return -1
@pyqtSlot(str, int)
def sort(self, role, order):
self.setSortRole(self.roleKey(role));
super().sort(0, order);
Now I can do the following in QML
MyModel {
id: mymodel
}
SortFilterProxyModel {
id: proxyModel
source: mymodel
}
TableView {
id: tableView
anchors.fill: parent
model: proxyModel
sortIndicatorVisible: true
onSortIndicatorOrderChanged: model.sort(getColumn(sortIndicatorColumn).role, sortIndicatorOrder)
onSortIndicatorColumnChanged: model.sort(getColumn(sortIndicatorColumn).role, sortIndicatorOrder)
TableViewColumn {
title: "Name"
role: "name"
}
}
Upvotes: 0