Python: цвет фона изображения на кнопке, пребывающей в режиме disabled

Есть в форме кнопка tkinter.Button. Ничего необычного в конфигурации нет:

mybutton.configure(background = "blue", disabledforeground = "white", foreground = "white", image = "image.png")

С кнопкой связан длительный процесс. Для того, чтоб не дать пользователю опять нажать на кнопку в процессе, я блокирую её:

mybutton.configure(state = "disabled")

Когда процесс завершается, я её деблокирую:

mybutton.configure(state = "normal")

Мне кажется, что такой путь предотвращения проблем с повторным запуском, выглядит более-менее. Но суть не в нём. Цвет фона кнопки остаётся неизменным в блокированном состоянии, цвет текста тоже. Но фон изображения меняется на серый. Фон изображения сам по себе - прозрачный. Это позволяет мне легко менять цвет фона кнопок, использовать более интересную цветовую стилизацию всего приложения, чем просто скучно-стандартная. Можно ли как-то предотвратить "дружескую" помощь tkinter по изменению цвета заблокированных кнопок? Я достаточно долго рылся в документации, ничего не нашёл. Варианта только два:

  • Отказаться от tkinter вообще. Это не так быстро и отсутствие "эффектов" тоже не гарантировано.
  • Делать серой всю кнопку. Во-первых, почему только в пользу серого цвета? А если завтра по умолчанию "засеривание" превратится в "засинивание"? Да и "зесеривание" в контексте стилизации цветов в моих приложениях выглядит, мягко говоря, уродливо.

Я не рассчитываю, что решение есть, но на всякий случай... Вдруг я что-то упустил. ttk, стили ttk - уже пробовал, ноль.


Ответы (2 шт):

Автор решения: Amgarak

Как понимаю, проблема такая: введите сюда описание изображения

Тогда предлагаю унаследоваться от tk.Button и просто закостомить свою кнопку:

Через kwargs.pop('command', None) - вырезать заданную функцию для дальнейшего её вызова.

Через self.bind("<Button-1>", self.on_click) - перебиндить на внутренний метод, тем самым перехватывать событие нажатия.

В методе on_click в зависимости от флага уже решать блокировать событие или вызывать его.

import tkinter as tk

class CustomButton(tk.Button):
    def __init__(self, master=None, normal_image=None, disabled_image=None, **kwargs):
        self.normal_image = normal_image
        self.disabled_image = disabled_image
        self.is_disabled = False
        self.command = kwargs.pop('command', None)
        super().__init__(master, image=self.normal_image, **kwargs)
        self.bind("<Button-1>", self.on_click)

    def on_click(self, event):
        if self.is_disabled:
            return "break"  # Блок события при заблокированной кнопке
        # Вызов привязанной функции
        if self.command:
            self.command()

    def disable(self):
        self.is_disabled = True
        self.config(image=self.disabled_image)

    def enable(self):
        self.is_disabled = False
        self.config(image=self.normal_image)

root = tk.Tk()

normal_image = tk.PhotoImage(file=r"C:\Users\Amgarak\Desktop\K_Enter.png")
disabled_image = tk.PhotoImage(file=r"C:\Users\Amgarak\Desktop\K_Enter2.png")

def on_button_click():
    print("Button clicked!")

custom_button = CustomButton(root, normal_image=normal_image, disabled_image=disabled_image, command=on_button_click)
custom_button.pack()

def disable_button():
    custom_button.disable()

def enable_button():
    custom_button.enable()

button_disable = tk.Button(root, text="Disable Button", command=disable_button)
button_disable.pack()

button_enable = tk.Button(root, text="Enable Button", command=enable_button)
button_enable.pack()

root.mainloop()

введите сюда описание изображения введите сюда описание изображения введите сюда описание изображения

Дополнительный пример для наглядности:

import tkinter as tk

class CustomButton(tk.Button):
    def __init__(self, master=None, normal_image=None, disabled_image=None, **kwargs):
        self.normal_image = normal_image
        self.disabled_image = disabled_image
        self.is_disabled = False
        self.command = kwargs.pop('command', None)
        super().__init__(master, image=self.normal_image, **kwargs)
        self.bind("<Button-1>", self.on_click)

    def on_click(self, event):
        if self.is_disabled:
            return "break"  # Блок события при заблокированной кнопке
        # Вызов привязанной функции
        if self.command:
            self.command()

    def disable(self):
        self.is_disabled = True
        self.config(image=self.disabled_image)

    def enable(self):
        self.is_disabled = False
        self.config(image=self.normal_image)

root = tk.Tk()

normal_image = tk.PhotoImage(file=r"C:\Users\Amgarak\Desktop\K_Enter.png")
disabled_image = tk.PhotoImage(file=r"C:\Users\Amgarak\Desktop\K_Enter2.png")

def click_button1():
    button1.disable()
    print("Button1 clicked!")
    root.after(4000, button1.enable)
    
def click_button2():
    button2.disable()
    print("Button2 clicked!")
    root.after(4000, button2.enable)
    
def click_button3():
    button3.disable()
    print("Button3 clicked!")
    root.after(4000, button3.enable)

button1 = CustomButton(root, normal_image=normal_image, disabled_image=disabled_image, command=click_button1)
button1.pack()

button2 = CustomButton(root, normal_image=normal_image, disabled_image=disabled_image, command=click_button2)
button2.pack()

button3 = CustomButton(root, normal_image=normal_image, disabled_image=disabled_image, command=click_button3)
button3.pack()

root.mainloop()

введите сюда описание изображения

→ Ссылка
Автор решения: Fox Fox

Решение, компактное и простое, хотя и обходное, я нашёл сам. Tkinter и ttk не написаны как следует и уже не будут дописаны. Значит, надо обходить проблему. Итак, в форме есть кнопка, связанная с длительной процедурой:

mybutton.configure(command = lambda: mylongprocedure())

Раньше проблема решалась так. Для того, чтоб пользователь не нажал ещё раз на эту же кнопку во время выполнения, кнопку приходилось блокировать:

def mylongprocedure:
# Блокирую кнопку
 mybutton.configure(state = "disabled")
.......................
Код процедуры. Кнопка заблокирована, изображение на ней "засерено". Именно это и не нравится.
.......................
# Деблокирую кнопку
 mybutton.configure(state = "normal")

Теперь проблема будет решаться так:

def mylongprocedure:
# Запоминаю связь кнопки с этой процедурой
 v_command = mybutton.cget("command")
# Обнуляю связь кнопки с этой процедурой
 mybutton.configure(command = "")
.......................
Код процедуры. Кнопка не заблокирована, но нажатие на неё не вызывает параллельное выполнение этой процедуры. Необходимости в блокировке кнопки нет, а значит и фон изображения не меняется.
.......................
# Восстанавливаю связь кнопки с этой процедурой
 mybutton.configure(command = v_command)
→ Ссылка