-
-
Save danieljfarrell/6e94aa6f8c3c437d901fd15b7b931afb to your computer and use it in GitHub Desktop.
| """ | |
| 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, QtGui | |
| from anytree import Node | |
| 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 appendData(self, rowItems): | |
| """ | |
| TODO: how to insert data, and update tree. | |
| """ | |
| print("appendData") | |
| model = self.tw.model() | |
| rootIdx = model.index(0, 0, QtCore.QModelIndex()) | |
| position = 3 | |
| new = CustomNode("new") | |
| new.addChild(CustomNode(rowItems)) | |
| model.beginInsertRows(rootIdx, position, position) | |
| model.addChild(new, None) | |
| model.endInsertRows() | |
| model.layoutChanged.emit() # must call this otherwise nope! | |
| if __name__ == "__main__": | |
| import sys | |
| import quamash | |
| import asyncio | |
| # hooking into Qt Event loop with Quamash do I don't | |
| # have to bother adding buttons to trigger the row | |
| # insert. | |
| app = QtWidgets.QApplication(sys.argv) | |
| loop = quamash.QEventLoop(app) | |
| asyncio.set_event_loop(loop) | |
| loop.set_debug(True) # optional | |
| app = QtWidgets.QApplication(sys.argv) | |
| mytree = MyTree() | |
| def doAddData(tree): | |
| print("doAddData") | |
| tree.appendData([1,2,3]) | |
| loop.call_later(1.0, doAddData, mytree) | |
| mytree.tw.show() | |
| with loop: | |
| loop.run_forever() |
I cannot check this right now. In the end I abandoned using QTreeView for my application. I simply could not find a nice reusable way of using it! It is such a horrible API. I come from a Cocoa background the NSOutlineView is the equivalent, the API is actually understandable, https://developer.apple.com/documentation/appkit/nsoutlineview.
If you are using Python and just want a quick UI I would recommend taking a look at TraitsUI. They have a wrapper around QTreeView that is it ridiculously easy to use, https://github.com/enthought/traitsui/blob/master/examples/demo/Standard_Editors/TreeEditor_demo.py
Thanks for your quick answer. Can you explain what Codeline 131 to 138 is for?
It's a first attempt and getting a new node into the model object.
Get tree model
model = self.tw.model()
Ask Qt for the an index. This should be for the node you want to new data to be appended.
rootIdx = model.index(0, 0, QtCore.QModelIndex())
We can't just insert a value. All Values must live in a node. So create a node and add the values we want to insert to that as children,
new = CustomNode("new")
new.addChild(CustomNode(rowItems))
Follow the formaisied if telling Qt we are mutating the data structure,
model.beginInsertRows(rootIdx, position, position)
model.addChild(new, None)
model.endInsertRows()
model.layoutChanged.emit() # must call this otherwise nope!
In the above I hardcoded position = 3. This is clearly wrong. position should be the index in the parent where you want to insert the new node.
Hi,
I'm trying to understand your example. I don't understand how the index method can be called in the CustomModel class. How can I get the index of an item in a dictionary?
Thanks