问题
I'm trying to do some things with styling in ttk for the first time. My goal just now is to highlight the background color of some styled buttons when the mouse goes over them, but the button has some states and will have different colors at different moments, so I tried this:
code for the button
from PIL.ImageTk import PhotoImage
import tkinter.ttk as ttk
from random import random
class ImgButton(ttk.Button):
def __init__(self, master=None, **kw):
super().__init__(master, **kw)
self.img = kw.get('image')
class DiceFrame(ttk.Frame):
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
currentImg = PhotoImage(file='anyFileYouWant.jpg')
style = ttk.Style()
style.configure('Die.TButton',
background='red',
borderwidth=8,
)
def active_color(self):
# Test code. Final goal is get the current color and modify it
return random.choice(['blue', 'yellow', 'black', 'purple', 'cyan', 'brown', 'orange'])
style.map('Die.TButton',
background=[('active', active_color), ])
# Don't worry. ImgButton extends the regular ttk Button. Almost equal
button = ImgButton(master, image=currentImg, style="Die.TButton")
button.pack(side=tk.LEFT)
if __name__ == "__main__":
root = tk.Tk()
DiceFrame(root).pack(side="top", fill="both", expand=True)
root.mainloop()
which attempts to set a random background color on the button.
My final goal is to get the current button's color and set that same color but lighter. For example, if the button is red, when the mouse goes over the button, set it with a lighter red. If it's yellow a lighter yellow, etc...
This attempt does nothing but show strange things on the button which you can experiment with the code. So I don't know how to dinamically set a function there which returns a valid color.
回答1:
You cannot give a function instead of a color for the active background like you did:
style.map('Die.TButton', background=[('active', active_color), ])
That's why the button has a strange behavior when it is active.
Anyway, each time you will want to change the button background, you will have to configure the 'Die.TButton' style, so you can change the active background at the same time:
import tkinter as tk
import tkinter.ttk as ttk
import random
def change_style():
color = random.choice(['red', 'blue', 'yellow', 'dark gray', 'purple', 'cyan', 'brown', 'orange'])
style.configure('Die.TButton', background=color)
style.map('Die.TButton', background=[('active', active_color(color))])
def active_color(color):
c = root.winfo_rgb(color)
r = c[0] / 65535 * 255
g = c[1] / 65535 * 255
b = c[2] / 65535 * 255
r += (255 - r) / 2
g += (255 - g) / 2
b += (255 - b) / 2
return ("#%2.2x%2.2x%2.2x" % (round(r), round(g), round(b))).upper()
root = tk.Tk()
style = ttk.Style(root)
button = ttk.Button(root, text='Test', style='Die.TButton')
change_style()
button.pack()
ttk.Button(root, command=change_style, text='Change style').pack(padx=4, pady=10)
root.mainloop()
active_color
returns a lighter version of color for the active background using winfo_rgb
to get the RGB code for the color.
回答2:
My final solution is this:
All the behaviour about color is encapsulated in the button widget.
I control the event with a handler which changes the background color for the active state with a lighter color.
Whenever the color changes, it does through a function of mine, so I trigger the '' event with .generate_event(), change the color and unbind the current handler for highlighting, and then bind a new handler for highlighting replacing the former.
I've made an auxiliar, reusable module for style-related methods and functions:
styleUtils
import tkinter as tk
import tkinter.ttk as ttk
import random as rnd
style = None
def random_color():
"""
Returns a random color as a string
:return: a color
"""
def r():
return rnd.randint(0, 0xffff)
return '#{:04x}{:04x}{:04x}'.format(r(), r(), r())
def get_style(master=None):
"""
Returns the style object instance for handling styles
:param master: the parent component
:return: the style
"""
global style
if not style:
style = ttk.Style(master) if master else ttk.Style()
return style
def get_style_name(widget):
"""
Returns the the name of the current style applied on this widget
:param widget: the widget
:return: the name of the style
"""
# .config('style') call returns the tuple
# ( option name, dbName, dbClass, default value, current value)
return widget.config('style')[-1]
def get_background_color(widget):
"""
Returns a string representing the background color of the widget
:param widget: a widget
:return: the color of the widget
"""
global style
color = style.lookup(get_style_name(widget), 'background')
return color
def highlighted_rgb(color_value):
"""
Returns a slightly modified rgb value
:param color_value: one of three possible rgb values
:return: one of three possible rgb values, but highlighted
"""
result = (color_value / 65535) * 255
result += (255 - result) / 2
return result
def highlighted_color(widget, color):
"""
Returns a highlighted color from the original entered
:param color: a color
:return: a highlight color for the one entered
"""
c = widget.winfo_rgb(color)
r = highlighted_rgb(c[0])
g = highlighted_rgb(c[1])
b = highlighted_rgb(c[2])
return ("#%2.2x%2.2x%2.2x" % (round(r), round(g), round(b))).upper()
def change_highlight_style(event=None):
"""
Applies the highlight style for a color
:param event: the event of the styled widget
"""
global style
widget = event.widget
current_color = get_background_color(widget)
color = highlighted_color(event.widget, current_color)
style.map(get_style_name(widget), background=[('active', color)])
It may be necessary to change the calling code a little bit to remove the unnecessary code now, but this will work straight away.
widgets.py (code for the button)
import os
import tkinter as tk
import tkinter.ttk as ttk
from PIL.ImageTk import PhotoImage
from user.myProject.view import styleUtils
class ImgButton(ttk.Button):
"""
This has all the behaviour for a button which has an image
"""
def __init__(self, master=None, **kw):
super().__init__(master, **kw)
self._img = kw.get('image')
# TODO Replace this temporal test handler for testing highlight color
self.bind('<Button-1>', self.change_color)
def change_color(self, __=None):
"""
Changes the color of this widget randomly
:param __: the event, which is no needed
"""
import random as rnd
#Without this, nothing applies until the mouse leaves the widget
self.event_generate('<Leave>')
self.set_background_color(rnd.choice(['black', 'white', 'red', 'blue',
'cyan', 'purple', 'green', 'brown',
'gray', 'yellow', 'orange', 'cyan',
'pink', 'purple', 'violet']))
self.event_generate('<Enter>')
def get_style_name(self):
"""
Returns the specific style name applied for this widget
:return: the style name as a string
"""
return styleUtils.get_style_name(self)
def set_background_color(self, color):
"""
Sets this widget's background color to that received as parameter
:param color: the color to be set
"""
styleUtils.get_style().configure(self.get_style_name(), background=color)
# If the color changes we don't want the current handler for the old color anymore
self.unbind('<Enter>')
# We replace the handler for the new color
self.bind('<Enter>', self.change_highlight_style)
def get_background_color(self):
"""
Returns a string representing the background color of the widget
:return: the color of the widget
"""
return styleUtils.get_style().lookup(self.get_style_name(), 'background')
def change_highlight_style(self, __=None):
"""
Applies the highlight style for a color
:param __: the event, which is no needed
"""
current_color = self.get_background_color()
# We get the highlight lighter color for the current color and set it for the 'active' state
color = styleUtils.highlighted_color(self, current_color)
styleUtils.get_style().map(self.get_style_name(), background=[('active', color)])
Calling code
import tkinter as tk
import tkinter.ttk as ttk
from widgets import ImgButton
class DiceFrame(ttk.Frame):
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
current_style = 'Die.TButton'
style = ttk.Style()
style.configure(current_style,
borderwidth=6,
)
button = ImgButton(master, style=current_style)
button.pack(side=tk.LEFT)
if __name__ == "__main__":
root = tk.Tk()
DiceFrame(root).pack(side="top", fill="both", expand=True)
root.mainloop()
回答3:
ttk Button appearances are driven by themes (3D/Color-alt/classic/default, Color-clam). Not setting/others leaves buttons flat/grey and settings don't change things. To make a ttk TButton change colors can be achieved using map. 3D appearance requires borderwidth. Only Classic forms an outer ring using highlight. Background logic for s.map below: Active & Pressed (Yellow), !Active (Green), Active & !Pressed (Cyan). Relief can be defined separately. No function required to modify these button aspects. However, highlightcolor must use an s.configure update. Only one theme may be invoked for the frame.
import tkinter as tk
from tkinter import ttk
root=tk.Tk();
s = ttk.Style();
s.theme_use('classic');
s.configure('zc.TButton',borderwidth='20')
s.configure('zc.TButton',highlightthickness='10')
s.configure('zc.TButton',highlightcolor='pink')
s.map('zc.TButton',background=[('active', 'pressed', 'yellow'),('!active','green'), ('active','!pressed', 'cyan')])
s.map('zc.TButton',relief=[('pressed','sunken'),('!pressed','raised')]);
calc_button=ttk.Button(root, text="classic", style='zc.TButton');
calc_button.grid(column=0,row=0,sticky='nsew');
root.mainloop()
来源:https://stackoverflow.com/questions/46583398/python-changing-ttk-button-color-depending-on-current-color