How to get delegate item when QTableView is clicked - python

The code below creates the single QTableView. There is a QItemDelegate is assigned as a PersistentEditor. There are two kind of editors created: QLineEdit is created for the column 0 and the 'QComboBox` is created for the column 1.
When the tableView is clicked I would like to get the instance of both editors: the instance of the LineEdit and the instance of comboBox. How to achieve this?
from PyQt4 import QtCore, QtGui
app = QtGui.QApplication([])
class Delegate(QtGui.QItemDelegate):
def __init__(self):
QtGui.QItemDelegate.__init__(self)
def createEditor(self, parent, option, index):
if index.column()==0:
lineedit=QtGui.QLineEdit(parent)
return lineedit
elif index.column()==1:
combo=QtGui.QComboBox(parent)
return combo
def setEditorData(self, editor, index):
row = index.row()
column = index.column()
value = index.model().items[row][column]
if isinstance(editor, QtGui.QComboBox):
editor.addItems(['Somewhere','Over','The Rainbow'])
editor.setCurrentIndex(index.row())
if isinstance(editor, QtGui.QLineEdit):
editor.setText('Somewhere over the rainbow')
class Model(QtCore.QAbstractTableModel):
def __init__(self):
QtCore.QAbstractTableModel.__init__(self)
self.items = [[1, 'one', 'ONE'], [2, 'two', 'TWO'], [3, 'three', 'THREE']]
def flags(self, index):
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable
def rowCount(self, parent=QtCore.QModelIndex()):
return 3
def columnCount(self, parent=QtCore.QModelIndex()):
return 3
def data(self, index, role):
if not index.isValid(): return
row = index.row()
column = index.column()
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
return self.items[row][column]
def tableViewClicked(index):
print 'clicked indedx: %s'%index
tableModel=Model()
tableView=QtGui.QTableView()
tableView.setModel(tableModel)
tableView.setItemDelegate(Delegate())
tableView.clicked.connect(tableViewClicked)
for row in range(tableModel.rowCount()):
for column in range(tableModel.columnCount()):
index=tableModel.index(row, column)
tableView.openPersistentEditor(index)
tableView.show()
app.exec_()

I recently set up something similar like this. This might be what you were after.
Step 1, create the delegate to do whatever it is you need, and store the QLineEdit as a variable.
In the case below, I want a signal to emit every time the user leaves the QLineEdit.
class LineEditDelegate(QtWidgets.QStyledItemDelegate):
on_focus_out = QtCore.Signal(object)
def __init__(self, *args, **kwargs):
super(LineEditDelegate, self).__init__(*args, **kwargs)
self.line_edit = None
def createEditor(self, parent, option, index):
self.line_edit = QtWidgets.QLineEdit(parent=parent)
self.line_edit.destroyed.connect(lambda: self._line_edit_left(index=index))
return self.line_edit
def _line_edit_left(self, index):
"""
Triggers when line edit was closed / destroyed
:param index: QModelIndex, index of edited item
:return: None
"""
self.on_focus_out.emit(index)
return
Step 2, Create you QTableWidget and setup however you want, adding the delegate where you need it.
In this example, on all items in column 0
self.table = QtWidgets.QTableWidget(parent=self.base_widget)
self.table.setRowCount(2)
self.table.setColumnCount(2)
self.line_edit_delegate = LineEditDelegate(self.main_window)
self.table.setItemDelegateForColumn(0, self.line_edit_delegate)
I also connect my function to the signal, fulfilling the purpose of this delegate.
self.line_edit_delegate.on_focus_out.connect(self._check_text)
Step 3, Getting the delegate item, i.e. the QLineEdit.
First you'll need to get the item; you can do this in many ways such as:
item_0 = self.table.currentRow()
item_0 = self.table.currentItem()
or connecting through one of the QTableWidget's signals that pass through the QTableWidgetItem, row, or whatever:
self.table.itemClicked.connect(self.item_clicked)
self.table.itemDoubleClicked.connect(self.item_double_clicked)
Then with the QTableWidgetItem you can get the delegate, and then get the QLineEdit.
delegate = self.table.itemDelegate(self.table.indexFromItem(item_0))
delegate.line_edit
For my use, I wanted the user to add a new row and then have the QLineEdit get automatically selected for editing. This is what I used:
row_position = self.table.currentRow() + 1
item_0 = QtWidgets.QTableWidgetItem()
item_0.setFont(font)
self.table.setItem(row_position, 0, item_0)
self.table.openPersistentEditor(item_0)
delegate = self.table.itemDelegate(self.table.indexFromItem(item_0))
delegate.line_edit.setFocus()

Related

Update QAbstractTableModel when combobox changed

I am trying to update my model when a combo box is changed. I have a function connected to the combobox that updates the model but from this question - "the delegates should use the base functions of the model whenever they provide standard behavior (accessing index.model()._data is not good)". I also want the change of the combobox to affect what appears in a QLineEdit.
QItemDelegate:
class Delegate(QItemDelegate):
def __init__(self):
QItemDelegate.__init__(self)
self.type_items = ["1", "2"]
def createEditor(self, parent, option, index):
if index.column() == 0:
comboBox = QComboBox(parent)
for text in self.type_items:
comboBox.addItem(text, (index.row(), index.column()))
comboBox.currentIndexChanged.connect(lambda: self.comboBoxChanged(comboBox, index))
return comboBox
return super().createEditor(parent, option, index)
# updates model but does not update view - shows what I want to happen
def comboBoxChanged(self, comboBox, index):
if comboBox.currentText() == "1":
index.model()._data[index.row()][0] = "1"
index.model()._data[index.row()][1] = "One"
elif comboBox.currentText() == "2":
index.model()._data[index.row()][0] = "2"
index.model()._data[index.row()][1] = "Two"
# self.dataChanged.emit(index, index) # can't call this here
QAbstractTableModel:
class TableModel(QAbstractTableModel):
def __init__(self, data):
super(TableModel, self).__init__()
self._data = data
def data(self, index, role):
if role in (Qt.DisplayRole, Qt.EditRole):
return self._data[index.row()][index.column()]
def rowCount(self, index=None):
return len(self._data)
def columnCount(self, index=None):
return len(self._data[0])
def flags(self, index):
return super().flags(index) | Qt.ItemIsEditable
def setData(self, index, value, role=Qt.EditRole):
if role == Qt.EditRole:
self._data[index.row()][index.column()] = value
self.dataChanged.emit(index, index)
return True
return False
Main:
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
localWidget = QWidget()
self.table = QTableView()
data = [["1", "One"]]
self.model = TableModel(data)
self.table.setModel(self.model)
self.table.setItemDelegate(Delegate())
self.print_data = QPushButton("Print data")
self.print_data.clicked.connect(self.printData)
for row in range(self.model.rowCount()):
for column in range(self.model.columnCount()):
index = self.model.index(row, column)
self.table.openPersistentEditor(index)
layout_v = QVBoxLayout()
layout_v.addWidget(self.table)
layout_v.addWidget(self.print_data)
localWidget.setLayout(layout_v)
self.setCentralWidget(localWidget)
self.show()
def printData(self):
print(self.model._data)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
How can I register the change in the qcombobox to correctly update the model?
and
How do I make this trigger the change in qlineedit?
The data of a model should be preferably updated by using the existing setData function, which provides a centralized interface that avoids confusing bugs and mistakes.
In your case, you need to set the data of the sibling index, which is the index at the same row in the next column (no need to set the index value itself, as Qt is able to get it automatically from the combo, and it should be done in setModelData() anyway).
def comboBoxChanged(self, comboBox, index):
if comboBox.currentText() == "1":
value = "One"
elif comboBox.currentText() == "2":
value = "Two"
sibling = index.sibling(index.row(), 1)
index.model().setData(sibling, value)
Just to be clear, your code could have worked, but dataChanged is a signal of the model, not the delegate, so it should have been:
index.model().dataChanged.emit(index, index)
But, as already said, it's better to avoid using different ways of changing data in the model, especially if done from outside it. The major benefit of having a single function that takes care of that is that whenever something is going to change in the structure and implementation of the model the only thing left to check is all the occurrences of that function call (in case the column order changes, for example).

Adding row to QTableView with model and and delegate widgets

I am trying to add a row to QTableView with a QAbstractTableModel and QItemDelegate where the widgets appear in the added row. From what I've read I need to call .edit(index) on each item of the added row to call createEditor where the widgets are created however I am getting edit: editing failed
QItemDelegate:
class Delegate(QItemDelegate):
def __init__(self):
QItemDelegate.__init__(self)
self.type_items = ["1", "2", "3"]
def createEditor(self, parent, option, index):
# COMBOBOX, LINEEDIT, TIMEDIT
if index.column() == 0:
comboBox = QComboBox(parent)
for text in self.type_items:
comboBox.addItem(text, (index.row(), index.column()))
return comboBox
elif index.column() == 1:
lineEdit = QLineEdit(parent)
return lineEdit
elif index.column() == 2:
timeEdit = QTimeEdit(parent)
return timeEdit
def setEditorData(self, editor, index):
value = index.model()._data[index.row()][index.column()]
if index.column() == 0:
editor.setCurrentIndex(self.type_items.index(value))
elif index.column() == 1:
editor.setText(str(value))
elif index.column() == 2:
editor.setTime(value)
QAbstractTableModel:
class TableModel(QAbstractTableModel):
def __init__(self, data):
super(TableModel, self).__init__()
self._data = data
def data(self, index, role):
pass
def rowCount(self, index=None):
return len(self._data)
def columnCount(self, index=None):
return len(self._data[0])
Main:
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
localWidget = QWidget()
self.table = QTableView(localWidget)
data = [["1", "Hi", QTime(2, 1)], ["2", "Hello", QTime(3, 0)]]
self.model = TableModel(data)
self.table.setModel(self.model)
self.table.setItemDelegate(Delegate())
self.add_row = QPushButton("Add Row", localWidget)
self.add_row.clicked.connect(self.addRow)
for row in range(self.model.rowCount()):
for column in range(self.model.columnCount()):
index = self.model.index(row, column)
self.table.openPersistentEditor(index)
layout_v = QVBoxLayout()
layout_v.addWidget(self.table)
layout_v.addWidget(self.add_row)
localWidget.setLayout(layout_v)
self.setCentralWidget(localWidget)
self.show()
def addRow(self):
new_row_data = ["3", "Howdy", QTime(9, 0)]
self.model.beginInsertRows(QModelIndex(), self.model.rowCount(), self.model.rowCount())
self.model._data.append(new_row_data)
self.model.endInsertRows()
for i in range(len(new_row_data)):
index = self.table.model().index(self.model.rowCount()-1, i)
self.table.edit(index)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
How can I trigger the QItemDelegate's createEditor to create the widgets for the added row's items and setEditorData to populate them?
First of all, you could use openPersistentEditor as you already did in the __init__.
There are two reasons for the error in self.table.edit():
in order to allow editing of an item through edit(), the flags() function must be overridden and provide the Qt.ItemIsEditable flag;
you cannot call edit() multiple times on different indexes within the same function;
Then, there are other important problems in your code:
you're not correctly using the model, as data() is not implemented;
the delegates should use the base functions of the model whenever they provide standard behavior (accessing index.model()._data is not good);
setEditorData is unnecessary, as long as the above aspects are respected: Qt automatically sets the data based on the data type and the editor class;
setData() should be implemented in order to correctly set the data on the model, otherwise the new data won't be accessible;
changes in the model structure (like creating a new row) should be done in the model class;
Here is a revised version of your code:
class Delegate(QItemDelegate):
def __init__(self):
QItemDelegate.__init__(self)
self.type_items = ["1", "2", "3"]
def createEditor(self, parent, option, index):
if index.column() == 0:
comboBox = QComboBox(parent)
for text in self.type_items:
comboBox.addItem(text, (index.row(), index.column()))
return comboBox
# no need to check for the other columns, as Qt automatically creates a
# QLineEdit for string values and QTimeEdit for QTime values;
return super().createEditor(parent, option, index)
# no setEditorData() required
class TableModel(QAbstractTableModel):
def __init__(self, data):
super(TableModel, self).__init__()
self._data = data
def appendRowData(self, data):
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self._data.append(data)
self.endInsertRows()
def data(self, index, role=Qt.DisplayRole):
if role in (Qt.DisplayRole, Qt.EditRole):
return self._data[index.row()][index.column()]
def setData(self, index, value, role=Qt.EditRole):
if role == Qt.EditRole:
self._data[index.row()][index.column()] = value
self.dataChanged.emit(index, index)
return True
return False
def rowCount(self, index=None):
return len(self._data)
def columnCount(self, index=None):
return len(self._data[0])
def flags(self, index):
# allow editing of the index
return super().flags(index) | Qt.ItemIsEditable
class MainWindow(QMainWindow):
# ...
def addRow(self):
row = self.model.rowCount()
new_row_data = ["3", "Howdy", QTime(9, 0)]
self.model.appendRowData(new_row_data)
for i in range(self.model.columnCount()):
index = self.model.index(row, i)
self.table.openPersistentEditor(index)

How to stop Item Delegate from blocking mousePressEvent

The QTableView was assigned QAbstractTableModel as a model. And ItemDelegate(QItemDelegate) was assigned with tableView.openPersistentEditor. Now when tableView is clicked the event does not propagate all the way to the tableView (does it get blocked by a delegated QLineEditor).
What would be a way to pass the QLineEdit'smousePressEventevent that is passed through theItemDelegatetoQTableView's modelwhen the tableView's item is clicked even while it is occupied byQItemInstance? Is there anyQItemDelegate` flags that can be used to get things done?
The code creates a single tableView with model and delegate.
The event triggered by clicking the column 0 or column 1 does not propagate to the tableView's item since the item stays deselected.
Uncommenting a line with tableView.setSelectionBehavior(QtGui.QTableView.SelectRows) fixes the problem. Does it?
from PyQt4 import QtCore, QtGui
app = QtGui.QApplication([])
class LineEdit(QtGui.QLineEdit):
def __init__(self, parent=None, total=20):
super(LineEdit, self).__init__(parent=parent)
def mousePressEvent(self, event):
print 'mousePressEvent', event
super(LineEdit, self).mousePressEvent(event)
class Delegate(QtGui.QItemDelegate):
def __init__(self):
QtGui.QItemDelegate.__init__(self)
def createEditor(self, parent, option, index):
if index.column()==0:
lineedit=LineEdit(parent)
return lineedit
elif index.column()==1:
combo=QtGui.QComboBox(parent)
return combo
def setEditorData(self, editor, index):
row = index.row()
column = index.column()
value = index.model().items[row][column]
if isinstance(editor, QtGui.QComboBox):
editor.addItems(['Somewhere','Over','The Rainbow'])
editor.setCurrentIndex(index.row())
if isinstance(editor, QtGui.QLineEdit):
editor.setText('Somewhere over the rainbow')
class Model(QtCore.QAbstractTableModel):
def __init__(self):
QtCore.QAbstractTableModel.__init__(self)
self.items = [[1, 'one', 'ONE'], [2, 'two', 'TWO'], [3, 'three', 'THREE']]
def flags(self, index):
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable
def rowCount(self, parent=QtCore.QModelIndex()):
return 3
def columnCount(self, parent=QtCore.QModelIndex()):
return 3
def data(self, index, role):
if not index.isValid(): return
row = index.row()
column = index.column()
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
return self.items[row][column]
def tableViewClicked(index):
print 'clicked: %s'%index
tableModel=Model()
tableView=QtGui.QTableView()
tableView.setModel(tableModel)
tableView.setItemDelegate(Delegate())
# tableView.setSelectionBehavior(QtGui.QTableView.SelectRows)
tableView.clicked.connect(tableViewClicked)
for row in range(tableModel.rowCount()):
for column in range(tableModel.columnCount()):
index=tableModel.index(row, column)
tableView.openPersistentEditor(index)
tableView.show()
app.exec_()
why not use a model object pointer in your delegate item's function ? e.g connect your delegate item's signal to model's slots, or call model's function in your delegate item's event handler?

How to set text to QLineEdit when it is used as QItemDelegate with QTableView

The code below creates a single QTableView. An instance of QAbstractTableModel is then created and assigned to it as its model. Lastly, the QItemDelegate is assigned to the QTableView.
The column 0 is being populated with QLineEdit. While column 1 is populated with QComboBox.
But even while QLineEdit is assigned a custom text value the QLineEdit remains to be blank. It doesn't happen with QComboBox which properly gets three items: 'Somewhere', 'Over', 'The Rainbow'.
What should be done to pre-populate column 0 QLineEdits with the text?
from PyQt4 import QtCore, QtGui
app = QtGui.QApplication([])
class Delegate(QtGui.QItemDelegate):
def __init__(self):
QtGui.QItemDelegate.__init__(self)
def createEditor(self, parent, option, index):
if index.column()==0:
lineedit=QtGui.QLineEdit(parent)
lineedit.setText('Somewhere over the rainbow')
return lineedit
elif index.column()==1:
combo=QtGui.QComboBox(parent)
combo.addItems(['Somewhere','Over','The Rainbow'])
combo.setCurrentIndex(index.row())
return combo
class Model(QtCore.QAbstractTableModel):
def __init__(self):
QtCore.QAbstractTableModel.__init__(self)
self.items = [[1, 'one', None], [2, 'two', None], [3, 'three', None]]
def rowCount(self, parent=QtCore.QModelIndex()):
return 3
def columnCount(self, parent=QtCore.QModelIndex()):
return 3
def data(self, index, role):
if not index.isValid(): return
row = index.row()
column = index.column()
if role == QtCore.Qt.DisplayRole:
return self.items[row][column]
tableModel=Model()
tableView=QtGui.QTableView()
tableView.setModel(tableModel)
tableView.setItemDelegate(Delegate())
for row in range(tableModel.rowCount()):
for column in range(tableModel.columnCount()):
index=tableModel.index(row, column)
tableView.openPersistentEditor(index)
tableView.show()
app.exec_()
EDITED LATER:
Big thanks Fabio for the trick.
Working code is posted below.
Instead of handling the value assignment inside of Delegate.createEditor() method we should implement Delegate.setEditorData(editor, index) instead.
Inside of setEditorData we can get both: index.column() and index.row() as well as to access self.items variable used by model using index argument value = index.model().items[row][column]
from PyQt4 import QtCore, QtGui
app = QtGui.QApplication([])
class Delegate(QtGui.QItemDelegate):
def __init__(self):
QtGui.QItemDelegate.__init__(self)
def createEditor(self, parent, option, index):
if index.column()==0:
lineedit=QtGui.QLineEdit(parent)
return lineedit
elif index.column()==1:
combo=QtGui.QComboBox(parent)
return combo
def setEditorData(self, editor, index):
row = index.row()
column = index.column()
value = index.model().items[row][column]
if isinstance(editor, QtGui.QComboBox):
editor.addItems(['Somewhere','Over','The Rainbow'])
editor.setCurrentIndex(index.row())
if isinstance(editor, QtGui.QLineEdit):
editor.setText('Somewhere over the rainbow')
class Model(QtCore.QAbstractTableModel):
def __init__(self):
QtCore.QAbstractTableModel.__init__(self)
self.items = [[1, 'one', 'ONE'], [2, 'two', 'TWO'], [3, 'three', 'THREE']]
def flags(self, index):
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable
def rowCount(self, parent=QtCore.QModelIndex()):
return 3
def columnCount(self, parent=QtCore.QModelIndex()):
return 3
def data(self, index, role):
if not index.isValid(): return
row = index.row()
column = index.column()
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
return self.items[row][column]
tableModel=Model()
tableView=QtGui.QTableView()
tableView.setModel(tableModel)
tableView.setItemDelegate(Delegate())
for row in range(tableModel.rowCount()):
for column in range(tableModel.columnCount()):
index=tableModel.index(row, column)
tableView.openPersistentEditor(index)
tableView.show()
app.exec_()
The item delegate set the editor data by setEditorData method, that takes the model data and set it to the editor. In this case, the item delegate takes the model data (that maybe is empty) and set it to the QLineEdit, so it set an empty string.
If you want to initialize the data in the editor, you have to reimplement setEditorData, or initialize the data in your model.
For QComboBox, the items are displayed correcly because setEditorData doesn't change the items of the combo box. In general you have to reimplement setEditorData to set the current index of the combo box depending on the model data.
I suggest to read the documentation: Model/View Programming and Delegate Classes

How to select QTableView index or row from inside of Model

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_())
You can achieve the selection within the filterAcceptsRow() method of the proxy model, but doing so would require the following:
That your proxy model (or source model) contain a reference to the QTableView instance.
That your proxy model contain an attribute indicating whether it is active. This is because you want to only select the table rows when the button is clicked, but 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!

Categories

Resources