Qt modelview: models
Models are where you store your data. The vast majority of the time you'll likely want to use a QStandardItemModel
, but there are also
other options available:
QFilesystemModel
QStringListModel
- ... and more ...
Basics
MVC provides a useful abstraction between your GUI and your program's logic.
- data is written to the model
- a view renders the information in the model.
It makes it easier to test your logic independently of your UI, and test your UI independently of your logic. Also, it makes it trivial to create different views of your data.
Example Table Model with Icons/Descriptions
from Qt import QtCore, QtGui # Build the Model model = QtGui.QStandardItemModel() item_A = QtGui.QStandardItem() item_A.setData('aaaaaaaaaaaaaaaaa', QtCore.Qt.DisplayRole) # text item_A.setData('/path/image_A.png', QtCore.Qt.DecorationRole) # icon/pixmap/color item_A.setData('The A item ......', QtCore.Qt.ToolTipRole) # tooltip of the item item_B = QtGui.QStandardItem('b') model.setItem(0, 0, item_A) model.setItem(1, 0, item_B) # View the Model tree = QtWidgets.QTreeView() tree.setModel(model)Example Tree Model
model = QtGui.QStandardItemModel() item_A_0 = QtGui.QStandardItem('a-0') item_A_1 = QtGui.QStandardItem('a-1') item_A_2 = QtGui.QStandardItem('a-2') model.setItem(0, 0, item_A_0) item_A_0.setChild(0, 0, item_A_1) item_A_1.setChild(0, 0, item_A_2)
Custom Models
You'll very quickly find that rather than directly using primitive objects it is much more useful to create subclasses of models specific to your needs.
Note that:
- rows/columns can be hidden in views
- headers cannot be changed in views (without creating a new model)
- items can be nested, but column-headers remain the same for each nested row
Custom Model Example
class ContactsModel(QtGui.QAbstractItemModel): columns = ('firstname', 'lastname', 'cell', 'email', 'imagepath') def __init__(self): super(ContactsModel, self).__init__() def clear(self): super(ContactsModel, self).clear() self._setup_headers() def _setup_headers(self): self.setColumnCount(len(self.columns)) self.setHorizontalHeaderLabels(self.columns) def load_from_contactslist(self, contacts): # ...
Moving Rows
moveRow example
I have not yet successfully performed this yet..
takeRow/insertRow example
row_items = model.takeRow(0) model.insertRow(0, row_items)
Signals
Call Chart
The cases where
QStandardItemView
methods are/are-not called may surprise you.
method called not called model.dataChanged.emit()
model.setItem(x, y, item)
model.item(x, y).setData(z)
model.insertRow(col, item)
model.appendRow(col, item)
model.removeRow()
model.endInsertRow()
model.beginInsertRow()
???
model.setRowCount()
increases row countmodel.insertRow()
is calledmodel.setItem()
adds an item to a new rowmodel.appendRow()
is called
model.beginRemoveRows()
model.beginRemoveRows()
model.endRemoveRow()
model.endRemoveRows()
???
model.removeRow()
model.removeRows()
model.itemChanged()
???
- When item is created, then added then added to model using
model.setItem()
- Modifying an item that already exists in model (ex:
model.item(x, y).setData(z)
)Best Practices
Keep away from Model Signals
Instead of relying on Qt's model signals (which seem unpredictable), I usually implement my own.
These signals can be silenced without interfering with model or view implementations.
Template Model BaseClass
class Model(QtGui.QStandardItemModel): model_items_changed = QtCore.Signal() # emitted any time an item in the model changes (insertRow, removeRows, dataChanged, ...) row_inserted = QtCore.Signal(row) # emitted insertRow/insertRows (new row has items when called) row_removed = QtCore.Signal(row) # emitted when row removed (not during clear) def __init__(self): super(Model, self).__init__() def insertRow(self, row, item): parent = self.index(-1, -1) first = row last = row # insert row self.beginInsertRows(parent, first, last) super(Model, self).insertRow(row, item) self.endInsertRows() # emit signals self.row_inserted.emit(row) self.model_items_changed.emit() # # NOTE: QStandardItemModel does not override insertRows() # def removeRow(self, row, *args): # get args parent = self.index(-1, -1) if len(args) > 0: parent = args[0] first = row last = row # remove row self.beginRemoveRows(parent, first, last) super(Model, self).removeRow(row, *args) self.endRemoveRows() # emit signals self.row_removed.emit(row) self.model_items_changed.emit() def removeRows(self, row, count, *args) # get args parent = self.index(-1, -1) if len(args) > 0: parent = args[0] first = row last = row + count # remove row self.beginRemoveRows(parent, first, last) super(Model, self).removeRow(row, count, *args) self.endRemoveRows() # emit signals for row in (row, row + count): self.row_removed.emit(row) self.model_items_changed.emit() def _handle_dataChanged(self): self.model_items_changed.emit()Use QModelIndexes instead of QStandardItems
Use
model.setData(modelindex, 'data', QtCore.Qt.DisplayRole)
instead of using the QStandardItem directly. I have had issues with QStandardItems getting garbage collected.See https://stackoverflow.com/questions/59012535/pyside2-model-contents-disappearing