Dynamic color setting button - python

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.

Related

Styling a cellWidget of a QTableWidget based on the :selected pseudo-state of the parent QTableWidget

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).

Disabling Button created in for loop tkinter

for bear in self.bears:
button=tk.Button(self,text=polar.spec(),command=lambda
id=polar.spec:self.polar_ad(id))
button.grid(row=_r,column=_c,sticky=W+E+N+S)
_row+=1
Hello, so i've created a number of buttons in a for loop, but when i click the button, i want to be able to disable it. If the buttons were created individually then i could use
button["state"]="disabled"
but how can i use this by using the id of the button? Thank you
I would add the buttons to an array upon creation, then loop through the array and check ids.
buttons = []
for bear in self.bears:
btn = tk.Button(self,text=polar.spec(),command=lambda id=polar.spec:self.polar_ad(id))
btn.grid(row=_r,column=_c,sticky=W+E+N+S)
buttons.append(btn)
_id += 1
_row += 1
Then check button IDs
for b in buttons:
if b.id == <id_of_button_press>:
b['state'] = "disabled"
I'm not sure if you can get a button's id this way, but the principal should make sense.
buttons=[]
num=0
for bear in self.bears:
button=tk.Button(self,text=polar.spec(),command=lambda
id=polar.spec,index=num:self.polar_ad(id,index))
button.grid(row=_r,column=_c,sticky=W+E+N+S)
num+=1
_row+=1
then
self.polar(id,index):
buttons[index]["state"]="disabled"
thank for your idea really helped me get on the right tracks #Uuuuuumm

Color of selected rows in PyQt

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.

Python tkinter main window improper size when .grid() widgets

I have a game board which is rows x columns list.
Min size is 2x2 and max 10x10, with unequal rows:columns being okay (e.g. 2x3, 4x9).
Main window object has no predetermines geometry size setting, and widgets (buttons) are being .grid() in it for each list element in a was that creates a 2D map.
Ideally, given the method used this would lead to a nice, edge=to-edge map inside the main window.
Unfortunately, testing has shown that while this is true for maps with columns count > 3, when columns <= 3 then the window seems to default to a certain X-size, where this ugly free space is present at the right of the window.
This is not the case for Y-axis, which is defined by rows.
Note that buttons placed are fixed 32x32 px (determined by image inside).
def createMap (): #creates rows x columns 2D list - a map
global rowsEntryVar, columnsEntryVar, mapList
mapList = []
for row in range(rowsEntryVar):
tempList = []
for column in range(columnsEntryVar):
tempList.append(Button(root, bd=0, bg=redMagenta, activebackground=redMagenta))
mapList.append(tempList)
and then:
def drawMap ():
global mapList
for row in range(len(mapList)):
for column in range(len(mapList[row])):
mapList[row][column].grid(row=row, column=column)
Image:
Image showing the problem
Please go easy on me, I'm quite new to programming. :)
This appears to be a platform-specific limitation. I can't duplicate the problem on my Mac, but I can on a windows VM. Apparently, Windows won't allow the width of the window to be smaller than the space required for the buttons and icon on the titlebar.
My advice is to give the rows and columns a positive weight so that they will grow to fit the window, and then use the sticky option to cause the buttons to fill the space given to them.
when columns <= 3 then the window seems to default to a certain X-size,
Tkinter defaults to the size of the widgets so you must be setting the geometry for "root" somewhere. The following works fine on my Slackware box (and using a function as a function eliminates the globals). If you are just starting, then it is good to form good habits, like conforming to the Python Style Guide https://www.python.org/dev/peps/pep-0008/ (variables and functions are all lower case with underlines).
from Tkinter import *
def create_map (rowsEntryVar, columnsEntryVar): #creates rows x columns 2D list - a map
mapList = []
for row in range(rowsEntryVar):
tempList = []
for column in range(columnsEntryVar):
tempList.append(Button(root, text="%s-%s" % (row, column),
bd=0, bg="magenta2", activebackground=r"magenta3"))
mapList.append(tempList)
return mapList
def draw_map(mapList):
for row in range(len(mapList)):
for column in range(len(mapList[row])):
mapList[row][column].grid(row=row, column=column)
root = Tk()
map_list=create_map(4, 3)
draw_map(map_list)
root.mainloop()

How to change button image at mouse hover over in tkinter in Python?

I've started my adventure with Python only recently, so please go easy on me :D
I've been working on my 2nd program with a gui in tkinter for a couple of days now, and it looks like I've run into a wall.
It's a game that creates a 2D map by making a 2D list.
Each element of the list is created as a Button tkinter object instance with a tile_basic_off image. So far so good.
Should I try to bind each of these buttons to 2 functions for mouse over/mouse leave things go south...
It should work like:
mouse over -> change image to tile_basic_on
mouse leave -> change tile_basic_off
However, once code below is run (there is a function that draws it by .grid() method) everything is the same, with tiles not changing image at mouse over or mouse leave.
Here is the question-wise important piece of code.
(note that redMagenta is a var holding an RGB, not a typo)
from tkinter import * #Python 3.4 here
def createMap (): #creates rows x columns 2D list - a map
global rowsEntryVar, columnsEntryVar, mapList
mapList = []
for row in range(rowsEntryVar):
tempList = []
for column in range(columnsEntryVar):
tempList.append(Button(root, bd=0, bg=redMagenta, activebackground=redMagenta))
mapList.append(tempList)
setTilesProperties()
tileBasicOffImage = PhotoImage(file="Resources/Tile/Tile_basic_off.png")
tileBasicOnImage = PhotoImage(file="Resources/Tile/Tile_basic_on.png")
def turnTileOn (row, column): #changes tile image to On
global mapList
mapList[row][column].configure(image=tileBasicOnImage)
mapList[row][column].image = tileBasicOnImage
def turnTileOff (row, column): #changes tile image to Off
global mapList
mapList[row][column].configure(image=tileBasicOffImage)
mapList[row][column].image = tileBasicOffImage
def setTilesProperties (): #sets basic properties that apply to all tiles
global mapList, tileBasicOffImage
for row in range(len(mapList)):
for column in range(len(mapList[row])):
mapList[row][column].configure(image=tileBasicOffImage)
mapList[row][column].bind("<Enter>", turnTileOn(row, column))
mapList[row][column].bind("<Leave>", turnTileOff(row, column))
What is wrong with my code?
Thanks for help and for not TLDRing this. ;]

Categories