Mike - SMT
Mike - SMT

Reputation: 15226

Tkinters highlight of button is not working for me

According to the accepted answer on this post the use of .configure(highlightbackground='red') on a button should apply a color around the button however in testing I cannot reproduce what the poster has demonstrated in their gif recording.

Here is my test case: (Note even copy pasting the posters code I cannot get the highlight effect they are showing)

import tkinter as tk


root = tk.Tk()

btn = tk.Button(root, text='test', bg="#000000", fg="#ffffff", highlightthickness=4, activebackground="#ffffff",
                activeforeground="#000000", highlightbackground='red', highlightcolor='red')
btn.pack()
btn.focus_set()
root.mainloop()

Resulting app:

enter image description here

With some extensive searching I am not finding much on highlightbackground in the way of Q/A about the same issue so maybe something is missing. I have also tried to set the focus as this documentation states the widget needs focus with the same non result.

Maybe it could be version or OS related...

OS - Windows 10 Pro

Python - 3.6.2

Updated example using Krrr's post. So this does kinda work now however the issue at hand here is that it is resizing the button and not providing the correct highlighted color.

import tkinter as tk


def ResponsiveWidget(widget, *args, **kwargs):
    bindings = {
        '<FocusIn>': {'highlightbackground': 'red', 'highlightcolor':'red'},
        '<FocusOut>': {'highlightbackground': '#d9d9d9', 'highlightcolor':'SystemButtonFace'},
        '<Enter>': {'state': 'active'},
        '<Leave>': {'state': 'normal'}
    }
    for k, v in bindings.items():
        root.bind_class('Button', k, lambda e, kwarg=v: e.widget.config(**kwarg))


def update_active(event):
    global previous_button
    if previous_button != event.widget:
        previous_button.config(default='normal')
        event.widget.config(default='active')
        previous_button = event.widget


root = tk.Tk()
button_list = []
previous_button = None

for i in range(5):
    if i == 0:
        button_list.append(tk.Button(root, text='test', bg="#000000", fg="#ffffff", highlightthickness=5,
                                     activebackground="#ffffff", activeforeground="#000000", default='active'))
        previous_button = button_list[-1]
    else:
        button_list.append(tk.Button(root, text='test', bg="#000000", fg="#ffffff", highlightthickness=5,
                                     activebackground="#ffffff", activeforeground="#000000", default='normal'))
    button_list[-1].pack(padx=5, pady=5)
    button_list[-1].bind('<ButtonRelease-1>', update_active)

root.mainloop()

Results:

enter image description here

Expectation:

enter image description here

Upvotes: 2

Views: 1743

Answers (2)

Mohammad Al Jadallah
Mohammad Al Jadallah

Reputation: 486

Thank you for your question, your code is good and to solve your problem just use default="active"

import tkinter as tk


root = tk.Tk()

btn = tk.Button(root, text='test', bg="#000000", fg="#ffffff", highlightthickness=4,  
activebackground="#ffffff",
                activeforeground="#000000", highlightbackground='red', 
default="active", highlightcolor='red')
btn.pack()
btn.focus_set()
root.mainloop()

Upvotes: 0

r.ook
r.ook

Reputation: 13888

Unfortunately it seems Windows OS doesn't seem to be triggering the state and default widget configs properly. However this is achievable by doing your own bindings.

If you only have a handful of widgets that need this behaviour, you can have create a widget wrapper:

def ResponsiveWidget(widget, *args, **kwargs):
    bindings = {
        '<FocusIn>': {'default':'active'},    # for Keyboard focus
        '<FocusOut>': {'default': 'normal'},  
        '<Enter>': {'state': 'active'},       # for Mouse focus
        '<Leave>': {'state': 'normal'}
    }
    # Create the widget instance
    w = widget(*args, **kwargs)

    # Set the bindings for the widget instance
    for k, v in bindings.items():
        w.bind(k, lambda e, kwarg=v: e.widget.config(**kwarg))

    # Remember to return the created and binded widget
    return w

btn = ResponsiveWidget(tk.Button, root, text='test3', bg="#000000", fg="#ffffff", highlightthickness=10, activebackground="#ffffff",
                activeforeground="#000000", highlightbackground='red', highlightcolor='green')

btn2 = ResponsiveWidget(tk.Button, root, text='test4', bg="#000000", fg="#ffffff", highlightthickness=10, activebackground="#ffffff",
                activeforeground="#000000", highlightbackground='green', highlightcolor='red')

On the other hand, if you wanted the entire class of the widget to always trigger the default/state properly, you can use bind_class instead:

bindings = {
    '<FocusIn>': {'default':'active'},    # for Keyboard focus
    '<FocusOut>': {'default': 'normal'},  
    '<Enter>': {'state': 'active'},       # for Mouse focus
    '<Leave>': {'state': 'normal'}
}
for k, v in bindings.items():
    root.bind_class('Button', k, lambda e, kwarg=v: e.widget.config(**kwarg))

This seem to trigger the events just fine.

If you just want to replicate the functionality of the highlight colour, a less desirable method would be to change the highlightcolor config on focus instead:

bindings = {
        '<FocusIn>': {'highlightcolor':'red'},
        '<FocusOut>': {'highlightcolor': 'SystemButtonFace'},
        '<Enter>': {'state': 'active'},
        '<Leave>': {'state': 'normal'}
    }
for k, v in bindings.items():
    root.bind_class('Button', k, lambda e, kwarg=v: e.widget.config(**kwarg))

# Note this method requires you to set the default='active' for your buttons

btn = tk.Button(root, text='test', bg="#000000", fg="#ffffff", highlightthickness=10, activebackground="#ffffff",
                activeforeground="#000000", highlightcolor='SystemButtonFace', default='active')

# ...

I'd consider this more a hacky method.

Edit: For completeness, here's a MCVE using bind_class:

import tkinter as tk

root = tk.Tk()
bindings = {
        '<FocusIn>': {'highlightcolor':'red'},
        '<FocusOut>': {'highlightcolor': 'SystemButtonFace'},
        '<Enter>': {'state': 'active'},
        '<Leave>': {'state': 'normal'}
    } 

for k, v in bindings.items():
    root.bind_class('Button', k, lambda e, kwarg=v: e.widget.config(**kwarg))

btns = list(range(5))
for btn in btns:
    btns[btn] = tk.Button(root, text='test', bg="#000000", fg="#ffffff", highlightthickness=5, activebackground="#ffffff",
        activeforeground="#000000", highlightcolor='SystemButtonFace', default='active', padx=5, pady=5)
    btns[btn].pack()

btns[0].focus_set()
root.mainloop()

And MCVE using ResponsiveWidget function:

import tkinter as tk

root = tk.Tk()
def ResponsiveWidget(widget, *args, **kwargs):
    bindings = {
        '<FocusIn>': {'highlightcolor':'red'},    # for Keyboard focus
        '<FocusOut>': {'highlightcolor': 'SystemButtonFace'},  
        '<Enter>': {'state': 'active'},       # for Mouse focus
        '<Leave>': {'state': 'normal'}
    }
    # Create the widget instance
    w = widget(*args, **kwargs)

    # Set the bindings for the widget instance
    for k, v in bindings.items():
        w.bind(k, lambda e, kwarg=v: e.widget.config(**kwarg))

    # Remember to return the created and binded widget
    return w

btns = list(range(5))
for btn in btns:
    btns[btn] = ResponsiveWidget(tk.Button, root, text=f'test{btn}', bg="#000000", fg="#ffffff", highlightthickness=10, activebackground="#ffffff",
        activeforeground="#000000", highlightcolor='SystemButtonFace', default='active', padx=5, pady=5)
    btns[btn].pack()

btns[0].focus_set()
root.mainloop()

Upvotes: 3

Related Questions