Python: 根据当前颜色更改 ttk 按钮颜色?

Python: Changing ttk button color depending on current color?

我第一次尝试在 ttk 中使用样式来做一些事情。我刚才的目标是在鼠标经过某些样式按钮时突出显示它们的背景颜色,但是按钮有一些状态并且在不同的时刻会有不同的颜色,所以我尝试了这个:

按钮代码

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

它试图在按钮上设置随机背景颜色。

我的最终目标是获取当前按钮的颜色并设置相同的颜色但更亮。例如,如果按钮是红色的,当鼠标经过按钮时,将其设置为较浅的红色。如果是黄色浅黄色等...

这种尝试只会在按钮上显示一些奇怪的东西,您可以用代码进行试验。所以我不知道如何动态地设置一个 returns 有效颜色的函数。

您不能像您那样为活动背景提供功能而不是颜色:

style.map('Die.TButton', background=[('active', active_color), ])

这就是按钮在激活时有奇怪行为的原因。

不管怎样,每次你想要改变按钮背景时,你都必须配置'Die.TButton'风格,这样你就可以同时改变活动背景:

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 使用 winfo_rgb 获取颜色的 RGB 代码的活动背景颜色的较浅版本。

我最终的解决方案是这样的:

所有关于颜色的行为都封装在按钮小部件中。

我使用处理程序控制事件,该处理程序将活动状态的背景颜色更改为较浅的颜色。

每当颜色改变时,它都会通过我的一个函数来实现,所以我用 .generate_event() 触发 '' 事件,改变颜色并取消绑定当前处理程序以突出显示,然后绑定一个用于突出显示替换前者的新处理程序。

我已经为样式相关的方法和函数制作了一个辅助的、可重用的模块:

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

现在可能需要稍微更改调用代码以删除不必要的代码,但这会立即起作用。

widgets.py(按钮代码)

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

调用代码

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

ttk 按钮外观由主题驱动(3D/Color-alt/classic/default、Color-clam)。 Not setting/others 保留按钮 flat/grey 并且设置不会改变任何东西。 要使 ttk TButton 更改颜色,可以使用 map 来实现。 3D 外观需要 borderwidth。只有 Classic 使用高光形成外环。下面 s.map 的背景逻辑:活动并按下(黄色)、!活动(绿色)、活动并按下(青色)。救济可以单独定义。不需要修改这些按钮方面的功能。但是,highlightcolor 必须使用 s.configure 更新。框架只能调用一个主题。

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