(Py)Qt - QTreeView, Model, insert row - qt

I am little bit confuse.
I am working with QTreeView as model I assigned QSortFilterProxyModel.
http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/qsortfilterproxymodel.html
//treeview
self.ProxyView = QtGui.QTreeView(self.centralwidget)
//model
self.ProxyModel = QtGui.QSortFilterProxyModel(self)
self.ProxyModel.setSourceModel(QtGui.QStandardItemModel(0, 3, self))
//assign model to tree
self.ProxyView.setModel(self.ProxyModel)
On button click I want to add row to this tree.
Here is my slot
def pushButton_addRow(self):
self.ProxyModel.insertRow(0)
self.ProxyModel.setData(self.ProxyModel.index(0,0), "hi")
It doesnt work, it works only for first row, then I added empty rows. But wenn I fill second cell it works.
self.ProxyModel.setData(self.ProxyModel.index(0,1), "hi")
Any Idea?

Maybe your new QModelIndex has wrong parent()?

Related

how to add QSpinBox to a QTableWidget?

I have a QTableWidget and an object of class product called p.
I wanna add items to this table.
I tried the code below but no use.
void MainWindow:: add_to_basket (product p){
ui->tableWidget->insertRow(1);
QLineEdit *qle=new QLineEdit();
qle->setText(p.get_name());
ui->tableWidget->setCellWidget(1,1,qle);
QLineEdit *qle1=new QLineEdit();
qle1->setText(QString::number(p.get_price()));
ui->tableWidget->setCellWidget(1,2,qle1);
QSpinBox *qsb=new QSpinBox();
qsb->setValue(p.get_count());
ui->tableWidget->setCellWidget(1,3,qsb);
}
what should I do?
QTableWidget should have defined rowCount and columnCount properties. It can be done either via QTableWidget constructor (https://doc.qt.io/qt-5/qtablewidget.html#QTableWidget-1) or via appropriate methods (setRowCounts and setColumnCounts). If it is done already, that's great.
insertRow inserts an empty row into the table at given position. ui->tableWidget->insertRow(1) would insert a new row at position 1 only if you have previously defined rowCount and columnCount (see point 1).
It depends what is your idea here - if you would like to have at least 4 columns (please note that we are counting from 0 and the QSpinBox is attempted to be put into third column) and insert new product always at first row, your code with point 1 fullfilled will be work fine. But, if your idea is to add a new row each time new product is added, you should rather call ui->tableWidget->insertRow(tableWidget->rowCount()) and use that value to address appropriate row.
You may also want to have a look here for example how to setup QTableWidget: https://wiki.qt.io/How_to_Use_QTableWidget

Behavior of QTreeView when inserting column at index 0

I'm trying to understand a strange (to my understanding) behavior when trying to insert a column in a treeview at index 0.
While I understand that this is not a suggested course of action, the result still puzzles me.
If I add a child to an item that is not in the first column, it is obviously not shown, since only children of the root index in first column are expanded; but it seems that when the new column is inserted, the existing children of the previous first column are "inherited" by the new item.
class TreeTest(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QVBoxLayout(self)
insertColBtn = QtWidgets.QPushButton('insert column')
layout.addWidget(insertColBtn)
insertColBtn.clicked.connect(self.insertColumn)
insertColAfterBtn = QtWidgets.QPushButton('insert child and insert column')
layout.addWidget(insertColAfterBtn)
insertColAfterBtn.clicked.connect(self.insertColumnAfter)
removeColBtn = QtWidgets.QPushButton('remove column')
layout.addWidget(removeColBtn)
removeColBtn.clicked.connect(self.removeColumn)
self.tree = QtWidgets.QTreeView()
layout.addWidget(self.tree)
self.model = QtGui.QStandardItemModel()
self.tree.setModel(self.model)
self.parent1 = QtGui.QStandardItem('parent 1')
self.parent2 = QtGui.QStandardItem('parent 2')
self.model.appendRow([self.parent1, self.parent2])
self.child = QtGui.QStandardItem('child of parent 1')
self.parent1.appendRow(self.child)
self.parent2.appendRow(QtGui.QStandardItem('child of parent 2'))
self.tree.expandAll()
self.tree.header().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
def insertColumn(self):
newParent = QtGui.QStandardItem('new parent')
self.model.insertColumn(0, [newParent])
newChild = QtGui.QStandardItem('new column child')
self.parent1.appendRow(newChild)
def insertColumnAfter(self):
newParent = QtGui.QStandardItem('new parent')
newChild = QtGui.QStandardItem('new column child')
self.parent1.appendRow(newChild)
self.model.insertColumn(0, [newParent])
def removeColumn(self):
self.model.removeColumn(0)
This is the what appears at the beginning (the "child of parent 2" is obviously hidden):
Then I insert a new first column, then I add a child to the previous parent (which is now on the second column); as you can see, the previous child is now apparently become a child of the new item, while the new child item is not visible:
Finally, I try to remove the first column, and the previously added child is now visible:
Strangely enough, if a new child item of the previous first column is added before inserting the new column (insertColumnAfter()), it works "as expected", probably due to the delay item views usually have before laying out the items after a model change:
Again, I realize this is not the "standard" way to deal with a tree model.
Still, I'd like to understand what's going on here, whether it's an expected behavior (probably by design, even if not perfect) or some kind of bug.
I've been able to reproduce this behavior with PyQt up to 5.12.4, I can't test with newer versions.

When dragging multiple items from QListWidget, non-draggable items get removed

I have two QListWidgets. The user can select multiple items from one list and drag them to the other list. But within each list, some items are draggable and some are not. If the selection contains both draggable and non-draggable items, a problem happens. Only the draggable items appear in the second list, which is correct. But all the items disappear from the first list.
In the animated image above, items 00, 01, and 02 are selected. Only items 00 and 02 are drag enabled. After the drag-and-drop, all three items are gone from the first list. How can I fix this?
Here is some code to reproduce the problem:
import random
import sys
from PySide import QtCore, QtGui
class TestMultiDragDrop(QtGui.QMainWindow):
def __init__(self, parent=None):
super(TestMultiDragDrop, self).__init__(parent)
centralWidget = QtGui.QWidget()
self.setCentralWidget(centralWidget)
layout = QtGui.QHBoxLayout(centralWidget)
self.list1 = QtGui.QListWidget()
self.list1.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
self.list1.setDefaultDropAction(QtCore.Qt.MoveAction)
self.list1.setSelectionMode(QtGui.QListWidget.ExtendedSelection)
self.list2 = QtGui.QListWidget()
self.list2.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
self.list2.setDefaultDropAction(QtCore.Qt.MoveAction)
self.list2.setSelectionMode(QtGui.QListWidget.ExtendedSelection)
layout.addWidget(self.list1)
layout.addWidget(self.list2)
self.fillListWidget(self.list1, 8, 'someItem')
self.fillListWidget(self.list2, 4, 'anotherItem')
def fillListWidget(self, listWidget, numItems, txt):
for i in range(numItems):
item = QtGui.QListWidgetItem()
newTxt = '{0}{1:02d}'.format(txt, i)
if random.randint(0, 1):
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
else:
# If the item is draggable, indicate it with a *
newTxt += ' *'
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsDragEnabled)
item.setText(newTxt)
listWidget.addItem(item)
def openMultiDragDrop():
global multiDragDropUI
try:
multiDragDropUI.close()
except:
pass
multiDragDropUI = TestMultiDragDrop()
multiDragDropUI.setAttribute(QtCore.Qt.WA_DeleteOnClose)
multiDragDropUI.show()
return multiDragDropUI
if __name__ == '__main__':
app = QtGui.QApplication([])
openMultiDragDrop()
sys.exit(app.exec_())
Here I have some suspicion on setDefaultDropAction(QtCore.Qt.MoveAction)
Read below para from documentation: Specially the bold line
In the simplest case, the target of a drag and drop action receives a copy of the data being dragged, and the source decides whether to delete the original. This is described by the CopyAction action. The target may also choose to handle other actions, specifically the MoveAction and LinkAction actions. If the source calls QDrag::exec(), and it returns MoveAction, the source is responsible for deleting any original data if it chooses to do so. The QMimeData and QDrag objects created by the source widget should not be deleted - they will be destroyed by Qt.
(http://doc.qt.io/qt-4.8/dnd.html#overriding-proposed-actions)
First give a try with QtCore.Qt.CopyAction
Second, if MoveAction is mandatory, try creating QMimeData and QDrag objects in your source list widget's mouseMoveEvent.
Here in below link, you can find some help for creating QMimeData and QDrag objects in your source list widget's mouseMoveEvent. (code is in C++, My intention is to get conceptual idea).
http://doc.qt.io/qt-4.8/dnd.html#overriding-proposed-actions
I think Kuba Ober is right that this is a Qt bug. In the C++ source code, there is a function void QAbstractItemViewPrivate::clearOrRemove(). It deletes all selected rows, but it does not look at whether each item is drag-enabled or not.
That being the case, I came up with a few workarounds:
Method 1: Make all non-draggable items non-selectable as well
This is the easiest method. Just remove the QtCore.Qt.ItemIsEnabled flag from all non-draggable items. Of course if you want all of your items to be selectable, this won't work.
Method 2: Recreate the "startDrag" function
Since the clearOrRemove function belongs to a private class, I cannot override it. But that function is called by the startDrag function, which can be overridden. So I essentially duplicated the function in Python and replaced the call to clearOrRemove with my own function removeSelectedDraggableItems.
The problem with this method is that startDrag contains calls to a few other functions belonging to a private class. And those functions call other private class functions. Specifically, these functions are responsible for controlling how the items are drawn during the drag event. Since I didn't want to recreate all the functions, I just ignored those. The result is that this method results in the correct functionality, but it loses the graphical indication of which items are being dragged.
class DragListWidget(QtGui.QListWidget):
def __init__(self):
super(DragListWidget, self).__init__()
def startDrag(self, supportedDragActions):
indexes = self.getSelectedDraggableIndexes()
if not indexes:
return
mimeData = self.model().mimeData(indexes)
if not mimeData:
return
drag = QtGui.QDrag(self)
rect = QtCore.QRect()
# "renderToPixmap" is from a private class in the C++ code, so I can't use it.
#pixmap = renderToPixmap(indexes, rect)
#drag.setPixmap(pixmap)
drag.setMimeData(mimeData)
# "pressedPosition" is from a private class in the C++ code, so I can't use it.
#drag.setHotSpot(pressedPostion() - rect.topLeft())
defaultDropAction = self.defaultDropAction()
dropAction = QtCore.Qt.IgnoreAction
if ((defaultDropAction != QtCore.Qt.IgnoreAction) and
(supportedDragActions & defaultDropAction)):
dropAction = defaultDropAction
elif ((supportedDragActions & QtCore.Qt.CopyAction) and
(self.dragDropMode() != self.InternalMove)):
dropAction = QtCore.Qt.CopyAction
dragResult = drag.exec_(supportedDragActions, dropAction)
if dragResult == QtCore.Qt.MoveAction:
self.removeSelectedDraggableItems()
def getSelectedDraggableIndexes(self):
""" Get a list of indexes for selected items that are drag-enabled. """
indexes = []
for index in self.selectedIndexes():
item = self.itemFromIndex(index)
if item.flags() & QtCore.Qt.ItemIsDragEnabled:
indexes.append(index)
return indexes
def removeSelectedDraggableItems(self):
selectedDraggableIndexes = self.getSelectedDraggableIndexes()
# Use persistent indices so we don't lose track of the correct rows as
# we are deleting things.
root = self.rootIndex()
model = self.model()
persistentIndices = [QtCore.QPersistentModelIndex(i) for i in selectedDraggableIndexes]
for pIndex in persistentIndices:
model.removeRows(pIndex.row(), 1, root)
Method 3: Hack "startDrag"
This method changes the drop action from "MoveAction" to "CopyAction" before calling the built-in "startDrag" method. Then it calls a custom function for deleting the selected drag-enabled items. This solves the problem of losing the graphical dragging animation.
This is a pretty easy hack, but it comes with its own problem. Say the user installs an event filter that changes the drop action from "MoveAction" to "IgnoreAction" in certain cases. This hack code doesn't get the updated value. It will still delete the items as though the action is "MoveAction". (Method 2 does not have this problem.) There are workarounds for this problem, but I won't go into them here.
class DragListWidget2(QtGui.QListWidget):
def startDrag(self, supportedDragActions):
dropAction = self.defaultDropAction()
if dropAction == QtCore.Qt.MoveAction:
self.setDefaultDropAction(QtCore.Qt.CopyAction)
super(DragListWidget2, self).startDrag(supportedDragActions)
if dropAction == QtCore.Qt.MoveAction:
self.setDefaultDropAction(dropAction)
self.removeSelectedDraggableItems()
def removeSelectedDraggableItems(self):
# Same code from Method 2. Removed here for brevity.
pass

Modify all selected cells in a QTableWidget?

It is a standard QTableWidget
All cells are QTableWidgetItem.
All cells are editable/selectable
Question: How can I modify all the cells I have selected?
Possible way is to use the dialog open. So the idea is like this :
Select the items
Make a button or something to open input dialog.
Apply the value of input dialog to all selected items.
I was facing a similar problem a couple years ago and I solved it like this:
I have inherited my own view and I have reimplemented methods commitData() and mouseReleaseEvent().
commitData takes all selected indices from the selection model and calls QAbstractItemModel::setData() for all of them. Data are taken from the editor like this:
QByteArray n = editor->metaObject()->userProperty().name();
if (n.isEmpty())
n = delegate->itemEditorFactory()->valuePropertyName(model()->data(index, Qt::EditRole).userType());
if (!n.isEmpty())
{
QVariant data = editor->property(n);
for (const QModelIndex & idx : selectedIndices)
{
model()->setData(idx, data);
}
}
mouseReleaseEvent() performs three steps:
Get a current selection from the selection model.
Call original event handler (QTableWidget::mouseReleaseEvent())
Restore selection: QItemSelectionModel::select()

avoid empty space in QTableView

In a QVBoxLayout added two QTableViews. The first one has constant number of rows (2), the second one has many. The goal is to make the second table begin just after last row in the first table.
I tried change QSizePolicy, setMinimumSize() - nothing helps. Here is sample of code:
layout = QVBoxLayout()
first_table = QTableView()
second_table = QTableVew()
first_table.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
second_table.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
layout.addWidget(first_table)
layout.addWidget(second_table)
It looks like it uses sizeHint() no matter of setMinimumSize(h, v) values. How to make the first table do not show space without rows?
You might want to do the following:
layout = QVBoxLayout()
first_table = QTableView()
second_table = QTableVew()
layout.addWidget(first_table)
layout.addWidget(second_table)
layout.addStretch(1); // <--- to keep both tables together.

Resources