Position QCheckBox top left of item in QListView - python

How can i position the Checkbox of an item in a QListView at the top left? I tried using a stylesheet but when i resize my item, the checkbox does not maintain it's position in the top left. It becomes hidden.
Does anyone have any suggestions on how to achieve this? Do I use QItemDelegate or QStyledItemDelegate to solve my problem?
My goal is to make it appear like this...
import os, sys, re
from Qt import QtWidgets, QtGui, QtCore
from PictureShop.Resources import StyleUtils
################################################################################
# Dummy Values
################################################################################
values = ['MomBod','Colonel','Tater','Tot','Ginger','Donut','Sport','LaLa','Itchy','Bruiser','Cotton','Cumulus','Toodles']
################################################################################
# Widgets
################################################################################
class ViewerWidget(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.resize(500,500)
self.uiIconSize = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.uiIconSize.setRange(32,256)
self.uiIconSize.setValue(96)
self.uiIconSize.setMinimumWidth(100)
self.viewerModel = QtGui.QStandardItemModel()
self.uiListView = QtWidgets.QListView()
self.uiListView.setSpacing(5)
self.uiListView.setMovement(QtWidgets.QListView.Static)
self.uiListView.setViewMode(QtWidgets.QListView.IconMode)
self.uiListView.setLayoutMode(QtWidgets.QListView.Batched)
self.uiListView.setBatchSize(100)
self.uiListView.setFlow(QtWidgets.QListView.LeftToRight)
self.uiListView.setWrapping(True)
self.uiListView.setResizeMode(QtWidgets.QListView.Adjust)
self.uiListView.setDragEnabled(False)
self.uiListView.setUniformItemSizes(True)
self.uiListView.setSelectionMode(QtWidgets.QListView.ExtendedSelection)
self.uiListView.setModel(self.viewerModel)
# layout
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.uiListView)
self.layout.addWidget(self.uiIconSize)
self.setLayout(self.layout)
# Signals
self.uiIconSize.valueChanged.connect(self.slotChangedIconSize)
self.uiIconSize.setValue(96)
self.uiListView.setIconSize(QtCore.QSize(96,96))
# Init
self.populateModel()
self.setStyleSheet('''
QWidget::indicator {
subcontrol-position: top center;
position: relative;
left: 30px;
top: -20px;
}
''')
# Methods
def populateModel(self):
model = self.viewerModel
model.clear()
icon = QtGui.QIcon('C:/Users/jmartini/Desktop/brokenImage.png')
for x in values:
newItem = QtGui.QStandardItem()
newItem.setCheckable(True)
newItem.setData(icon, role=QtCore.Qt.DecorationRole)
model.appendRow(newItem)
# Slots
def slotChangedIconSize(self):
size = self.uiIconSize.value()
self.uiListView.setIconSize(QtCore.QSize(size,size))
################################################################################
# Unit Testing
################################################################################
def test_ViewerWidget():
app = QtWidgets.QApplication(sys.argv)
app.setOrganizationName('mine')
app.setApplicationName('browser')
ex = ViewerWidget()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
pass
test_ViewerWidget()
Help from answers below. I have a bug shown here:
Got a working solution based on comments below:
import os, sys, re
from Qt import QtWidgets, QtGui, QtCore
################################################################################
# Dummy Values
################################################################################
values = ['MomBod','Colonel','Tater','Tot','Ginger','Donut','Sport','LaLa','Itchy','Bruiser','Cotton','Cumulus','Toodles']
class ItemDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent=None, *args):
QtWidgets.QStyledItemDelegate.__init__(self, parent, *args)
# overrides
def sizeHint(self, option, index):
isw, ish = option.decorationSize.toTuple()
return QtCore.QSize(isw, ish)
def getCheckboxRect(self, option):
return QtCore.QRect(4, 4, 18, 18).translated(option.rect.topLeft())
def paint(self, painter, option, index):
painter.save()
# Draw
isw, ish = option.decorationSize.toTuple()
x, y, dx, dy = option.rect.x(), option.rect.y(), option.rect.width(), option.rect.height()
# Decoration
pic = index.data(QtCore.Qt.DecorationRole)
if pic:
painter.drawPixmap(x, y, pic.pixmap(isw, ish))
# Indicate Selected
painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))
if option.state & QtWidgets.QStyle.State_Selected:
painter.setBrush(QtGui.QBrush(QtGui.QColor(0,70,240,128)))
else:
painter.setBrush(QtGui.QBrush(QtCore.Qt.NoBrush))
painter.drawRect(QtCore.QRect(x, y, dx, dy))
# Checkstate
value = index.data(QtCore.Qt.CheckStateRole)
if value is not None:
opt = QtWidgets.QStyleOptionViewItem()
opt.rect = self.getCheckboxRect(option)
opt.state = opt.state & ~QtWidgets.QStyle.State_HasFocus
if value == QtCore.Qt.Unchecked:
opt.state |= QtWidgets.QStyle.State_Off
elif value == QtCore.Qt.PartiallyChecked:
opt.state |= QtWidgets.QStyle.State_NoChange
elif value == QtCore.Qt.Checked:
opt.state = QtWidgets.QStyle.State_On
style = QtWidgets.QApplication.style()
style.drawPrimitive(
QtWidgets.QStyle.PE_IndicatorViewItemCheck, opt, painter, None
)
painter.restore()
def editorEvent(self, event, model, option, index):
flags = model.flags(index)
if (
not (flags & QtCore.Qt.ItemIsUserCheckable)
or not (option.state & QtWidgets.QStyle.State_Enabled)
or not (flags & QtCore.Qt.ItemIsEnabled)
):
return False
value = index.data(QtCore.Qt.CheckStateRole)
if value is None:
return False
style = QtWidgets.QApplication.style()
if event.type() in (
QtCore.QEvent.MouseButtonRelease,
QtCore.QEvent.MouseButtonDblClick,
QtCore.QEvent.MouseButtonPress,
):
viewOpt = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(viewOpt, index)
checkRect = self.getCheckboxRect(viewOpt)
if event.button() != QtCore.Qt.LeftButton or not checkRect.contains(
event.pos()
):
return False
if event.type() in (
QtCore.QEvent.MouseButtonPress,
QtCore.QEvent.MouseButtonDblClick,
):
return True
elif event.type() == QtCore.QEvent.KeyPress:
if event.key() not in (QtCore.Qt.Key_Space, QtCore.Qt.Key_Select):
return False
else:
return False
state = value
if flags & QtCore.Qt.ItemIsTristate:
state = QtCore.Qt.CheckState((state + 1) % 3)
else:
state = (
QtCore.Qt.Unchecked if state == QtCore.Qt.Checked else QtCore.Qt.Checked
)
return model.setData(index, state, QtCore.Qt.CheckStateRole)
################################################################################
# Widgets
################################################################################
class ViewerWidget(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.resize(500,500)
self.uiIconSize = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.uiIconSize.setRange(32,256)
self.uiIconSize.setValue(96)
self.uiIconSize.setMinimumWidth(100)
self.viewerModel = QtGui.QStandardItemModel()
self.uiListView = QtWidgets.QListView()
self.uiListView.setSpacing(5)
self.uiListView.setMovement(QtWidgets.QListView.Static)
self.uiListView.setViewMode(QtWidgets.QListView.IconMode)
self.uiListView.setLayoutMode(QtWidgets.QListView.Batched)
self.uiListView.setBatchSize(100)
self.uiListView.setFlow(QtWidgets.QListView.LeftToRight)
self.uiListView.setWrapping(True)
self.uiListView.setResizeMode(QtWidgets.QListView.Adjust)
self.uiListView.setDragEnabled(False)
self.uiListView.setUniformItemSizes(True)
self.uiListView.setSelectionMode(QtWidgets.QListView.ExtendedSelection)
self.uiListView.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.uiListView.setModel(self.viewerModel)
self.uiListView.setItemDelegate(ItemDelegate())
# layout
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.uiListView)
self.layout.addWidget(self.uiIconSize)
self.setLayout(self.layout)
# Signals
self.uiIconSize.valueChanged.connect(self.slotChangedIconSize)
self.uiIconSize.setValue(96)
self.uiListView.setIconSize(QtCore.QSize(96,96))
# Init
self.populateModel()
# Methods
def populateModel(self):
model = self.viewerModel
model.clear()
icon = QtGui.QIcon("C:/Users/JokerMartini-Asus/Desktop/thumbnail_image.png")
for x in values:
newItem = QtGui.QStandardItem()
newItem.setCheckable(True)
newItem.setData(icon, role=QtCore.Qt.DecorationRole)
model.appendRow(newItem)
# Slots
def slotChangedIconSize(self):
size = self.uiIconSize.value()
self.uiListView.setIconSize(QtCore.QSize(size,size))
################################################################################
# Unit Testing
################################################################################
def test_ViewerWidget():
app = QtWidgets.QApplication(sys.argv)
app.setOrganizationName('mine')
app.setApplicationName('browser')
ex = ViewerWidget()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
pass
test_ViewerWidget()

Using a delegate you can paint after the icon is painted:
class Delegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
super(Delegate, self).initStyleOption(option, index)
option.features &= ~QtWidgets.QStyleOptionViewItem.HasCheckIndicator
def paint(self, painter, option, index):
super(Delegate, self).paint(painter, option, index)
value = index.data(QtCore.Qt.CheckStateRole)
if value is not None:
opt = QtWidgets.QStyleOptionViewItem()
opt.rect = self.checkRect(option)
opt.state = opt.state & ~QtWidgets.QStyle.State_HasFocus
if value == QtCore.Qt.Unchecked:
opt.state |= QtWidgets.QStyle.State_Off
elif value == QtCore.Qt.PartiallyChecked:
opt.state |= QtWidgets.QStyle.State_NoChange
elif value == QtCore.Qt.Checked:
opt.state = QtWidgets.QStyle.State_On
widget = option.widget
style = QtWidgets.QApplication.style() if widget is None else widget.style()
style.drawPrimitive(
QtWidgets.QStyle.PE_IndicatorViewItemCheck, opt, painter, widget
)
def checkRect(self, option):
height = option.rect.height()
x, y, w, h = (f * height for f in (0.15, 0.15, 0.2, 0.2))
return QtCore.QRect(x, y, w, h).translated(option.rect.topLeft())
def editorEvent(self, event, model, option, index):
# https://code.qt.io/cgit/qt/qtbase.git/tree/src/widgets/itemviews/qstyleditemdelegate.cpp?h=5.13#n278
flags = model.flags(index)
if (
not (flags & QtCore.Qt.ItemIsUserCheckable)
or not (option.state & QtWidgets.QStyle.State_Enabled)
or not (flags & QtCore.Qt.ItemIsEnabled)
):
return False
value = index.data(QtCore.Qt.CheckStateRole)
if value is None:
return False
widget = option.widget
style = widget.style() if widget is not None else QtWidgets.QApplication.style()
if event.type() in (
QtCore.QEvent.MouseButtonRelease,
QtCore.QEvent.MouseButtonDblClick,
QtCore.QEvent.MouseButtonPress,
):
viewOpt = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(viewOpt, index)
checkRect = self.checkRect(viewOpt)
if event.button() != QtCore.Qt.LeftButton or not checkRect.contains(
event.pos()
):
return False
if event.type() in (
QtCore.QEvent.MouseButtonPress,
QtCore.QEvent.MouseButtonDblClick,
):
return True
elif event.type() == QtCore.QEvent.KeyPress:
if event.key() not in (QtCore.Qt.Key_Space, QtCore.Qt.Key_Select):
return False
else:
return False
state = value
if flags & QtCore.Qt.ItemIsUserTristate:
state = QtCore.Qt.CheckState((state + 1) % 3)
else:
state = (
QtCore.Qt.Unchecked if state == QtCore.Qt.Checked else QtCore.Qt.Checked
)
return model.setData(index, state, QtCore.Qt.CheckStateRole)

Related

What is the best widget in pyqt5 to show a checked list with columns

I am looking for a widget which is a list box, and one entry has different attributes represented by column names.
I want to be able to select multiple entries from the list using a check box on the left side.
Please, also tell me how to configure it. Thanks.
The solution in this case is to use a QTableView (customizing some aspects like the header checkbox):
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class HeaderView(QtWidgets.QHeaderView):
checked = QtCore.pyqtSignal(bool)
def __init__(self, orientation, parent=None):
super().__init__(orientation, parent)
self._checkable_column = -1
self._state = False
self._column_down = -1
#property
def checkable_column(self):
return self._checkable_column
#checkable_column.setter
def checkable_column(self, c):
self._checkable_column = c
#property
def state(self):
return self._state
#state.setter
def state(self, c):
if self.checkable_column == -1:
return
self._state = c
self.checked.emit(c)
self.updateSection(self.checkable_column)
def paintSection(self, painter, rect, logicalIndex):
painter.save()
super().paintSection(painter, rect, logicalIndex)
painter.restore()
if logicalIndex != self.checkable_column:
return
opt = QtWidgets.QStyleOptionButton()
checkbox_rect = self.style().subElementRect(
QtWidgets.QStyle.SE_CheckBoxIndicator, opt, None
)
checkbox_rect.moveCenter(rect.center())
opt.rect = checkbox_rect
opt.state = QtWidgets.QStyle.State_Enabled | QtWidgets.QStyle.State_Active
if logicalIndex == self._column_down:
opt.state |= QtWidgets.QStyle.State_Sunken
opt.state |= (
QtWidgets.QStyle.State_On if self.state else QtWidgets.QStyle.State_Off
)
self.style().drawPrimitive(QtWidgets.QStyle.PE_IndicatorCheckBox, opt, painter)
def mousePressEvent(self, event):
super().mousePressEvent(event)
li = self.logicalIndexAt(event.pos())
self._column_down = li
self.updateSection(li)
def mouseReleaseEvent(self, event):
super().mouseReleaseEvent(event)
li = self.logicalIndexAt(event.pos())
self._column_down = -1
if li == self.checkable_column:
self.state = not self.state
class Dialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.model = QtGui.QStandardItemModel(0, 4, self)
self.model.setHorizontalHeaderLabels(["", "First Name", "Last Name", "Company"])
for state, firstname, lastname, company in (
(True, "Larry", "Ellison", "Oracle"),
(True, "Steve", "Jobs", "Apple"),
(True, "Steve", "Ballmer", "Microsoft"),
(True, "Bill", "Gates", "Microsoft"),
):
it_state = QtGui.QStandardItem()
it_state.setEditable(False)
it_state.setCheckable(True)
it_state.setCheckState(QtCore.Qt.Checked if state else QtCore.Qt.UnChecked)
it_firstname = QtGui.QStandardItem(firstname)
it_lastname = QtGui.QStandardItem(lastname)
it_company = QtGui.QStandardItem(company)
self.model.appendRow([it_state, it_firstname, it_lastname, it_company])
self.view = QtWidgets.QTableView(
showGrid=False, selectionBehavior=QtWidgets.QAbstractItemView.SelectRows
)
self.view.setModel(self.model)
headerview = HeaderView(QtCore.Qt.Horizontal, self.view)
headerview.checkable_column = 0
headerview.checked.connect(self.change_state_of_model)
self.view.setHorizontalHeader(headerview)
self.view.verticalHeader().hide()
self.view.horizontalHeader().setMinimumSectionSize(0)
self.view.horizontalHeader().setSectionResizeMode(
0, QtWidgets.QHeaderView.ResizeToContents
)
self.view.horizontalHeader().setStretchLastSection(True)
self.box = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.Ok
| QtWidgets.QDialogButtonBox.Cancel
| QtWidgets.QDialogButtonBox.NoButton
| QtWidgets.QDialogButtonBox.Help,
QtCore.Qt.Vertical,
)
hlay = QtWidgets.QHBoxLayout(self)
hlay.addWidget(self.view)
hlay.addWidget(self.box)
self.box.accepted.connect(self.accept)
self.box.rejected.connect(self.reject)
self.box.helpRequested.connect(self.on_helpRequested)
self.resize(500, 240)
#QtCore.pyqtSlot()
def on_helpRequested(self):
QtWidgets.QMessageBox.aboutQt(self)
#QtCore.pyqtSlot(bool)
def change_state_of_model(self, state):
for i in range(self.model.rowCount()):
it = self.model.item(i)
if it is not None:
it.setCheckState(QtCore.Qt.Checked if state else QtCore.Qt.Unchecked)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = Dialog()
w.show()
sys.exit(app.exec_())

Removing dotted rectangle on selected item

I have been trying to learn a bit more about the QGraphicsView widget by taking a look at this example here:
Toggle QPen for selected items after QGraphicsScene selection change
I found this fairly helpful, however i noticed that upon selection there is marquee thing that pops up when an item is selected. I was looking at ways to get rid of this, and it seems like there is a consistent answer to this in C++ which I see as an example here:
https://www.qtcentre.org/threads/39659-About-QGraphicsItem-question
But i am very confused as to how to translate this into pyqt, so I was wondering if anyone can provide any insight to how to do this within the example project from the first link....
I've been searching around for possible equivalents by looking at codes such as this:
https://www.riverbankcomputing.com/pipermail/pyqt/2012-November/032110.html
Although to be honest I don't even know what the application of these example codes are...
If you want to remove the rectangle from the selection of the item in python you must use the following:
class GraphicsEllipseItem(QGraphicsEllipseItem):
def paint(self, painter, option, widget):
option.state &= ~QStyle.State_Selected
super(GraphicsEllipseItem, self).paint(painter, option, widget)
Complete script
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import random
class GraphicsEllipseItem(QGraphicsEllipseItem):
def paint(self, painter, option, widget):
option.state &= ~QStyle.State_Selected
super(GraphicsEllipseItem, self).paint(painter, option, widget)
class MyGraphicsView(QGraphicsView):
def __init__(self):
super(MyGraphicsView, self).__init__()
self.setDragMode(QGraphicsView.RubberBandDrag)
self._isPanning = False
self._mousePressed = False
self.setCacheMode(QGraphicsView.CacheBackground)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setScene(MyGraphicsScene(self))
self.scene().selectionChanged.connect(self.selection_changed)
self._current_selection = []
def select_items(self, items, on):
pen = QPen(
QColor(255, 255, 255) if on else QColor(255, 128, 0),
0.5,
Qt.SolidLine,
Qt.RoundCap,
Qt.RoundJoin,
)
for item in items:
item.setPen(pen)
def selection_changed(self):
try:
self.select_items(self._current_selection, False)
self._current_selection = self.scene().selectedItems()
self.select_items(self._current_selection, True)
except RuntimeError:
pass
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self._mousePressed = True
if self._isPanning:
self.setCursor(Qt.ClosedHandCursor)
self._dragPos = event.pos()
event.accept()
else:
super(MyGraphicsView, self).mousePressEvent(event)
elif event.button() == Qt.MiddleButton:
self._mousePressed = True
self._isPanning = True
self.setCursor(Qt.ClosedHandCursor)
self._dragPos = event.pos()
event.accept()
def mouseMoveEvent(self, event):
if self._mousePressed and self._isPanning:
newPos = event.pos()
diff = newPos - self._dragPos
self._dragPos = newPos
self.horizontalScrollBar().setValue(
self.horizontalScrollBar().value() - diff.x()
)
self.verticalScrollBar().setValue(
self.verticalScrollBar().value() - diff.y()
)
event.accept()
else:
super(MyGraphicsView, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton:
if self._isPanning:
self.setCursor(Qt.OpenHandCursor)
else:
self._isPanning = False
self.setCursor(Qt.ArrowCursor)
self._mousePressed = False
elif event.button() == Qt.MiddleButton:
self._isPanning = False
self.setCursor(Qt.ArrowCursor)
self._mousePressed = False
super(MyGraphicsView, self).mouseReleaseEvent(event)
def mouseDoubleClickEvent(self, event):
self.fitInView(self.sceneRect(), Qt.KeepAspectRatio)
pass
def keyPressEvent(self, event):
if event.key() == Qt.Key_Space and not self._mousePressed:
self._isPanning = True
self.setCursor(Qt.OpenHandCursor)
else:
super(MyGraphicsView, self).keyPressEvent(event)
def keyReleaseEvent(self, event):
if event.key() == Qt.Key_Space:
if not self._mousePressed:
self._isPanning = False
self.setCursor(Qt.ArrowCursor)
else:
super(MyGraphicsView, self).keyPressEvent(event)
def wheelEvent(self, event):
# zoom factor
factor = 1.25
# Set Anchors
self.setTransformationAnchor(QGraphicsView.NoAnchor)
self.setResizeAnchor(QGraphicsView.NoAnchor)
# Save the scene pos
oldPos = self.mapToScene(event.pos())
# Zoom
if event.delta() < 0:
factor = 1.0 / factor
self.scale(factor, factor)
# Get the new position
newPos = self.mapToScene(event.pos())
# Move scene to old position
delta = newPos - oldPos
self.translate(delta.x(), delta.y())
class MyGraphicsScene(QGraphicsScene):
def __init__(self, parent):
super(MyGraphicsScene, self).__init__(parent)
self.setBackgroundBrush(QBrush(QColor(50, 50, 50)))
# self.setSceneRect(50,50,0,0)
class MyMainWindow(QMainWindow):
def __init__(self):
super(MyMainWindow, self).__init__()
self.setWindowTitle("Test")
self.resize(800, 600)
self.gv = MyGraphicsView()
self.setCentralWidget(self.gv)
self.populate()
def populate(self):
scene = self.gv.scene()
for i in range(500):
x = random.randint(0, 1000)
y = random.randint(0, 1000)
r = random.randint(2, 8)
rect = GraphicsEllipseItem(x, y, r, r)
rect.setPen(
QPen(QColor(255, 128, 0), 0.5, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
)
rect.setBrush(QBrush(QColor(255, 128, 20, 128)))
scene.addItem(rect)
rect.setFlag(QGraphicsItem.ItemIsSelectable)
rect.setFlag(QGraphicsItem.ItemIsMovable)
rect = GraphicsEllipseItem(300, 500, 20, 20)
rect.setPen(
QPen(QColor(255, 128, 0), 0.5, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
)
rect.setBrush(QBrush(QColor(255, 0, 0, 128)))
scene.addItem(rect)
rect.setFlag(QGraphicsItem.ItemIsSelectable)
rect.setFlag(QGraphicsItem.ItemIsMovable)
def main():
app = QApplication(sys.argv)
ex = MyMainWindow()
ex.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Explanation of option.state &= ~QStyle.State_Selected:
state is an attribute of QStyleOptionGraphicsItem that stores the information of the state of the painting that may contain a combination of the following flags:
In this case the flags can be combined with using the "|" operator, and deactivate using the "&~".
To understand it better, let's use the following example: we set the state in State_Active and State_Editing as initial state:
state = State_Active | State_Editing
= 0x00010000 | 0x00400000
= 0x00410000
And to deactivate the State_Active flag:
state & ~State_Active
0x00410000 & ~(0x00010000)
0x00410000 & 0xFFFEFFFF
0x00400000
As you can see, flag State_Active is removed without removing other flags only with binary operations.
So how do you want to deactivate the State_Selected flag and replace it to the state then it must be done:
option.state = option.state & ~QStyle.State_Selected
option.state &= ~QStyle.State_Selected

How to highlight a words in QTableWidget from a Searchlist

I want to search a QTableWidget-Table by a list of words, if they've been found i want them to bee highlighted.
I tried to modify the code from here so the table is beeing searched by a list of words, not just one. Unfortunatly my results keep getting overwritten. I always only get the result for the last word in the list.
Does anyone know how to modify the code so it will show the result of the whole list of words ?
Here is the code:
from PyQt5 import QtCore, QtGui, QtWidgets
import random
import html
words_1 = ["Hello dseerfd", "world sdfsdf sdfgsdf sdfsdf", "Stack dasdf", "Overflow", "Hello world", """<font color="red">Hello world</font>"""]
class HTMLDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent=None):
super(HTMLDelegate, self).__init__(parent)
self.doc = QtGui.QTextDocument(self)
def paint(self, painter, option, index):
substring = index.data(QtCore.Qt.UserRole)
painter.save()
options = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(options, index)
res = ""
color = QtGui.QColor("red")
if substring:
substrings = options.text.split(substring)
res = """<font color="{}">{}</font>""".format(color.name(QtGui.QColor.HexRgb), substring).join(list(map(html.escape, substrings)))
else:
res = html.escape(options.text)
self.doc.setHtml(res)
options.text = ""
style = QtWidgets.QApplication.style() if options.widget is None \
else options.widget.style()
style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, options, painter)
ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
if option.state & QtWidgets.QStyle.State_Selected:
ctx.palette.setColor(QtGui.QPalette.Text, option.palette.color(
QtGui.QPalette.Active, QtGui.QPalette.HighlightedText))
else:
ctx.palette.setColor(QtGui.QPalette.Text, option.palette.color(
QtGui.QPalette.Active, QtGui.QPalette.Text))
textRect = style.subElementRect(
QtWidgets.QStyle.SE_ItemViewItemText, options)
if index.column() != 0:
textRect.adjust(5, 0, 0, 0)
the_constant = 4
margin = (option.rect.height() - options.fontMetrics.height()) // 2
margin = margin - the_constant
textRect.setTop(textRect.top() + margin)
painter.translate(textRect.topLeft())
painter.setClipRect(textRect.translated(-textRect.topLeft()))
self.doc.documentLayout().draw(painter, ctx)
painter.restore()
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
hlay = QtWidgets.QHBoxLayout()
lay = QtWidgets.QVBoxLayout(self)
self.table = QtWidgets.QTableWidget(5, 5)
lay.addLayout(hlay)
lay.addWidget(self.table)
self.table.setItemDelegate(HTMLDelegate(self.table))
for i in range(self.table.rowCount()):
for j in range(self.table.columnCount()):
it = QtWidgets.QTableWidgetItem(random.choice(words_1))
self.table.setItem(i, j, it)
text_list = ['ello', 'ack']
# clear
allitems = self.table.findItems("", QtCore.Qt.MatchContains)
selected_items =[]
for words in text_list:
for item in allitems:
selected_items = self.table.findItems(words, QtCore.Qt.MatchContains)
selected_items.append(self.table.findItems(words, QtCore.Qt.MatchContains)) ## i tried to make a list which is beeing appened but using this list it returns only the same as the input
item.setData(QtCore.Qt.UserRole, words if item in selected_items else None)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
In the previous case I wanted to filter the cases so as not to have to paint unnecessarily but in this case because it was more complex I decided to implement the highlight logic using QTextCharFormat and not HTML as I show below:
from PyQt5 import QtCore, QtGui, QtWidgets
import random
words = ["Hello dseerfd", "world sdfsdf sdfgsdf sdfsdf", "Stack dasdf", "Overflow", "Hello world", """<font color="red">Hello world</font>"""]
class HighlightDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent=None):
super(HighlightDelegate, self).__init__(parent)
self.doc = QtGui.QTextDocument(self)
self._filters = []
def paint(self, painter, option, index):
painter.save()
options = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(options, index)
self.doc.setPlainText(options.text)
self.apply_highlight()
options.text = ""
style = QtWidgets.QApplication.style() if options.widget is None \
else options.widget.style()
style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, options, painter)
ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
if option.state & QtWidgets.QStyle.State_Selected:
ctx.palette.setColor(QtGui.QPalette.Text, option.palette.color(
QtGui.QPalette.Active, QtGui.QPalette.HighlightedText))
else:
ctx.palette.setColor(QtGui.QPalette.Text, option.palette.color(
QtGui.QPalette.Active, QtGui.QPalette.Text))
textRect = style.subElementRect(
QtWidgets.QStyle.SE_ItemViewItemText, options)
if index.column() != 0:
textRect.adjust(5, 0, 0, 0)
the_constant = 4
margin = (option.rect.height() - options.fontMetrics.height()) // 2
margin = margin - the_constant
textRect.setTop(textRect.top() + margin)
painter.translate(textRect.topLeft())
painter.setClipRect(textRect.translated(-textRect.topLeft()))
self.doc.documentLayout().draw(painter, ctx)
painter.restore()
def apply_highlight(self):
cursor = QtGui.QTextCursor(self.doc)
cursor.beginEditBlock()
fmt = QtGui.QTextCharFormat()
fmt.setForeground(QtCore.Qt.red)
for f in self.filters():
highlightCursor = QtGui.QTextCursor(self.doc)
while not highlightCursor.isNull() and not highlightCursor.atEnd():
highlightCursor = self.doc.find(f, highlightCursor)
if not highlightCursor.isNull():
highlightCursor.mergeCharFormat(fmt)
cursor.endEditBlock()
#QtCore.pyqtSlot(list)
def setFilters(self, filters):
if self._filters == filters: return
self._filters = filters
def filters(self):
return self._filters
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.table = QtWidgets.QTableWidget(30, 6)
self._delegate = HighlightDelegate(self.table)
self.table.setItemDelegate(self._delegate)
for i in range(self.table.rowCount()):
for j in range(self.table.columnCount()):
it = QtWidgets.QTableWidgetItem(random.choice(words))
self.table.setItem(i, j, it)
self.table.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
le = QtWidgets.QLineEdit()
le.textChanged.connect(self.on_textChanged)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(le)
lay.addWidget(self.table)
le.setText("ello ack")
#QtCore.pyqtSlot(str)
def on_textChanged(self, text):
self._delegate.setFilters(list(set(text.split())))
self.table.viewport().update()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.showMaximized()
sys.exit(app.exec_())

PyQt4: How to modify spinbox button direction to left and right

I found a C++ example which modifies the QSpinBox button direction as left and right, and it works. So I used PyQt-4.7 to imitate the example, but the button didn't show. Below is my code:
from PyQt4 import QtGui, QtCore
from PyQt4.QtGui import QStyle, QCommonStyle
class ScientificDoubleSpinBox(QtGui.QDoubleSpinBox):
def __init__(self, *args, **kwargs):
super(ScientificDoubleSpinBox, self).__init__(*args, **kwargs)
self.setMinimum(0)
self.setMaximum(1000)
self.setDecimals(1000)
style = SpinBoxStyle()
#here
self.setStyle(style)
class SpinBoxStyle(QtGui.QCommonStyle):
def __init__(self):
super(SpinBoxStyle, self).__init__()
def drawComplexControl(self, control, option, painter, widget = None):
if(control == QtGui.QStyle.CC_SpinBox):
self.drawSpinBoxButton(QtGui.QStyle.SC_SpinBoxDown,option,painter);
self.drawSpinBoxButton(QtGui.QStyle.SC_SpinBoxUp,option,painter);
rect = self.subControlRect(QtGui.QStyle.CC_SpinBox,option,QtGui.QStyle.SC_SpinBoxEditField, widget).adjusted(-1,0,+1,0)
painter.setPen(QtGui.QPen(option.palette.mid(),1.0))
painter.drawLine(rect.topLeft(),rect.bottomLeft())
painter.drawLine(rect.topRight(),rect.bottomRight())
else:
return QCommonStyle().drawComplexControl(which,option,painter)
def drawSpinBoxButton(self, subControl, option, painter, widget = None):
buttonWidth = 16
arrow = QtGui.QStyle.PE_IndicatorSpinDown
buttonRect = option.rect
if((subControl==QtGui.QStyle.SC_SpinBoxUp)!=(option.direction==1)): #Qt.RightToLeft
arrow = QtGui.QStyle.PE_IndicatorSpinUp
buttonRect.translate(buttonRect.width()/2,0)
else:
buttonRect.translate(buttonRect.width()/2-buttonWidth,0)
buttonRect.setWidth((buttonRect.width()+1)*2);
#????
#buttonOpt = qstyleoption_cast(option)
buttonOpt = QtGui.QStyleOption(option)
painter.save()
painter.setClipRect(buttonRect, 2) #Qt.IntersectClip
#????
#if(not(option.activeSubControls & subControl)):
#buttonOpt.state &=not(QtGui.QStyle.State_MouseOver|QtGui.QStyle.State_On|QtGui.QStyle.State_Sunken)
arrowOpt= QtGui.QStyleOption(buttonOpt)
arrowOpt.rect = self.subControlRect(QtGui.QStyle.CC_SpinBox,option,subControl, widget);
if(arrowOpt.rect.isValid()):
self.drawPrimitive(arrow,arrowOpt,painter)
painter.restore()
def subControlRect(self, whichControl, option, whichSubControl, widget):
if(whichControl==QtGui.QStyle.CC_SpinBox):
frameWidth = self.pixelMetric(QtGui.QStyle.PM_DefaultFrameWidth, option, widget)
buttonWidth = 16;
if whichSubControl == QtGui.QStyle.SC_SpinBoxFrame:
return option.rect
elif whichSubControl == QtGui.QStyle.SC_SpinBoxEditField:
return option.rect.adjusted(0, +frameWidth,-buttonWidth*4,-frameWidth)
elif whichControl == QtGui.QStyle.SC_SpinBoxDown:
return self.visualRect(option.direction, option.rect, QtCore.QRect(option.rect.right()-buttonWidth*2,
option.rect.y(), buttonWidth, option.rect.height()))
elif whichControl == QtGui.QStyle.SC_SpinBoxUp:
return self.visualRect(option.direction, option.rect, QtCore.QRect(option.rect.right()-buttonWidth,
option.rect.y(), buttonWidth, option.rect.height()))
else:
return QtCore.QRect()
else:
return QCommonStyle.subControlRect(whichControl,option,whichSubControl)

PyQt - Column of Checkboxes in a QTableView

I am dynamically creating a QTableView from a Pandas dataframe. I have example code here.
I can create the table, with the checkboxes but I cannot get the checkboxes to reflect the model data, or even to change at all to being unchecked.
I am following example code from this previous question and taking #raorao answer as a guide. This will display the boxes in the table, but non of the functionality is working.
Can anyone suggest any changes, or what is wrong with this code. Why is it not reflecting the model, and why can it not change?
Do check out my full example code here.
Edit one : Update after comment from Frodon :
corrected string cast to bool with a comparison xxx == 'True'
class CheckBoxDelegate(QtGui.QStyledItemDelegate):
"""
A delegate that places a fully functioning QCheckBox in every
cell of the column to which it's applied
"""
def __init__(self, parent):
QtGui.QItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
'''
Important, otherwise an editor is created if the user clicks in this cell.
** Need to hook up a signal to the model
'''
return None
def paint(self, painter, option, index):
'''
Paint a checkbox without the label.
'''
checked = index.model().data(index, QtCore.Qt.DisplayRole) == 'True'
check_box_style_option = QtGui.QStyleOptionButton()
if (index.flags() & QtCore.Qt.ItemIsEditable) > 0:
check_box_style_option.state |= QtGui.QStyle.State_Enabled
else:
check_box_style_option.state |= QtGui.QStyle.State_ReadOnly
if checked:
check_box_style_option.state |= QtGui.QStyle.State_On
else:
check_box_style_option.state |= QtGui.QStyle.State_Off
check_box_style_option.rect = self.getCheckBoxRect(option)
# this will not run - hasFlag does not exist
#if not index.model().hasFlag(index, QtCore.Qt.ItemIsEditable):
#check_box_style_option.state |= QtGui.QStyle.State_ReadOnly
check_box_style_option.state |= QtGui.QStyle.State_Enabled
QtGui.QApplication.style().drawControl(QtGui.QStyle.CE_CheckBox, check_box_style_option, painter)
def editorEvent(self, event, model, option, index):
'''
Change the data in the model and the state of the checkbox
if the user presses the left mousebutton or presses
Key_Space or Key_Select and this cell is editable. Otherwise do nothing.
'''
print 'Check Box editor Event detected : '
if not (index.flags() & QtCore.Qt.ItemIsEditable) > 0:
return False
print 'Check Box edior Event detected : passed first check'
# Do not change the checkbox-state
if event.type() == QtCore.QEvent.MouseButtonRelease or event.type() == QtCore.QEvent.MouseButtonDblClick:
if event.button() != QtCore.Qt.LeftButton or not self.getCheckBoxRect(option).contains(event.pos()):
return False
if event.type() == QtCore.QEvent.MouseButtonDblClick:
return True
elif event.type() == QtCore.QEvent.KeyPress:
if event.key() != QtCore.Qt.Key_Space and event.key() != QtCore.Qt.Key_Select:
return False
else:
return False
# Change the checkbox-state
self.setModelData(None, model, index)
return True
Here's a port of the same code above for PyQt5.
Posting here as there doesn't appear to be another example of a working CheckBox Delegate in QT5, and i was tearing my hair out trying to get it working. Hopefully it's useful to someone.
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import QModelIndex
from PyQt5.QtGui import QStandardItemModel
from PyQt5.QtWidgets import QApplication, QTableView
class CheckBoxDelegate(QtWidgets.QItemDelegate):
"""
A delegate that places a fully functioning QCheckBox cell of the column to which it's applied.
"""
def __init__(self, parent):
QtWidgets.QItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
"""
Important, otherwise an editor is created if the user clicks in this cell.
"""
return None
def paint(self, painter, option, index):
"""
Paint a checkbox without the label.
"""
self.drawCheck(painter, option, option.rect, QtCore.Qt.Unchecked if int(index.data()) == 0 else QtCore.Qt.Checked)
def editorEvent(self, event, model, option, index):
'''
Change the data in the model and the state of the checkbox
if the user presses the left mousebutton and this cell is editable. Otherwise do nothing.
'''
if not int(index.flags() & QtCore.Qt.ItemIsEditable) > 0:
return False
if event.type() == QtCore.QEvent.MouseButtonRelease and event.button() == QtCore.Qt.LeftButton:
# Change the checkbox-state
self.setModelData(None, model, index)
return True
return False
def setModelData (self, editor, model, index):
'''
The user wanted to change the old state in the opposite.
'''
model.setData(index, 1 if int(index.data()) == 0 else 0, QtCore.Qt.EditRole)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
model = QStandardItemModel(4, 3)
tableView = QTableView()
tableView.setModel(model)
delegate = CheckBoxDelegate(None)
tableView.setItemDelegateForColumn(1, delegate)
for row in range(4):
for column in range(3):
index = model.index(row, column, QModelIndex())
model.setData(index, 1)
tableView.setWindowTitle("Check Box Delegate")
tableView.show()
sys.exit(app.exec_())
I've found a solution for you. The trick was:
to write the setData method of the model
to always return a QVariant in the data method
Here it is. (I had to create a class called Dataframe, to make the example work without pandas. Please replace all the if has_pandas statements by yours):
from PyQt4 import QtCore, QtGui
has_pandas = False
try:
import pandas as pd
has_pandas = True
except:
pass
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, parent=None, *args):
super(TableModel, self).__init__()
self.datatable = None
self.headerdata = None
def update(self, dataIn):
print 'Updating Model'
self.datatable = dataIn
print 'Datatable : {0}'.format(self.datatable)
if has_pandas:
headers = dataIn.columns.values
else:
headers = dataIn.columns
header_items = [
str(field)
for field in headers
]
self.headerdata = header_items
print 'Headers'
print self.headerdata
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.datatable.index)
def columnCount(self, parent=QtCore.QModelIndex()):
if has_pandas:
return len(self.datatable.columns.values)
else:
return len(self.datatable.columns)
def data(self, index, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole:
i = index.row()
j = index.column()
return QtCore.QVariant('{0}'.format(self.datatable.iget_value(i, j)))
else:
return QtCore.QVariant()
def setData(self, index, value, role=QtCore.Qt.DisplayRole):
if index.column() == 4:
self.datatable.iset_value(index.row(), 4, value)
return value
return value
def headerData(self, col, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return '{0}'.format(self.headerdata[col])
def flags(self, index):
if index.column() == 4:
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled
else:
return QtCore.Qt.ItemIsEnabled
class TableView(QtGui.QTableView):
"""
A simple table to demonstrate the QComboBox delegate.
"""
def __init__(self, *args, **kwargs):
QtGui.QTableView.__init__(self, *args, **kwargs)
self.setItemDelegateForColumn(4, CheckBoxDelegate(self))
class CheckBoxDelegate(QtGui.QStyledItemDelegate):
"""
A delegate that places a fully functioning QCheckBox in every
cell of the column to which it's applied
"""
def __init__(self, parent):
QtGui.QItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
'''
Important, otherwise an editor is created if the user clicks in this cell.
** Need to hook up a signal to the model
'''
return None
def paint(self, painter, option, index):
'''
Paint a checkbox without the label.
'''
checked = index.data().toBool()
check_box_style_option = QtGui.QStyleOptionButton()
if (index.flags() & QtCore.Qt.ItemIsEditable) > 0:
check_box_style_option.state |= QtGui.QStyle.State_Enabled
else:
check_box_style_option.state |= QtGui.QStyle.State_ReadOnly
if checked:
check_box_style_option.state |= QtGui.QStyle.State_On
else:
check_box_style_option.state |= QtGui.QStyle.State_Off
check_box_style_option.rect = self.getCheckBoxRect(option)
# this will not run - hasFlag does not exist
#if not index.model().hasFlag(index, QtCore.Qt.ItemIsEditable):
#check_box_style_option.state |= QtGui.QStyle.State_ReadOnly
check_box_style_option.state |= QtGui.QStyle.State_Enabled
QtGui.QApplication.style().drawControl(QtGui.QStyle.CE_CheckBox, check_box_style_option, painter)
def editorEvent(self, event, model, option, index):
'''
Change the data in the model and the state of the checkbox
if the user presses the left mousebutton or presses
Key_Space or Key_Select and this cell is editable. Otherwise do nothing.
'''
print 'Check Box editor Event detected : '
print event.type()
if not (index.flags() & QtCore.Qt.ItemIsEditable) > 0:
return False
print 'Check Box editor Event detected : passed first check'
# Do not change the checkbox-state
if event.type() == QtCore.QEvent.MouseButtonPress:
return False
if event.type() == QtCore.QEvent.MouseButtonRelease or event.type() == QtCore.QEvent.MouseButtonDblClick:
if event.button() != QtCore.Qt.LeftButton or not self.getCheckBoxRect(option).contains(event.pos()):
return False
if event.type() == QtCore.QEvent.MouseButtonDblClick:
return True
elif event.type() == QtCore.QEvent.KeyPress:
if event.key() != QtCore.Qt.Key_Space and event.key() != QtCore.Qt.Key_Select:
return False
else:
return False
# Change the checkbox-state
self.setModelData(None, model, index)
return True
def setModelData (self, editor, model, index):
'''
The user wanted to change the old state in the opposite.
'''
print 'SetModelData'
newValue = not index.data().toBool()
print 'New Value : {0}'.format(newValue)
model.setData(index, newValue, QtCore.Qt.EditRole)
def getCheckBoxRect(self, option):
check_box_style_option = QtGui.QStyleOptionButton()
check_box_rect = QtGui.QApplication.style().subElementRect(QtGui.QStyle.SE_CheckBoxIndicator, check_box_style_option, None)
check_box_point = QtCore.QPoint (option.rect.x() +
option.rect.width() / 2 -
check_box_rect.width() / 2,
option.rect.y() +
option.rect.height() / 2 -
check_box_rect.height() / 2)
return QtCore.QRect(check_box_point, check_box_rect.size())
###############################################################################################################################
class Dataframe(dict):
def __init__(self, columns, values):
if len(values) != len(columns):
raise Exception("Bad values")
self.columns = columns
self.values = values
self.index = values[0]
super(Dataframe, self).__init__(dict(zip(columns, values)))
pass
def iget_value(self, i, j):
return(self.values[j][i])
def iset_value(self, i, j, value):
self.values[j][i] = value
if __name__=="__main__":
from sys import argv, exit
class Widget(QtGui.QWidget):
"""
A simple test widget to contain and own the model and table.
"""
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
l=QtGui.QVBoxLayout(self)
cdf = self.get_data_frame()
self._tm=TableModel(self)
self._tm.update(cdf)
self._tv=TableView(self)
self._tv.setModel(self._tm)
for row in range(0, self._tm.rowCount()):
self._tv.openPersistentEditor(self._tm.index(row, 4))
self.setGeometry(300, 300, 550, 200)
l.addWidget(self._tv)
def get_data_frame(self):
if has_pandas:
df = pd.DataFrame({'Name':['a','b','c','d'],
'First':[2.3,5.4,3.1,7.7], 'Last':[23.4,11.2,65.3,88.8], 'Class':[1,1,2,1], 'Valid':[True, False, True, False]})
else:
columns = ['Name', 'First', 'Last', 'Class', 'Valid']
values = [['a','b','c','d'], [2.3,5.4,3.1,7.7], [23.4,11.2,65.3,88.8], [1,1,2,1], [True, False, True, False]]
df = Dataframe(columns, values)
return df
a=QtGui.QApplication(argv)
w=Widget()
w.show()
w.raise_()
exit(a.exec_())
I added
if event.type() == QEvent.MouseButtonPress or event.type() == QEvent.MouseMove:
return False
to prevent checkbox from flickering when moving the mouse

Categories