my UI crashes without returning any error message when I try to delete the last child of an Item in my QTreeView. I saw this post that gave a solution but it dosn't work for me, even if I add those lines in the index method:
if not self.hasIndex(row, column, parent):
return QModelIndex()
So I tried to use the QAbstractItemModelTester(self.model, QAbstractItemModelTester.FailureReportingMode.Fatal) to see if my model has any problem. It returns this error message : FAIL! flags == Qt::ItemIsDropEnabled || flags == 0 () returned FALSE (C:\Users\qt\work\qt\qtbase\src\testlib\qabstractitemmodeltester.cpp:373)
Firstly, I don't know how to fix this error, so if you have any ideas to do so you are welcome. Secondly, I have no idea if it is the reason why the UI crashes when deleting the last child of an item.
I tried to make an MRE, but the error doesn't show up in it. The Ui doesn't crash at all, so I'll just leave it bellow to let you check if you see any error that I didn't see and that explains this error in the model.
import sys
from PySide6.QtGui import *
from PySide6.QtCore import *
from PySide6.QtWidgets import *
from PySide6.QtTest import QAbstractItemModelTester
class MainWindow(QMainWindow):
def __init__(self, parent: QWidget = None):
super().__init__(parent)
self.resize(573, 468)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.contextMenuEvent)
self.menu : QMenu = QMenu(self)
self.delete_action = self.menu.addAction("Delete")
self.delete_action.triggered.connect(self.delitem)
self.frame = QFrame()
self.setCentralWidget(self.frame)
self.hlayout = QHBoxLayout()
self.frame.setLayout(self.hlayout)
self.view = QTreeView()
self.view.setAlternatingRowColors(True)
self.view.setSelectionBehavior(QAbstractItemView.SelectItems)
self.view.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
self.view.setAnimated(False)
self.view.setAllColumnsShowFocus(True)
self.hlayout.addWidget(self.view)
headers = ["value",'Type','Progress Bar','Test','Test','Test','Test']
self.model = TreeModel(headers, self)
self.view.setModel(self.model)
# QAbstractItemModelTester(self.model, QAbstractItemModelTester.FailureReportingMode.Fatal)
self.view.expandAll()
for column in range(self.model.columnCount()):
self.view.resizeColumnToContents(column)
def delitem(self) :
index: QModelIndex = self.view.currentIndex()
model: QAbstractItemModel = self.model
model.removeRow(index.row(), index.parent())
print('deleted')
def contextMenuEvent(self, event: QContextMenuEvent, point : QPoint = QPoint()) -> None:
selection_model = self.view.selectionModel()
has_selection: bool = not selection_model.selection().isEmpty()
if has_selection :
self.menu.exec(QCursor.pos())
class TreeItem:
def __init__(self, data: list, parent: 'TreeItem' = None):
self.item_data = data
self.parent_item = parent
self.child_items = []
def child(self, number: int) -> 'TreeItem':
if number < 0 or number >= len(self.child_items):
return None
return self.child_items[number]
def last_child(self) -> 'TreeItem':
return self.child_items[-1] if self.child_items else None
def child_count(self) -> int:
return len(self.child_items)
def child_number(self) -> int:
if self.parent_item:
return self.parent_item.child_items.index(self)
return 0
def column_count(self) -> int:
return len(self.item_data)
def data(self, column: int):
if column < 0 or column >= len(self.item_data):
return None
return self.item_data[column]
def insert_children(self, position: int, count: int, columns: int) -> bool:
if position < 0 or position > len(self.child_items):
return False
for row in range(count):
data = [None] * columns
item = TreeItem(data.copy(), self)
self.child_items.insert(position, item)
return True
def insert_columns(self, position: int, columns: int) -> bool:
if position < 0 or position > len(self.item_data):
return False
for column in range(columns):
self.item_data.insert(position, None)
for child in self.child_items:
child.insert_columns(position, columns)
return True
def parent(self):
return self.parent_item
def has_parent(self) -> bool:
if self.parent_item :
return(True)
else :
return(False)
def remove_children(self, position: int, count: int) -> bool:
if position < 0 or position + count > len(self.child_items):
return False
for row in range(count):
self.child_items.pop(position)
return True
def remove_columns(self, position: int, columns: int) -> bool:
if position < 0 or position + columns > len(self.item_data):
return False
for column in range(columns):
self.item_data.pop(position)
for child in self.child_items:
child.remove_columns(position, columns)
return True
def set_data(self, column: int, value):
if column < 0 or column >= len(self.item_data):
return False
self.item_data[column] = value
return True
def __repr__(self) -> str:
result = f"<treeitem.TreeItem at 0x{id(self):x}"
for d in self.item_data:
result += f' "{d}"' if d else " <None>"
result += f", {len(self.child_items)} children>"
return result
def __str__(self) -> str:
result : str = str(self.parent())
for data in self.item_data :
result += ',' + str(data)
return(result)
class TreeModel(QAbstractItemModel):
def __init__(self, headers: list, parent=None):
super().__init__(parent)
self.root_data = headers
self.root_item = TreeItem(self.root_data.copy())
self.setup_model_data(self.root_item)
def columnCount(self, parent: QModelIndex = None) -> int:
return self.root_item.column_count()
def data(self, index: QModelIndex, role: int = None):
if not index.isValid():
return None
if role not in (0,1,2) :
return None
item: TreeItem = self.get_item(index)
return item.data(index.column())
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
if not index.isValid():
return Qt.ItemIsEditable
return Qt.ItemIsEditable | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled | QAbstractItemModel.flags(self, index)
def get_item(self, index: QModelIndex = QModelIndex()) -> TreeItem:
if index.isValid():
item: TreeItem = index.internalPointer()
if item:
return item
return self.root_item
def headerData(self, section: int, orientation: Qt.Orientation,
role: int = Qt.DisplayRole):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.root_item.data(section)
return None
def index(self, row: int, column: int, parent: QModelIndex = QModelIndex()) -> QModelIndex:
# if not self.hasIndex(row, column, parent) :
# return QModelIndex()
if parent.isValid() and parent.column() != 0:
return QModelIndex()
parent_item: TreeItem = self.get_item(parent)
if not parent_item:
return QModelIndex()
child_item: TreeItem = parent_item.child(row)
if child_item:
return self.createIndex(row, column, child_item)
return QModelIndex()
def insertColumns(self, position: int, columns: int,
parent: QModelIndex = QModelIndex()) -> bool:
self.beginInsertColumns(parent, position, position + columns - 1)
success: bool = self.root_item.insert_columns(position, columns)
self.endInsertColumns()
return success
def insertRows(self, position: int, rows: int,
parent: QModelIndex = QModelIndex()) -> bool:
parent_item: TreeItem = self.get_item(parent)
if not parent_item:
return False
self.beginInsertRows(parent, position, position + rows - 1)
column_count = self.root_item.column_count()
success: bool = parent_item.insert_children(position, rows, column_count)
self.endInsertRows()
return success
def parent(self, index: QModelIndex = QModelIndex()) -> QModelIndex:
if not index.isValid():
return QModelIndex()
child_item: TreeItem = self.get_item(index)
if child_item:
parent_item: TreeItem = child_item.parent()
else:
parent_item = None
if parent_item == self.root_item or not parent_item:
return QModelIndex()
return self.createIndex(parent_item.child_number(), 0, parent_item)
def removeColumns(self, position: int, columns: int,
parent: QModelIndex = QModelIndex()) -> bool:
self.beginRemoveColumns(parent, position, position + columns - 1)
success: bool = self.root_item.remove_columns(position, columns)
self.endRemoveColumns()
if self.root_item.column_count() == 0:
self.removeRows(0, self.rowCount())
return success
def removeRows(self, position: int, rows: int,
parent: QModelIndex = QModelIndex()) -> bool:
parent_item: TreeItem = self.get_item(parent)
if not parent_item:
return False
self.beginRemoveRows(parent, position, position + rows - 1)
success: bool = parent_item.remove_children(position, rows)
self.endRemoveRows()
return success
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
if parent.isValid() and parent.column() > 0:
return 0
parent_item: TreeItem = self.get_item(parent)
if not parent_item:
return 0
return parent_item.child_count()
def setData(self, index: QModelIndex, value, role: int) -> bool:
if role != Qt.EditRole:
return False
item: TreeItem = self.get_item(index)
result: bool = item.set_data(index.column(), value)
if result:
self.dataChanged.emit(index, index, [Qt.DisplayRole, Qt.EditRole])
return result
def setData2(self, index: QModelIndex, value, role: int) -> bool:
if role != Qt.EditRole:
return False
item: TreeItem = self.get_item(index)
result: bool = item.set_data(index.column(), value)
return result
def setHeaderData(self, section: int, orientation: Qt.Orientation, value,
role: int = None) -> bool:
if role != Qt.EditRole or orientation != Qt.Horizontal:
return False
result: bool = self.root_item.set_data(section, value)
if result:
self.headerDataChanged.emit(orientation, section, section)
return result
def setup_model_data(self, parent: TreeItem):
parents = [parent]
parent : TreeItem = parents[0]
col_count = self.root_item.column_count()
parent.insert_children(parent.child_count(), 1, col_count)
column_data = ['test 1','test 2','test 3','test 4','test 5']
for column in range(len(column_data)):
child = parent.last_child()
child.set_data(column, column_data[column])
for i in range(4) :
child : TreeItem = parent.last_child()
child.insert_children(child.child_count(), 1, col_count)
column_data = ['test 4','test 9','test 4','test 0','test 6']
for column in range(len(column_data)):
child2 = child.last_child()
child2.set_data(column, column_data[column])
def _repr_recursion(self, item: TreeItem, indent: int = 0) -> str:
result = " " * indent + repr(item) + "\n"
for child in item.child_items:
result += self._repr_recursion(child, indent + 2)
return result
def __repr__(self) -> str:
return self._repr_recursion(self.root_item)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
Now the following piece of code shows what setup_model_data method I have :
def setup_model_data(self, parent: TreeItem, id_projet):
parents = [parent]
con : Connexion = Connexion()
with con.pool.acquire() as connexion :
with connexion.cursor() as cursor :
for ensemble in cursor.execute("SELECT nom,id_sous_ensemble,id FROM Sous_Ensemble WHERE id_sous_ensemble = 0 AND id_projet = :1 ORDER BY id_sous_ensemble,nom",[id_projet]) :
parent : TreeItem = parents[0]
col_count = self.root_item.column_count()
parent.insert_children(parent.child_count(), 1, col_count)
column_data = [ensemble[0],None,None,-2,ensemble[2]]
for column in range(len(column_data)):
child = parent.last_child()
child.set_data(column, column_data[column])
self.recursiviter(ensemble[2],child)
with connexion.cursor() as cursor1 :
for piece in cursor1.execute("SELECT designation, id_sous_ensemble, id FROM piece WHERE id_sous_ensemble = 0") :
with connexion.cursor() as cursor2 :
for rev_info in cursor2.execute("SELECT id_rev,id_mat FROM link_piece_rev WHERE id_piece = :1 ORDER BY id_rev DESC FETCH NEXT 1 ROWS ONLY",[piece[2]]) :
id_rev = rev_info[0]
id_mat = rev_info[1]
parent.insert_children(parent.child_count(), 1, col_count)
column_data =[piece[0],id_rev,id_mat,-1,piece[2]]
for column in range(len(column_data)):
child = parent.last_child()
child.set_data(column, column_data[column])
def recursiviter(self,id, parent : TreeItem, id_projet = 1) :
con1 : Connexion = Connexion()
with con1.pool.acquire() as connexion1 :
with connexion1.cursor() as cursor2 :
for ensemble in (cursor2.execute("SELECT nom,id_sous_ensemble,id FROM SOUS_ENSEMBLE WHERE id_sous_ensemble = :1 AND id_projet = :2",[id,id_projet])) :
if ensemble :
col_count = self.root_item.column_count()
parent.insert_children(parent.child_count(),1,col_count)
column_data = [ensemble[0],None,None,-2,ensemble[2]]
for column in range(len(column_data)):
child = parent.last_child()
child.set_data(column, column_data[column])
self.recursiviter(ensemble[2],parent.last_child())
else :
return
with connexion1.cursor() as cursor3 :
for piece in cursor3.execute("SELECT designation, id_sous_ensemble, id FROM piece WHERE id_sous_ensemble = :1",[id]) :
if piece :
with connexion1.cursor() as cursor4 :
for rev_info in cursor4.execute("SELECT id_rev,id_mat FROM link_piece_rev WHERE id_piece = :1 ORDER BY id_rev DESC FETCH NEXT 1 ROWS ONLY",[piece[2]]) :
id_rev = rev_info[0]
id_mat = rev_info[1]
col_count = self.root_item.column_count()
parent.insert_children(parent.child_count(), 1, col_count)
column_data =[piece[0],id_rev,id_mat,-1,piece[2]]
for column in range(len(column_data)):
child = parent.last_child()
child.set_data(column, column_data[column])
else :
return
I have no idea if this will help you, especially the setup_model_data and recusititer methods as I am creating the model from my database. I followed the exemple that Qt's official website gives to create my QTreeView. I have no clue what I can give you to solve my problem. Feel free to ask for any information in the comments, I will be pleased to answer it quickly.
I was able to solve the issue by clearing the model selection after reading out and saving the currentIndex value: