I'm using a QStyledItemDelegate to set a custom Button on a model in a tableView. I set a styleSheet to the button with a blue background-color and red background-color when the button will be pressed.
The tableview with the button and its blue background shows correctly, but when I press the button, the button's stylesheet doesn't works, its background color doesn't change to red color.
Window with blue buttons:
Expected behaivor: when a button is pressed its Background should change to red
This is my code:
import sys
from PySide2 import QtCore, QtWidgets, QtGui
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
class DelegateButton(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
self._mybtn = None
self._rfbutton = QPushButton()
self._rfbutton.setStyleSheet(''' QPushButton{
background-color: blue;
border:1px solid blue;
}
QPushButton:pressed{
background-color: red;
border:1px solid red;
}
''')
def createEditor(self, parent, option, index):
return None
def paint(self, painter, option, index):
self._mybtn = QStyleOptionButton()
top_span_height = (option.rect.height() - 24)/2
pos_x = 8 option.rect.x()
pos_y = option.rect.y() top_span_height
self._mybtn.rect = QRect(pos_x, pos_y, 24, 24)
self._mybtn.state = QStyle.State_Enabled
self._rfbutton.style().drawControl(QStyle.CE_PushButton, self._mybtn, painter, self._rfbutton)
def editorEvent(self, event, model, option, index):
if event.type() == QEvent.MouseButtonPress:
if self._mybtn.rect.contains(event.x(), event.y()):
self._mybtn.state = QStyle.State_Sunken | QStyle.State_Enabled
return False
return False
if __name__ == '__main__':
app = QApplication(sys.argv)
rows = 5
cols = 2
model = QStandardItemModel(rows, cols)
tableView = QTableView()
tableView.setWindowTitle("Delegate Issue")
tableView.setModel(model)
delegate = DelegateButton()
tableView.setItemDelegateForColumn(1, delegate)
tableView.setColumnWidth(0, 200)
tableView.setColumnWidth(1, 200)
tableView.verticalHeader().setDefaultSectionSize(32)
tableView.setSelectionBehavior(QAbstractItemView.SelectRows)
for row in range(rows):
index = model.index(row, 1, QModelIndex())
model.setData(index, 1)
tableView.show()
tableView.resize(450,300)
sys.exit(app.exec_())
How could I solve this issue?
CodePudding user response:
As is intended to just change the background color when the "button" is pressed as described in the stylesheet. I found out the reason it didn't change:
The code starts with self._mybtn.state
set as QStyle.State_Enabled
and buttons are painted blue as describe the stylesheet, when any mouse button is pressed in the "button" area, in the EvenEditor the MousePressed event changes the state to QStyle.State_Sunken | QStyle.State_Enabled
, but when the paint function is called again and draws the items, the self._mybtn.state
is set to be enable, so it changes instantly the state ending up with button colored blue.
I declare a self._is_pressed
variable where QEvent.MouseButtonPress
will store the position of the cursor when a button is pressed in the button's area. And in paint function, before it draws items, I evaluate if the self._is_pressed
have stored the position, if it do, I evaluated the position is cointained inside the buttons area, so set the state to QStyle.State_Sunken | QStyle.State_Enabled
and the stylesheet now can be applied and color red the button.
Otherwise the state is set to QStyle.State_Enabled
Background to red:
The code ends up:
import sys
from PySide2 import QtCore, QtWidgets, QtGui
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
class DelegateButton(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
self._mybtn = None
self._is_pressed = None
self._rfbutton = QPushButton()
self._rfbutton.setStyleSheet(''' QPushButton{
background-color: blue;
border:1px solid blue;
}
QPushButton:pressed{
background-color: red;
border:1px solid red;
}
''')
def createEditor(self, parent, option, index):
return None
def paint(self, painter, option, index):
self._mybtn = QStyleOptionButton()
top_span_height = (option.rect.height() - 24)/2
pos_x = 8 option.rect.x()
pos_y = option.rect.y() top_span_height
self._mybtn.rect = QRect(pos_x, pos_y, 24, 24)
if self._is_pressed and self._mybtn.rect.contains(self._is_pressed):
self._mybtn.state = QStyle.State_Enabled | QStyle.State_Sunken
else:
self._mybtn.state = QStyle.State_Enabled
self._rfbutton.style().drawControl(QStyle.CE_PushButton, self._mybtn, painter, self._rfbutton)
def editorEvent(self, event, model, option, index):
if event.type() == QEvent.MouseButtonPress:
if self._mybtn.rect.contains(event.x(), event.y()):
self._is_pressed = QPoint(event.x(), event.y())
return True
return False
if __name__ == '__main__':
app = QApplication(sys.argv)
rows = 5
cols = 2
model = QStandardItemModel(rows, cols)
tableView = QTableView()
tableView.setWindowTitle("Delegate Issue")
tableView.setModel(model)
delegate = DelegateButton()
tableView.setItemDelegateForColumn(1, delegate)
tableView.setColumnWidth(0, 200)
tableView.setColumnWidth(1, 200)
tableView.verticalHeader().setDefaultSectionSize(32)
tableView.setSelectionBehavior(QAbstractItemView.SelectRows)
for row in range(rows):
index = model.index(row, 1, QModelIndex())
model.setData(index, 1)
tableView.show()
tableView.resize(450,300)
sys.exit(app.exec_())
The index could be used to achive the same result but i rather opted to use the position.
in the paint function:
if self._is_pressed and self._is_pressed == (index.row(), index.column()):
self._mybtn.state = QStyle.State_Enabled | QStyle.State_Sunken
else:
self._mybtn.state = QStyle.State_Enabled
in eventEditor:
if event.type() == QEvent.MouseButtonPress:
if self._mybtn.rect.contains(event.x(), event.y()):
self._is_pressed = (index.row(), index.column())
return True
if it is wanted to restore to blue the background after the button is released then the QEvent.MouseButtonRelease
must be implemented.
Blue Background after release button:
import sys
from PySide2 import QtCore, QtWidgets, QtGui
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
class DelegateButton(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
self._mybtn = None
self._is_pressed = None
self._rfbutton = QPushButton()
self._rfbutton.setStyleSheet(''' QPushButton{
background-color: blue;
border:1px solid blue;
}
QPushButton:pressed{
background-color: red;
border:1px solid red;
}
''')
def createEditor(self, parent, option, index):
return None
def paint(self, painter, option, index):
self._mybtn = QStyleOptionButton()
top_span_height = (option.rect.height() - 24)/2
pos_x = 8 option.rect.x()
pos_y = option.rect.y() top_span_height
self._mybtn.rect = QRect(pos_x, pos_y, 24, 24)
if self._is_pressed and self._mybtn.rect.contains(self._is_pressed):
self._mybtn.state = QStyle.State_Enabled | QStyle.State_Sunken
else:
self._mybtn.state = QStyle.State_Enabled | QStyle.State_Raised
self._rfbutton.style().drawControl(QStyle.CE_PushButton, self._mybtn, painter, self._rfbutton)
def editorEvent(self, event, model, option, index):
if event.type() == QEvent.MouseButtonPress:
if self._mybtn.rect.contains(event.x(), event.y()):
self._is_pressed = QPoint(event.x(), event.y())
return True
if event.type() == QEvent.MouseButtonRelease:
if self._mybtn.rect.contains(event.x(), event.y()):
self._is_pressed = None
return True
return False
I hope this help to anyone in aplying styles when drawing styled delegates.
CodePudding user response:
A fundamental aspect that must be kept in mind is that delegate are abstract entities that do not represent a specific index in the model. They are used by the view to provide proper user interaction and item display depending on the indexes those delegates are set for.
This means that the delegate functions are called indistinctly and at very different moments depending on their purpose, and there could be no "persistent" reference to a specific index, since the view could call those functions on its own and completely out of our control.
Specifying coordinates for a unique "button" is wrong, as the paint function can be called for other items for other reasons: for instance, when the user hovers another index, or minimizes and restores the window.
The only viable solution (as long as you are actually and exclusively interested in appearance) is to keep a reference to the pressed buttons, possibly by using the index they refer to.
Note that while using a "ghost button" to use the QStyle functions might be a possibility, that is completely pointless if you are only drawing a simple rectangle: just use the basic drawRect()
function of QPainter.
class DelegateButton(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
self._pressed_rows = set()
def _buttonRect(self, optionRect):
return QRect(
optionRect.x() 8,
optionRect.y() (optionRect.height() - 24) // 2,
24, 24
)
def paint(self, painter, option, index):
if index.row() in self._pressed_rows:
color = QColor('red')
else:
color = QColor('blue')
painter.save()
painter.setPen(color)
painter.setBrush(color)
painter.drawRect(self._buttonRect(option.rect))
painter.restore()
def createEditor(self, *args):
return
def editorEvent(self, event, model, option, index):
if event.type() == event.MouseButtonPress:
if self._buttonRect(option.rect).contains(event.pos()):
self._pressed_rows.add(index.row())
else:
self._pressed_rows.discard(index.row())
return True
# comment the following block to make the pressed state persistent
elif event.type() == event.MouseButtonRelease:
self._pressed_rows.discard(index.row())
return True
return super().editorEvent(event, model, option, index)
Note that the above will not work properly if the model changes during runtime, specifically if rows are inserted/removed before any already "pressed index" (including using filter models) and if the model is sorted.
A proper implementation should use alternative ways to achieve so, for instance by setting basic bool value for a custom role in the model: in this way the "pressed" indexes will always be consistent, no matter what happens to the model; the fact that you don't actually use those values for the data model is irrelevant, the important thing is to keep consistency of indexes (which are not persistent by nature). Another alternative is to check/store the rows of the QPersistentModelIndex of each index, both for the editorEvent()
and the paint()
functions.