QTableWidget and setCellWidget: resize problem - python

I tried to insert some QRadioButton inside some cell of a QTableWidget. The situation is similar to the one of this post. In particular, the solution by #eyllanesc, with PySide2 is the following
import sys
from PySide2.QtWidgets import QApplication, QTableWidget, QTableWidgetItem, \
QButtonGroup, QRadioButton
app = QApplication(sys.argv)
searchView = QTableWidget(0, 4)
colsNames = ['A', 'B', 'C']
searchView.setHorizontalHeaderLabels(['DIR'] + colsNames)
dirNames = {'A': ['/tmp', '/tmp/dir1'], 'B': ['/tmp/dir2'],
'C': ['/tmp/dir3']}
rowCount = sum(len(v) for (name, v) in dirNames.items())
searchView.setRowCount(rowCount)
index = 0
for letter, paths in dirNames.items():
for path in paths:
it = QTableWidgetItem(path)
searchView.setItem(index, 0, it)
group = QButtonGroup(searchView)
for i, name in enumerate(colsNames):
button = QRadioButton()
group.addButton(button)
searchView.setCellWidget(index, i + 1, button)
if name == letter:
button.setChecked(True)
index += 1
searchView.show()
sys.exit(app.exec_())
When resizing the columns or rows, I notice a weird behavior: while I'm pressing the mouse button and resizing the column or the raw, the QRadioButtons remain still at their places, causing some clashes; then, when I finally release the mouse button, every QRadioButton come to its place. Is there a way to avoid that aka to make the QRadioButtons move as well during the resizing process?

Since my previous solution generates other problems then in this solution I will show another alternative:
Implement the logic of exclusion through a delegate using the Qt :: CheckStateRole.
A QProxyStyle can be used for painting
import sys
from PySide2.QtCore import Qt, QEvent
from PySide2.QtWidgets import (
QApplication,
QTableWidget,
QTableWidgetItem,
QStyledItemDelegate,
QStyle,
QStyleOptionViewItem,
QProxyStyle,
)
class RadioButtonDelegate(QStyledItemDelegate):
def editorEvent(self, event, model, option, index):
flags = model.flags(index)
if (
not (flags & Qt.ItemIsUserCheckable)
or not (option.state & QStyle.State_Enabled)
or not (flags & Qt.ItemIsEnabled)
):
return False
state = index.data(Qt.CheckStateRole)
if state is None:
return False
widget = option.widget
style = widget.style() if widget is not None else QApplication.style()
# make sure that we have the right event type
if (
(event.type() == QEvent.MouseButtonRelease)
or (event.type() == QEvent.MouseButtonDblClick)
or (event.type() == QEvent.MouseButtonPress)
):
viewOpt = QStyleOptionViewItem(option)
self.initStyleOption(viewOpt, index)
checkRect = style.subElementRect(
QStyle.SE_ItemViewItemCheckIndicator, viewOpt, widget
)
me = event
if me.button() != Qt.LeftButton or not checkRect.contains(me.pos()):
return False
if (event.type() == QEvent.MouseButtonPress) or (
event.type() == QEvent.MouseButtonDblClick
):
return True
else:
return False
if state != Qt.Checked:
for c in range(model.columnCount()):
if c not in (0, index.column()):
ix = model.index(index.row(), c)
model.setData(ix, Qt.Unchecked, Qt.CheckStateRole)
return model.setData(index, Qt.Checked, Qt.CheckStateRole)
return False
class RadioStyle(QProxyStyle):
def drawPrimitive(self, element, option, painter, widget=None):
if element == QStyle.PE_IndicatorItemViewItemCheck:
element = QStyle.PE_IndicatorRadioButton
super().drawPrimitive(element, option, painter, widget)
app = QApplication(sys.argv)
searchView = QTableWidget(0, 4)
style = RadioStyle(searchView.style())
searchView.setStyle(style)
delegate = RadioButtonDelegate(searchView)
searchView.setItemDelegate(delegate)
colsNames = ["A", "B", "C"]
searchView.setHorizontalHeaderLabels(["DIR"] + colsNames)
dirNames = {"A": ["/tmp", "/tmp/dir1"], "B": ["/tmp/dir2"], "C": ["/tmp/dir3"]}
rowCount = sum(len(v) for (name, v) in dirNames.items())
searchView.setRowCount(rowCount)
index = 0
for letter, paths in dirNames.items():
for path in paths:
it = QTableWidgetItem(path)
searchView.setItem(index, 0, it)
for i, name in enumerate(colsNames):
it = QTableWidgetItem()
searchView.setItem(index, i + 1, it)
it.setCheckState(Qt.Checked if name == letter else Qt.Unchecked)
index += 1
searchView.show()
sys.exit(app.exec_())

I had this problem and managed to fix it with a fairly straightforward solution.
Create a connection from your QTableWidget's QHeader's sectionResized signal to a custom method (_refresh_row_size in my case).
self.table.horizontalHeader().sectionResized.connect(self._refresh_row_size)
self.table.verticalHeader().sectionResized.connect(self._refresh_row_size)
I only wanted the first row and first column to get resized, as that is where I have inserted a QCheckbox. This is what I used:
def _refresh_row_size(self, logicalIndex, oldSize, newSize):
self.table.rowResized(0, oldSize, newSize)
self.table.columnResized(0, oldSize, newSize)
return
For extra context, this is the QTableItem I added to the QTable:
# Create checkbox widget
self.widget = QtWidgets.QWidget()
self.checkbox = QtWidgets.QCheckBox(self.widget)
self.layout = QtWidgets.QHBoxLayout(self.widget)
self.layout.addWidget(self.checkbox)
self.layout.setAlignment(QtCore.Qt.AlignCenter)
self.layout.setContentsMargins(0, 0, 0, 0)
# Add new row to table
row_position = self.table.rowCount()
self.table.insertRow(row_position)
# Create new table item, add item, add widget
item = QtWidgets.QTableWidgetItem()
self.table.setItem(0, 0, item)
self.table.setCellWidget(0, 0, self.widget)

Related

Drag and Drop a Widget in QTreeWidget

I have a QTreeWidget where I want to move around the items. This works fine with the cities (see example) but not with the buttons.
First Question: What do I need to do to make the buttons moveable like the cities?
Second Question: If I move the cities I get a copy, but I want to move the city only (delete from original place)
class Example(QTreeWidget):
def __init__(self):
super().__init__()
self.setDragEnabled(True)
self.setAcceptDrops(True)
self.setWindowTitle('Drag and Drop Button')
self.setGeometry(300, 300, 550, 450)
self.cities = QTreeWidgetItem(self)
self.cities.setText(0,"Cities")
self.setDragDropMode(self.InternalMove)
osloItem = QTreeWidgetItem(self.cities)
osloItem.setText(0,"Oslo")
bergenItem = QTreeWidgetItem(self.cities)
bergenItem.setText(0,"Bergen")
stavangerItem = QTreeWidgetItem(self.cities)
stavangerItem.setText(0,"Stavanger")
button1 = QPushButton('Button1',self)
button2 = QPushButton("Button2",self)
label = QLabel("dragHandle")
container = QWidget()
containerLayout = QHBoxLayout()
container.setLayout(containerLayout)
containerLayout.addWidget(label)
containerLayout.addWidget(button1)
containerLayout.addWidget(button2)
b1 = QTreeWidgetItem(self.cities)
self.setItemWidget(b1,0,container)
def main():
app = QApplication(sys.argv)
ex = Example()
ex.show()
app.exec_()
if __name__ == '__main__':
main()
An important aspect about index widgets (including persistent editors) is that the view takes complete ownership on the widget, and whenever index is removed for any reason, the widget associated to that widget gets automatically destroyed. This also happens when calling again setIndexWidget() with another widget for the same index.
There is absolutely no way to prevent that, the destruction is done internally (by calling deleteLater()), and reparenting the widget won't change anything.
The only way to "preserve" the widget is to set a "fake" container as the index widget, create a layout for it, and add the actual widget to it.
Then, the problem comes when using drag&drop, because item views always use serialization of items, even when the InternalMove flag is set.
This means that when an item is moved, the original index gets removed (and the widget along with it, including widgets for any child item).
The solution, then, is to "capture" the drop operation before it's performed, reparent the contents of the container with a similar copy, proceed with the base implementation and then restore the widgets for the new target indexes.
Since we are dealing with tree models, this automatically calls for recursive functions, both for reparenting and restoration.
In the following code I've created an implementation that should be compatible for all standard views (QTreeView, QTableView, QListView) and their higher level widgets. I didn't consider QColumnView, as it's a quite peculiar view and rarely has such requirement.
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class Handle(QWidget):
'''
A custom widget that shows a handle for item dragging whenever
the editor doesn't support it
'''
def __init__(self):
super().__init__()
self.setFixedWidth(self.style().pixelMetric(
QStyle.PM_ToolBarHandleExtent))
def paintEvent(self, event):
qp = QPainter(self)
opt = QStyleOption()
opt.initFrom(self)
style = self.style()
opt.state |= style.State_Horizontal
style.drawPrimitive(style.PE_IndicatorToolBarHandle, opt, qp)
class Example(QTreeWidget):
def __init__(self):
super().__init__()
self.setColumnCount(2)
self.setDragEnabled(True)
self.setAcceptDrops(True)
self.setWindowTitle('Drag and Drop Button')
self.cities = QTreeWidgetItem(self)
self.cities.setText(0, 'Cities')
self.setDragDropMode(self.InternalMove)
osloItem = QTreeWidgetItem(self.cities)
osloItem.setText(0,'Oslo')
bergenItem = QTreeWidgetItem(self.cities)
bergenItem.setText(0,'Bergen')
stavangerItem = QTreeWidgetItem(self.cities)
stavangerItem.setText(0, 'Stavanger')
button1 = QPushButton('Button1', self)
button2 = QPushButton('Button2', self)
label = QLabel('dragHandle')
container = QWidget()
containerLayout = QGridLayout()
container.setLayout(containerLayout)
containerLayout.addWidget(label)
containerLayout.addWidget(button1, 0, 1)
containerLayout.addWidget(button2, 0, 2)
b1 = QTreeWidgetItem(self.cities)
self.setItemWidget(b1, 0, container)
anotherItem = QTreeWidgetItem(self, ['Whatever'])
anotherChild = QTreeWidgetItem(anotherItem, ['Another child'])
grandChild = QTreeWidgetItem(anotherChild, ['a', 'b'])
self.setItemWidget(grandChild, 1, QPushButton('Whatever'))
self.expandAll()
height = self.header().sizeHint().height() + self.frameWidth() * 2
index = self.model().index(0, 0)
while index.isValid():
height += self.rowHeight(index)
index = self.indexBelow(index)
self.resize(500, height + 100)
def setItemWidget(self, item, column, widget, addHandle=False):
if widget and addHandle or widget.layout() is None:
container = QWidget()
layout = QHBoxLayout(container)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
layout.addWidget(Handle())
layout.addWidget(widget)
widget = container
super().setItemWidget(item, column, widget)
def createNewContainer(self, index):
'''
create a copy of the container and its layout, then reparent all
child widgets by adding them to the new layout
'''
oldWidget = self.indexWidget(index)
if oldWidget is None:
return
oldLayout = oldWidget.layout()
if oldLayout is None:
return
newContainer = oldWidget.__class__()
newLayout = oldLayout.__class__(newContainer)
newLayout.setContentsMargins(oldLayout.contentsMargins())
newLayout.setSpacing(oldLayout.spacing())
if isinstance(oldLayout, QGridLayout):
newLayout.setHorizontalSpacing(
oldLayout.horizontalSpacing())
newLayout.setVerticalSpacing(
oldLayout.verticalSpacing())
for r in range(oldLayout.rowCount()):
newLayout.setRowStretch(r,
oldLayout.rowStretch(r))
newLayout.setRowMinimumHeight(r,
oldLayout.rowMinimumHeight(r))
for c in range(oldLayout.columnCount()):
newLayout.setColumnStretch(c,
oldLayout.columnStretch(c))
newLayout.setColumnMinimumWidth(c,
oldLayout.columnMinimumWidth(c))
items = []
for i in range(oldLayout.count()):
layoutItem = oldLayout.itemAt(i)
if not layoutItem:
continue
if layoutItem.widget():
item = layoutItem.widget()
elif layoutItem.layout():
item = layoutItem.layout()
elif layoutItem.spacerItem():
item = layoutItem.spacerItem()
if isinstance(oldLayout, QBoxLayout):
items.append((item, oldLayout.stretch(i), layoutItem.alignment()))
else:
items.append((item, ) + oldLayout.getItemPosition(i))
for item, *args in items:
if isinstance(item, QWidget):
newLayout.addWidget(item, *args)
elif isinstance(item, QLayout):
newLayout.addLayout(item, *args)
else:
if isinstance(newLayout, QBoxLayout):
newLayout.addSpacerItem(item)
else:
newLayout.addItem(item, *args)
return newContainer
def getNewIndexWidgets(self, parent, row):
'''
A recursive function that returns a nested list of widgets and those of
child indexes, by creating new parent containers in the meantime to
avoid their destruction
'''
model = self.model()
rowItems = []
for column in range(model.columnCount()):
index = model.index(row, column, parent)
childItems = []
if column == 0:
for childRow in range(model.rowCount(index)):
childItems.append(self.getNewIndexWidgets(index, childRow))
rowItems.append((
self.createNewContainer(index),
childItems
))
return rowItems
def restoreIndexWidgets(self, containers, parent, startRow=0, startCol=0):
'''
Restore index widgets based on the previously created nested list of
widgets, based on the new parent and drop row (and column for tables)
'''
model = self.model()
for row, rowItems in enumerate(containers, startRow):
for column, (widget, childItems) in enumerate(rowItems, startCol):
index = model.index(row, column, parent)
if widget:
self.setIndexWidget(index, widget)
self.restoreIndexWidgets(childItems, index)
def dropEvent(self, event):
'''
Assume that the selected index is the source of the drag and drop
operation, then create a nested list of possible index widget that
are reparented *before* the drop is applied to avoid their destruction
and then restores them based on the drop index
'''
containers = []
if event.source() == self:
dropTarget = QPersistentModelIndex(self.indexAt(event.pos()))
dropPos = self.dropIndicatorPosition()
selection = self.selectedIndexes()
if selection and len(set(i.row() for i in selection)) == 1:
index = selection[0]
containers.append(
self.getNewIndexWidgets(index.parent(), index.row()))
super().dropEvent(event)
if containers:
model = self.model()
startCol = 0
if dropPos == self.OnViewport:
parent = QModelIndex()
dropRow = model.rowCount() - 1
else:
if dropPos == self.OnItem:
if isinstance(self, QTreeView):
# tree views move items as *children* of the drop
# target when the action is *on* an item
parent = model.index(
dropTarget.row(), dropTarget.column(),
dropTarget.parent())
dropRow = model.rowCount(parent) - 1
else:
# QTableView and QListView use the drop index as target
# for the operation, so the parent index is actually
# the root index of the view
parent = self.rootIndex()
dropRow = model.rowCount(dropTarget.parent()) - 1
dropRow = dropTarget.row()
startCol = dropTarget.column() - index.column()
else:
if isinstance(self, QTreeView):
parent = dropTarget.parent()
else:
parent = self.rootIndex()
if dropPos == self.AboveItem:
dropRow = dropTarget.row() - 1
else:
dropRow = dropTarget.row() + 1
# try to restore the widgets based on the above
self.restoreIndexWidgets(containers, parent, dropRow, startCol)
# ensure that all geometries are updated after the drop operation
self.updateGeometries()
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
ex = Example()
ex.show()
app.exec_()
Notes:
while the above is done for QTreeWidget, it can be easily ported for generic usage of other views; the only difference is the optional override of setItemWidget (which would be setIndexWidget() for basic views or setCellWidget() for QTableWidget, and with the relative argument differences;
item views (and "item widget views") and models might have different behaviors on drag&drop operations, and depending on their settings (starting with dragDropMode); always consider the default behavior of those classes, and also the defaultDropAction;
please study all the code above with extreme care: it's a generic example, drag and drop operations are quite complex, and you might need custom behavior; I strongly suggest to patiently and carefully read that code to understand its aspects whenever you need more advanced control over d&d, especially when dealing with tree structures;
remember that index widgets, while useful, are often problematic; most of the times, using a custom delegate might be a better choice, even for interactive widgets such as buttons or complex widgets; remember that the base source of item views is the model data, including eventual "widgets" (or even editors and their contents);

How to select rows on QTableWidget and return the value to the QLineEdit

I want to make that when i click on particular cell on the QTableWidget it will block the corresponding rows and I want to return the value of each row selected into the QLineEdit.
I couldnt seem to find the solution my code only return when i click it will block the rows but not getting the value.
def click_inventorytable(self):
self.tableInventory.setSelectionBehavior(QtWidgets.QTableView.SelectRows)
index = (self.tableInventory.selectionModel().currentIndex())
value = index.row()
list = [value]
if(len(list)==6):
self.lineproductnameinv.setText((list[1]))
self.linedescinv.setText((list[2]))
self.combocateinv.setText((list[3]))
self.linepriceinv.setText((list[4]))
self.linecurrentstock.setText((list[5]))
self.addstock.setText('')
Since the OP does not provide an MRE then I will create a simple demo of how you can implement the functionality of mapping the elements of the selected row of a table in various widgets.
The appropriate widgets must be chosen so that the user does not enter incorrect values, for example in the case of stock if a QLineEdit is used the user could enter a word which does not make sense since a number is expected so it is better to use a QSpinBox.
Also when the data is saved in the table it is not good to convert it to a string since it loses the way to differentiate them, it is better to save the value through setData() associated with the Qt::DisplayRole role.
Finally, the key to the solution is to use a QDataWidgetMapper that allows mapping parts of a model in widgets, so each time a row is selected the currentIndex of the mapper is updated and it sends the information to the editors.
from functools import cached_property
import random
import sys
from PyQt5 import QtCore, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
LABELS = (
"Product ID",
"Product Name",
"Description",
"Category",
"Price",
"Stock",
)
CATEGORY_OPTIONS = (
"OPTION1",
"OPTION2",
"OPTION3",
"OPTION4",
)
def __init__(self, parent=None):
super().__init__(parent)
self.mapper.setModel(self.tableWidget.model())
self.tableWidget.selectionModel().currentChanged.connect(
self.mapper.setCurrentModelIndex
)
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
lay = QtWidgets.QHBoxLayout(central_widget)
flay = QtWidgets.QFormLayout()
lay.addLayout(flay)
lay.addWidget(self.tableWidget, stretch=1)
editors = (
self.name_edit,
self.description_edit,
self.category_combo,
self.price_edit,
self.stock_edit,
)
for i, (label, widget) in enumerate(zip(self.LABELS[1:], editors)):
flay.addRow(label, widget)
self.mapper.addMapping(widget, i)
self.fillTable()
self.resize(960, 480)
#cached_property
def tableWidget(self):
table = QtWidgets.QTableWidget(
0,
len(self.LABELS),
selectionBehavior=QtWidgets.QAbstractItemView.SelectRows,
selectionMode=QtWidgets.QAbstractItemView.SingleSelection,
)
table.setHorizontalHeaderLabels(self.LABELS)
return table
#cached_property
def name_edit(self):
return QtWidgets.QLineEdit()
#cached_property
def description_edit(self):
return QtWidgets.QLineEdit()
#cached_property
def category_combo(self):
combo = QtWidgets.QComboBox()
combo.addItems(["--Null--"] + list(self.CATEGORY_OPTIONS))
combo.setCurrentIndex(0)
return combo
#cached_property
def price_edit(self):
return QtWidgets.QDoubleSpinBox(maximum=2147483647)
#cached_property
def stock_edit(self):
return QtWidgets.QSpinBox(maximum=2147483647)
#cached_property
def mapper(self):
return QtWidgets.QDataWidgetMapper()
def fillTable(self):
self.tableWidget.setRowCount(0)
for i in range(30):
self.tableWidget.insertRow(self.tableWidget.rowCount())
values = (
i,
f"name-{i}",
f"Description-{i}",
random.choice(self.CATEGORY_OPTIONS),
random.uniform(100, 2000),
random.randint(0, 100),
)
for j, value in enumerate(values):
it = QtWidgets.QTableWidgetItem()
it.setData(QtCore.Qt.DisplayRole, value)
it.setFlags(it.flags() & ~QtCore.Qt.ItemIsEditable)
self.tableWidget.setItem(i, j, it)
def main():
app = QtWidgets.QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()

How to add red circles next to items in QTreeView (breakpoint style)

I have something like this:
I can't show you more, but this is a simple QTreeView with QStandardItems in it. The items in the figure have a parent item which has a parent item as well.
When I activate the breakpoint on a item I have this:
which is ok but I also would like to add a circle next it as the majority of IDEs do (I took as example PyCharm):
The problem is that I have no idea how to do it. Anyone can help?
A possible solution is to override the drawRow method of QTreeView and use the information from the QModelIndex to do the painting:
import sys
from PySide2.QtCore import Qt, QRect
from PySide2.QtGui import QColor, QStandardItem, QStandardItemModel
from PySide2.QtWidgets import QAbstractItemView, QApplication, QTreeView
IS_BREAKPOINT_ROLE = Qt.UserRole + 1
class TreeView(QTreeView):
def drawRow(self, painter, option, index):
super().drawRow(painter, option, index)
if index.column() == 0:
if not index.data(IS_BREAKPOINT_ROLE):
return
rect = self.visualRect(index)
if not rect.isNull():
margin = 4
r = QRect(0, rect.top(), rect.height(), rect.height()).adjusted(
margin, margin, -margin, -margin
)
painter.setBrush(QColor("red"))
painter.drawEllipse(r)
def main(args):
app = QApplication(args)
view = TreeView()
view.setSelectionBehavior(QAbstractItemView.SelectRows)
model = QStandardItemModel()
model.setHorizontalHeaderLabels(["col1", "col2"])
view.setModel(model)
counter = 0
for i in range(10):
item1 = QStandardItem("Child 1-{}".format(i))
item2 = QStandardItem("Child 2-{}".format(i))
for j in range(10):
child1 = QStandardItem("Child {}-1".format(counter))
child2 = QStandardItem("Child {}-2".format(counter))
child1.setData(counter % 2 == 0, IS_BREAKPOINT_ROLE)
item1.appendRow([child1, child2])
counter += 1
model.appendRow([item1, item2])
view.show()
view.resize(320, 240)
view.expandAll()
sys.exit(app.exec_())
if __name__ == "__main__":
main(sys.argv)
I'd like to propose an alternate solution based on the answer by eyllanesc, which adds a left margin to the viewport, avoiding painting over the hierarchy lines (which could hide expanding decoration arrows for parent items that still need to show the circle).
Some important notes:
the left margin is created using setViewportMargins(), but all item views automatically reset those margins when calling updateGeometries() (which happens almost everytime the layout is changed), so that method needs overriding;
painting on the margins means that painting does not happen in the viewport, so we cannot implement paintEvent() (which by default is called for updates on the viewport); this results in implementing the drawing in the event() instead;
updates must be explicitly called when the scroll bar change or items are expanded/collapsed, but Qt only updates the region interested by items that have been actually "changed" (thus possibly excluding other "shifted" items); in order to request the update on the full extent we need to call the base implementation of QWidget (not that of the view, as that method is overridden);
class TreeView(QTreeView):
leftMargin = 14
def __init__(self, *args, **kwargs):
super().__init__()
self.leftMargin = self.fontMetrics().height()
self.verticalScrollBar().valueChanged.connect(self.updateLeftMargin)
self.expanded.connect(self.updateLeftMargin)
self.collapsed.connect(self.updateLeftMargin)
def updateLeftMargin(self):
QWidget.update(self,
QRect(0, 0, self.leftMargin + self.frameWidth(), self.height()))
def setModel(self, model):
if self.model() != model:
if self.model():
self.model().dataChanged.disconnect(self.updateLeftMargin)
super().setModel(model)
model.dataChanged.connect(self.updateLeftMargin)
def updateGeometries(self):
super().updateGeometries()
margins = self.viewportMargins()
if margins.left() < self.leftMargin:
margins.setLeft(margins.left() + self.leftMargin)
self.setViewportMargins(margins)
def event(self, event):
if event.type() == event.Paint:
pos = QPoint()
index = self.indexAt(pos)
qp = QPainter(self)
border = self.frameWidth()
bottom = self.height() - border * 2
qp.setClipRect(QRect(border, border, self.leftMargin, bottom))
top = .5
if self.header().isVisible():
top += self.header().height()
qp.translate(.5, top)
qp.setBrush(Qt.red)
qp.setRenderHints(qp.Antialiasing)
deltaY = self.leftMargin / 2 - border
circle = QRect(
border + 1, 0, self.leftMargin - 2, self.leftMargin - 2)
row = 0
while index.isValid():
rect = self.visualRect(index)
if index.data(IS_BREAKPOINT_ROLE):
circle.moveTop(rect.center().y() - deltaY)
qp.drawEllipse(circle)
row += 1
pos.setY(rect.bottom() + 2)
if pos.y() > bottom:
break
index = self.indexAt(pos)
return super().event(event)

Replacing QListView items that are dropped onto

I have a QTreeView and a QListView. Both of these use the QStandardItemModel as models. The QTreeView should not accept drops. The QListView should accept drops.
If an item is dragged from the QTreeView and dropped onto an item in the QListView, then a copy of the item dragged from the QTreeView should replace the item in the QListView that it is dropped onto. If an item is dragged from the QTreeView and dropped at the top of the QListView, or between existing items in the QListView, or at the bottom of the QListView, then it should be copied to there.
If an item is dragged from the QListView and dropped onto an item in the QListView, then it should be moved to replace the item in the QListView that it is dropped onto. If an item is dragged from the QListView and dropped at the top of the QListView, or between existing items in the QListView, or at the bottom of the QListView, then it should be moved to there.
The items in the QTreeView should not be editable. Once they are copied to the QListView they should become editable in the QListView.
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
def decode_byte_array(byte_array):
role_value_dict_list = []
data_stream = QDataStream(byte_array, QIODevice.ReadOnly)
while not data_stream.atEnd():
row = data_stream.readInt32()
column = data_stream.readInt32()
count = data_stream.readInt32()
role_value_dict = {}
for i in range(count):
role = data_stream.readInt32()
value = QVariant()
data_stream >> value
role_value_dict[Qt.ItemDataRole(role)] = value
role_value_dict_list.append(role_value_dict)
return role_value_dict_list
class MyListModel(QStandardItemModel):
def dropMimeData(self, data, action, row, column, parent):
if data.hasFormat('application/x-qabstractitemmodeldatalist'):
byte_array = QByteArray(data.data("application/x-qabstractitemmodeldatalist"))
role_value_dict_list = decode_byte_array(byte_array)
item_list = []
for role_value_dict in role_value_dict_list:
item = QStandardItem()
for role, value in role_value_dict.items():
item.setData(value, role)
item.setEditable(True)
item_list.append(item)
parent_item = self.itemFromIndex(parent)
if row == -1 and column == -1:
if parent_item == None:
# Drop is after last row.
for item in item_list:
self.appendRow(item)
else:
# Drop is on row.
self.setItem(parent.row(), parent.column(), item_list[0])
row = parent.row() + 1
for item in item_list[1:]:
self.insertRow(row, item)
row = row + 1
elif row >= 0 and column >= 0:
# Drop is before first row or between rows.
for item in item_list:
self.insertRow(row, item)
row = row + 1
else:
return False
else:
return False
return True
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
centralWidget = QWidget()
layout = QHBoxLayout()
tree_view_model = QStandardItemModel()
parent_item = tree_view_model.invisibleRootItem()
item_1 = QStandardItem('item 1')
item_1.setDragEnabled(False)
item_1.setEditable(False)
item_2 = QStandardItem('item 2')
item_2.setEditable(False)
item_3 = QStandardItem('item 3')
item_3.setEditable(False)
item_1.appendRow(item_2)
item_1.appendRow(item_3)
parent_item.appendRow(item_1)
tree_view = QTreeView()
tree_view.setModel(tree_view_model)
tree_view.setHeaderHidden(True)
tree_view.header().setSectionResizeMode(QHeaderView.ResizeToContents)
tree_view.setDragEnabled(True)
list_view_model = MyListModel()
list_view = QListView()
list_view.setModel(list_view_model)
list_view.setDragEnabled(True)
list_view.setAcceptDrops(True)
list_view.setDefaultDropAction(Qt.MoveAction)
list_view.setDragDropOverwriteMode(False)
list_view.setSelectionBehavior(QListView.SelectRows)
list_view.setSelectionMode(QListView.SingleSelection)
layout.addWidget(tree_view)
layout.addWidget(list_view)
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
app = QApplication([])
f = QFont('Courier')
f.setPointSize(16)
app.setFont(f)
window = MainWindow()
window.show()
sys.exit(app.exec_())

How to manage visibility of data in PyQt5 QTableWidgetItem

I want to make some data hidden in QTableWidget until a specific cell is clicked. Right now I only manage to display "*" instead of actual value. I think I could connect somehow with action when the specific cell is clicked and replaced the value of the clicked cell. I also know that there is a function setData() that can be invoked on QTableWidgetItem and give me wanted behavior. But I cannot find any useful example for Python implementation of qt. What is the best solution to this problem?
def setTableValues(self):
self.table.setRowCount(len(self.tableList))
x = 0
for acc in self.tableList:
y = 0
for val in acc.getValuesAsList():
if isinstance(val,Cipher):
val = "*"*len(val.getDecrypted())
item = QTableWidgetItem(val)
self.table.setItem(x,y,item)
y += 1
x += 1
self.table.resizeRowsToContents()
self.table.resizeColumnsToContents()
You can associate a flag with a role that indicates the visibility of the text and then use a delegate to hide the text. That flag will change when the items are pressed:
from PyQt5 import QtCore, QtGui, QtWidgets
VisibilityRole = QtCore.Qt.UserRole + 1000
class VisibilityDelegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
if not index.data(VisibilityRole):
option.text = "*" * len(option.text)
class TableWidget(QtWidgets.QTableWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
delegate = VisibilityDelegate(self)
self.setItemDelegate(delegate)
self.visibility_index = QtCore.QModelIndex()
self.pressed.connect(self.on_pressed)
#QtCore.pyqtSlot(QtCore.QModelIndex)
def on_pressed(self, index):
if self.visibility_index.isValid():
self.model().setData(self.visibility_index, False, VisibilityRole)
self.visibility_index = index
self.model().setData(self.visibility_index, True, VisibilityRole)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = TableWidget(10, 4)
for i in range(w.rowCount()):
for j in range(w.columnCount()):
it = QtWidgets.QTableWidgetItem("{}-{}".format(i, j))
w.setItem(i, j, it)
w.show()
w.resize(640, 480)
sys.exit(app.exec_())

Categories

Resources