Change font size without messing with Tkinter button size

Typically, when you give a button a width, that width is measured in characters (ie: width=1 means the width of one average sized character). However, if the button has an image then the width specifies a size in pixels.  

A button can contain both an image and text, so one strategy is to put a 1x1 pixel as an image so that you can specify the button size in pixels. When you do that and you change the font size, the button will not grow since it was given an absolute size.

Here is an example that illustrates the technique. Run the code, then click on "bigger" or "smaller" to see that the text changes size but the button does not.

import Tkinter as tk
import tkFont

def bigger():
    size = font.cget("size")
    font.configure(size=size+2)

def smaller():
    size = font.cget("size")
    size = max(2, size-2)
    font.configure(size=size)

root = tk.Tk()
font = tkFont.Font(family="Helvetica", size=12)

toolbar = tk.Frame(root)
container = tk.Frame(root)

toolbar.pack(side="top", fill="x")
container.pack(side="top", fill="both", expand=True)

bigger = tk.Button(toolbar, text="Bigger", command=bigger)
smaller = tk.Button(toolbar, text="Smaller", command=smaller)

bigger.pack(side="left")
smaller.pack(side="left")

pixel = tk.PhotoImage(width=1, height=1)
for row in range(3):
    container.grid_rowconfigure(row, weight=1)
    for column in range(3):
        container.grid_columnconfigure(column, weight=1)
        button = tk.Button(container, font=font, text="x",
            image=pixel, compound="center", width=20, height=20)
        button.grid(row=row, column=column)

root.mainloop()

All of that being said, there is almost never a time when this is a good idea. If the user wants a larger font, the whole UI should adapt. Tkinter is really good at making that happen, to the point where it all mostly works by default.


The width of the button is defined in units of character width. In your case the button is defined to be 17 characters wide. So changing the character width by (ie changing the font size) changes the width of the button. AFAIK, the only way around that is to put the button into a Frame, because a Frame can define it's size in pixels. Here's a new kind of Button that does exactly that:

import Tkinter as tk

class Warspyking(tk.Frame):
    '''A button that has it's width and height set in pixels'''
    def __init__(self, master=None, **kwargs):
        tk.Frame.__init__(self, master)
        self.rowconfigure(0, minsize=kwargs.pop('height', None))
        self.columnconfigure(0, minsize=kwargs.pop('width', None))
        self.btn = tk.Button(self, **kwargs)
        self.btn.grid(row=0, column=0, sticky="nsew")
        self.config = self.btn.config

#example usage:
MyWindow = tk.Tk()
MyWindow.geometry("500x550")

from itertools import cycle
fonts = cycle((('Helvetica', '11'),('Helvetica', '15'),('Helvetica', '20')))
def chg():
    button.config(font=next(fonts))

button = Warspyking(MyWindow,text="Click me!",width=200,height=100 ,font=next(fonts), command=chg)
button.grid(row=1, column=1)

MyWindow.mainloop()

EDIT: Based on what I learned from Bryan Oakley, here's a much neater implementation:

class Warspyking(tk.Button):
    def __init__(self, master=None, **kwargs):
        self.img = tk.PhotoImage()
        tk.Button.__init__(self, master, image=self.img, compound='center', **kwargs)

I should also add that I very much agree with Bryan: Using this is probably a sign that you are doing something wrong. You should let tkinter handle sizing.