python – How to resize custom tickbox images in Tkinter.ttk created with the element_create() method of ttk.Style() object?

I managed to define a function on_resizewhich resizes my font and images on widgets after scaling the window.

For the comboboxes reizing the text seems to be a bug (?) as they need to be adressed with an additional command.

Now everything resizes perfectly, except the tickbox, which I created. Even the default tickboxes won’t resize, which is very frustraing for smaller window sizes. However, I don’t have a clue if there is a command to customize the images of the style I created for the checkbutton.

Here are the images I used so you can replicate the issue:

customize.png

unticed_box.png

ticked_box.png

MWE:

import tkinter as tk
import tkinter.font as tkFont
from tkinter import ttk
from PIL import Image, ImageTk
import os

darkmodebg = '#%02x%02x%02x' % (25, 39, 52)
darkmodelight="#%02x%02x%02x" % (21, 32, 43)
darkmodebuttons="#%02x%02x%02x" %(255,103,31)

class MWE():
    def __init__(self, window):
        self.img_list_dict = {}  # dict list to store images for resizing
        
        self.defaultsize = 11
        self.customFont = tkFont.Font(family="Helevicta", size=self.defaultsize)
        # Creating the style
        self.style = ttk.Style()
        self.style.theme_use('default')
        
        # Label
        self.style.configure('TLabel', font=self.customFont, background=darkmodebg, foreground="white")
        
        # Combobox
        self.style.map(     # mapping allows setting styles for specific states
        'TCombobox',
        fieldbackground=[('readonly', darkmodebg),('disabled', darkmodelight)],
        background=[('readonly', darkmodebuttons),('disabled', darkmodelight)],
        foreground=[('readonly', 'white'),('disabled', "gray")],
        arrowcolor=[('readonly', darkmodebg), ('disabled', "gray")],
        )
        
        # Checkbutton
        self.style.configure('TCheckbutton', background=darkmodebg, fieldbackground=darkmodebuttons, foreground="white",
                              font=self.customFont, selectforeground=darkmodebuttons, selectbackground=darkmodebuttons, 
                              selectcolor=darkmodebuttons
                              )
        self.style.map('TCheckbutton',
        fieldbackground=[('readonly', darkmodebg),('disabled', darkmodelight)],
        background=[('readonly', darkmodebuttons),('disabled', darkmodelight)],
        foreground=[('readonly', 'white'),('disabled', "gray")],
        arrowcolor=[('readonly', darkmodebg), ('disabled', "gray")])
        
        # Creating the Photoimages to replace old images 
        # Use some random images to replicate
        self.img_unticked_box = self.CreatePhotoImage("unticked_box.png", group = None)
        self.img_ticked_box = self.CreatePhotoImage("ticked_box.png", group = None)
        self.style.element_create("tickbox", "image", self.img_unticked_box[0], ("selected", self.img_ticked_box[0]))
        # replace the checkbutton indicator with the custom tickbox in the Checkbutton's layout
        self.style.layout(
            "TCheckbutton", 
            [('Checkbutton.padding',
              {'sticky': 'nswe',
                'children': [('Checkbutton.tickbox', {'side': 'left', 'sticky':     'w'}),  # replaces with image
            ('Checkbutton.focus',
                  {'side': 'left',
                  'sticky': 'w',
              'children': [('Checkbutton.label', {'sticky': 'nswe'})]})]})]
        )
        
        """
        Initalizing the window
        """
        self.master = window
        self.master.geometry('1920x1080')
        self.master.bind('<Configure>', self.on_resize)

        self.mainframe = tk.Frame(self.master, bg=darkmodebg)
        self.mainframe.pack()
        
        self.combobox = ttk.Combobox(self.mainframe,
                                     values=('Some random box...', 1, 2, 3),
                                     font=self.customFont)
        self.combobox.current(0)
        self.combobox.grid(row=0, column=0)
        # The function adds the image automatically to img_list_dict
        self.img_label = self.CreatePhotoImage("customize.png", var_name="customize", size=(30,30))
        self.label = ttk.Label(self.mainframe, text="This is a nice label image, which resizes",
                               font=self.customFont, compound="left",
                               image=self.img_label[0], wraplength=100)
        self.label.grid(row=0, column=1)
        self.img_list_dict['customize']['widget'].append(self.label) # Stores the label so the resized image can be updated via the on_resize function
        
        self.checkbutton = ttk.Checkbutton(self.mainframe, text="I do not know how to dynamically resize the tickbox image")
        self.checkbutton.grid(row=1, column=1)
        
        self.master.mainloop()
    
    def on_resize(self, event):
        if (event.widget == self.master and  # Ignore calls for children. ADDED.
            (self.master.winfo_height() != event.width
             or self.master.winfo_width() != event.height)
           ):
            # Compute window size dependent font size.
            calc_size = (event.height+event.width*.85) / 250
            intsize=int(calc_size)
            scaleFactor=calc_size/self.defaultsize
            self.customFont['size'] = intsize
            self.combobox['font'] = self.customFont  # ADDED.
            for dict_ in self.img_list_dict.values():
                        photoimage=dict_['image'][0]
                        image=dict_['image'][1]
                        cur_width=photoimage.width()
                        cur_height=photoimage.height()
                        resized_img=image.resize((round(cur_width*scaleFactor), round(cur_height*scaleFactor)), Image.ANTIALIAS)
                        new_photoimg = ImageTk.PhotoImage(resized_img)
                        for widget in dict_['widget']:
                            widget.config(image=new_photoimg)
                            
                            widget.image = new_photoimg
            
    def CreatePhotoImage(self, file, size=(20,20), var_name=None, group=True):
        """
        This function stores and creates (when not modified automatically) 
        a dict entry of the image file in self.img_list_dict.
        
        The 'widget' key needs to be adressed manually. Each widget 
        using this image has to be stored in this key.
        Returns
        -------
        list
            Eeturns the photoimage and the image stored in a list. 
            At index 0: photoimage object 
            This is vital for resizing
        """
        i = Image.open(file)
        i = i.resize(size, Image.ANTIALIAS)
        photo_image = ImageTk.PhotoImage(i)
        img_array=[photo_image, i]
        if var_name is None:
            dict_entry = os.path.splitext(file)[0]
        else:
            dict_entry = var_name
        if group is not None:
            self.img_list_dict.update({dict_entry: {'image':img_array, 'widget':[]}})
        return img_array

    
MWE(tk.Tk())

Leave a Comment