A code below creates a single QListView. Clicking its item should delete it from .model(). While an item gets deleted there is IndexError: list index out of range. What is wrong with the code?
import os,sys
from PyQt4 import QtCore, QtGui
app=QtGui.QApplication(sys.argv)
elements={'Animals':{1:'Bison',2:'Panther',3:'Elephant'},'Birds':{1:'Duck',2:'Hawk',3:'Pigeon'},'Fish':{1:'Shark',2:'Salmon',3:'Piranha'}}
class Model(QtCore.QAbstractListModel):
def __init__(self):
QtCore.QAbstractListModel.__init__(self)
self.items=[]
self.modelDict={}
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.modelDict)
def data(self, index, role):
if index.isValid():
if role==QtCore.Qt.ItemDataRole:
key=str(index.data().toString())
returnedValue=self.modelDict.get(key)
return QtCore.QVariant(returnedValue)
elif role==QtCore.Qt.DisplayRole:
row=index.row()
itemTitle=self.items[row]
return QtCore.QVariant(itemTitle)
def addItems(self):
for key in self.modelDict:
index=QtCore.QModelIndex()
self.beginInsertRows(index, 0, 0)
self.items.append(key)
inst=self.modelDict.get(key)
self.setData(index, QtCore.QVariant(inst), QtCore.Qt.DisplayRole)
self.endInsertRows()
def removeByIndex(self, index):
if index.isValid():
row=index.row()
self.beginRemoveRows(QtCore.QModelIndex(), row, 0)
self.items=[each for i,each in enumerate(self.items[:]) if i!=row]
self.endRemoveRows()
class ListView(QtGui.QListView):
def __init__(self):
super(ListView, self).__init__()
self.model= Model()
self.model.modelDict=elements
self.model.addItems()
self.setModel(self.model)
self.clicked.connect(self.itemClicked)
self.show()
def itemClicked(self, index):
itemTitle=self.model.data(index, QtCore.Qt.DisplayRole).toString()
itemData=self.model.data(index, QtCore.Qt.ItemDataRole).toPyObject()
print 'itemTitle: "%s" itemData: %s'%(itemTitle,itemData)
self.model.removeByIndex(index)
window=ListView()
sys.exit(app.exec_())
You were deleting rows without updating the rowCount; and latterly this led to empty elements where you were not supplying data. This works (note that I have removed the QVariants, for simplicity):
class Model(QtCore.QAbstractListModel):
def __init__(self):
QtCore.QAbstractListModel.__init__(self)
self.items=[]
self.modelDict={}
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.items)
def data(self, index, role):
if index.isValid():
if role==QtCore.Qt.ItemDataRole:
key=str(index.data().toString())
returnedValue=self.modelDict.get(key)
return QtCore.QVariant(returnedValue)
elif role==QtCore.Qt.DisplayRole:
row=index.row()
itemTitle=self.items[row]
return itemTitle
def addItems(self):
for key in self.modelDict:
index=QtCore.QModelIndex()
self.beginInsertRows(index, 0, 0)
self.items.append(key)
inst=self.modelDict.get(key)
self.setData(index, inst, QtCore.Qt.DisplayRole)
self.endInsertRows()
def removeByIndex(self, index):
if index.isValid():
row=index.row()
self.beginRemoveRows(QtCore.QModelIndex(), row, 0)
self.items=[each for i,each in enumerate(self.items[:]) if i!=row]
self.endRemoveRows()
class ListView(QtGui.QListView):
def __init__(self):
super(ListView, self).__init__()
self.model= Model()
self.model.modelDict=elements
self.model.addItems()
self.setModel(self.model)
self.clicked.connect(self.itemClicked)
self.show()
def itemClicked(self, index):
itemTitle=str(self.model.data(index, QtCore.Qt.DisplayRole))
print 'itemTitle: "%s"'%(itemTitle)
self.model.removeByIndex(index)
Related
This code I have taken from https://bravo.hatenablog.jp/entry/2016/01/18/093059 .
In this I want to add header "Quantity" at run time if needed.
Before creating table it should ask for how many headers? If user supply 4 then create table with "Quantity" headers also. Otherwise create default headers (Three).
I have tried it but not found any solution.
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class Model(QAbstractItemModel):
headers = "Toppings", "Udon/Soba", "Hot/Cold"
def __init__(self, parent=None):
super(Model, self).__init__(parent)
self.items = []
def index(self, row, column, parent=QModelIndex()):
return self.createIndex(row, column, None)
def parent(self, child):
return QModelIndex()
def rowCount(self, parent=QModelIndex()):
return len(self.items)
def columnCount(self, parent=QModelIndex()):
return len(self.headers)
def data(self, index, role=Qt.DisplayRole):
if role == Qt.DisplayRole:
try:
return self.items[index.row()][index.column()]
except:
return
return
def headerData(self, section, orientation, role=Qt.DisplayRole):
if role != Qt.DisplayRole:
return
if orientation == Qt.Horizontal:
return self.headers[section]
def addRow(self, topping, menkind, hotcold):
self.beginInsertRows(QModelIndex(), len(self.items), 1)
self.items.append([topping, menkind, hotcold])
self.endInsertRows()
def removeRows(self, rowIndexes):
for row in sorted(rowIndexes, reverse=True):
self.beginRemoveRows(QModelIndex(), row, row + 1)
del self.items[row]
self.endRemoveRows()
def flags(self, index):
return super(Model, self).flags(index) | Qt.ItemIsEditable
def setData(self, index, value, role=Qt.EditRole):
if role == Qt.EditRole:
self.items[index.row()][index.column()] = value
return True
return False
class View(QTreeView):
def __init__(self, parent=None):
super(View, self).__init__(parent)
self.setItemsExpandable(False)
self.setIndentation(0)
self.setSelectionMode(QAbstractItemView.ExtendedSelection)
def drawBranches(self, painter, rect, index):
return
class InputWidget(QWidget):
def __init__(self, parent=None):
super(InputWidget, self).__init__(parent)
layout = QVBoxLayout()
toppings = ("Kitsune", "Tanuki", "Tempura", "Tsukimi", "Meat", "Curry")
self.toppingInput = QComboBox()
for topping in toppings:
self.toppingInput.addItem(topping)
layout.addWidget(self.toppingInput)
self.bgrp = QGroupBox()
udon = QRadioButton('Udon')
udon.setChecked(True)
soba = QRadioButton('Soba')
btnlayout = QHBoxLayout()
btnlayout.addWidget(udon)
btnlayout.addWidget(soba)
self.bgrp.setLayout(btnlayout)
layout.addWidget(self.bgrp)
self.udonsoba = udon, soba
self.bgrp_temp = QGroupBox()
hot = QRadioButton('Warm')
hot.setChecked(True)
cold = QRadioButton('Cold')
btnlayout_temp = QHBoxLayout()
btnlayout_temp.addWidget(hot)
btnlayout_temp.addWidget(cold)
self.bgrp_temp.setLayout(btnlayout_temp)
layout.addWidget(self.bgrp_temp)
self.hotcold = hot, cold
self.addButton = QPushButton('OK')
layout.addWidget(self.addButton)
layout.addStretch()
self.setLayout(layout)
def values(self):
topping = self.toppingInput.currentText()
udonsoba = '?'
for btn in self.udonsoba:
if btn.isChecked():
udonsoba = btn.text()
break
hotcold = '?'
for btn in self.hotcold:
if btn.isChecked():
hotcold = btn.text()
break
return topping, udonsoba, hotcold
class Delegate(QStyledItemDelegate):
def __init__(self, parent=None):
super(Delegate, self).__init__(parent)
def createEditor(self, parent, option, index):
return QLineEdit(parent)
def setEditorData(self, editor, index):
value = index.model().data(index, Qt.DisplayRole)
editor.setText(value)
def setModelData(self, editor, model, index):
model.setData(index, editor.text())
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.view = View(self)
self.model = Model(self)
self.view.setModel(self.model)
self.view.setItemDelegate(Delegate())
self.setCentralWidget(self.view)
self.inputWidget = InputWidget()
self.inputWidget.addButton.clicked.connect(self.addItem)
self.addDock = QDockWidget('Input', self)
self.addDock.setWidget(self.inputWidget)
self.addDock.setAllowedAreas(Qt.AllDockWidgetAreas)
self.addDockWidget(Qt.RightDockWidgetArea, self.addDock)
self.addDock.hide()
toolBar = QToolBar()
self.addToolBar(toolBar)
delButton = QPushButton('Delete')
delButton.clicked.connect(self.removeItems)
toolBar.addWidget(delButton)
self.addButton = QPushButton('Add')
self.addButton.clicked.connect(self.addDock.show)
toolBar.addWidget(self.addButton)
def addItem(self):
self.model.addRow(*self.inputWidget.values())
def selectedRows(self):
rows = []
for index in self.view.selectedIndexes():
if index.column() == 0:
rows.append(index.row())
return rows
def removeItems(self):
self.model.removeRows(self.selectedRows())
def main():
app = QApplication(sys.argv)
w = MainWindow()
w.show()
w.raise_()
app.exec_()
if __name__ == '__main__':
main()
CustomMenu class inherits from QMenu. Its custom setData() method accepts an argument and sets it to a class variable model. I did this because QToolButton, QToolMenu and QAction do not support model/view framework. From what I know aside from all QList-QTable-QTree Views and Trees only the QComboBox does support model.
So the question: would it be possible to extend the functionality of other non-model widgets so they could be used as driven-by-model widgets too?
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys, os
class CustomMenu(QMenu):
def __init__(self):
super(CustomMenu, self).__init__()
self.model=None
def setModel(self, model):
self.model=model
self.pupulate()
def pupulate(self):
for item in self.model.items:
self.addAction(item)
class Model(QAbstractTableModel):
def __init__(self, parent=None, *args):
QAbstractTableModel.__init__(self, parent, *args)
self.items = ['Row0_Column0','Row1_Column0','Row2_Column0']
def flags(self, index):
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
def rowCount(self, QModelIndex):
return len(self.items)
def columnCount(self, QModelIndex):
return 1
def data(self, index, role):
if not index.isValid(): return QVariant()
if role == Qt.DisplayRole:
return QVariant(self.items[index.row()])
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
if index.isValid():
if role == Qt.EditRole:
self.items[index.row()]=value
return True
return False
class MyWindow(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
layout=QVBoxLayout(self)
self.setLayout(layout)
tablemodel=Model(self)
tableView=QTableView()
tableView.horizontalHeader().setStretchLastSection(True)
tableView.setModel(tablemodel)
layout.addWidget(tableView)
combo=QComboBox()
combo.setModel(tablemodel)
layout.addWidget(combo)
toolButton=QToolButton(self)
toolButton.setText('Tool Button')
toolButton.setPopupMode(QToolButton.InstantPopup)
toolButton.setSizePolicy(QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed))
menu=CustomMenu()
menu.setModel(tablemodel)
toolButton.setMenu(menu)
layout.addWidget(toolButton)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
EDITED LATER: An attempt to implement a QDataWidgetMapper():
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys, os
class CustomMenuButton(QWidget):
def __init__(self, parent):
super(CustomMenuButton, self).__init__(parent)
self.dataMapper=QDataWidgetMapper()
layout=QVBoxLayout()
self.setLayout(layout)
toolButton=QToolButton(self)
toolButton.setText('Tool Button')
toolButton.setPopupMode(QToolButton.InstantPopup)
self.menu=QMenu(toolButton)
toolButton.setMenu(self.menu)
def setModel(self, model):
self.dataMapper.setModel(model)
for row in range(model.rowCount()):
action=self.menu.addAction('Item')
self.dataMapper.addMapping(action, row)
class Model(QAbstractTableModel):
def __init__(self, parent=None, *args):
QAbstractTableModel.__init__(self, parent, *args)
self.items = ['Row0_Column0','Row1_Column0','Row2_Column0']
def flags(self, index):
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
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()
if role == Qt.DisplayRole:
return QVariant(self.items[index.row()])
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
if index.isValid():
if role == Qt.EditRole:
self.items[index.row()]=value
self.dataChanged.emit(index, index)
return True
return False
class MyWindow(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
layout=QVBoxLayout(self)
self.setLayout(layout)
tablemodel=Model(self)
tableView=QTableView()
tableView.horizontalHeader().setStretchLastSection(True)
tableView.setModel(tablemodel)
layout.addWidget(tableView)
combo=QComboBox()
combo.setModel(tablemodel)
layout.addWidget(combo)
menuButton=CustomMenuButton(self)
layout.addWidget(menuButton)
menuButton.setModel(tablemodel)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
EDIT 2: Defined a class CustomAction(QAction) with dataChanged = pyqtSignal(QModelIndex,QModelIndex)...
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys, os
class CustomAction(QAction):
dataChanged = pyqtSignal(QModelIndex,QModelIndex)
def __init__(self, name, parent):
super(CustomAction, self).__init__(name, parent)
class CustomMenuButton(QWidget):
def __init__(self, parent):
super(CustomMenuButton, self).__init__(parent)
self.dataMapper=QDataWidgetMapper()
layout=QVBoxLayout()
self.setLayout(layout)
toolButton=QToolButton(self)
toolButton.setText('Tool Button')
toolButton.setPopupMode(QToolButton.InstantPopup)
self.menu=QMenu(toolButton)
toolButton.setMenu(self.menu)
def setModel(self, model):
self.dataMapper.setModel(model)
for row in range(model.rowCount()):
index=model.index(row,0)
itemName=model.data(index, Qt.DisplayRole).toPyObject()
actn=CustomAction(itemName, self.menu)
self.menu.addAction(actn)
self.dataMapper.addMapping(actn, row)
class Model(QAbstractTableModel):
def __init__(self, parent=None, *args):
QAbstractTableModel.__init__(self, parent, *args)
self.items = ['Row0_Column0','Row1_Column0','Row2_Column0']
def flags(self, index):
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
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()
if role == Qt.DisplayRole:
return QVariant(self.items[index.row()])
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
if index.isValid():
if role == Qt.EditRole:
self.items[index.row()]=value
self.dataChanged.emit(index, index)
return True
return False
class MyWindow(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
layout=QVBoxLayout(self)
self.setLayout(layout)
tablemodel=Model(self)
tableView=QTableView()
tableView.horizontalHeader().setStretchLastSection(True)
tableView.setModel(tablemodel)
layout.addWidget(tableView)
combo=QComboBox()
combo.setModel(tablemodel)
layout.addWidget(combo)
menuButton=CustomMenuButton(self)
layout.addWidget(menuButton)
menuButton.setModel(tablemodel)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
This is what the QDataWidgetMapper class was designed for. The following blog has a great tutorial covering exactly how to implement it: http://www.yasinuludag.com/blog/?p=98
EDIT: I should add that QDataWidgetMapper works with QWidget and its subclasses, so QAction would not apply. For classes that are not QWidget or its subclasses, you might have to implement your own custom model-view binding.
With two views:
listView=QtGui.QListView()
tableView==QtGui.QTableView()
I go ahead and define a custom DataModel to be used by both listView and tableView:
class DataModel(QtCore.QAbstractTableModel):
def __init__(self):
QtCore.QAbstractTableModel.__init__(self)
self.data=[['One':'Two'],['Three','Four']]
def data(self, index, role):
row=index.row()
if requested by listView:
return self.data[row][0]
elif requested by tableView:
return self.data[row][1]
Finally assigning the instance of the model to both views:
model=DataModel()
listView.setModel(model)
tableView.setModel(model)
Since both views share the same model there is a chance the model would need to return different values based on what view widget is requesting it. If you take a look at data() method there is fake if/elif statement showing what I need. Is there any way to do this logic from inside of model methods: if requested by ListView... and elif requested by TableView
Edited later:
import os,sys
from PyQt4 import QtCore, QtGui
app=QtGui.QApplication(sys.argv)
elements={'Animals':{0:'Bison',1:'Panther',2:'Elephant'},'Birds':{0:'Duck',1:'Hawk',2:'Pigeon'},'Fish':{0:'Shark',1:'Salmon',2:'Piranha'}}
class DataModel(QtCore.QAbstractTableModel):
def __init__(self):
QtCore.QAbstractTableModel.__init__(self)
self.modelDict={}
self.items=[]
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.items)
def columnCount(self, index=QtCore.QModelIndex()):
return 3
def data(self, index, role):
if not index.isValid() or not (0<=index.row()<len(self.items)): return QtCore.QVariant()
key=str(self.items[index.row()])
column=index.column()
if role==QtCore.Qt.ItemDataRole: return self.modelDict.get(str(index.data().toString()))
if role==QtCore.Qt.DisplayRole:
if column==0 and not self.columnCount():
return key
else:
return self.modelDict.get(key).get(column)
def addItem(self, itemName=None, column=0):
totalItems=self.rowCount()+1
self.beginInsertRows(QtCore.QModelIndex(), totalItems, column)
if not itemName: itemName='Item %s'%self.rowCount()
self.items.append(itemName)
self.endInsertRows()
def buildItems(self):
for key in self.modelDict:
index=QtCore.QModelIndex()
self.addItem(key)
class ProxyTableModel(QtGui.QSortFilterProxyModel):
def __init__(self, parent=None):
super(ProxyTableModel, self).__init__(parent)
def headerData(self, column, orientation, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.TextAlignmentRole:
if orientation == QtCore.Qt.Horizontal:
return QtCore.QVariant(int(QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter))
return QtCore.QVariant(int(QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter))
if role != QtCore.Qt.DisplayRole:
return QtCore.QVariant()
if orientation==QtCore.Qt.Horizontal:
if column==0:
return QtCore.QVariant("Spicie 0")
elif column==1:
return QtCore.QVariant("Spicie 1")
elif column==2:
return QtCore.QVariant("Spicie 2")
return QtCore.QVariant(int(column + 1))
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
mainLayout=QtGui.QHBoxLayout()
self.setLayout(mainLayout)
self.dataModel=DataModel()
self.dataModel.modelDict=elements
self.dataModel.buildItems()
self.proxyModel=ProxyTableModel()
self.proxyModel.setFilterKeyColumn(0)
self.proxyModel.setSourceModel(self.dataModel)
self.viewA=QtGui.QListView()
self.viewA.setModel(self.dataModel)
self.viewA.clicked.connect(self.onClick)
self.viewB=QtGui.QTableView()
self.viewB.setModel(self.proxyModel)
mainLayout.addWidget(self.viewA)
mainLayout.addWidget(self.viewB)
self.show()
def onClick(self):
index=self.viewA.currentIndex()
key=self.dataModel.data(index, QtCore.Qt.DisplayRole)
value=self.dataModel.data(index, QtCore.Qt.ItemDataRole)
self.proxyModel.setFilterRegExp('%s'%key)
print 'onClick(): key: %s'%key
window=Window()
sys.exit(app.exec_())
Done, by hiding the zeroth "key" column:
class DataModel(QtCore.QAbstractTableModel):
def __init__(self):
QtCore.QAbstractTableModel.__init__(self)
self.modelDict={}
self.names=[]
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.names)
def columnCount(self, index=QtCore.QModelIndex()):
return 4
def data(self, index, role):
if not index.isValid() or not (0<=index.row()<len(self.names)): return QtCore.QVariant()
row,col = index.row(),index.column()
if col==0:
if role==QtCore.Qt.DisplayRole:
return self.names[row]
else:
if role==QtCore.Qt.DisplayRole:
return self.modelDict[self.names[row]][col]
def addItem(self, itemName=None, column=0):
totalItems=self.rowCount()+1
self.beginInsertRows(QtCore.QModelIndex(), totalItems, column)
if not itemName: itemName='Item %s'%self.rowCount()
self.names.append(itemName)
self.endInsertRows()
def buildItems(self):
for key in self.modelDict:
index=QtCore.QModelIndex()
self.addItem(key)
class ProxyModel(QtGui.QSortFilterProxyModel):
def __init__(self, parent=None):
super(ProxyModel, self).__init__(parent)
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
mainLayout=QtGui.QHBoxLayout()
self.setLayout(mainLayout)
self.dataModel=DataModel()
self.dataModel.modelDict=elements
self.dataModel.buildItems()
self.proxyModel=ProxyModel()
self.proxyModel.setFilterKeyColumn(0)
self.proxyModel.setSourceModel(self.dataModel)
self.viewA=QtGui.QListView()
self.viewA.setModel(self.dataModel)
self.viewA.clicked.connect(self.onClick)
self.viewB=QtGui.QTableView()
self.viewB.setModel(self.proxyModel)
self.viewB.setColumnHidden(0,True)
mainLayout.addWidget(self.viewA)
mainLayout.addWidget(self.viewB)
self.show()
def onClick(self):
index=self.viewA.currentIndex()
key=self.dataModel.data(index, QtCore.Qt.DisplayRole)
self.proxyModel.setFilterRegExp('%s'%key)
The code below creates a single QListView. An instance of MyClass (it is inherited from QAbstractListModel) is assigned to its self.setModel(self.model). If I click the view I can select the items in a list (so they do exist). But no names of the items displayed. How do I control how the QListView items are displayed?
from PyQt4 import QtCore, QtGui
app=QtGui.QApplication(sys.argv)
class MyClass(QtCore.QAbstractListModel):
def __init__(self):
super(MyClass, self).__init__()
self.elements={'Animals':['Bison','Panther','Elephant'],'Birds':['Duck','Hawk','Pigeon'],'Fish':['Shark','Salmon','Piranha']}
def rowCount(self, index=QtCore.QModelIndex()):
return len(self.elements)
def data(self, index, role=QtCore.Qt.DisplayRole):
print'MyClass.data():',index,role
class ListView(QtGui.QListView):
def __init__(self):
super(ListView, self).__init__()
self.model=MyClass()
self.setModel(self.model)
self.show()
window=ListView()
sys.exit(app.exec_())
I don't know how to use a dictionary for the elements, but using lists:
self.elements=['Bison','Panther','Elephant','Duck','Hawk','Pigeon','Shark','Salmon','Piranha']
You just have to return self.elements[index.row()] in the data() method. For instance:
class MyClass(QtCore.QAbstractListModel):
def __init__(self):
super(MyClass, self).__init__()
self.elements=['Bison','Panther','Elephant','Duck','Hawk','Pigeon','Shark','Salmon','Piranha']
def rowCount(self, index=QtCore.QModelIndex()):
return len(self.elements)
def data(self, index, role=QtCore.Qt.DisplayRole):
print'MyClass.data():',index,role
if index.isValid() and role == QtCore.Qt.DisplayRole:
return QtCore.QVariant(self.elements[index.row()])
else:
return QtCore.QVariant()
A code below create a window with QListView on a left side and QTableView on a right.
Using .setModel() QListView was assigned ListModel and QTableView was assigned TableModel.
On a window start-up only a List View gets populated with the items. A right-table-view gets populated only when left-side-list-view gets clicked.
Question: Why this code crashes? Is it because two models in use in the same time?
import sys, os
from PyQt4 import QtCore, QtGui
app=QtGui.QApplication(sys.argv)
elements={'Animals':{1:'Bison',2:'Panther',3:'Elephant'},'Birds':{1:'Duck',2:'Hawk',3:'Pigeon'},'Fish':{1:'Shark',2:'Salmon',3:'Piranha'}}
class ListModel(QtCore.QAbstractTableModel):
def __init__(self):
QtCore.QAbstractTableModel.__init__(self)
self.items=[]
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.items)
def columnCount(self, index=QtCore.QModelIndex()):
return 1
def data(self, index, role):
if not index.isValid() or not (0<=index.row()<len(self.items)): return QtCore.QVariant()
key=str(self.items[index.row()])
if role==QtCore.Qt.UserRole:
return key
if role==QtCore.Qt.DisplayRole:
return key
def addItem(self, key=None, column=0):
totalItems=self.rowCount()+1
self.beginInsertRows(QtCore.QModelIndex(), totalItems, column)
self.items.append(str(key))
self.endInsertRows()
def buildItems(self):
for key in elements:
self.addItem(key)
class TableModel(QtCore.QAbstractTableModel):
def __init__(self):
QtCore.QAbstractTableModel.__init__(self)
self.items=[]
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.items)
def columnCount(self, index=QtCore.QModelIndex()):
return 4
def data(self, index, role):
if not index.isValid() or not (0<=index.row()<len(self.items)): return QtCore.QVariant()
if role==QtCore.Qt.DisplayRole:
return key
def addItem(self, each=None, column=0):
totalItems=self.rowCount()+1
self.beginInsertRows(QtCore.QModelIndex(), totalItems, column)
self.items.append(str(each))
self.endInsertRows()
def rebuildItems(self, index):
key = index.data(QtCore.Qt.UserRole)
if not key: return
key=str(key.toString())
for each in elements[key]:
self.addItem(str(each))
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
mainLayout=QtGui.QHBoxLayout()
self.setLayout(mainLayout)
self.dataModel=ListModel()
self.dataModel.buildItems()
self.dataModelB=TableModel()
self.viewA=QtGui.QListView()
self.viewA.setModel(self.dataModel)
self.viewA.clicked.connect(self.onClick)
self.viewB=QtGui.QTableView()
self.viewB.setModel(self.dataModelB)
mainLayout.addWidget(self.viewA)
mainLayout.addWidget(self.viewB)
self.show()
def onClick(self, index):
self.viewB.model().rebuildItems(index)
window=Window()
sys.exit(app.exec_())
EDITED LATER:
Below is a fixed code. In the original example the problem was caused by improper usage of .beginInsertRows() method. I mistakenly thought that the last argument to be supplied is a column number. But according to the documentation (thanks to three_pineapples for pointing out) the last argument should be the last row-number to be inserted.
import os,sys
from PyQt4 import QtCore, QtGui
app=QtGui.QApplication(sys.argv)
elements={'Animals':{1:'Bison',2:'Panther',3:'Elephant'},'Birds':{1:'Duck',2:'Hawk',3:'Pigeon'},'Fish':{1:'Shark',2:'Salmon',3:'Piranha'}}
class ListModel(QtCore.QAbstractTableModel):
def __init__(self):
QtCore.QAbstractTableModel.__init__(self)
self.items=[]
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.items)
def columnCount(self, index=QtCore.QModelIndex()):
return 1
def data(self, index, role):
if not index.isValid() or not (0<=index.row()<len(self.items)): return QtCore.QVariant()
key=str(self.items[index.row()])
if role==QtCore.Qt.UserRole:
return key
if role==QtCore.Qt.DisplayRole:
return key
def addItem(self, key=None):
self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount())
self.items.append(str(key))
self.endInsertRows()
def buildItems(self):
for key in elements:
self.addItem(key)
class TableModel(QtCore.QAbstractTableModel):
def __init__(self):
QtCore.QAbstractTableModel.__init__(self)
self.items=[]
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.items)
def columnCount(self, index=QtCore.QModelIndex()):
return 4
def data(self, index, role):
key=str(self.items[index.row()])
column=index.column()
if not index.isValid() or not (0<=index.row()<len(self.items)): return QtCore.QVariant()
if role==QtCore.Qt.DisplayRole:
if not column: return key
else:
print key, column, elements.get(key,{}).get(column)
return elements.get(key,{}).get(column)
def rebuildItems(self, index):
key=index.data(QtCore.Qt.UserRole).toString()
self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount())
self.items.append(key)
self.endInsertRows()
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
mainLayout=QtGui.QHBoxLayout()
self.setLayout(mainLayout)
self.dataModel=ListModel()
self.dataModel.buildItems()
self.dataModelB=TableModel()
self.viewA=QtGui.QListView()
self.viewA.setModel(self.dataModel)
self.viewA.clicked.connect(self.onClick)
self.viewB=QtGui.QTableView()
self.viewB.setModel(self.dataModelB)
mainLayout.addWidget(self.viewA)
mainLayout.addWidget(self.viewB)
self.show()
def onClick(self, index):
self.viewB.model().rebuildItems(index)
window=Window()
sys.exit(app.exec_())
I can't reproduce your crash on PyQt v4.11.1, 32-bit Python 2.7, Windows 8.1.
However, your TableModel implementation is completely broken, so presumably that would explain why it crashes on your Mac?
Specifically:
The signature for beginInsertRows appears to be wrong. It doesn't follow the documentation here (linked to from the QAbstractTableModel page here). The signature is not beginInsertRows(parent, row, column) but rather beginInsertRows(parent, row, numRows).
The value for the row you are inserting to should be self.rowCount() as row indexing starts from 0. So when you have 0 items in your model, you insert to row 0 (the first row). When you have 1 item in your model, you insert into row 1 (the second row), etc.
The TableModel.data() method is broken. Specifically it appears to be missing the line key=str(self.items[index.row()])
My question would be, since you seem to be having trouble with models quite regularly (I feel like I've seen many questions on here from you in regards to implementing a custom model), why aren't you using the predefined Qt model QStandardItemModel which does all of the complex stuff for you? (You don't need to subclass it to use it)
If you want help translating the example you've posted above to using QStandardItemModel, please post a new question. I'm sure either I or someone else will answer it quickly.