Reputation: 19329
The posted code creates a singe Model/Proxy
QTableView
. The multi-selection feature has been enabled for it.
There are four items total. Two of them include a character "A". Other two include character "B" in their "item" names.
QPushButton
when pressed calls for the clicked()
method.
When called this method first queries a Proxy Model
connected to the QTableView
:
proxyModel=self.tableview.model()
Then the method asks a proxyModel
to return a total number of rows:
rows=proxyModel.rowCount()
Knowing how many rows in a QTabelView
's model it iterates each row. First it is querying a row index:
index=proxyModel.index(row, 0)
Knowing index
it proceeds with asking for a value stored in self.items
variable by calling data()
method supplying it with a queried in a previous step a QModelIndex
(a variable index
here) and a Role
flag.
item=proxyModel.data(index, Qt.DisplayRole).toPyObject()
'toPyObject()' is used to convert the data received from .data()
method to a "regular" Python variable.
Lastly it checks if the characters "B" in a received string. If so it selects QTableView row using:
self.tableview.selectRow(row)
Now what I want is to get the same selection functionality from inside of the scope of Proxy Model's filterAcceptsRow()
if that is possible.
If it is not possible I would like to know if there is any other way of doing it... should be I using QItemSelectionModel
? Then how?
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
class Model(QAbstractTableModel):
def __init__(self, parent=None, *args):
QAbstractTableModel.__init__(self, parent, *args)
self.items = ['Item_A_001','Item_A_002','Item_B_001','Item_B_002']
def rowCount(self, parent=QModelIndex()):
return len(self.items)
def columnCount(self, parent=QModelIndex()):
return 1
def data(self, index, role):
if not index.isValid(): return QVariant()
elif role != Qt.DisplayRole:
return QVariant()
row=index.row()
if row<len(self.items):
return QVariant(self.items[row])
else:
return QVariant()
class Proxy(QSortFilterProxyModel):
def __init__(self):
super(Proxy, self).__init__()
def filterAcceptsRow(self, row, parent):
return True
class MyWindow(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
tableModel=Model(self)
proxyModel=Proxy()
proxyModel.setSourceModel(tableModel)
self.tableview=QTableView(self)
self.tableview.setModel(proxyModel)
self.tableview.horizontalHeader().setStretchLastSection(True)
self.tableview.setSelectionMode(QAbstractItemView.MultiSelection)
button=QPushButton(self)
button.setText('Select Items with B')
button.clicked.connect(self.clicked)
layout = QVBoxLayout(self)
layout.addWidget(self.tableview)
layout.addWidget(button)
self.setLayout(layout)
def clicked(self, arg):
proxyModel=self.tableview.model()
self.tableview.clearSelection()
rows=proxyModel.rowCount()
for row in range(rows):
index=proxyModel.index(row, 0)
item=proxyModel.data(index, Qt.DisplayRole).toPyObject()
if '_B_' in item:
self.tableview.selectRow(row)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
Upvotes: 1
Views: 6329
Reputation: 1410
You can achieve the selection within the filterAcceptsRow()
method of the proxy model, but doing so would require the following:
QTableView
instance. filterAcceptsRow()
is called automatically by the proxy model. Therefore, you would want to avoid calling the view's selectRow()
method until the button is clicked.To achieve #1, you could define a simple setter method in your proxy model class:
def setView(self, view):
self._view = view
You would also need to of course invoke that setter within your MyWindow
class's constructor:
proxyModel.setView(self.tableview)
Achieving #2 is a simple matter of creating this attribute in the proxy model class's constructor
self.filterActive = False
Now that your classes are prepared, you can implement your desired behavior. In your filterAcceptsRow()
re-implementation, you only want to select the rows if they contain '_B_'
and is the filter is active (that is, the button was clicked):
def filterAcceptsRow(self, row, parent):
if self.filterActive and '_B_' in self.sourceModel().data(self.sourceModel().index(row, 0), Qt.DisplayRole).toPyObject():
self._view.selectRow(row)
return True
Finally, you want to make sure that these conditions are met once the button is clicked, so in your clicked() method you need to set the proxyModel
's filterActive
attribute to True and you need to call the QSortFilterProxyModel
class's invalidateFilter()
method to indicate that the existing filter is invalid and therefore filterAcceptsRow()
should be called again:
def clicked(self, arg):
proxyModel=self.tableview.model()
self.tableview.clearSelection()
proxyModel.filterActive = True
proxyModel.invalidateFilter()
So the new code, in full, is:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
class Model(QAbstractTableModel):
def __init__(self, parent=None, *args):
QAbstractTableModel.__init__(self, parent, *args)
self.items = ['Item_A_001','Item_A_002','Item_B_001','Item_B_002']
def rowCount(self, parent=QModelIndex()):
return len(self.items)
def columnCount(self, parent=QModelIndex()):
return 1
def data(self, index, role):
if not index.isValid(): return QVariant()
elif role != Qt.DisplayRole:
return QVariant()
row=index.row()
if row<len(self.items):
return QVariant(self.items[row])
else:
return QVariant()
class Proxy(QSortFilterProxyModel):
def __init__(self):
super(Proxy, self).__init__()
self.filterActive = False
def setView(self, view):
self._view = view
def filterAcceptsRow(self, row, parent):
if self.filterActive and '_B_' in self.sourceModel().data(self.sourceModel().index(row, 0), Qt.DisplayRole).toPyObject():
self._view.selectRow(row)
return True
class MyWindow(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
tableModel=Model(self)
proxyModel=Proxy()
proxyModel.setSourceModel(tableModel)
self.tableview=QTableView(self)
self.tableview.setModel(proxyModel)
self.tableview.horizontalHeader().setStretchLastSection(True)
self.tableview.setSelectionMode(QAbstractItemView.MultiSelection)
proxyModel.setView(self.tableview)
button=QPushButton(self)
button.setText('Select Items with B')
button.clicked.connect(self.clicked)
layout = QVBoxLayout(self)
layout.addWidget(self.tableview)
layout.addWidget(button)
self.setLayout(layout)
def clicked(self, arg):
proxyModel=self.tableview.model()
self.tableview.clearSelection()
proxyModel.filterActive = True
proxyModel.invalidateFilter()
if __name__ == "__main__":
app = QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
Having said all of that, the purpose of filterAcceptsRow()
is so that you can implement your own custom filtering in a subclass of QSortFilterProxyModel
. So, a more typical implementation (following your desired rule) would be:
def filterAcceptsRow(self, row, parent):
if not self.filterActive or '_B_' in self.sourceModel().data(self.sourceModel().index(row, 0), Qt.DisplayRole).toPyObject():
return True
return False
And even then, because the filtering could be done with regex, reimplementation of filterAcceptsRow()
isn't even necessary. You could just call proxyModel.setFilterRegExp(QRegExp("_B_", Qt.CaseInsensitive, QRegExp.FixedString))
and proxyModel.setFilterKeyColumn(0)
to achieve the same thing, filter-wise.
Hope that helps!
Upvotes: 3