Issues with mapping QSqlTableModel to QTreeView using QAbstractProxyModel - qt

I am trying to implement a QAbstractProxyModel that maps an SqlTableModel to a tree-like data structure. The table has a column called parent_id, whose value is added to the createIndex call as a third argument. The question is similar to this user's post, only that I am working in Python not in C++.
The TreeView loads correctly:
But when I try to expand an item, the application crashes. Debugging tells me that there seems to be an infinite loop of index, rowCount and mapToSource being called.
I am at the end of my wits. Do you have any ideas? See the MWE below.
from __future__ import annotations
from PySide6.QtWidgets import QGridLayout
from PySide6.QtWidgets import QTreeView
from PySide6.QtWidgets import QApplication
from PySide6.QtWidgets import QMainWindow
from PySide6.QtWidgets import QWidget
from PySide6.QtCore import QModelIndex
from PySide6.QtCore import Qt
from PySide6.QtCore import Slot
from PySide6.QtCore import QAbstractProxyModel
from PySide6.QtSql import QSqlDatabase
from PySide6.QtSql import QSqlQuery
from PySide6.QtSql import QSqlTableModel
class CustomTreeModel(QAbstractProxyModel):
def __init__(self, database: str, parent: QWidget = None):
QAbstractProxyModel.__init__(self, parent)
sourceModel = QSqlTableModel(parent, database)
sourceModel.setTable('test')
sourceModel.select()
self.setSourceModel(sourceModel)
def flags(self, proxyIndex: QModelIndex) -> Qt.ItemFlags:
return Qt.ItemIsEnabled | Qt.ItemIsEditable
def data(self, proxyIndex: QModelIndex, role: int):
print("data")
if proxyIndex.isValid:
sourceIndex = self.mapToSource(proxyIndex)
return sourceIndex.data(role)
return None
def index(
self,
row: int,
column: int,
parentIndex: QModelIndex
) -> QModelIndex:
print("index")
if row < 0 and column < 0:
return QModelIndex()
parentId = parentIndex.internalPointer()
return self.createIndex(row, column, parentId)
def mapFromSource(self, sourceIndex: QModelIndex) -> QModelIndex:
print("mapFromSource")
if self.isRootItem(sourceIndex):
return QModelIndex()
if sourceIndex.column() == 0:
sourceId = sourceIndex.data()
else:
sourceId = sourceIndex.siblingAtColumn(0).data()
parentId = self.getParentId(sourceId)
childIds = self.getChildIds(parentId)
row = childIds.index(sourceId)
column = sourceIndex.column()
proxyIndex = self.createIndex(row, column, parentId)
return proxyIndex
def mapToSource(self, proxyIndex: QModelIndex) -> QModelIndex:
print("mapToSource")
if self.isRootItem(proxyIndex):
return QModelIndex()
parentId = proxyIndex.internalPointer()
childIds = self.getChildIds(parentId)
rowId = childIds[proxyIndex.row()]
rowIds = self.getAllIds()
sourceRow = rowIds.index(rowId)
sourceColumn = proxyIndex.column()
sourceIndex = self.sourceModel().index(sourceRow, sourceColumn)
return sourceIndex
def rowCount(self, parentIndex: QModelIndex) -> int:
print("rowCount")
if parentIndex.column() > 0:
return 0
parentId = parentIndex.internalPointer()
childIds = self.getChildIds(parentId)
return len(childIds)
def columnCount(self, parentIndex: QModelIndex) -> int:
print("columnCount")
if parentIndex.column() > 0:
return 0
numColumns = self.sourceModel().columnCount(parentIndex)
return numColumns
def parent(self, childIndex: QModelIndex) -> QModelIndex:
print("parent")
if childIndex.column() > 0:
return QModelIndex()
sourceIndex = self.mapToSource(childIndex)
childId = sourceIndex.siblingAtColumn(0).data()
parentId = self.getParentId(childId)
if not parentId:
return QModelIndex()
parentParentId = self.getParentId(parentId)
parentIds = self.getChildIds(parentParentId)
parentRow = parentIds.index(parentId)
parentIndex = self.createIndex(parentRow, 0, parentId)
return parentIndex
def getParentId(self, childId: str) -> str | None:
table = self.sourceModel().tableName()
query = QSqlQuery()
query.prepare(f"""
SELECT parent_id
FROM {table}
WHERE id=?
""")
query.addBindValue(childId)
query.exec_()
if query.first():
parentId = query.value(0)
return parentId if parentId else None
return None
def hasChildren(self, parentIndex: QModelIndex) -> bool:
if parentIndex.column() > 0:
return False
parentId = parentIndex.internalPointer()
childIds = self.getChildIds(parentId)
return len(childIds) > 0
def getAllIds(self) -> list[str]:
table = self.sourceModel().tableName()
query = QSqlQuery()
query.prepare(f"""
SELECT id
FROM {table}
""")
query.exec_()
ids = []
while query.next():
ids.append(query.value(0))
return ids
def getChildIds(self, parentId: str | None) -> list[str]:
table = self.sourceModel().tableName()
query = QSqlQuery()
if not parentId or parentId == '':
query.prepare(f"""
SELECT id
FROM {table}
WHERE parent_id IS NULL OR parent_id=''
""")
else:
query.prepare(f"""
SELECT id
FROM {table}
WHERE parent_id=?""")
query.addBindValue(parentId)
query.exec_()
childIds = []
while query.next():
childIds.append(query.value(0))
return childIds
def isRootItem(self, index: QModelIndex):
return index.row() == -1 and index.column() == -1
class CustomTreeWidget(QWidget):
def __init__(self, parent: QWidget = None):
QWidget.__init__(self, parent)
self.model: CustomTreeModel
self.view = QTreeView(self)
layout = QGridLayout(self)
layout.addWidget(self.view)
self.setLayout(layout)
#Slot()
def setDatabase(self):
database = QSqlDatabase.database()
model = CustomTreeModel(database, self)
self.view.setModel(model)
self.model = model
def initTestDatabase():
query = QSqlQuery()
query.prepare("""
CREATE TABLE test (
"id" TEXT,
"text" TEXT,
"parent_id" TEXT,
PRIMARY KEY("id")
);
""")
query.exec_()
query = QSqlQuery()
query.prepare("""
INSERT INTO test (
id, text, parent_id)
VALUES
(?, ?, ?),
(?, ?, ?),
(?, ?, ?),
(?, ?, ?);
""")
query.addBindValue("ID101")
query.addBindValue("Text")
query.addBindValue(None)
query.addBindValue("ID102")
query.addBindValue("Text")
query.addBindValue("ID101")
query.addBindValue("ID103")
query.addBindValue("Text")
query.addBindValue("ID101")
query.addBindValue("ID104")
query.addBindValue("Text")
query.addBindValue(None)
query.exec_()
if __name__ == "__main__":
projectDb = QSqlDatabase.addDatabase("QSQLITE")
projectDb.setDatabaseName(":memory:")
projectDb.open()
initTestDatabase()
app = QApplication()
mainWindow = QMainWindow()
widget = CustomTreeWidget(mainWindow)
widget.setDatabase()
mainWindow.setCentralWidget(widget)
mainWindow.showMaximized()
app.exec_()

Related

QAbstractItemModel checkboxes are not checkable

Why are the checkboxes in my TreeView not checkable?
I think there is an issue with the index() and parent() methods, but do not know how to fix it.
setData() also never called...
Any help is really appreciated...
import logging
import sys
from PySide6 import QtCore, QtWidgets
from PySide6.QtCore import QSortFilterProxyModel, QPersistentModelIndex
class DBObject:
def __init__(self, name, parent, children=None, is_checkable=False):
self.name = name
self.parent = parent
self.children = children or list()
self.is_checkable = is_checkable
def __repr__(self):
return f"name: {self.name}, parent: {self.parent.name if self.parent is not None else '-'}"
class Model(QtCore.QAbstractItemModel):
def __init__(self, parent=None):
super().__init__(parent)
self._root = DBObject("root", None)
self.newData()
self.checks = {}
def checkState(self, index):
if index in self.checks.keys():
return self.checks[index]
else:
return QtCore.Qt.CheckState.Unchecked
def newData(self):
items = ["foo", "bar", "baz"]
for x in items:
child = DBObject(x + "0", self._root)
self._root.children.append(child)
for y in items:
child.children.append(DBObject(y + "1", child, None, True))
def columnCount(self, parent=QtCore.QModelIndex()):
return 1
def rowCount(self, parent=QtCore.QModelIndex()):
if not parent.isValid():
return 1
parentItem = parent.internalPointer()
rowCount = len(parentItem.children)
logging.info(f"rowCount({parentItem}): rowCount={rowCount}")
return rowCount
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
item = index.internalPointer()
parentItem = item.parent
logging.info(f"parent({item}): parent={parentItem}")
if parentItem is None:
return QtCore.QModelIndex()
else:
if parentItem.parent is None:
return self.createIndex(0, 0, parentItem)
else:
return self.createIndex(parentItem.parent.children.index(parentItem), 0, parentItem)
def index(self, row, column, parent=QtCore.QModelIndex()):
if not parent.isValid():
if row != 0 or column != 0:
return QtCore.QModelIndex()
else:
logging.info(f"index({row}, {column}, None): index={self._root}")
return self.createIndex(0, 0, self._root)
parentItem = parent.internalPointer()
if 0 <= row < len(parentItem.children):
logging.info(f"index({row}, {column}, {parentItem}): index={parentItem.children[row]}")
return self.createIndex(row, column, parentItem.children[row])
else:
logging.info(f"index({row}, {column}, {parentItem}): index=None")
return QtCore.QModelIndex()
def data(self, index, role=QtCore.Qt.ItemDataRole.DisplayRole):
row = index.row()
col = index.column()
if not index.isValid():
return None
item = index.internalPointer()
if role == QtCore.Qt.ItemDataRole.CheckStateRole and col == 0 and item.is_checkable:
return QtCore.Qt.CheckState.Checked
if role == QtCore.Qt.ItemDataRole.DisplayRole:
return item.name
else:
return None
def setData(self, index, value, role=QtCore.Qt.ItemDataRole.EditRole):
if not index.isValid():
return False
if role == QtCore.Qt.ItemDataRole.CheckStateRole:
self.checks[QPersistentModelIndex(index)] = value
return True
return False
class ProxyModel(QSortFilterProxyModel):
def __init__(self, parent=None):
super().__init__(parent)
self.setFilterKeyColumn(0)
self.setRecursiveFilteringEnabled(True)
def flags(self, index):
if not index.isValid():
return QtCore.Qt.ItemFlag.NoItemFlags
return (
QtCore.Qt.ItemFlag.ItemIsEnabled
| QtCore.Qt.ItemFlag.ItemIsSelectable)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setMinimumSize(640, 480)
centralWidget = QtWidgets.QWidget(self)
self.setCentralWidget(centralWidget)
layout = QtWidgets.QVBoxLayout(centralWidget)
self._treeView = QtWidgets.QTreeView(self)
layout.addWidget(self._treeView)
self._model = Model()
self._proxyModel = ProxyModel()
self._proxyModel.setSourceModel(self._model)
self._treeView.setModel(self._proxyModel)
# self._proxyModel.setFilterFixedString("bar1")
button = QtWidgets.QPushButton("Add")
layout.addWidget(button)
button.clicked.connect(self._Clicked)
def _Clicked(self):
self._model.newData()
self._treeView.expandAll()
def main():
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
app.exec()
if __name__ == "__main__":
main()
There are various issues with your code:
the most important one is that you're not returning the ItemIsUserCheckable flag;
you're overriding the flag() in the proxy model (uselessly, by the way, since you're just returning the default flags of QAbstractItemModel);
data() always returns Checked no matter its value;
setData() should always emit dataChanged (as the documentation points out);
it's not really clear why you return a QModelIndex() if the top level item has row or column greater than 0;
Change the flag() behavior of the source model, remove that of the proxy (or implement it properly, using the default implementation), and correct both data() and setData().
class Model(QtCore.QAbstractItemModel):
def __init__(self, parent=None):
super().__init__(parent)
self._root = DBObject("root", None)
self.newData()
self.checks = {}
def newData(self):
items = ["foo", "bar", "baz"]
for x in items:
child = DBObject(x + "0", self._root)
self._root.children.append(child)
for y in items:
child.children.append(DBObject(y + "1", child, None, True))
def columnCount(self, parent=QtCore.QModelIndex()):
return 1
def flags(self, index):
flags = super().flags(index)
item = index.internalPointer()
if item and item.is_checkable:
flags |= QtCore.Qt.ItemIsUserCheckable
return flags
def rowCount(self, parent=QtCore.QModelIndex()):
if not parent.isValid():
return 1
return len(parent.internalPointer().children)
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
item = index.internalPointer()
parentItem = item.parent
if parentItem is None:
return QtCore.QModelIndex()
if parentItem.parent is None:
return self.createIndex(0, 0, parentItem)
else:
return self.createIndex(
parentItem.parent.children.index(parentItem), 0, parentItem)
def index(self, row, column, parent=QtCore.QModelIndex()):
if not parent.isValid():
if row != 0 or column != 0:
return QtCore.QModelIndex()
else:
return self.createIndex(0, 0, self._root)
parentItem = parent.internalPointer()
if 0 <= row < len(parentItem.children):
return self.createIndex(row, column, parentItem.children[row])
return QtCore.QModelIndex()
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return None
item = index.internalPointer()
col = index.column()
if role == QtCore.Qt.CheckStateRole and col == 0 and item.is_checkable:
return self.checks.get(QPersistentModelIndex(index), QtCore.Qt.Unchecked)
if role == QtCore.Qt.DisplayRole:
return item.name
def setData(self, index, value, role=QtCore.Qt.EditRole):
if not index.isValid():
return False
if role == QtCore.Qt.CheckStateRole:
pIndex = QPersistentModelIndex(index)
if self.checks.get(pIndex) != value:
self.checks[pIndex] = value
self.dataChanged.emit(index, index)
return True
return False
Also:
consider that models must be as fast as possible, so you should avoid unnecessary variable definitions (unless they really help coding and readability) and improve code logic;
avoid unnecessary else: blocks that are implicit in the function flow (especially if used to just return None at the end of a function);

Qt for Python: Qtreeview from a Dictionary

Using Qt's 'Simple Tree Model Example' as a basis the following builds most of the QTreeView from the given dictionary.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from PySide2.QtWidgets import QApplication, QWidget, QGroupBox, QHBoxLayout, QVBoxLayout, QTreeView
from PySide2.QtCore import Qt, QAbstractItemModel, QModelIndex
class GUIWindow(QWidget):
def __init__(self, parent=None):
super(GUIWindow, self).__init__(parent)
""" Setup UI """
self.setWindowTitle("QTreeView from Dictionary")
groupbox_model = QGroupBox('TreeView') # Create a Group Box for the Model
hbox_model = QHBoxLayout() # Create a Horizontal layout for the Model
vbox = QVBoxLayout() # Create a Vertical layout for the Model Horizontal layout
tree_view = QTreeView() # Instantiate the View
headers = ["Dictionary"]
tree = {'Root': {"Level_1": {"Item_1": 1.10, "Item_2": 1.20, "Item_3": 1.30},
"Level_2": {"SubLevel_1":
{"SubLevel_1_item1": 2.11, "SubLevel_1_Item2": 2.12, "SubLevel_1_Item3": 2.13},
"SubLevel_2":
{"SubLevel_2_Item1": 2.21, "SubLevel_2_Item2": 2.22, "SubLevel_2_Item3": 2.23}},
"Level_3": {"Item_1": 3.10, "Item_2": 3.20, "Item_3": 3.30}}}
# Set the models
model = TreeModel(headers, tree)
tree_view.setModel(model)
hbox_model.addWidget(tree_view) # Add the Widget to the Model Horizontal layout
groupbox_model.setLayout(hbox_model) # Add the hbox_model to layout of group box
vbox.addWidget(groupbox_model) # Add groupbox elements to vbox
self.setLayout(vbox)
class TreeModel(QAbstractItemModel):
def __init__(self, header, data, parent=None):
super(TreeModel, self).__init__(parent)
""" subclassing the standard interface item models must use and
implementing index(), parent(), rowCount(), columnCount(), and data()."""
rootData = header
self.rootItem = TreeNode(rootData)
level = 0
self.createData(data, level)
def createData(self, data, level):
parents = [self.rootItem]
sublevel = 2
if type(data) == dict:
level += 1
for key, values in data.items():
if level > sublevel:
parents.append(parents[-1].child(parents[-1].childCount() - 1))
if level > sublevel + 1:
sublevel += 1
if level > sublevel:
parents.append(parents[-1].child(parents[-1].childCount() - 1))
# Append a new item to the current parent's list of children
parent_item = parents[-1]
parent_item.insertChildren(parent_item.childCount())
parent_item.child(parent_item.childCount() - 1).setData([key])
self.createData(values, level) # Recursion to iterate through nested dict
sublevel += 1
def index(self, row, column, index=QModelIndex()):
""" Returns the index of the item in the model specified by the given row, column and parent index """
if not self.hasIndex(row, column, index):
return QModelIndex()
if not index.isValid():
item = self.rootItem
else:
item = index.internalPointer()
child = item.child(row)
if child:
return self.createIndex(row, column, child)
return QModelIndex()
def parent(self, index):
""" Returns the parent of the model item with the given index
If the item has no parent, an invalid QModelIndex is returned """
if not index.isValid():
return QModelIndex()
item = index.internalPointer()
if not item:
return QModelIndex()
parent = item.parentItem
if parent == self.rootItem:
return QModelIndex()
else:
return self.createIndex(parent.childNumber(), 0, parent)
def rowCount(self, index=QModelIndex()):
""" Returns the number of rows under the given parent
When the parent is valid it means that rowCount is returning the number of children of parent """
parent = self.getItem(index)
return parent.childCount()
def columnCount(self, index=QModelIndex()):
""" Returns the number of columns for the children of the given parent """
if index.isValid():
return index.internalPointer().columnCount()
else:
return self.rootItem.columnCount()
def data(self, index, role=Qt.DisplayRole):
""" Returns the data stored under the given role for the item referred to by the index """
if index.isValid() and role == Qt.DisplayRole:
return index.internalPointer().data(index.column())
elif not index.isValid():
return self.rootItem.getData()
def getItem(self, index):
""" Retrieves the tree node with a given index """
if index.isValid():
item = index.internalPointer()
if item:
return item
return self.rootItem
def headerData(self, section, orientation, role):
""" Returns the data for the given role and section in the header with the specified orientation """
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.rootItem.data(section)
class TreeNode(object):
def __init__(self, data, parent=None):
self.parentItem = parent
self.itemData = data
self.children = []
def child(self, row):
return self.children[row]
def childCount(self):
return len(self.children)
def childNumber(self):
if self.parentItem:
return self.parentItem.children.index(self)
return 0
def columnCount(self):
return 1
def data(self, column):
return self.itemData[column]
def insertChildren(self, position):
item = TreeNode([1], self)
self.children.insert(position, item)
return True
def parent(self):
return self.parentItem
def setData(self, value):
self.itemData[0] = value
return True
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyle("plastique") # ("cleanlooks")
form = GUIWindow()
form.show()
sys.exit(app.exec_())
Is the above a good approach?
And what code (required/changes) would be necessary to:
Get 'Root' as the top level?
Show the 'values' e.g. 1.10 for key 'Item_1' as the child of 'Item_1'?
Appreciate suggestions.
Solved - If interested the full code that resolves the 2 questions is shown below.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from PySide2.QtWidgets import QApplication, QWidget, QGroupBox, QHBoxLayout, QVBoxLayout, QTreeView
from PySide2.QtCore import Qt, QAbstractItemModel, QModelIndex
class GUIWindow(QWidget):
def __init__(self, parent=None):
super(GUIWindow, self).__init__(parent)
""" Setup UI """
self.setWindowTitle("QTreeView from Dictionary")
groupbox_model = QGroupBox('TreeView') # Create a Group Box for the Model
hbox_model = QHBoxLayout() # Create a Horizontal layout for the Model
vbox = QVBoxLayout() # Create a Vertical layout for the Model Horizontal layout
tree_view = QTreeView() # Instantiate the View
headers = ["Dictionary Keys", "Dictionary Values"]
tree = {'Root': {"Level_1": {"Item_1": 1.10, "Item_2": 1.20, "Item_3": 1.30},
"Level_2": {"SubLevel_1":
{"SubLevel_1_item1": 2.11, "SubLevel_1_Item2": 2.12, "SubLevel_1_Item3": 2.13},
"SubLevel_2":
{"SubLevel_2_Item1": 2.21, "SubLevel_2_Item2": 2.22,
"SubLevel_2_Item3": 2.23}},
"Level_3": {"Item_1": 3.10, "Item_2": 3.20, "Item_3": 3.30}}}
# Set the models
model = TreeModel(headers, tree)
tree_view.setModel(model)
tree_view.expandAll()
tree_view.resizeColumnToContents(0)
hbox_model.addWidget(tree_view) # Add the Widget to the Model Horizontal layout
groupbox_model.setLayout(hbox_model) # Add the hbox_model to layout of group box
vbox.addWidget(groupbox_model) # Add groupbox elements to vbox
self.setLayout(vbox)
class TreeModel(QAbstractItemModel):
def __init__(self, headers, data, parent=None):
super(TreeModel, self).__init__(parent)
""" subclassing the standard interface item models must use and
implementing index(), parent(), rowCount(), columnCount(), and data()."""
rootData = [header for header in headers]
self.rootItem = TreeNode(rootData)
indent = -1
self.parents = [self.rootItem]
self.indentations = [0]
self.createData(data, indent)
def createData(self, data, indent):
if type(data) == dict:
indent += 1
position = 4 * indent
for dict_keys, dict_values in data.items():
if position > self.indentations[-1]:
if self.parents[-1].childCount() > 0:
self.parents.append(self.parents[-1].child(self.parents[-1].childCount() - 1))
self.indentations.append(position)
else:
while position < self.indentations[-1] and len(self.parents) > 0:
self.parents.pop()
self.indentations.pop()
parent = self.parents[-1]
parent.insertChildren(parent.childCount(), 1, parent.columnCount())
parent.child(parent.childCount() - 1).setData(0, dict_keys)
if type(dict_values) != dict:
parent.child(parent.childCount() - 1).setData(1, str(dict_values))
self.createData(dict_values, indent)
def index(self, row, column, index=QModelIndex()):
""" Returns the index of the item in the model specified by the given row, column and parent index """
if not self.hasIndex(row, column, index):
return QModelIndex()
if not index.isValid():
item = self.rootItem
else:
item = index.internalPointer()
child = item.child(row)
if child:
return self.createIndex(row, column, child)
return QModelIndex()
def parent(self, index):
""" Returns the parent of the model item with the given index
If the item has no parent, an invalid QModelIndex is returned """
if not index.isValid():
return QModelIndex()
item = index.internalPointer()
if not item:
return QModelIndex()
parent = item.parentItem
if parent == self.rootItem:
return QModelIndex()
else:
return self.createIndex(parent.childNumber(), 0, parent)
def rowCount(self, index=QModelIndex()):
""" Returns the number of rows under the given parent
When the parent is valid it means that rowCount is returning the number of children of parent """
if index.isValid():
parent = index.internalPointer()
else:
parent = self.rootItem
return parent.childCount()
def columnCount(self, index=QModelIndex()):
""" Returns the number of columns for the children of the given parent """
return self.rootItem.columnCount()
def data(self, index, role=Qt.DisplayRole):
""" Returns the data stored under the given role for the item referred to by the index """
if index.isValid() and role == Qt.DisplayRole:
return index.internalPointer().data(index.column())
elif not index.isValid():
return self.rootItem.data(index.column())
def headerData(self, section, orientation, role=Qt.DisplayRole):
""" Returns the data for the given role and section in the header with the specified orientation """
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.rootItem.data(section)
class TreeNode(object):
def __init__(self, data, parent=None):
self.parentItem = parent
self.itemData = data
self.children = []
def child(self, row):
return self.children[row]
def childCount(self):
return len(self.children)
def childNumber(self):
if self.parentItem is not None:
return self.parentItem.children.index(self)
def columnCount(self):
return len(self.itemData)
def data(self, column):
return self.itemData[column]
def insertChildren(self, position, count, columns):
if position < 0 or position > len(self.children):
return False
for row in range(count):
data = [v for v in range(columns)]
item = TreeNode(data, self)
self.children.insert(position, item)
def parent(self):
return self.parentItem
def setData(self, column, value):
if column < 0 or column >= len(self.itemData):
return False
self.itemData[column] = value
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyle("plastique") # ("cleanlooks")
form = GUIWindow()
form.show()
sys.exit(app.exec_())

QTableView does not show values in column after setItemDelegateForColumn

I haveve implemented a table model (inherited QAbstractTableModel) and added it on a QTableView.
I wanted to add delegate to use custom edit widgets for my data items (for example, use a QSlider for some double values). Alas, after I've added a delegate, the table view does not shows values in this column anymore.
Can anyone tell me, how to fix it?
Here's the code:
# -*- coding: utf-8 -*-
import os, sys
from enum import IntEnum, unique
from collections import OrderedDict
from PyQt5 import QtCore, QtGui, QtWidgets
#unique
class BGModelCols(IntEnum):
alpha = 0
alpha_for_nans = 1
color_map = 2
is_boolean = 3
x_size_px = 4
y_size_px = 5
is_visible = 6
class ScanRadarSimulator(QtCore.QObject):
backgrounds_supported = ["height", "elevation", "visibility", "closing_angles", "land_rcs"]
#property
def background_names(self):
return [self.tr("Land height"), self.tr("Elevation"), self.tr("Visibility"), self.tr("Closing angles"),\
self.tr("Land RCS")]
def __init__(self, parent=None):
super().__init__(parent)
class BackgroundTableModel(QtCore.QAbstractTableModel):
def __init__(self, radar_simulator, parent=None):
super().__init__(parent)
self._radar_simulator = radar_simulator
self._background_names = self._radar_simulator.background_names
assert isinstance(self._radar_simulator, ScanRadarSimulator)
self.column_names = {BGModelCols.alpha: self.tr("α-channel"),
BGModelCols.alpha_for_nans: self.tr("α-channel for NANs"),
BGModelCols.color_map: self.tr("Color map"),
BGModelCols.is_boolean: self.tr("Is boolean mask"),
BGModelCols.x_size_px: self.tr("X pixels"),
BGModelCols.y_size_px: self.tr("Y pixels"),
BGModelCols.is_visible: self.tr("Is visible")}
self._background_items = OrderedDict()
for bg_id in radar_simulator.backgrounds_supported:
bg_dict = {BGModelCols.alpha: 0.7,
BGModelCols.alpha_for_nans: 0.0,
BGModelCols.color_map: "jet",
BGModelCols.is_boolean: False,
BGModelCols.x_size_px: 4000,
BGModelCols.y_size_px: 4000,
BGModelCols.is_visible: False}
self._background_items[bg_id] = bg_dict
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self._radar_simulator.backgrounds_supported)
def columnCount(self, parent=QtCore.QModelIndex()):
return len(BGModelCols)
def flags(self, index=QtCore.QModelIndex()):
if index.isValid():
return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable
def data(self, index=QtCore.QModelIndex(), role=QtCore.Qt.DisplayRole):
if not index.isValid():
return QtCore.QVariant()
row, col = index.row(), index.column()
print("DATA", row, col)
col_item = BGModelCols(col)
if role == QtCore.Qt.DisplayRole:
return str(self._background_items[radar_simulator.backgrounds_supported[row]][col_item])
elif role == QtCore.Qt.EditRole:
return self._background_items[radar_simulator.backgrounds_supported[row]][col_item]
else:
return QtCore.QVariant()
def setData(self, index, value, role=QtCore.Qt.EditRole):
if not index.isValid():
return False
if role == QtCore.Qt.EditRole:
row, col = index.row(), index.column()
col_item = BGModelCols(col)
self._background_items[radar_simulator.backgrounds_supported[row]][col_item] = value
self.dataChanged.emit(index, index)
return True
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole:
if orientation == QtCore.Qt.Horizontal:
return self.column_names[BGModelCols(section)]
elif orientation == QtCore.Qt.Vertical:
return self._background_names[section]
else:
return QtCore.QVariant()
class AlphaChannelDelegate(QtWidgets.QItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
def createEditor(self, parent, option, index):
print("CREATING EDITOR")
slider = QtWidgets.QSlider(parent)
slider.setMinimum(0)
slider.setMaximum(100)
slider.setOrientation(QtCore.Qt.Horizontal)
return slider
def setModelData(self, editor, model, index):
# row, col = index.row(), index.column()
# col_item = BGModelCols(col)
# model._background_items[model._radar_simulator.backgrounds_supported[row]][col_item] = editor.value() / 100.
print("setModelData")
model.setData(index, editor.value() / 100., QtCore.Qt.EditRole)
def setEditorData(self, editor, index):
print("setEditorData")
row, col = index.row(), index.column()
col_item = BGModelCols(col)
val = int(index.model()._background_items[index.model()._radar_simulator.backgrounds_supported[row]][col_item] * 100)
editor.setValue(val)
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
def paint(self, painter, option, index):
QtWidgets.QApplication.style().drawControl(QtWidgets.QStyle.CE_ItemViewItem, option, painter)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
app.setStyle("fusion")
radar_simulator = ScanRadarSimulator()
table_view = QtWidgets.QTableView()
alpha_delegate = AlphaChannelDelegate(table_view)
table_view.setItemDelegateForColumn(int(BGModelCols.alpha), alpha_delegate)
table_view.setEditTriggers(QtWidgets.QAbstractItemView.AllEditTriggers)
table_view.setModel(BackgroundTableModel(radar_simulator))
table_view.show()
sys.exit(app.exec_())
Well, it will work if comment out the paint method.
^_^

Editable tree view - changing type of editing field

I have dictionary like this:
{"key1": 1, "key2": 0, "key3": {"key": 1}, "dupa": None}
I would like to edit it as a tree view but I can't change key1 to store a string.
I saw editable model view example. And edited it to display my dictionary.
Here is example:
Look at the read 'area' as u can expect there is no way to insert string in this filed.
So my questions are:
Witch class or function should I override to be allowed control edit item that is displayed (I wanna add control that allow change type and remove whole item or add new)? In other words: How can I be able to put string in this filed?
Where I can find originally definition of this function/class?
I used qt tag because You can answer my in C++ example, I can translate it to python.
My edited code: (ready to copy and run) but I think there is no need to look at it, to answer my questions.
from PyQt5.QtCore import (QAbstractItemModel, QFile, QIODevice,
QItemSelectionModel, QModelIndex, Qt, QAbstractItemModel, QObject)
from PyQt5.QtWidgets import QApplication, QMainWindow, QHBoxLayout
from PyQt5 import QtCore, QtGui, QtWidgets
class TreeItem(object):
def __init__(self, data, parent=None):
self.parentItem = parent
self.itemData = data # it's also []
self.childItems = []
def child(self, row):
return self.childItems[row]
def childCount(self):
return len(self.childItems)
def childNumber(self):
if self.parentItem is not None:
return self.parentItem.childItems.index(self)
return 0
def columnCount(self):
return len(self.itemData)
def data(self, column):
return self.itemData[column]
def insertChildren(self, position, count, columns):
if position < 0 or position > len(self.childItems):
return False
for row in range(count):
data = [None for v in range(columns)]
item = TreeItem(data, self)
self.childItems.insert(position, item)
return True
def appendChild_by_item(self, item):
item.parentItem = self
self.childItems.append(item)
def appendChild_by_data(self, data):
self.childItems.append(TreeItem(data, self))
def insertColumns(self, position, columns):
if position < 0 or position > len(self.itemData):
return False
for column in range(columns):
self.itemData.insert(position, None)
for child in self.childItems:
child.insertColumns(position, columns)
return True
def parent(self):
return self.parentItem
def removeChildren(self, position, count):
if position < 0 or position + count > len(self.childItems):
return False
for row in range(count):
self.childItems.pop(position)
return True
def removeColumns(self, position, columns):
if position < 0 or position + columns > len(self.itemData):
return False
for column in range(columns):
self.itemData.pop(position)
for child in self.childItems:
child.removeColumns(position, columns)
return True
def setData(self, column, value):
if column < 0 or column >= len(self.itemData):
return False
self.itemData[column] = value
return True
class TreeModel(QAbstractItemModel):
def __init__(self, headers, data, parent=None):
super(TreeModel, self).__init__(parent)
rootData = [header for header in headers]
self.rootItem = TreeItem(rootData)
self.setupModelData(data, self.rootItem)
print(self.rootItem.childCount())
def columnCount(self, parent=QModelIndex()):
return self.rootItem.columnCount()
def data(self, index, role):
if not index.isValid():
return None
if role != Qt.DisplayRole and role != Qt.EditRole:
return None
item = self.getItem(index)
return item.data(index.column())
def flags(self, index):
if not index.isValid():
return 0
return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable
def getItem(self, index):
if index.isValid():
item = index.internalPointer()
if item:
return item
return self.rootItem
def headerData(self, section, orientation, role=Qt.DisplayRole):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.rootItem.data(section)
return None
def index(self, row, column, parent=QModelIndex()):
if parent.isValid() and parent.column() != 0:
return QModelIndex()
parentItem = self.getItem(parent)
childItem = parentItem.child(row)
if childItem:
return self.createIndex(row, column, childItem)
else:
return QModelIndex()
def insertColumns(self, position, columns, parent=QModelIndex()):
self.beginInsertColumns(parent, position, position + columns - 1)
success = self.rootItem.insertColumns(position, columns)
self.endInsertColumns()
return success
def insertRows(self, position, rows, parent=QModelIndex()):
parentItem = self.getItem(parent)
self.beginInsertRows(parent, position, position + rows - 1)
success = parentItem.insertChildren(position, rows,
self.rootItem.columnCount())
self.endInsertRows()
return success
def parent(self, index):
if not index.isValid():
return QModelIndex()
childItem = self.getItem(index)
parentItem = childItem.parent()
if parentItem == self.rootItem:
return QModelIndex()
return self.createIndex(parentItem.childNumber(), 0, parentItem)
def removeColumns(self, position, columns, parent=QModelIndex()):
self.beginRemoveColumns(parent, position, position + columns - 1)
success = self.rootItem.removeColumns(position, columns)
self.endRemoveColumns()
if self.rootItem.columnCount() == 0:
self.removeRows(0, self.rowCount())
return success
def removeRows(self, position, rows, parent=QModelIndex()):
parentItem = self.getItem(parent)
self.beginRemoveRows(parent, position, position + rows - 1)
success = parentItem.removeChildren(position, rows)
self.endRemoveRows()
return success
def rowCount(self, parent=QModelIndex()):
parentItem = self.getItem(parent)
return parentItem.childCount()
def setData(self, index, value, role=Qt.EditRole):
if role != Qt.EditRole:
return False
item = self.getItem(index)
result = item.setData(index.column(), value)
if result:
self.dataChanged.emit(index, index)
return result
def setHeaderData(self, section, orientation, value, role=Qt.EditRole):
if role != Qt.EditRole or orientation != Qt.Horizontal:
return False
result = self.rootItem.setData(section, value)
if result:
self.headerDataChanged.emit(orientation, section, section)
return result
def setupModelData(self, nested_dict, parent):
print(nested_dict)
for k, v in nested_dict.items():
if isinstance(v, dict):
parent.appendChild_by_data([k, None])
self.setupModelData(v, parent.child(parent.childCount() - 1))
else:
parent.appendChild_by_data([k, v])
class MainWindow(QMainWindow):
"""docstring for MainWindow"""
def __init__(self, data, parent=None):
super(MainWindow, self).__init__(parent=None)
headersLabels = ("Key", "value")
self.orginal_data = data
print(data)
self.m_model = TreeModel(headersLabels, data)
self.container = QtWidgets.QWidget()
self.m_view = QtWidgets.QTreeView()
self.m_view.setModel(self.m_model)
self.testButton = QtWidgets.QPushButton("Test")
self.testButton.clicked.connect(self.testAction)
self.buttonLayout = QHBoxLayout()
self.buttonLayout.addWidget(self.testButton)
self.container_layout = QtWidgets.QVBoxLayout()
self.container_layout.addWidget(self.m_view)
self.container_layout.addLayout(self.buttonLayout)
self.container.setLayout(self.container_layout)
self.setCentralWidget(self.container)
def testAction(self):
selceteds = self.m_view.selectedIndexes()
print(selceteds)
for i in selceteds:
item = self.m_model.getItem(i)
print(item.data(i.column()))
if __name__ == '__main__':
import sys
_d = {"key1": 1, "key2": 0, "key3": {"key": 1}, "dupa": None}
app = QApplication(sys.argv)
window = MainWindow(_d)
window.show()
sys.exit(app.exec_())
I Hope my English is understanding enough
You need to implement a custom delegate for editing your model items if you don't like the editor provided by the built-in delegate.
In your case, you'd need to subclass QStyledItemDelegate and reimplement methods createEditor (most likely you'd like to return QLineEdit from it), setEditorData (to set the string converted from a number to the editor), setModelData (to set the number converted from string back into the model). Then you'd need to set your delegate to the view.
You might want to look at StarDelegate example although your use-case seems simpler: you don't need to reimplement item painting.

qt: pyqt: QTreeView internal drag and drop almost working... dragged item disappears

I almost have a completely working drag and drop re-order within a QTreeView. Everything seems to be ok except the dropped object never appears (though I can reference it numerous different ways that proves to me that it actually exists where it should be). If anyone has a moment and could run the following code and let me know what I am doing wrong I would really appreciate it. I have been banging my head against this process for over a week now:
You should be able to just copy and run the following code (I have a bunch of print statements in it that seem to indicate that everything is working correctly but obviously something is off):
import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
################################################################################
class Branch(object):
"""
Basic branch/leaf node.
"""
#---------------------------------------------------------------------------
def __init__(self, name, value, parent=None):
"""
Constructor.
"""
super(Branch, self).__init__()
#name and parent are both to be stored directly as variables
self.name = name
self.parent = parent
self.value = value
#store sub-objects (usually other branches)
self.objD = dict()
self.nameL = list()
#---------------------------------------------------------------------------
def get_name(self):
"""
Getter.
"""
return self.name
#---------------------------------------------------------------------------
def get_parent(self):
"""
Returns the parent of this object.
"""
return self.parent
#---------------------------------------------------------------------------
def set_value(self, value):
"""
Generic setter for all settings.
"""
self.value = value
#---------------------------------------------------------------------------
def get_value(self):
"""
Generic getter for all settings. Returns the display value
"""
return self.value
#---------------------------------------------------------------------------
def add_child_obj(self, obj, row=None):
"""
Adds the param object to the dict and list.
"""
self.objD[obj.get_name()] = obj
if row == None:
self.nameL.append(obj.get_name())
else:
self.nameL.insert(row, obj.get_name())
print "JUST ADDED CHILD AT ROW:", self.nameL.index(obj.get_name())
#---------------------------------------------------------------------------
def remove_child_at_row(self, row):
"""
Removes the param object from the dict and list.
"""
childName = self.nameL[row]
del(self.nameL[row])
del(self.objD[childName])
#---------------------------------------------------------------------------
def get_child_count(self):
"""
Returns the number of children in this branch.
"""
return len(self.nameL)
#---------------------------------------------------------------------------
def get_child_list(self):
"""
Returns a list of the visible children names.
"""
return self.nameL
#---------------------------------------------------------------------------
def get_child_at_row(self, row):
"""
Returns a specific child object based on its ordinal (only consider
visible children).
"""
childName = self.nameL[row]
return self.objD[childName]
#---------------------------------------------------------------------------
def get_child_by_name(self, childName):
"""
Returns a specific child object based on its name.
"""
return self.objD[childName]
#---------------------------------------------------------------------------
def get_index(self):
"""
Returns this object's index position with regard to its siblings.
"""
siblingsL = self.parent.get_child_list()
return siblingsL.index(self.get_name())
################################################################################
class MyTreeView(QtGui.QTreeView):
"""
Overrides the QTreeView to handle keypress events.
"""
#---------------------------------------------------------------------------
def __init__(self, model, parent=None):
"""
Constructor for the TreeView class.
"""
super(MyTreeView, self).__init__(parent)
self.setModel(model)
################################################################################
class MyTreeModel(QtCore.QAbstractItemModel):
"""
My tree view data model
"""
#---------------------------------------------------------------------------
def __init__(self, root):
"""
Constructor for the TreeModel class
"""
super(MyTreeModel, self).__init__()
self.root = root
self.fontSize = 8
self.selection = None
#---------------------------------------------------------------------------
def columnCount(self, index=QtCore.QModelIndex()):
"""
Returns the number of columns in the treeview.
"""
return 1
#---------------------------------------------------------------------------
def rowCount(self, index=QtCore.QModelIndex()):
"""
Returns the number of children of the current index obj.
"""
if index.column() > 0:
return 0
if not index.isValid():
item = self.root
else:
item = index.internalPointer()
if item:
return item.get_child_count()
return 0
#---------------------------------------------------------------------------
def index(self, row, column, parent):
"""
Returns a QModelIndex item for the current row, column, and parent.
"""
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
if not parent.isValid():
parentItem = self.root
else:
parentItem = parent.internalPointer()
childItem = parentItem.get_child_at_row(row)
if childItem:
return self.createIndex(row, column, childItem)
else:
return QtCore.QModelIndex()
#---------------------------------------------------------------------------
def parent(self, index):
"""
Returns a QModelIndex item for the parent of the given index.
"""
if not index.isValid():
return QtCore.QModelIndex()
childItem = index.internalPointer()
if not childItem:
return QtCore.QModelIndex()
parentItem = childItem.get_parent()
if parentItem == self.root:
return QtCore.QModelIndex()
return self.createIndex(parentItem.get_index(), 0, parentItem)
#---------------------------------------------------------------------------
def data(self, index, role=QtCore.Qt.DisplayRole):
"""
Returns the text or formatting for a particular cell, depending on the
role supplied.
"""
#invalid indexes return invalid results
if not index.isValid():
return QtCore.QVariant()
#access the underlying referenced object
item = index.internalPointer()
#edit role displays the raw values
if role == QtCore.Qt.EditRole:
return item.get_value()
#return the data to display
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
return item.get_value()
return QtCore.QVariant()
#---------------------------------------------------------------------------
def headerData(self, index, orientation, role=QtCore.Qt.DisplayRole):
"""
Returns the text for the horizontal headers (parameter names)
"""
if role == QtCore.Qt.TextAlignmentRole:
if orientation == QtCore.Qt.Horizontal:
alignment = int(QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
return QtCore.QVariant(alignment)
alignment = int(QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
return QtCore.QVariant(alignment)
if role != QtCore.Qt.DisplayRole:
return QtCore.QVariant()
if orientation == QtCore.Qt.Horizontal:
if int(index) == 0:
return "Name"
#---------------------------------------------------------------------------
def supportedDropActions(self):
"""
We allow re-ordering.
"""
return QtCore.Qt.MoveAction
#---------------------------------------------------------------------------
def flags(self, index):
"""
Returns whether or not the current item is editable/selectable/etc.
"""
if not index.isValid():
return QtCore.Qt.ItemIsEnabled
#by default, you can't do anything
enabled = QtCore.Qt.ItemIsEnabled
selectable = QtCore.Qt.ItemIsSelectable
editable = QtCore.Qt.ItemIsEditable
draggable = QtCore.Qt.ItemIsDragEnabled
droppable = QtCore.Qt.ItemIsDropEnabled
#return our flags.
return enabled | selectable| editable| draggable| droppable
#---------------------------------------------------------------------------
def setData(self, index, value, role=QtCore.Qt.EditRole):
"""
Sets the data.
"""
#convert the value into a string
if value:
item = index.internalPointer()
item.set_value(value)
self.emit(QtCore.SIGNAL("dataChanged(QModelIndex,QModelIndex)"),
index, index)
return True
#---------------------------------------------------------------------------
def supportedDropActions(self):
"""
Only allow moves
"""
return QtCore.Qt.MoveAction
#---------------------------------------------------------------------------
def mimeTypes(self):
"""
Only accept the internal custom drop type which is plain text
"""
types = QtCore.QStringList()
types.append('text/plain')
return types
#---------------------------------------------------------------------------
def mimeData(self, index):
"""
Wrap the index up as a list of rows and columns of each
parent/grandparent/etc
"""
rc = ""
theIndex = index[0] #<- for testing purposes we only deal with 1st item
while theIndex.isValid():
rc = rc + str(theIndex.row()) + ";" + str(theIndex.column())
theIndex = self.parent(theIndex)
if theIndex.isValid():
rc = rc + ","
mimeData = QtCore.QMimeData()
mimeData.setText(rc)
return mimeData
#---------------------------------------------------------------------------
def dropMimeData(self, data, action, row, column, parentIndex):
"""
Extract the whole ancestor list of rows and columns and rebuild the
index item that was originally dragged
"""
if action == QtCore.Qt.IgnoreAction:
return True
if data.hasText():
ancestorL = str(data.text()).split(",")
ancestorL.reverse() #<- stored from the child up, we read from ancestor down
pIndex = QtCore.QModelIndex()
for ancestor in ancestorL:
srcRow = int(ancestor.split(";")[0])
srcCol = int(ancestor.split(";")[1])
itemIndex = self.index(srcRow, srcCol, pIndex)
pIndex = itemIndex
item = itemIndex.internalPointer()
parent = parentIndex.internalPointer()
#modify the row if it is -1 (we want to append to the end of the list)
if row == -1:
row = parent.get_child_count()
self.beginInsertRows(parentIndex, row-1, row)
print "------------------"
parentIndex.internalPointer().add_child_obj(item)
print "------------------"
self.endInsertRows()
print "sanity check:"
print "dragged Node", item.get_name()
print "parent Node", parent.get_name()
print "inserted at row",row
print "inserted Node:",parent.get_child_at_row(row).get_name()
print row, column
print "from index():",self.index(row, 0, parentIndex).internalPointer().get_name()
self.emit(QtCore.SIGNAL("dataChanged(QModelIndex,QModelIndex)"),
self.index(row, 0, parentIndex),
self.index(row, 0, parentIndex))
return True
#---------------------------------------------------------------------------
def insertRow(self, row, parent):
print "insertRow"
return self.insertRows(row, 1, parent)
#---------------------------------------------------------------------------
def insertRows(self, row, count, parent):
print "insertRows"
self.beginInsertRows(parent, row, (row + (count - 1)))
self.endInsertRows()
return True
#---------------------------------------------------------------------------
def removeRow(self, row, parentIndex):
print "removeRow"
return self.removeRows(row, 1, parentIndex)
#---------------------------------------------------------------------------
def removeRows(self, row, count, parentIndex):
self.beginRemoveRows(parentIndex, row, row)
print "about to remove child at row:",row
print "which is under the parent named:",parentIndex.internalPointer().get_name()
print "and whose own name is:",parentIndex.internalPointer().get_child_at_row(row).get_name()
parentIndex.internalPointer().remove_child_at_row(row)
self.endRemoveRows()
return True
class Ui_MainWindow(object):
def printChildren(self, item):
print item.name
for child in item.get_child_list():
self.printChildren(item.get_child_by_name(child))
def printit(self):
self.printChildren(self.root)
def setupUi(self, MainWindow):
root = Branch("root", "root", QtCore.QVariant)
item1 = Branch("ITEM1","ITEM1",root)
item2 = Branch("ITEM2","ITEM2",root)
item3 = Branch("ITEM3","ITEM3",root)
root.add_child_obj(item1)
root.add_child_obj(item2)
root.add_child_obj(item3)
item1a = Branch("thinga","thinga",item1)
item1b = Branch("thingb","thingb",item1)
item1.add_child_obj(item1a)
item1.add_child_obj(item1b)
item2a = Branch("thingc","thingc",item2)
item2b = Branch("thingd","thingd",item2)
item2.add_child_obj(item2a)
item2.add_child_obj(item2b)
item3a = Branch("thinge","thinge",item3)
item3b = Branch("thingf","thingf",item3)
item3.add_child_obj(item3a)
item3.add_child_obj(item3b)
item1a1 = Branch("___A","___A",item1a)
item1a2 = Branch("___B","___B",item1a)
item1a.add_child_obj(item1a1)
item1a.add_child_obj(item1a2)
item1b1 = Branch("___C","___C",item1b)
item1b2 = Branch("___D","___D",item1b)
item1b.add_child_obj(item1b1)
item1b.add_child_obj(item1b2)
item2a1 = Branch("___E","___E",item2a)
item2a2 = Branch("___F","___F",item2a)
item2a.add_child_obj(item2a1)
item2a.add_child_obj(item2a2)
item2b1 = Branch("___G","___G",item2b)
item2b2 = Branch("___H","___H",item2b)
item2b.add_child_obj(item2b1)
item2b.add_child_obj(item2b2)
item3a1 = Branch("___J","___J",item3a)
item3a2 = Branch("___K","___K",item3a)
item3a.add_child_obj(item3a1)
item3a.add_child_obj(item3a2)
item3b1 = Branch("___L","___L",item3b)
item3b2 = Branch("___M","___M",item3b)
item3b.add_child_obj(item3b1)
item3b.add_child_obj(item3b2)
self.root = root
MainWindow.setObjectName("MainWindow")
MainWindow.resize(600, 400)
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.horizontalLayout = QtGui.QHBoxLayout(self.centralwidget)
self.horizontalLayout.setObjectName("horizontalLayout")
self.myModel = MyTreeModel(root)
self.treeView = MyTreeView(self.myModel, self.centralwidget)
self.treeView.setObjectName("treeView")
self.treeView.dragEnabled()
self.treeView.acceptDrops()
self.treeView.showDropIndicator()
self.treeView.setDragDropMode(QtGui.QAbstractItemView.InternalMove)
self.treeView.expandAll()
self.horizontalLayout.addWidget(self.treeView)
self.btn = QtGui.QPushButton('print', MainWindow)
MainWindow.connect(self.btn, QtCore.SIGNAL("clicked()"), self.printit)
self.horizontalLayout.addWidget(self.btn)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtGui.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 600, 22))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtGui.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8))
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
MainWindow = QtGui.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
This doesn't solve all the problems, but changing dropMimeData() to the following will at least allow moving leaf items.
self.beginInsertRows(parentIndex, row, row)
parentIndex.internalPointer().add_child_obj(Branch(item.get_name(), item.get_value(), parent), row)
self.endInsertRows()
A drag-drop operation is effectively two steps, an insert (done in dropMimeData) and a remove (done automatically by the Move drag operation). The changes above insert a new item rather than trying to insert an item which is already in the model and which will be removed from the old location after the insert occurs.

Resources