Python: Changing ttk button color depending on current color?

耗尽温柔 提交于 2021-02-05 09:32:28

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!