Search code examples
pythontkinterttk

Python: How to change the border color of ttk.Checkbutton when it is active?


I want to change the bordercolor of the ttk.Checkbutton widget, which is a light blue by default. I tried to use the configure function of ttk.Style() and managed to change the color of the whole button or other things when it is active, but not the border of the Checkbox. Additional question is where I could find this kind of information in for example some kind of documentation of all options that can be changed in the ttk style?

Here is an example of what I tried:

from tkinter import ttk

root = Tk()

s = ttk.Style()
s.configure('MyOwn.TCheckbutton',
    background='white',)
s.map('MyOwn.TCheckbutton',
       foreground=[('active', 'red')])

cb = ttk.Checkbutton(root,text='Text',style='MyOwn.TCheckbutton')
cb.pack()
root.mainloop()

I can change the color of the text when i hover over the checkbutton, but I don't find how to change the border color of the Checkbox.


Solution

  • Your Issue

    I believe the issue you are having is the fact the Ttk Checkbutton is not in fact a drawn widget but is in fact an image.

    How does one find out?

    If one takes a look at the github project ttkthemes and takes a look at the themes folder, and goes to, for example, the radiance folder (this is an UBUNTU like theme) and then to the folder inside that labelled "radiance".

    One will see something like this: gifs We see a folder filled with "GIFs", these are image files, if one scrolls down and finds the files:

    check-dc.gif (Disabled Deselected)
    check-du.gif (Disabled Deselected)
    check-nc.gif (Normal Selected)
    check-nu.gif (Normal Deselected)
    

    As we can see if we look through these files these are all the different possible states of a checkbutton.

    Under windows these checkbuttons would look like so (generated with this code):

    checkbuttons

    How does one include custom checkbuttons?

    A great post about this can be found here, where they create a custom checkbutton by adding a green and a red square to a canvas, but what if one wants to make something similiar to what you want. Where you change the colour of the checkbutton.

    How does one change the border colour of a default checkbutton?

    Firstly let's convert all of the Checkbutton images into the gif format, I have done this for you and they are available to download from my Google Drive or my Gofile.

    From my Google Drive you will need to fetch:

    check-nc.gif (Normal Selected)
    check-nu.gif (Normal Deselected)
    

    as you don't need to worry about the deactivated values because they will never be focused.

    Now lets look at how they load the images in the afforementioned post:

    on_image = tk.PhotoImage(width=48, height=24)
    off_image = tk.PhotoImage(width=48, height=24)
    on_image.put(("green",), to=(0, 0, 23,23))
    off_image.put(("red",), to=(24, 0, 47, 23))
    

    Here they create two blank images, which are 48px X 24px, and two one of them they add a green square on the left hand side, and the other they add one to the right hand side.

    Our approach to this is going to be similiar but slightly different, instead we are going to load the two gif files you downloaded and then we will write a script that will make 4 rectangles that'll cover the currently gray outline of the checkbutton. Like so:

    import tkinter as tk
    
    root = tk.Tk()
    
    def edit_check(colour, image):
        image.put((colour,), to=(0, 0, 1, 13)) # LEFT
        image.put((colour,), to=(0, 0, 13, 1)) # TOP
        image.put((colour,), to=(12, 0, 13, 13)) # RIGHT
        image.put((colour,), to=(0, 12, 13, 13)) # BOTTOM
    
    def focus_in(event):
        print(event)
        image = off_image if var1.get() == 0 else on_image
        edit_check("red", image)
        cb1.configure(image=image)
        cb1.image = image
    
    def focus_out(event):
        print(event)
        image = off_image if var1.get() == 0 else on_image
        edit_check("black", image)
        cb1.configure(image=image)
        cb1.image = image
    
    on_image = tk.PhotoImage(file="check-nc.gif") # Instead of creating a new image we open an existing one
    off_image = tk.PhotoImage(file="check-nu.gif") # Instead of creating a new image we open an existing one
    
    var1 = tk.IntVar(value=0)
    cb1 = tk.Checkbutton(root, image=off_image, selectimage=on_image, indicatoron=False,
                         onvalue=1, offvalue=0, variable=var1, offrelief='sunken')
    cb1.pack(padx=20, pady=10)
    
    # THESE IF YOU WANT TRUE FOCUS
    #cb1.bind("<FocusIn>", focus_in)
    #cb1.bind("<FocusOut>", focus_out)
    
    # THESE IF YOU WANT MOUSE OVER
    cb1.bind("<Enter>", focus_in)
    cb1.bind("<Leave>", focus_out)
    root.mainloop()
    

    Improving the code with Object Orientated Programming

    This here is the much better system for your use case (note it doesn't require any image files as the files are saved as base64 strings)

    import tkinter as tk
    
    check_nu = b'iVBORw0KGgoAAAANSUhEUgAAAA0AAAANCAYAAABy6+R8AAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9TRZEWB4uIOGSoTi2IijpKFYtgobQVWnUwufQLmjQkKS6OgmvBwY/FqoOLs64OroIg+AHi4uqk6CIl/i8ptIjx4Lgf7+497t4BQqPCVLNrHFA1y0jFY2I2tyr2vCKAIAYRwYzETD2RXszAc3zdw8fXuyjP8j735wgqeZMBPpF4jumGRbxBPL1p6Zz3iUOsJCnE58QRgy5I/Mh12eU3zkWHBZ4ZMjKpeeIQsVjsYLmDWclQiaeIw4qqUb6QdVnhvMVZrdRY6578hYG8tpLmOs0RxLGEBJIQIaOGMiqwEKVVI8VEivZjHv5hx58kl0yuMhg5FlCFCsnxg//B727NwuSEmxSIAd0vtv0xCvTsAs26bX8f23bzBPA/A1da219tALOfpNfbWvgI6N8GLq7bmrwHXO4AQ0+6ZEiO5KcpFArA+xl9Uw4YuAX61tzeWvs4fQAy1NXyDXBwCIwVKXvd4929nb39e6bV3w/0UXLbKEvbjQAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB+MMDRctIGmzOYIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAALElEQVQoz2M0Njb+z0AiYGFgYGA4c+YMI7EaTExM/jMxkAFGNQ1jTYzkpD0ATtMHS/nRiQwAAAAASUVORK5CYII='
    check_nc = b'iVBORw0KGgoAAAANSUhEUgAAAA0AAAANCAYAAABy6+R8AAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9TRZEWB4uIOGSoTi2IijpKFYtgobQVWnUwufQLmjQkKS6OgmvBwY/FqoOLs64OroIg+AHi4uqk6CIl/i8ptIjx4Lgf7+497t4BQqPCVLNrHFA1y0jFY2I2tyr2vCKAIAYRwYzETD2RXszAc3zdw8fXuyjP8j735wgqeZMBPpF4jumGRbxBPL1p6Zz3iUOsJCnE58QRgy5I/Mh12eU3zkWHBZ4ZMjKpeeIQsVjsYLmDWclQiaeIw4qqUb6QdVnhvMVZrdRY6578hYG8tpLmOs0RxLGEBJIQIaOGMiqwEKVVI8VEivZjHv5hx58kl0yuMhg5FlCFCsnxg//B727NwuSEmxSIAd0vtv0xCvTsAs26bX8f23bzBPA/A1da219tALOfpNfbWvgI6N8GLq7bmrwHXO4AQ0+6ZEiO5KcpFArA+xl9Uw4YuAX61tzeWvs4fQAy1NXyDXBwCIwVKXvd4929nb39e6bV3w/0UXLbKEvbjQAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB+MMDRctDrVlNE0AAAAjdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVAgd2l0aCBsb3ZlyGW0XgAAAJdJREFUKM+d0rENAyEMBdDvKA1zeART07MIGzAFE7EDK1B5CMqf7pIUlyP3JZdP+rItZkb8mScAjDFkF8QY+cCNbCF3R86ZvXduIXdHKYWqipSSXKJP0FqTEMK7Xu+dOWe6+yU4UEpJVBWlFLr7TwAAYmYcY8haC7VWzjkBAGfga+UhBLTWRFVPwREzI0nsjpndO67c+b0XBDxvkWRMW24AAAAASUVORK5CYII='
    
    
    class example_window:
        def __init__(self, master):
            self.master = master
            chckbox = custom_checkbox(master, "red")
            chckbox.pack(padx=20, pady=10)
            
    
    class custom_checkbox(tk.Checkbutton):
        def __init__(self, parent, colour, *args, **kwargs):
            default_kwargs = {'image': None, 'selectimage': None, 'indicatoron': False,
                              'onvalue': 1, 'offvalue': 0, 'variable': None, 'offrelief': 'sunken'}
            for key, value in default_kwargs.items():
                if key not in kwargs:
                    kwargs[key] = value
    
            self.colour = colour
            self.curr_colour = "black"
            self.hover = False
            if kwargs['variable'] is None:
                self.variable = kwargs['variable'] = tk.IntVar(value=1)
            else:
                self.variable = kwargs['variable']
            print(kwargs, kwargs['variable'].get())
            if kwargs['variable'].get() == 0:
                if kwargs['image'] is None:
                    self.off_image = kwargs['image'] = tk.PhotoImage(data=check_nu)
                else:
                    self.off_image = kwargs['image']
                if kwargs['selectimage'] is None:
                    self.on_image = tk.PhotoImage(data=check_nc)
                else:
                    self.on_image = kwargs['selectimage']
            else:
                if kwargs['image'] is None:
                    self.on_image = kwargs['image'] = tk.PhotoImage(data=check_nc)
                else:
                    self.on_image = kwargs['image']
                if kwargs['selectimage'] is None:
                    self.off_image = tk.PhotoImage(data=check_nu)
                else:
                    self.off_image = kwargs['selectimage']
    
            tk.Checkbutton.__init__(self, parent, *args, **kwargs)
            self.bind("<Enter>", self.focus_in)
            self.bind("<Leave>", self.focus_out)
            self.variable.trace("w", self.focus_update)
    
        def edit_check(self, colour, image):
            image.put((colour,), to=(0, 0, 1, 13)) # LEFT
            image.put((colour,), to=(0, 0, 13, 1)) # TOP
            image.put((colour,), to=(12, 0, 13, 13)) # RIGHT
            image.put((colour,), to=(0, 12, 13, 13)) # BOTTOM
    
            
        def focus_in(self, event=None):
            image = self.on_image if self.variable.get() == self['onvalue'] else self.off_image
            self.edit_check(self.colour, image)
            self.curr_colour = self.colour
            self.configure(image=image)
            self.image = image
            self.hover = True
    
        def focus_out(self, event=None):
            image = self.on_image if self.variable.get() == self['onvalue'] else self.off_image
            self.edit_check("black", image)
            self.curr_colour = "black"
            self.configure(image=image)
            self.image = image
            self.hover = False
    
        def focus_update(self, *args):
    
            if self.variable.get() == self['onvalue']:
                image = self.on_image
            else:
                image = self.off_image
            self.configure(image=image)
            self.image = image
            if self.hover:
                self.focus_in()
    
    def main():
        root = tk.Tk()
        example_window_gui = example_window(root)
        root.mainloop()
    
    if __name__ == '__main__':
        main()
    

    If you want to be able to add text to this widget, check out this code