如何与 QTableView 和 QComboBox 共享相同的模型
How to share same model with QTableView and QComboBox
榜单:
items = [['Pet', 'Dog'],['Pet', 'Cat'],['Bird','Eagle'],['Bird','Jay'],['Bird','Falcon']]
由分配给 QTableView
和 QComboBox
的 model
使用。
我希望Combobox
只显示"Pet"和"Bird"
而 QTableView
显示:"Dog"、"Eagle" 和 "Jay"。
如何实现?
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_())
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.
为此,您不需要子类化任何模型。您可以直接用字符串列表填充 QComboBox
,也可以使用 QListWidget
来显示动物名称。
为了方便起见,我将您的列表转换成字典:
self.items = [['Pet', 'Dog'],['Pet', 'Cat'],['Bird','Eagle'],['Bird','Jay'],['Bird','Falcon']]
变成
self.myDict={"Pet":['Dog','Cat'],"Bird":['Eagle','Jay','Falcon']}
字典的关键字(Pet,Bird)可以用于QComboBox
。当用户select一个物种(信号:QComboBox.currentIndexChanged
),你显示列表关联到一个QListWidget
.
中的键
这是完整的工作示例:
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_())
可以做你想做的事。您需要为每个视图实现一个单独的 QAbstractProxyModel。组合框应该过滤并仅保留第一列的非重复元素。另一个应将第二行过滤为仅与组合框视图的当前状态相关的行。
如果你这样做,在这个例子中,你最终会得到四个与模型相关的对象:你的数据(在 Model.items)
、你的 Model
和两个代理。
但我不认为这是一个好方法根本。这两个代理都很复杂,完全改变了原来模型的行、列结构。想象一下,当模型第一列中的数据发生变化时,每个代理中处理 dataChanged
信号所需的复杂性。
在实践中,我认为使用三对象版本会更好:一个适当的(稍微详细说明的)数据 class 然后一对 QAbstractItemModels 共享该数据并且每个都专门用于它们支持的视图.可以把它想象成同一文件系统上的一对 QFileSystemModels。
必须向两个 QAbstractItemModel 发送数据更改信号,您将无法享受 Qt 框架自动实现的好处。但是,正如我在上面指出的那样,实际上在任何情况下代理都不会轻易工作。
所以这里有一个基于您的代码的示例,其中的更改尽可能少。请注意,我必须修复很多问题 - 您的导入不起作用,并且您的组合模型为某些角色返回了不正确的数据。
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_())
但这是另一个版本,它更基于您的想法,是我过去使用的模式。这里我们有一个您要求的模型,但提供两个接口,实际上都是 QAbstractItemModels。在这个例子中并没有什么不同,但使单一模型的想法更加清晰。
第一个版本有一个由底层数据表示驱动的数据对象,因此可以非常干净和清晰。但是它需要一个自定义的信号方法来通知两个模型的变化。
第二个版本有一个封装该信令的集成模型对象。但是在真实的系统中,它可能仍然需要连接到一个单独的数据对象——所以实际上这个版本中可能有更多的对象。
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_())
榜单:
items = [['Pet', 'Dog'],['Pet', 'Cat'],['Bird','Eagle'],['Bird','Jay'],['Bird','Falcon']]
由分配给 QTableView
和 QComboBox
的 model
使用。
我希望Combobox
只显示"Pet"和"Bird"
而 QTableView
显示:"Dog"、"Eagle" 和 "Jay"。
如何实现?
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_())
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.
为此,您不需要子类化任何模型。您可以直接用字符串列表填充 QComboBox
,也可以使用 QListWidget
来显示动物名称。
为了方便起见,我将您的列表转换成字典:
self.items = [['Pet', 'Dog'],['Pet', 'Cat'],['Bird','Eagle'],['Bird','Jay'],['Bird','Falcon']]
变成
self.myDict={"Pet":['Dog','Cat'],"Bird":['Eagle','Jay','Falcon']}
字典的关键字(Pet,Bird)可以用于QComboBox
。当用户select一个物种(信号:QComboBox.currentIndexChanged
),你显示列表关联到一个QListWidget
.
这是完整的工作示例:
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_())
可以做你想做的事。您需要为每个视图实现一个单独的 QAbstractProxyModel。组合框应该过滤并仅保留第一列的非重复元素。另一个应将第二行过滤为仅与组合框视图的当前状态相关的行。
如果你这样做,在这个例子中,你最终会得到四个与模型相关的对象:你的数据(在 Model.items)
、你的 Model
和两个代理。
但我不认为这是一个好方法根本。这两个代理都很复杂,完全改变了原来模型的行、列结构。想象一下,当模型第一列中的数据发生变化时,每个代理中处理 dataChanged
信号所需的复杂性。
在实践中,我认为使用三对象版本会更好:一个适当的(稍微详细说明的)数据 class 然后一对 QAbstractItemModels 共享该数据并且每个都专门用于它们支持的视图.可以把它想象成同一文件系统上的一对 QFileSystemModels。
必须向两个 QAbstractItemModel 发送数据更改信号,您将无法享受 Qt 框架自动实现的好处。但是,正如我在上面指出的那样,实际上在任何情况下代理都不会轻易工作。
所以这里有一个基于您的代码的示例,其中的更改尽可能少。请注意,我必须修复很多问题 - 您的导入不起作用,并且您的组合模型为某些角色返回了不正确的数据。
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_())
但这是另一个版本,它更基于您的想法,是我过去使用的模式。这里我们有一个您要求的模型,但提供两个接口,实际上都是 QAbstractItemModels。在这个例子中并没有什么不同,但使单一模型的想法更加清晰。
第一个版本有一个由底层数据表示驱动的数据对象,因此可以非常干净和清晰。但是它需要一个自定义的信号方法来通知两个模型的变化。
第二个版本有一个封装该信令的集成模型对象。但是在真实的系统中,它可能仍然需要连接到一个单独的数据对象——所以实际上这个版本中可能有更多的对象。
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_())