Reputation: 19329
The list:
items = [['Pet', 'Dog'],['Pet', 'Cat'],['Bird','Eagle'],['Bird','Jay'],['Bird','Falcon']]
is used by model
that is assigned to QTableView
and QComboBox
.
I want Combobox
to only display "Pet" and "Bird"
while QTableView
to display: "Dog", "Eagle" and "Jay".
How to achieve this?
from PySide import QtGui, QtCore
class Model(QAbstractTableModel):
def __init__(self, parent=None, *args):
QAbstractTableModel.__init__(self, parent, *args)
self.items = [['Pet', 'Dog'],['Pet', 'Cat'],['Bird','Eagle'],['Bird','Jay'],['Bird','Falcon']]
def rowCount(self, parent=QModelIndex()):
return len(self.items)
def columnCount(self, parent=QModelIndex()):
return 2
def data(self, index, role):
if not index.isValid(): return
row=index.row()
column=index.column()
return self.items[row][column]
class Proxy(QSortFilterProxyModel):
def __init__(self):
super(Proxy, self).__init__()
def filterAcceptsRow(self, rowProc, parentProc):
modelIndex=self.sourceModel().index(rowProc, 0, parentProc)
item=self.sourceModel().data(modelIndex, Qt.DisplayRole)
return True
class MyWindow(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
vLayout=QVBoxLayout(self)
self.setLayout(vLayout)
model=Model(self)
proxy=Proxy()
proxy.setSourceModel(model)
self.combo=QtGui.QComboBox()
self.combo.activated.connect(self.comboActivated)
vLayout.addWidget(self.combo)
self.combo.setModel(proxy)
self.ViewA=QTableView(self)
self.ViewA.setModel(model)
self.ViewA.clicked.connect(self.viewClicked)
vLayout.addWidget(self.ViewA)
def viewClicked(self, indexClicked):
print 'indexClicked() row: %s column: %s'%(indexClicked.row(), indexClicked.column() )
proxy=indexClicked.model()
def comboActivated(self, arg):
print 'comboClicked() arg:', arg
if __name__ == "__main__":
app = QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
Upvotes: 0
Views: 1755
Reputation: 3477
It is possible to do what you want. You will need to implement a separate QAbstractProxyModel for each view. The one for the combo box should filter and leave only the non-duplicated elements of the first column. The other should filter the second row down to only the rows relevant to the current state of the combo box view.
If you did that, in this example, you would end up with four model-related objects: your data (in Model.items)
, your Model
, and the two proxies.
But I don't think this is a good approach at all. Both of these proxies are complex and completely change the row,column structure of the original model. Imagine the complexity that will be needed in the handling of the dataChanged
signal in each proxy when data in the first column of the model changes.
In practice I think you would be much better off with a three object version: a proper (slightly elaborated) data class and then a pair of QAbstractItemModels sharing that data and each specialised to the View they support. Think of it like a pair of QFileSystemModels on the same file system.
Changes to the data will have to be signaled to the two QAbstractItemModels and you won't have the benefit of having that automatically implemented by the Qt framework. But, as I pointed out above, in practice that won't work easily for the proxies in any case.
So here's an example based on your code with as few changes as possible. Note that I had to fix quite a few things - your imports didn't work and your combo model was returning incorrect data for some roles.
from PySide import QtGui, QtCore
import sys
class Data(object):
def __init__(self):
self.items = [['Pet', 'Dog'],['Pet', 'Cat'],['Bird','Eagle'],['Bird','Jay'],['Bird','Falcon']]
self.selectors = list({ k[0] for k in self.items})
def currentItems(self,select_on):
return [k[1] for k in self.items if k[0] == select_on]
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, data, parent=None):
QtCore.QAbstractTableModel.__init__(self, parent)
self.data_obj = data
self.currentSelection = None
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.data_obj.currentItems(self.currentSelection))
def columnCount(self, parent=QtCore.QModelIndex()):
return 1
def data(self, index, role):
if not index.isValid() or role != QtCore.Qt.DisplayRole: return
row=index.row()
return self.data_obj.currentItems(self.currentSelection)[row]
def setSelection(self,combo_row):
self.currentSelection = self.data_obj.selectors[combo_row]
self.layoutChanged.emit()
class ComboModel(QtCore.QAbstractListModel):
def __init__(self, data, parent=None):
QtCore.QAbstractListModel.__init__(self, parent)
self.data_obj = data
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.data_obj.selectors)
def data(self, index, role):
if not index.isValid() or role != QtCore.Qt.DisplayRole: return
row=index.row()
return self.data_obj.selectors[row]
class MyWindow(QtGui.QWidget):
def __init__(self, *args):
QtGui.QWidget.__init__(self, *args)
vLayout=QtGui.QVBoxLayout(self)
self.setLayout(vLayout)
self.data=Data()
self.tableModel = TableModel(self.data)
self.comboModel = ComboModel(self.data)
self.combo=QtGui.QComboBox()
self.combo.setModel(self.comboModel)
self.combo.activated.connect(self.tableModel.setSelection)
vLayout.addWidget(self.combo)
self.ViewA=QtGui.QTableView(self)
self.ViewA.setModel(self.tableModel)
self.ViewA.clicked.connect(self.viewClicked)
vLayout.addWidget(self.ViewA)
self.tableModel.setSelection(0)
def viewClicked(self, indexClicked):
print('indexClicked() row: %s column: %s'%(indexClicked.row(), indexClicked.column() ))
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
But here is another version which is more based on your idea, and is a pattern I have used in the past. Here we have a single model as you requested, but providing two interfaces both actually QAbstractItemModels. It's not that different in this example but makes the single model idea more clear.
The first version has a data object which is driven by the underlying data representation and consequently can be quite clean and clear. But it needs a custom signalling method to signal changes to the two models.
The second version has a single integrated model object which encapsulates that signalling. But in a real system it will probably still need to connect to a separate data object - so in practice there are probably more objects in this version.
from PySide import QtGui, QtCore
import sys
class CombinedModel(object):
def __init__(self):
self.items = [['Pet', 'Dog'],['Pet', 'Cat'],['Bird','Eagle'],['Bird','Jay'],['Bird','Falcon']]
self.selectors = list({ k[0] for k in self.items})
self.table_if = TableModel(self)
self.combo_if = ComboModel(self)
self.currentSelection = None
def currentItems(self):
return [k[1] for k in self.items if k[0] == self.currentSelection]
def setSelection(self,combo_row):
self.currentSelection = self.selectors[combo_row]
self.table_if.layoutChanged.emit()
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, model, parent=None):
QtCore.QAbstractTableModel.__init__(self, parent)
self.model = model
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.model.currentItems())
def columnCount(self, parent=QtCore.QModelIndex()):
return 1
def data(self, index, role):
if not index.isValid() or role != QtCore.Qt.DisplayRole: return
row=index.row()
return self.model.currentItems()[row]
class ComboModel(QtCore.QAbstractListModel):
def __init__(self, model, parent=None):
QtCore.QAbstractListModel.__init__(self, parent)
self.model = model
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.model.selectors)
def data(self, index, role):
if not index.isValid() or role != QtCore.Qt.DisplayRole: return
row=index.row()
return self.model.selectors[row]
class MyWindow(QtGui.QWidget):
def __init__(self, *args):
QtGui.QWidget.__init__(self, *args)
vLayout=QtGui.QVBoxLayout(self)
self.setLayout(vLayout)
self.model=CombinedModel()
self.combo=QtGui.QComboBox()
self.combo.setModel(self.model.combo_if)
self.combo.activated.connect(self.model.setSelection)
vLayout.addWidget(self.combo)
self.ViewA=QtGui.QTableView(self)
self.ViewA.setModel(self.model.table_if)
self.ViewA.clicked.connect(self.viewClicked)
vLayout.addWidget(self.ViewA)
self.model.setSelection(0)
def viewClicked(self, indexClicked):
print('indexClicked() row: %s column: %s'%(indexClicked.row(), indexClicked.column() ))
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
Upvotes: 3
Reputation: 6065
I want ComboBox to display two generic categories (Pet and Bird). After the user selects the category QTableView would display a list of every specie associtated under the selected category.
To do this, you don't need to subclass any model. You can populate a QComboBox
directly with a list of strings, and you can use a QListWidget
to display the animal names.
To make things easier, I transformed your list into a dictionary:
self.items = [['Pet', 'Dog'],['Pet', 'Cat'],['Bird','Eagle'],['Bird','Jay'],['Bird','Falcon']]
becomes
self.myDict={"Pet":['Dog','Cat'],"Bird":['Eagle','Jay','Falcon']}
The keys of the dictionary (Pet, Bird) can be use for the QComboBox
. When the user select one specie (signal: QComboBox.currentIndexChanged
), you display the list associated to the key in a QListWidget
.
Here's the full working example:
import sys, signal
from PyQt4 import QtCore, QtGui
class myWidget(QtGui.QWidget):
def __init__( self, parent=None):
super(myWidget, self ).__init__( parent )
self.myDict={"Pet":['Dog','Cat'],"Bird":['Eagle','Jay','Falcon']}
self.listWidget=QtGui.QListWidget()
self.comboBox=QtGui.QComboBox()
self.comboBox.addItems(list(self.myDict.keys()))
#use overload signal: emits the text associated to the index
self.comboBox.currentIndexChanged[str].connect(self.on_change)
#set initial index so the listWidget is not empty
self.comboBox.setCurrentIndex(1)
layout=QtGui.QVBoxLayout()
layout.addWidget(self.comboBox)
layout.addWidget(self.listWidget)
self.setLayout(layout)
def on_change(self,key):
#clear everything
self.listWidget.clear()
#fill with list of corresponding key
for name in self.myDict[key]:
item=QtGui.QListWidgetItem(name)
item.setFlags(item.flags()|QtCore.Qt.ItemIsUserCheckable)
item.setCheckState(QtCore.Qt.Unchecked)
self.listWidget.addItem(item)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
win= myWidget()
signal.signal(signal.SIGINT, signal.SIG_DFL)
win.show()
sys.exit(app.exec_())
Upvotes: 0