PDA

View Full Version : How to reset model without collapsing tree?



neuronet
20th February 2015, 17:35
I have a QTreeView of a QStandardItemModel. I have a button that allows the user to move a row up in the tree among its siblings, and I do this using QStandardItem.takeRow() and QStandardItem.insertRow().

In pseudocode, I have:
model.beginResetModel()

#perform insert and take operations here

model.endResetModel()
view.expandAll()My concern is that I am doing something wrong because I need to put in the expandAll() for things to work: otherwise when I reset the model the view collapses the entire tree. Is there a way to reset my model without having to re-expand my tree?

ChrisW67
21st February 2015, 05:31
Removing a row and inserting a row should update the view correctly. The model should not reset.

neuronet
21st February 2015, 12:57
If I don't reset, subsequent selection behavior does not work properly, unfortunately (I programmatically keep the content that was moved selected, and without resetting the model this selection behavior is fubar). So I need it unfortunately. Well, I need *something*. Here is my 'moveRowUp' method:


def moveRowUp(self, selectedIndexes):
nameIndex = selectedIndexes[self.columnIndices["nameIndex"]]
sourceRowNum = nameIndex.row()
if sourceRowNum == 0:
pass
else:
targetRowNum = sourceRowNum - 1
self.todoModel.beginResetModel()
indexParent = nameIndex.parent()
if indexParent.isValid():
itemParent = self.todoModel.itemFromIndex(indexParent)
sourceRowItems = itemParent.takeRow(sourceRowNum)
targetRowItems = itemParent.takeRow(targetRowNum)
itemParent.insertRow(targetRowNum, sourceRowItems)
itemParent.insertRow(sourceRowNum, targetRowItems) #targetRowItems)
else:
sourceRowItems = self.todoModel.takeRow(sourceRowNum)
targetRowItems = self.todoModel.takeRow(targetRowNum)
self.todoModel.insertRow(targetRowNum, sourceRowItems)
self.todoModel.insertRow(sourceRowNum, targetRowItems) #targetRowItems)

self.todoModel.endResetModel()
self.todoView.expandAll()

selectIndex = self.todoModel.index(targetRowNum, 0, indexParent)
rowSelection = QtGui.QItemSelection(selectIndex, selectIndex)
self.todoView.selectionModel().select(rowSelection , QtGui.QItemSelectionModel.Rows | QtGui.QItemSelectionModel.SelectCurrent)

wysota
21st February 2015, 13:12
So what's wrong exactly with QAbstractItemModel::moveRows()?

neuronet
21st February 2015, 14:02
Wysota I am in PySide, which is bound to Qt 4.8. moveRows came with Qt 5.

(Aside: even in Qt5, isn't moveRows just a virtual function? As it says int he docs "The base class implementation does nothing and returns false. If you implement your own model, you can reimplement this function if you want to support moving." )

wysota
21st February 2015, 16:13
Wysota I am in PySide, which is bound to Qt 4.8. moveRows came with Qt 5.
You still have beginMoveRows() and endMoveRows() so you can implement moveRows() yourself.

neuronet
21st February 2015, 17:35
wysota bear in mind this is a QStandardItemModel. We don't have to call beginInsertRows and endinsertRows, I believe. That said, even when I do overkill and include begin/endRemoveRows and begin/endInsertRows around the take/insert operattions, the behavior is the exact same. I still get the bad selection behavior after swapping the rows unless I reset the model, at which point I have to expandAll again.

Is it not possible to reset while keeping the view expanded? If it is not, at least it's easy enough to just invoke expandAll: it just seemed inefficient.

wysota
21st February 2015, 22:22
The point is you don't have to reset. Using either insert or move commands is enough to update the model and keep the view in sync. If you observe something different then you probably have some flaw in your code somewhere. You shouldn't need to call beginResetModel and friends.

neuronet
22nd February 2015, 01:22
Here an sscce that reproduces the problem. If I remove the resetmodel stuff, the selection behavior at the end of MyStandardTree.moveRowUp() does not work (instead of just selecting the moved row, it selects two rows, one correct, the other above the correct).


from PySide import QtGui, QtCore

class MainWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self, parent = None)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.view = MyStandardTree(self)
self.addMoveAction()
self.setCentralWidget(self.view)

def addMoveAction(self):
itemUpAction=QtGui.QAction("Move up", self)
itemUpAction.setIcon(QtGui.QIcon("images/moveItemUp.png"))
itemUpAction.triggered.connect(self.moveRowUp)
self.toolbar = self.addToolBar("Mover")
self.toolbar.addAction(itemUpAction)

def moveRowUp(self):
selectedIndexes = self.view.selectedIndexes()
self.view.moveRowUp(selectedIndexes[0])

class MyStandardTree(QtGui.QTreeView):
def __init__(self, parent = None):
QtGui.QTreeView.__init__(self, parent)

self.model = QtGui.QStandardItemModel()
self.model.setHorizontalHeaderLabels(['Title', 'Summary'])

rootItem = self.model.invisibleRootItem()
titles = ['Parent', 'Child0', 'Child1', 'Child2']
summaries =['I am the parent', 'I am child0', 'I am child1', 'I am child2']
dataItems = [list(datPair) for datPair in zip(titles, summaries)]
parentRow = [QtGui.QStandardItem(x) for x in dataItems[0]]
child0 = [QtGui.QStandardItem(x) for x in dataItems[1]]
child1 = [QtGui.QStandardItem(x) for x in dataItems[2]]
child2 = [QtGui.QStandardItem(x) for x in dataItems[3]]
rootItem.appendRow(parentRow)
parentRow[0].appendRow(child0)
parentRow[0].appendRow(child1)
parentRow[0].appendRow(child2)

self.setModel(self.model)
self.expandAll()

def moveRowUp(self, index):
'''Moves selected row up'''
sourceRowNum = index.row()
if sourceRowNum == 0:
pass
else:
targetRowNum = sourceRowNum - 1
self.model.beginResetModel()
indexParent =index.parent()
if indexParent.isValid():
itemParent = self.model.itemFromIndex(indexParent)
sourceRowItems = itemParent.takeRow(sourceRowNum)
targetRowItems = itemParent.takeRow(targetRowNum)
itemParent.insertRow(targetRowNum, sourceRowItems)
itemParent.insertRow(sourceRowNum, targetRowItems)
else:
sourceRowItems = self.todoModel.takeRow(sourceRowNum)
targetRowItems = self.todoModel.takeRow(targetRowNum)
self.model.insertRow(targetRowNum, sourceRowItems)
self.model.insertRow(sourceRowNum, targetRowItems)

self.model.endResetModel()
self.expandAll()

selectIndex = self.model.index(targetRowNum, 0, indexParent)
rowSelection = QtGui.QItemSelection(selectIndex, selectIndex)
selectionModel = QtGui.QItemSelectionModel.Rows | QtGui.QItemSelectionModel.SelectCurrent
self.selectionModel().select(rowSelection, selectionModel)

import sys
app = QtGui.QApplication(sys.argv)
myTree = MainWindow()
myTree.show()
sys.exit(app.exec_())

neuronet
22nd February 2015, 04:55
deleted post....(link to SO post I subsequently deleted once ChrisW67 pointed out the problem was a boner on my part)

ChrisW67
22nd February 2015, 05:05
Your code as published works as expected using both PySide and PyQt4 on my machine (PySide 1.1.2 and Qt 4.8.5 on Linux)
If I remove the unnecessary calls to beginResetModel, endResetModel, and expandAll the move behaviour continues to be OK, the tree does not collapse, but your re-selection logic is wrong (tending to select two rows).

Here is my take on your moveRow function:


def moveRowUp(self, index):
'''Moves selected row up'''
sourceRowNum = index.row()
if sourceRowNum > 0:
targetRowNum = sourceRowNum - 1
indexParent =index.parent()
if indexParent.isValid():
itemParent = self.model.itemFromIndex(indexParent)
sourceRowItems = itemParent.takeRow(sourceRowNum)
itemParent.insertRow(targetRowNum, sourceRowItems)
else:
sourceRowItems = self.todoModel.takeRow(sourceRowNum)
self.model.insertRow(targetRowNum, sourceRowItems)

selectIndex = self.model.index(targetRowNum, 0, indexParent)
self.selectionModel().clear()
self.selectionModel().select(selectIndex, QtGui.QItemSelectionModel.Rows | QtGui.QItemSelectionModel.SelectCurrent)

Moving a single row up involves removing and inserting only a single row, not two.

neuronet
22nd February 2015, 05:14
Chris thanks for helping me understand the logic of row moving better. :)

Strangely, with your new and improved moveUpRow, when I have a row with children, when I move that row up among its siblings the moved row collapses. Example follows.

Further, if I add children to its children, the child also collapses. So it collapses the node that has been moved, and all its descendents. But I suppose it is more efficient to recursively expand() on that one node and its descendents, than to run expandAll(), right? I just wonder why things collapse at all with movement. :mad:


from PySide import QtGui, QtCore

class MainWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self, parent = None)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.view = MyStandardTree(self)
self.addMoveAction()
self.setCentralWidget(self.view)

def addMoveAction(self):
itemUpAction=QtGui.QAction("Move up", self)
itemUpAction.setIcon(QtGui.QIcon("images/moveItemUp.png"))
itemUpAction.triggered.connect(self.moveRowUp)
self.toolbar = self.addToolBar("Mover")
self.toolbar.addAction(itemUpAction)

def moveRowUp(self):
selectedIndexes = self.view.selectedIndexes()
self.view.moveRowUp(selectedIndexes[0])

class MyStandardTree(QtGui.QTreeView):
def __init__(self, parent = None):
QtGui.QTreeView.__init__(self, parent)

self.model = QtGui.QStandardItemModel()
self.model.setHorizontalHeaderLabels(['Title', 'Summary'])

rootItem = self.model.invisibleRootItem()
titles = ['Parent', 'Child0', 'Child1', 'Child2', 'Child10', 'Child11']
summaries =['I am the parent', 'I am child0', 'I am child1', 'I am child2', 'foo0', 'foo1']
dataItems = [list(datPair) for datPair in zip(titles, summaries)]
parentRow = [QtGui.QStandardItem(x) for x in dataItems[0]]
child0 = [QtGui.QStandardItem(x) for x in dataItems[1]]
child1 = [QtGui.QStandardItem(x) for x in dataItems[2]]
child2 = [QtGui.QStandardItem(x) for x in dataItems[3]]
child10 = [QtGui.QStandardItem(x) for x in dataItems[4]]
child11 = [QtGui.QStandardItem(x) for x in dataItems[5]]
rootItem.appendRow(parentRow)
parentRow[0].appendRow(child0)
parentRow[0].appendRow(child1)
parentRow[0].appendRow(child2)
child1[0].appendRow(child10)
child1[0].appendRow(child11)

self.setModel(self.model)
self.expandAll()

def moveRowUp(self, index):
'''Improved version'''
sourceRowNum = index.row()
if sourceRowNum > 0:
targetRowNum = sourceRowNum - 1
indexParent =index.parent()
if indexParent.isValid():
itemParent = self.model.itemFromIndex(indexParent)
sourceRowItems = itemParent.takeRow(sourceRowNum)
itemParent.insertRow(targetRowNum, sourceRowItems)
else:
sourceRowItems = self.model.takeRow(sourceRowNum)
self.model.insertRow(targetRowNum, sourceRowItems)

selectIndex = self.model.index(targetRowNum, 0, indexParent)
self.selectionModel().clear()
self.selectionModel().select(selectIndex, QtGui.QItemSelectionModel.Rows | QtGui.QItemSelectionModel.SelectCurrent)

import sys
app = QtGui.QApplication(sys.argv)
myTree = MainWindow()
myTree.show()
sys.exit(app.exec_())

wysota
22nd February 2015, 07:36
If you take an item out of the model then its selection will not be retained. That's why beginMoveRows and endMoveRows should be used instead. No idea if you can do it with QStandardItemModel though.

neuronet
22nd February 2015, 17:34
wysota I just get weird errors when I start trying to insert those methods in my QStandardItemModel code (not even Python errors, but weird errors about invalid indexes sent directly from Qt to my command line). At any rate, aside from collapsing things, it seems to be working as expected, given what I know about QStandardItemModel which I'm pretty sure takes care of begin/end calls. I'll start a separate question if I need to, as the problem has shifted as I realize now you were right I don't need to reset. :)