简体   繁体   English

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

[英]Python: Changing ttk button color depending on current color?

I'm trying to do some things with styling in ttk for the first time.我第一次尝试在 ttk 中使用样式做一些事情。 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.所以我不知道如何在那里动态设置一个返回有效颜色的函数。

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:无论如何,每次您想要更改按钮背景时,您都必须配置“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 a lighter version of color for the active background using winfo_rgb to get the RGB code for the color. active_color使用winfo_rgb为活动背景返回较浅的颜色版本,以获取颜色的 RGB 代码。

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.每当颜色改变时,它通过我的一个函数来完成,所以我用 .generate_event() 触发 '' 事件,改变颜色并取消绑定当前处理程序以进行高亮显示,然后绑定一个新处理程序进行高亮显示替换前者。

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

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

ttk Button appearances are driven by themes (3D/Color-alt/classic/default, Color-clam). ttk 按钮外观由主题驱动(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.要使 ttk TButton 更改颜色,可以使用 map 来实现。 3D appearance requires borderwidth. 3D 外观需要边框宽度。 Only Classic forms an outer ring using highlight.只有经典使用高光形成外环。 Background logic for s.map below: Active & Pressed (Yellow), !Active (Green), Active & !Pressed (Cyan).下面 s.map 的背景逻辑: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.但是,highlightcolor 必须使用 s.configure 更新。 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() 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM