I want to change color of selected rows in QTableWidget using python.
http://postimg.org/image/iyxb0wm4r/
I want that green part to be red.
If i try:
table = self.my_table
palette = QtGui.QPalette(table.palette())
palette.setColor(QtGui.QPalette.Highlight, Qt.red)
table.setPalette(palette)
it producies next picture. I need to double click, so the selected text is red.
http://postimg.org/image/rtc06mlil/
If I try:
table = self.my_table
table.setAutoFillBackground(True)
p = table.palette()
p.setColor(table.backgroundRole(), Qt.red)
table.setPalette(p)
it does this: (I can't post images and I can post only two links!) I try to put the link in comment. Anyway, it just set the color of grid to red.
So I found solution.
p = QtGui.QPalette(table.palette())
#This two for setting text color
p.setBrush(QtGui.QPalette.Active, QtGui.QPalette.HighlightedText,
QtGui.QBrush(QColor("red")))
p.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.HighlightedText,
QtGui.QBrush(QColor("red")))
#this two for setting background color
p.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Highlight,
QtGui.QBrush(QColor(255,0,0,127)))
p.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Highlight,
QtGui.QBrush(QColor("255,0,0,127")))
One thing - works bit differently on Linux and Windows, but for my purposes it is more than suitable.
Related
My GUI is built by QTableView's.
Each QTableView has a QStyledItemDelegate.
In the QStyledItemDelegate the background color will be changed with:
def initStyleOption(self, option, index):
super(ValidatedIntItemDelegate, self).initStyleOption(option, index)
option.backgroundBrush = self.calculate_color_for_column(index)
Detail: self.calculate_color_for_column(index) just does the validation of the cell value, dependent of the validity, a different color is returned.
All background coloring is working perfect as long I just edit within the same table. If I select a cell in another table, the last selected cell in the old table remains with a grey background not coming from my validation.
Scenario:
Edit in Leading edge->a1 value
Move with Tab or Mouse click to Leading edge->b1
Selected Trailing edge->a1 cell
Leading edge->b1 cell background is not updated
Not doing any edits in Trailing edge->a1 select again Leading edge->x1
ALL Leading edge cells are shown with correct background again!!
BUT Trailing edge->a1 has now the wrong background color
The cells not updating correctly the background color are the ones
selected
but in an inactive table/ delegate
So, how to catch this state and make sure the backround reflects the color returned from self.calculate_color_for_column(index)?
What you are seeing is not the background not being updated, but the highlight color that is used to show the selection.
In order to ensure that the actual selected item can always be identified, the selection always has the same color, and completely disregards the background of the item. This is probably done for reasons related to UX aspects: for instance, if you use a color with alpha for the selection, you may end up confusing items that are not selected with other that are, but have a color that mixed up with the selection becomes similar to the others.
Since you're using quite pale colors, a possibility could be to use a darker version of the color.
To avoid inconsistency between styles, though, that color won't be used for the item background (the option.backgroundBrush) but for the option palette, using the Normal and Inactive roles with different values of "darkness".
class Delegate(QStyledItemDelegate):
def initStyleOption(self, opt, index):
super().initStyleOption(opt, index)
if opt.backgroundBrush.style() and opt.state & QStyle.State_Selected:
color = opt.backgroundBrush.color()
opt.palette.setColor(QPalette.Active, QPalette.Highlight,
color.darker(150))
opt.palette.setColor(QPalette.Inactive, QPalette.Highlight,
color.darker(125))
Another possibility would be what written above: use a blend of the background and the palette brush.
Since, as explained, the delegate always ignores the color whenever the item is selected, we need to work around this:
paint the basic item primitive PE_PanelItemViewItem without the State_Selected flag, so that it paints the background as expected;
alter the opt palette in initStyleOption() and change the alpha of the Highlight roles;
call the base implementation, which will paint over the previously drawn background;
class Delegate(QStyledItemDelegate):
def initStyleOption(self, opt, index):
super().initStyleOption(opt, index)
if opt.backgroundBrush.style() and opt.state & QStyle.State_Selected:
normal = opt.palette.color(QPalette.Active, QPalette.Highlight)
normal.setAlphaF(.5)
opt.palette.setColor(QPalette.Active, QPalette.Highlight, normal)
inactive = opt.palette.color(QPalette.Inactive, QPalette.Highlight)
inactive.setAlphaF(.5)
opt.palette.setColor(QPalette.Inactive, QPalette.Highlight, inactive)
def paint(self, qp, opt, index):
# we should *never* change the option provided in the arguments when
# doing painting: that option is always reused for other items, and
# its properties are not always cleared
vopt = QStyleOptionViewItem(opt)
self.initStyleOption(vopt, index)
widget = opt.widget
style = widget.style()
if vopt.backgroundBrush.style() and vopt.state & style.State_Selected:
vopt.state &= ~style.State_Selected
style.drawPrimitive(style.PE_PanelItemViewItem, vopt, qp, widget)
# now we *do* use the original option
super().paint(qp, opt, index)
I have a QTableWidget with some rows.
In each row, one of the cells has another widget set via setCellWidget.
I would like to style this cellWidget based on whether or not the row is selected. For reference, the cellWidget is another QTableWidget but it is disabled/not editable and essentially read-only.
I have found the syntax for accessing sub-controls (in particular, accessing the item of the parent QTableWidget) -- namely MainTable::item https://doc.qt.io/qt-5/stylesheet-reference.html#list-of-sub-controls
I have also found the (more standard) css-syntax for accessing the pseudo-state of the control -- namely MainTable::item:selected. https://doc.qt.io/qt-5/stylesheet-reference.html#list-of-pseudo-states
If I naively use this to style the selected item (tablerow) as yellow as below
def add_file(self, row, element):
"""populate a new row in the table"""
# self is the parent QTableWidget
self.setRowHeight(row, self.ICON_SIZE.height())
img = self.create_thumbnail(element['filepath'])
# add an image to the first column
item = QTableWidgetItem("",0)
item.setFlags(Qt.ItemIsEnabled|Qt.ItemIsSelectable)
item.setData(Qt.DecorationRole, img)
item.setData(Qt.TextAlignmentRole, Qt.AlignHCenter|Qt.AlignCenter)
item.setData(Qt.SizeHintRole, self.ICON_SIZE)
self.setItem(row, self.THUMBCOL, item)
# StatsTable is another nested QTableWidget
stats = StatsTable(element)
# add the new nested table to the outer main tables second column
self.setCellWidget(row, self.STATSCOL, stats)
self.setStyleSheet("""
MainTable::item:selected
{
background: yellow;
color: purple;
}
""")
The entire row except for the cellWidget will have a yellow background.
Now if I modify the QSS-selector in an attempt to access the child widget, I get unexpected results:
MainTable::item:selected QTableWidget
{
background: yellow;
color: purple;
}
this results in every row having its cellWidget-table given a yellow background independent of the selection-status of the row (unlike before where only the selected row sans the nested table had a yellow background).
Is there something simple I am overlooking here, or do I have to create some callbacks to manually apply and unapply the style when a row is selected?
this is a selected row with the first QSS applied
this is a selected row with the second QSS applied
neither of these two has the cellWidget styled if the row is selected.
As an alternative to using item delegates, I added a callback to the itemSelectionChanged-signal, and iterate through the rows in the main table. I set a property value on the child-tableWidget depending on whether the row is selected or not. This property is accessed in the stylesheet.
Unfortunately it seems I have to force a recalculation of the stylesheet by setting it in its entirety, so the seemingly clever workaround is not all that clever after all.
Since my nested widget is very restricted (read only, disabled so it can not be navigated to, ...) I do not think I need the flexibility of a custom item delegate, even though it probably is a better solution. I also expect far less than 100 rows, so performance may not be an issue.
def __init__(self, ...):
...
# called whenever the main table has its selected row(s) change.
self.itemSelectionChanged.connect(self.update_selection)
def update_selection(self):
for row in range(self.rowCount()):
item = self.item(row, 0)
widg = self.cellWidget(row, 1)
if item.isSelected():
widg.setProperty("row_is_selected", "true")
else:
widg.setProperty("row_is_selected", "false")
# it is apparently necessary to force a recalculation anyway so the
# above property-change is a roundabout way to adjust the style
# compared to just setting or removing it below.
# this forces a recalculation nonetheless.
widg.setStyleSheet(widg.styleSheet())
def add_file(self, row, element):
...
stats.setProperty("row_is_selected", "false")
self.setStyleSheet("""
StatsTable[row_is_selected="true"]
{
background: yellow;
color: purple;
}
""")
The subcontrol and pseudo elements cannot be used for parenthood selectors, and it's also impossible to set the background of a specific item based on the selection if the whole row is selected.
The background of an item view is painted using the Base palette color role, which is normally white and opaque. What you could do is to override it and make it transparent:
def add_file(self, row, element):
# ...
palette = stats.palette()
palette.setColor(palette.Base, QtCore.Qt.transparent)
stats.setPalette(palette)
Unfortunately, this will only fix the background part, and won't change the color of the displayed text. In order to achieve that, you need to know the state of the selection and update the stylesheets depending on the item selection.
You could connect to the selectionChanged of the main table's selectionModel() (or itemSelectionChanged for a QTableWidget), and then style items accordingly:
# somewhere in the __init__
self.TableQSS = '''
QTableWidget
{
background: yellow;
color: purple;
}
'''
self.itemSelectionChanged.connect(self.updateTables)
def updateTables(self):
selected = self.selectedIndexes()
for row in range(self.rowCount()):
table = self.cellWidget(row, self.STATSCOL)
if not isinstance(table, StatsTable):
continue
if self.model().index(row, self.STATSCOL) in selected:
table.setStyleSheet(self.TableQSS)
else:
table.setStyleSheet('')
Consider that stylesheets and palette don't always play well together, and setting palette colors is normally the preferred solution as it's (theoretically) safer with the current style which will use the palette to define other colors, such gradients, shades, etc.
So, keep setting the palette as explained at the beginning, still connect the itemSelectionChanged signal as above, and then:
def updateTables(self):
# get the default colors for the text from the palette of the main
# table (we cannot rely on the child tables as they've been changed)
basePalette = self.palette()
colors = [basePalette.color(cg, basePalette.Text) for cg in range(3)]
selected = self.selectedIndexes()
for row in range(self.rowCount()):
table = self.cellWidget(row, self.STATSCOL)
if not isinstance(table, StatsTable):
continue
palette = table.palette()
if self.model().index(row, self.STATSCOL) in selected:
palette.setColor(palette.Text, QtGui.QColor('purple'))
else:
# restore default colors
for cg, color in enumerate(colors):
palette.setColor(cg, palette.Text, color)
table.setPalette(palette)
Note that using a nested item view is normally not a good idea, as it makes things much more complex (especially with selections and keyboard/mouse interaction) and could potentially create issues in certain situations.
Since it seems that you only need to display data, you should consider implementing your own item delegate (see QStyledItemDelegate) and eventually draw formatted text using a basic HTML table (see this answer).
Alternatively, use a QPlainTextEdit with disabled scroll bars and set in read only mode (in this case, you still need to do what explained above).
The following code works but doesn't do in full what exactly is needed:
nameList = ('John','Tom','Henry','Michelle','Ashish','Jo-Huang', ...)
colorNameList = ('Michelle','Jennifer','Claudia','JimSung', ...)
callBackObj.NameComboBox.clear()
callBackObj.NameComboBox.addItem(QString('Account Names'))
model = callBackObj.NameComboBox.model()
for name in nameList:
item = PyQt4.QtGui.QStandardItem(str(account))
if name in colorNameList:
item.setBackground(PyQt4.QtGui.QColor('red'))
model.appedRow(item)
Now whenever I expand and view the dropdown, the names in colorNameList appear in red background. So far so good. However when I select one of these red-background names they do not show as red-background when left selected.
Any ideas how I can ensure that on selection red items appear red and non-red appears non-red.
Many thanks!
Rahul
I'm trying to leave one third of the image stock, change all the black to yellow in the middle, and change the bottom third black to blue. I know how to change the colours, the problem I'm facing is I'm unaware of how I can select only one third of the pixels to manipulate them. Here s what I have..
def changeSpots1():
file = pickAFile()
picture = makePicture(file)
show(picture)
pix = getAllPixels(picture)
for p in pix:
intensity = (getRed(p) + getGreen(p) + getBlue(p))
c = getColor(p)
if (intensity < 150):
newColour = setColor(p, yellow)
repaint(picture)
I am using a program called JES to write this, incase you're wondering about commands like pickAFile.
Thank you for any help!
I know nothing about JES, but I'm going to guess that getAllPixels returns the pixels in the usual order: the first row, then the next row, then the next, etc.
If so:
pix = getAllPixels(picture)
third = len(pix) // 3
for p in pix[:third]:
# do top-third stuff
for p in pix[third:third*2]:
# do middle-third stuff
for p in pix[third*2:]:
# do bottom-third stuff
This does assume that the picture s divisible perfectly into thirds. If it's not, you will need to know the picture's width so you can round to the nearest complete row (because otherwise the top third might actually be 250 complete rows and the first 47 pixels of the 251st, which won't look very good). I don't know what function JES has to get the width, but I'm sure it's simple.
How to dynamically created button to set the blue color as in the Windows game Sapper? This is a part of code
self.buttons = []
for i in xrange(self.HeightOfField):
l=[]
for j in xrange(self.WidthOfField):
b=QtGui.QPushButton()
b.setFixedSize(40,30)
l.append(b)
self.gridLayout.addWidget(b, i, j)
self.gridLayout.setColumnMinimumWidth(j, 40)
self.buttons.append(l)
self.gridLayout.setRowMinimumHeight(i, 26)
For me, the easiest way to set the colour of a button would be to use a stylesheet using .setStyleSheet.
b.setStyleSheet('background-color: blue;')
You can use RGB colours, but it's easier to use HTML keyword names. Take a look at this table from W3C.