Appending multiple Python 3 QStandardItems on 1 QStandardItem row - pyqt5

I'd like to concatenate more than 1 QStandardItem to 1 row of a QTreeView and have all QStandardItems be displayed. At present only the first item in the QList being sent to appendRow() is being displayed. I've searched the pyqt5 documentation but haven't found an explanation for displaying multiple QStandardItems once appended to a row. I'm using Python 3.8. Here is my code:
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.Qt import QStandardItemModel, QStandardItem
from PyQt5.QtGui import QFont, QColor
from PyQt5.QtWidgets import QWidget, QTabWidget, QTreeView
from PyQt5 import QtCore
import sys
DefaultFont = "Droid Sans"
DefaultFontColor = "black"
DefaultBackgroundColor = "white"
class StandardItem(QStandardItem):
def __init__(self, txt='', font=DefaultFont, fontSize=10, setBold=False, fgColor=DefaultFontColor, bgColor=DefaultBackgroundColor):
super().__init__()
defFont = QFont(font, fontSize)
defFont.setBold(setBold)
self.setEditable(False)
self.setBackground(QColor(bgColor))
self.setForeground(QColor(fgColor))
self.setFont(defFont)
self.setText(txt)
#end __init__
#end class StandardItem
class MyMainWindow(object):
def __init__(self, MainWindow):
self.__addToTree(MainWindow)
#end __init__()
def __addToTree(self, MainWindow):
MainWindow.resize(640, 500)
self.centralWidget = QWidget(MainWindow)
self.centralWidget.setObjectName("centralWidget")
self.tabWidget = QTabWidget(self.centralWidget)
self.tabWidget.setGeometry(QtCore.QRect(10, 50, 620, 440))
self.tabWidget.setObjectName("tabWidget")
MainWindow.setCentralWidget(self.centralWidget)
self.myTab= QWidget()
self.treeView = QTreeView(self.myTab)
self.treeModel = QStandardItemModel()
self.treeRootNode = self.treeModel.invisibleRootItem()
self.treeView.setGeometry(QtCore.QRect(7, 7, 900, 600))
self.tabWidget.addTab(self.myTab, "")
item1row1 = StandardItem("Row 1: Hi There!", fontSize=10, setBold=False)
item1row2 = StandardItem("This is row 2, item1", fontSize=11, setBold=True, fgColor="blue", bgColor="antiquewhite")
item2row2 = StandardItem("This is row 2, item2", fontSize=12, setBold=True, fgColor="yellowgreen")
item3row2 = StandardItem("This is row 2, item3", fontSize=12, setBold=True, fgColor="red")
item1row1.appendRow((item1row2, item2row2, item3row2))
self.treeRootNode.appendRow(item1row1)
self.treeView.setModel(self.treeModel)
#end __addToTree()
#end class MyMainWindow
qtApp = QApplication(sys.argv)
MainWindow = QMainWindow()
myMainWindow = MyMainWindow(MainWindow)
MainWindow.show()
sys.exit(qtApp.exec_())

The column count of a tree view is always based on the column count of the root index, which is the index that contains all top level items (invisibleRootItem() for QStandardItemModel).
For QStandardItemModel, unless explicitly set using setColumnCount(), the column count is always the maximum column count of all top level rows. In your case, you only have a top level row with just one item, so the view thinks that there is only one column, and all the remaining items of the top level items will not be visible.
So, you have 2 possibilities:
manually set the column count, either by using setColumnCount(3) or by adding the row/column arguments in the constructor (QStandardItemModel(0, 3));
use appendRow() with None as placeholders for the remaining columns: treeRootNode.appendRow((item1row1, None, None));
Note: you're not expected to modify a pyuic file, as it's considered bad practice for a lot of reasons; follow the official guidelines about using Designer instead. You should also always use layout managers.

Related

How to add tristate select box to QTreeView in PyQt5/PySide

I am using QTreeView rather than QTreeWidget. I would like to add a checkbox to each top level item to the left of the name (first item in row). When the top item is selected/deselected, all child items should be selected/deselected. When the selection state of a child item is changed to be different from the parent item, the parent item should be changed to tristate. I have found this answer, which is for a QTreeWidget. How is this done in a QTreeView? Current code can be seen below
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class MyWidget(QWidget):
def __init__(self):
super(MyWidget, self).__init__()
self.tree = QTreeView(self)
self.tree.setAlternatingRowColors(True)
layout = QVBoxLayout(self)
layout.addWidget(self.tree)
self.model = QStandardItemModel()
self.model.setHorizontalHeaderLabels(['Name', 'Height', 'Weight'])
self.tree.header().setDefaultSectionSize(180)
self.tree.setModel(self.model)
self.populate_tree()
self.tree.expandAll()
def populate_tree(self):
self.model.setRowCount(0)
root = self.model.invisibleRootItem()
class1 = QStandardItem('Class 1')
class1.setFlags(class1.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable) #Doesn't work
class2 = QStandardItem('Class 2')
class1.setFlags(class2.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
root.appendRow(class1)
root.appendRow(class2)
class1.appendRow([
QStandardItem('Joe'),
QStandardItem('178'),
QStandardItem('76')
])
class1.appendRow([
QStandardItem('Judith'),
QStandardItem('165'),
QStandardItem('55')
])
class2.appendRow([
QStandardItem('Tobias'),
QStandardItem('180'),
QStandardItem('95')
])
if __name__ == '__main__':
app = QApplication(sys.argv)
view = MyWidget()
view.setGeometry(300, 100, 600, 300)
view.setWindowTitle('QTreeview Example')
view.show()
sys.exit(app.exec_())

QApplication.focusWidget().pos() always returning 0

I have a custom QWidget that I have embedded into a QTableWidget.
When I toggle the QCheckBoxes and modify the text in the QLineEdit widgets, the program is not able to distinguish the widgets in rows 2 and 1 from the widgets in row 0. How can I change the program so that it prints the correct row and column of the QLineEdit widget that is being edited or the Checkbox that is being toggled?
Figure 1 shows a screenshot of the program with the output after selecting the third checkbox many times in Visual Studio Code. The output is expected to read “2 0” repeatedly but instead it reads “0 0”.
Figure 2 Similarly, when I modify the text in the QLineEdit in cell 2,0 from “My Custom Text” to “Text” the program prints “Handle Cell Edited 0,0”, although it is expected to print “Handle Cell Edited 2,0 Cell 2,0 was changed to Text”.
Code:
# Much of this code is copy pasted form user: three_pineapples post on stackoverflow:
# https://stackoverflow.com/a/26311179/18914416
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QTableWidget, \
QApplication, QTableWidgetItem, QLineEdit, QCheckBox
from PyQt5 import QtGui
class SimpleTable(QTableWidget):
def __init__(self,window):
# Call the parent constructor
QTableWidget.__init__(self)
self.window = window
class myWidget(QWidget):
#This code is adapted paritally form a post by user sebastian at:
#https://stackoverflow.com/a/29764770/18914416
def __init__(self,parent=None):
super(myWidget,self).__init__()
self.Layout1 = QHBoxLayout()
self.item = QLineEdit("My custom text")
#https://stackabuse.com/working-with-pythons-pyqt-framework/
self.Checkbox = QCheckBox()
self.Checkbox.setCheckState(Qt.CheckState.Unchecked)
self.Layout1.addWidget(self.Checkbox)
self.Layout1.addWidget(self.item)
#https://stackoverflow.com/questions/29764395/adding-multiple-widgets-to-qtablewidget-cell-in-pyqt
self.item.home(True)
#https://www.qtcentre.org/threads/58387-Left-text-alignment-for-long-text-on-QLineEdit
self.setLayout(self.Layout1)
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
layout = QHBoxLayout()
self.setLayout(layout)
self.table_widget = SimpleTable(window=self)
layout.addWidget(self.table_widget)
self.table_widget.setColumnCount(3)
self.table_widget.setHorizontalHeaderLabels(['Colour', 'Model'])
items = [('Red', 'Toyota'), ('Blue', 'RV'), ('Green', 'Beetle')]
for i in range(len(items)):
c = QTableWidgetItem(items[i][0])
m = QTableWidgetItem(items[i][1])
self.table_widget.insertRow(self.table_widget.rowCount())
self.table_widget.setItem(i, 1, c)
self.table_widget.setItem(i, 2, m)
myWidget1 = myWidget()
myWidget1.Checkbox.stateChanged.connect(self.handleButtonClicked)
myWidget1.item.editingFinished.connect(self.handle_cell_edited)
self.table_widget.setCellWidget(i,0,myWidget1)
myWidget1.Layout1.setContentsMargins(50*i+10,0,0,0)
self.show()
self.table_widget.itemChanged.connect(self.handle_cell_edited)
def handleButtonClicked(self):
#Adapted from a post by user: Andy at:
# https://stackoverflow.com/a/24149478/18914416
button = QApplication.focusWidget()
# or button = self.sender()
index = self.table_widget.indexAt(button.pos())
if index.isValid():
print(index.row(), index.column())
# I added this fuction:
def handle_cell_edited(self):
if QApplication.focusWidget() != None:
index = self.table_widget.indexAt(QApplication.focusWidget().pos())
x,y = index.column(),index.row()
if index.isValid():
print("Handle Cell Edited",index.row(), index.column())
if self.table_widget.item(y,x)!= None:
print(f"Cell {x},{y} was changed to {self.table_widget.item(y,x).text()}.")
def main():
app = QApplication(sys.argv)
window = Window()
sys.exit(app.exec_())
main()
What I've Tried So Far:
I learned that QT has two types of widgets that can be embedded in a table; a QTableWigetItem which can be inserted into a table using setItem()(3) and Qwidgets, which can be placed into a table using setCellWidget().(4) Generally, I know that using a QTableWigetItem one can set the item.setFlags(Qt.ItemFlag.ItemIsUserCheckable)
flag to create a checkbox in the cell. (3) However, when using the QTableWigetItem, I wasn’t able to find a way to indent the checkboxes. Because giving each checkbox its own indentation level is important in the context of my program, I’ve decided to use Qwidgets instead of QTableWigetItems in the few select cells where indenting is important.
I’ve read that by creating a QItemDelegate(5)(6), you can do a lot more with setting QWidgets in boxes. However, creating a delegate seems complicated, so I’d prefer to avoid this if possible. If there is no other way to make the program register the correct cell number of the cell being edited, creating a delegate will be the next thing I look into.
For anyone who might want to experiment with QTableWigetItems in this application, here is an equivalent program that uses QTableWigetItems instead of QWidgets but doesn't permit separate indentation or editing of the text field in column 0. For either and both of these two reasons, a QTableWigetItem seems not to be usable for the checkboxes in column 0.
Less Successful Attempt using QTableWidgetItem:
#Much of this code is copy pasted form user: three_pineapples post on stackoverflow:
# https://stackoverflow.com/a/26311179/18914416
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QTableWidget, \
QApplication, QTableWidgetItem, QLineEdit, QCheckBox
from PyQt5 import QtGui
class SimpleTable(QTableWidget):
def __init__(self,window):
QTableWidget.__init__(self)
self.window = window
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
layout = QHBoxLayout()
self.setLayout(layout)
self.table_widget = SimpleTable(window=self)
layout.addWidget(self.table_widget)
self.table_widget.setColumnCount(3)
self.table_widget.setHorizontalHeaderLabels(['Colour', 'Model'])
items = [('Red', 'Toyota'), ('Blue', 'RV'), ('Green', 'Beetle')]
for i in range(len(items)):
c = QTableWidgetItem(items[i][0])
m = QTableWidgetItem(items[i][1])
self.table_widget.insertRow(self.table_widget.rowCount())
self.table_widget.setItem(i, 1, c)
self.table_widget.setItem(i, 2, m)
item = QTableWidgetItem("My Custom Text")
item.setFlags(Qt.ItemFlag.ItemIsUserCheckable| Qt.ItemFlag.ItemIsEnabled)
item.setCheckState(Qt.CheckState.Unchecked)
self.table_widget.setItem(i,0,item)
#https://youtu.be/DM8Ryoot7MI?t=251
self.show()
#I added this line:
self.table_widget.itemChanged.connect(self.handle_cell_edited)
def handleButtonClicked(self):
#Adapted from a post by user: Andy at:
# https://stackoverflow.com/a/24149478/18914416
button = QApplication.focusWidget()
# or button = self.sender()
index = self.table_widget.indexAt(button.pos())
if index.isValid():
print(index.row(), index.column())
# I added this fuction:
def handle_cell_edited(self):
if QApplication.focusWidget() != None:
index = self.table_widget.indexAt(QApplication.focusWidget().pos())
x,y = index.column(),index.row()
if index.isValid():
print("Handle Cell Edited",index.row(), index.column())
if self.table_widget.item(y,x)!= None:
print(f"Cell {x},{y} was changed to {self.table_widget.item(y,x).text()}.")
def main():
app = QApplication(sys.argv)
window = Window()
sys.exit(app.exec_())
main()
Bibliography:
1.https://i.stack.imgur.com/FudE3.png
2.https://i.stack.imgur.com/C2ypp.png
3.https://youtu.be/DM8Ryoot7MI?t=251
4.https://stackoverflow.com/questions/24148968/how-to-add-multiple-qpushbuttons-to-a-qtableview/24149478#24149478
5.Creating a QItemDelegate for QWidgets, https://stackoverflow.com/a/35418141/18914416
6.Need to create a QItemDelegate to add a stylesheet to QTableWidgetItems: https://forum.qt.io/topic/13124/solved-qtablewidgetitem-set-stylesheet
The geometry of a widget is always relative to its parent.
In your first example, the problem is that the pos() returned for the widget is relative to the myWidget container, and since the vertical position is always a few pixels below the top of the parent (the layout margin), you always get the same value.
The second example has another conceptual problem: the checkbox of a checkable item is not an actual widget, so the widget you get is the table itself.
def handle_cell_edited(self):
# this will print True
print(isinstance(QApplication.focusWidget(), QTableWidget))
As explained above, the geometry is always relative to the parent, so you will actually get the position of the table relative to the window.
The solution to the first case is quite simple, as soon as you understand the relativity of coordinate systems. Note that you shall not rely on the focusWidget() (the widget might not accept focus), but actually get the sender(), which is the object that emitted the signal:
def handleButtonClicked(self):
sender = self.sender()
if not self.table_widget.isAncestorOf(sender):
return
# the widget coordinates must *always* be mapped to the viewport
# of the table, as the headers add margins
pos = sender.mapTo(self.table_widget.viewport(), QPoint())
index = self.table_widget.indexAt(pos)
if index.isValid():
print(index.row(), index.column())
In reality, this might not be that necessary, as an item delegate will suffice if the indentation is the only requirement: the solution is to properly set the option.rect() within initStyleOption() and use a custom role for the indentation:
IndentRole = Qt.UserRole + 1
class IndentDelegate(QStyledItemDelegate):
def initStyleOption(self, opt, index):
super().initStyleOption(opt, index)
indent = index.data(IndentRole)
if indent is not None:
left = min(opt.rect.right(),
opt.rect.x() + indent)
opt.rect.setLeft(left)
class SimpleTable(QTableWidget):
def __init__(self,window):
QTableWidget.__init__(self)
self.window = window
self.setItemDelegateForColumn(0, IndentDelegate(self))
class Window(QWidget):
def __init__(self):
# ...
for i in range(len(items)):
# ...
item.setData(IndentRole, 20 * i)

Tkinter: How can I check if any of the widgets of a specific frame have changed?

What is the best way to check if any widget (or variable linked to it) of a given frame(frm1) has changed and take an action. For example activate a button.
I would like that when something was typed in the entries or changed the combobox or checkbox, the 'changed_content' function would be executed
from tkinter import *
from tkinter import ttk
from tkinter.messagebox import showinfo
class Defaultframe(Tk):
def __init__(self):
Tk.__init__(self)
self.geometry('500x300')
self.title('Tkinter')
self.text1 = StringVar()
self.text2 = StringVar()
self.text3 = StringVar()
self.var1 = IntVar()
self.var2 = IntVar()
self.set_widgets()
return
def changed_content(self):
showinfo('Information', 'The content has been changed')
self.btn2.configure(state='normal')
return
def set_widgets(self):
#Frame1
self.frm1 = ttk.Frame(self).pack(side=TOP)
self.lbl = ttk.Label(self.frm1, text='Text1').pack(pady=5)
self.ent1 = ttk.Entry(self.frm1, textvariable=self.text1).pack(pady=5)
self.lbl = ttk.Label(self.frm1, text='Text2').pack(pady=5)
self.my_ent = ttk.Entry(self.frm1, textvariable=self.text2).pack(pady=5)
self.cbb = ttk.Combobox(self.frm1,
values=[0, 30, 60, 90, 120, 150, 180],
state='readonly',
textvariable=self.var2)
self.cbb.pack(pady=5)
self.cbb.current(3)
self.ckb = ttk.Checkbutton(self.frm1, text='Hello', variable=self.var1, onvalue=1, offvalue=0).pack(pady=5)
#---
#Frame2
self.frm2 = ttk.Frame(self).pack(side=BOTTOM, fill=X)
ttk.Separator(self.frm2, orient=HORIZONTAL).pack(side=TOP, expand=1, fill=X)
self.my_ent3 = ttk.Entry(self.frm2, textvariable=self.text3).pack(side=LEFT, padx=1)
self.btn1 = ttk.Button(self.frm2, text='Cancel').pack(side=RIGHT, padx=1)
self.btn2 = ttk.Button(self.frm2, text='Save')
self.btn2.pack(side=RIGHT, padx=1)
self.btn2.configure(state=DISABLED)
#---
if __name__== '__main__':
app = Defaultframe()
app.mainloop()
Solving for any widget is tough - you'll have to write code specifically for a canvas or a scrollbar or any other widget that isn't associated with a tkinter variable.
For widgets that are associated with a tkinter variable you can apply a trace that will call a function whenever the value changes.
In your code it might look something like this:
class Defaultframe(Tk):
def __init__(self):
...
self._watch_variables(self.text1, self.text2, self.text3, self.var1, self.var2)
def _watch_variables(self, *vars):
for var in vars:
var.trace_add("write", self._handle_trace)
def _handle_trace(self, *args):
self.changed_content()

QGraphicsItem disappearing from scene on group remove

I'm facing an issue with QGraphicsItem and QGraphicsItemGroup. I can add to group and move the group around but when i try to remove the item from group it get's deleted. I can't find the object in the scene at all.
Same issue if i use a standard class (QGraphicsRectItem)
What is the correct way to remove an item from group while keeping it on the scene.
Code is below:
from PyQt5.QtCore import Qt,QRect
from PyQt5.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsItem,QApplication, QGraphicsItemGroup, QGraphicsEllipseItem,QPushButton , QVBoxLayout
from PyQt5 import QtCore, QtGui, QtWidgets
class Rectangle(QtWidgets.QGraphicsItem):
def __init__(self,rect):
super(Rectangle, self).__init__(parent=None)
self.rect = QtCore.QRectF(rect[0], rect[1], rect[2], rect[3])
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable)
self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges)
def boundingRect(self):
return self.rect
def paint(self, painter, option, widget=None):
painter.save()
painter.setRenderHints(
QtGui.QPainter.Antialiasing
| QtGui.QPainter.TextAntialiasing
| QtGui.QPainter.SmoothPixmapTransform
| QtGui.QPainter.HighQualityAntialiasing,
True,
)
painter.drawEllipse(self.rect)
painter.restore()
class Group(QGraphicsItemGroup):
def __init__(self):
super(Group, self).__init__()
self.setFlag(QGraphicsItemGroup.ItemIsMovable)
def boundingRect(self):
#if self.childItems():
# return self.childrenBoundingRect()
return QtCore.QRectF(200,200,20,20)
def paint(self,
painter: QtGui.QPainter,
option: QtWidgets.QStyleOptionGraphicsItem,
widget: QtWidgets.QWidget = None):
painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))
painter.setBrush(QtGui.QBrush(QColor(0, 0, 255, 127)))
painter.drawRect(self.boundingRect())
class MyView(QGraphicsView):
def __init__(self):
super().__init__()
self.scene = QGraphicsScene()
self.setScene(self.scene)
self.setWindowTitle('Test')
self.setSceneRect(0, 0, 450, 450)
#add items
nodeItem = Rectangle([20,200,20,20])
self.scene.addItem(nodeItem)
nodeItem2 = Rectangle([40,300,20,20])
self.scene.addItem(nodeItem2)
#rect_item = QtWidgets.QGraphicsRectItem(QtCore.QRectF(0, 0, 100, 100))
#rect_item.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
#self.scene.addItem(rect_item)
#add buttons
self.b1 = QPushButton("ungroup")
self.b1.clicked.connect(self.ungroup)
self.b1.setGeometry(QRect(10,10,100,100))
self.b2 = QPushButton("group")
self.b2.clicked.connect(self.cgroup)
self.b2.setGeometry(QRect(150,10,100,100))
self.b3 = QPushButton("print obj")
self.b3.clicked.connect(self.printobj)
self.b3.setGeometry(QRect(250,10,100,100))
self.scene.addWidget(self.b1)
self.scene.addWidget(self.b2)
self.scene.addWidget(self.b3)
def printobj(self):
for item in self.scene.items():
print(item)
def ungroup(self):
for item in self.group.childItems():
self.group.removeFromGroup(item)
print(f'item {item} removed from group {self.group}')
self.scene.destroyItemGroup(self.group)
#self.scene.update()
def cgroup(self):
self.group = Group()
self.scene.addItem(self.group)
for n in self.scene.items():
if isinstance(n,Rectangle):
print('n')
n.setParentItem(self.group)
#self.scene.update()
if __name__ == '__main__':
app = QApplication([])
f = MyView()
f.show()
sys.exit(app.exec_())
The problem is that cycling through the items creates a python wrapper for them, and being it a local reference, it gets automatically garbage collected when the function returns.
The python wrapper tries to check the ownership of the graphics item, but removing an item from the group doesn't automatically retransfer the ownership to the scene as it would happen by doing addItem(). The result is that the local reference to the items is removed, the garbage collection calls the destructors for the items, and they get deleted from the scene.
Keeping a persistent reference to the removed items could solve the problem:
def ungroup(self):
self.items = []
for item in self.group.childItems():
self.group.removeFromGroup(item)
self.items.append(item)
But consider that if the list is deleted or cleared, those items will be deleted as well, unless they are reparented.
Alternatively, you can explicitly remove the items from the scene and add them again (you should not add an item to the same scene):
def ungroup(self):
for item in self.group.childItems():
self.group.removeFromGroup(item)
self.scene.removeItem(item)
self.scene.addItem(item)
But, since you're going to destroy the group anyway, the solution is to only remove (and properly delete) the other items, and destroy the group, so that the remaining item will be transferred back to the scene:
def ungroup(self):
for item in self.group.childItems():
if not isinstance(item, Rectangle):
self.group.removeFromGroup(item)
del item
self.scene.destroyItemGroup(self.group)
Notes: 1. if you want to draw an ellipse, just use QGraphicsEllipseItem, so you don't need to override paint; 2. the ItemIsSelectable flag can cause recursive movements when the item is part of a group;

How to modify drop event in QListView

I have a QListView where items can be reordered with drag-and-drop by setting dragDropMode to DragDrop and defaultDropAction to MoveAction. How can I intercept the drop event, to find out what is trying to be moved where in relation to the rest of the list, so that I can cancel this action under certain conditions? E.g. I want to forbid moving some items behind other.
You can access the indexes and items involved in the dropEvent and setDropAction to Qt.IgnoreAction to cancel the drop depending on your criteria. Since you weren't specific, for this demonstration I just created one item that stays at the bottom.
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class List(QListView):
def dropEvent(self, event):
i = self.selectedIndexes()[0]
j = self.indexAt(event.pos())
# To access the items involved
source = self.model().itemFromIndex(i)
target = self.model().itemFromIndex(j)
bottom = (self.model().rowCount() - 1, -1)
if i.row() in bottom or j.row() in bottom:
event.setDropAction(Qt.IgnoreAction)
else:
super().dropEvent(event)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = QWidget()
lv = List()
lv.setDragDropMode(QAbstractItemView.DragDrop)
lv.setDefaultDropAction(Qt.MoveAction)
model = QStandardItemModel(5, 1)
for i in range(4):
item = QStandardItem(f'Item {i + 1}')
item.setFlags(item.flags() & ~Qt.ItemIsDropEnabled)
model.setItem(i, 0, item)
item = QStandardItem('I stay at the bottom ._.')
model.setItem(4, 0, item)
lv.setModel(model)
vbox = QVBoxLayout(window)
vbox.addWidget(lv)
window.show()
sys.exit(app.exec_())