Created
April 29, 2018 15:03
-
-
Save nbassler/342fc56c42df27239fa5276b79fca8e6 to your computer and use it in GitHub Desktop.
PyQt5 TreeView with QAbstractItemModel
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Reworked code based on | |
http://trevorius.com/scrapbook/uncategorized/pyqt-custom-abstractitemmodel/ | |
Adapted to Qt5 and fixed column/row bug. | |
TODO: handle changing data. | |
""" | |
import sys | |
from PyQt5 import QtCore, QtWidgets | |
class CustomNode(object): | |
def __init__(self, data): | |
self._data = data | |
if type(data) == tuple: | |
self._data = list(data) | |
if type(data) is str or not hasattr(data, '__getitem__'): | |
self._data = [data] | |
self._columncount = len(self._data) | |
self._children = [] | |
self._parent = None | |
self._row = 0 | |
def data(self, column): | |
if column >= 0 and column < len(self._data): | |
return self._data[column] | |
def columnCount(self): | |
return self._columncount | |
def childCount(self): | |
return len(self._children) | |
def child(self, row): | |
if row >= 0 and row < self.childCount(): | |
return self._children[row] | |
def parent(self): | |
return self._parent | |
def row(self): | |
return self._row | |
def addChild(self, child): | |
child._parent = self | |
child._row = len(self._children) | |
self._children.append(child) | |
self._columncount = max(child.columnCount(), self._columncount) | |
class CustomModel(QtCore.QAbstractItemModel): | |
def __init__(self, nodes): | |
QtCore.QAbstractItemModel.__init__(self) | |
self._root = CustomNode(None) | |
for node in nodes: | |
self._root.addChild(node) | |
def rowCount(self, index): | |
if index.isValid(): | |
return index.internalPointer().childCount() | |
return self._root.childCount() | |
def addChild(self, node, _parent): | |
if not _parent or not _parent.isValid(): | |
parent = self._root | |
else: | |
parent = _parent.internalPointer() | |
parent.addChild(node) | |
def index(self, row, column, _parent=None): | |
if not _parent or not _parent.isValid(): | |
parent = self._root | |
else: | |
parent = _parent.internalPointer() | |
if not QtCore.QAbstractItemModel.hasIndex(self, row, column, _parent): | |
return QtCore.QModelIndex() | |
child = parent.child(row) | |
if child: | |
return QtCore.QAbstractItemModel.createIndex(self, row, column, child) | |
else: | |
return QtCore.QModelIndex() | |
def parent(self, index): | |
if index.isValid(): | |
p = index.internalPointer().parent() | |
if p: | |
return QtCore.QAbstractItemModel.createIndex(self, p.row(), 0, p) | |
return QtCore.QModelIndex() | |
def columnCount(self, index): | |
if index.isValid(): | |
return index.internalPointer().columnCount() | |
return self._root.columnCount() | |
def data(self, index, role): | |
if not index.isValid(): | |
return None | |
node = index.internalPointer() | |
if role == QtCore.Qt.DisplayRole: | |
return node.data(index.column()) | |
return None | |
class MyTree(): | |
""" | |
""" | |
def __init__(self): | |
self.items = [] | |
# Set some random data: | |
for i in 'abc': | |
self.items.append(CustomNode(i)) | |
self.items[-1].addChild(CustomNode(['d', 'e', 'f'])) | |
self.items[-1].addChild(CustomNode(['g', 'h', 'i'])) | |
self.tw = QtWidgets.QTreeView() | |
self.tw.setModel(CustomModel(self.items)) | |
def add_data(self, data): | |
""" | |
TODO: how to insert data, and update tree. | |
""" | |
# self.items[-1].addChild(CustomNode(['1', '2', '3'])) | |
# self.tw.setModel(CustomModel(self.items)) | |
if __name__ == "__main__": | |
app = QtWidgets.QApplication(sys.argv) | |
mytree = MyTree() | |
mytree.tw.show() | |
sys.exit(app.exec_()) |
Hello,
I have spent the whole afternoon studying your code and I still understand this :
How does the QModelIndex keep a reference to the underlying data.
For example if I want the retrieve the row = 2, col = 3 index, I would go for :
cm = CustomModel()
theQModelIndex = cm.index(2,3)
myData = cm.data(theQModelIndex, QtCore.Qt.DisplayRole)
And what I don't understand is here at the index.internalPointer()
def data(self, index, role):
if not index.isValid():
return None
node = index.internalPointer() # How does index.internalPointer() reference my node, I don't see anylink created before ???
if role == QtCore.Qt.DisplayRole:
return node.data(index.column())
return None
It just seems like magic to me !
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Does this implementation handle drag and drop?