3

I have encountered a problem while coding in Python that I cannot find a solution to. I have a tkinter grid that automatically resizes to fit the size of window it is in, but I want the text within the buttons/labels to also resize to fit the new grid. A basic example of this is below:

from tkinter import *

root = Tk()

for x in range(3):
    for y in range(3):
        Label(root, width = 2, height = 4, text = (y * 3 + x) + 1, relief = GROOVE).grid(row = y, column = x, sticky = N+E+S+W)
        Grid.rowconfigure(root, y, weight = 1)
        Grid.columnconfigure(root, x, weight = 1)

root.mainloop()

I would like the text to increase as you drag the window to expand it. How can this be done?


EDIT:

Mike - SMT's solution works well, however not with the situation I described in the comment on his reply.

Here is the code I have:

from tkinter import *
import tkinter.font as tkFont

grey = [0,1,2,6,7,8,9,10,11,15,16,17,18,19,20,24,25,26,30,31,32,39,40,41,48,49,50,54,55,56,60,61,62,63,64,65,69,70,71,72,73,74,78,79,80]
btn_list = []

filled = []
filled_ID = []

class GUI(Frame):
    def __init__(self, master = None):
        Frame.__init__(self, master)

        self.screen_init()

        self.key = None
        self.keydetection = False
        self.detect()
        self.bind_all("<Key>", self.key_event)

    def screen_init(self, master = None):
        for x in range(9):
            for y in range(9):
                btn_list.append(Button(master, width = 40, height = 40, image = pixel, compound = CENTER, bg = "#D3D3D3" if 9 * y + x in grey else "#FFFFFF", command = lambda c = 9 * y + x: self.click(c)))
                btn_list[-1].grid(row = y, column = x, sticky = N+E+S+W)
                Grid.rowconfigure(root, y, weight = 1)
                Grid.columnconfigure(root, x, weight = 1)

    def update(self, btn_ID, number = None, colour = "#000000", master = None):
            y = btn_ID // 9
            x = btn_ID % 9
            Button(master, width = 40, height = 40, image = pixel, text = number, compound = CENTER, fg = colour, bg = "#D3D3D3" if 9 * y + x in grey else "#FFFFFF", command = lambda c = 9 * y + x: self.click(c)).grid(row = y, column = x, sticky = N+E+S+W)

    def detect(self):
        self.keydetection = not self.keydetection

    def key_event(self, event):
        try:
            self.key = int(event.keysym)
        except:
            print("Numbers Only!")

    def click(self, btn_ID):
        if btn_ID in filled:
            filled_ID.pop(filled.index(btn_ID))
            filled.remove(btn_ID)
            window.update(btn_ID)
        else:
            filled.append(btn_ID)
            filled_ID.append(self.key)
            window.update(btn_ID, self.key)

def font_resize(event=None):
    for btn in btn_list:
        x = btn.winfo_width()
        y = btn.winfo_height()
        if x < y:
            btn.config(font=("Courier", (x-10)))
        else:
            btn.config(font=("Courier", (y-10)))

if __name__ == '__main__':
    root = Tk()
    root.title("Example")
    pixel = PhotoImage(width = 1, height = 1)
    font = tkFont.Font(family = "Helvetica")
    window = GUI(root)
    root.bind("<Configure>", font_resize)
    root.mainloop()

This means that the text is not in the grid when the code is originally run as the user presses a number key to select a number, and then clicks where they would like that number to appear in the grid, and this seems to break Mike - SMT's solution. Any fixes?

2
  • There are several questions on this site related to changing the font size when a label grows or shrinks. Have you searched for them before asking? For example, Python3: How to dynamically resize button text in tkinter/ttk? or Python/Tkinter: expanding fontsize dynamically to fill frame Commented Aug 9, 2018 at 15:24
  • 1
    It's a bad practice to update the question with an additional new question after the original question was answered. This invalidates the answer and the effort to write it. Good practice would be to accept the answer which solved the original problem and eventually ask another question. stackoverflow is a Q&A site and not a chat where you can replace your question with a new one after the original one was answered. Commented Oct 25, 2023 at 12:41

5 Answers 5

2

I would use a list to keep track of each label. Then apply a new text size to the label when the window resizes. You will also need to provide a 1x1 pixel image in order for the label to read its size in pixels vs text height.

I will be using grid() for this but it could just as easily be done with pack().

Because we are using grid() we need to make sure we set weights on each column/row the labels will be set to using rowconfigure() and columnconfigure().

The function we will need should check the height and width of the widget and then based on what number is smaller set the size of the font object. This will prevent the font from growing bigger than the label size.

import tkinter as tk
import tkinter.font as tkFont


root = tk.Tk()
label_list = []
font = tkFont.Font(family="Helvetica")
pixel = tk.PhotoImage(width=1, height=1)

for x in range(3):
    for y in range(3):
        root.rowconfigure(y, weight=1)
        root.columnconfigure(x, weight=1)

        label_list.append(tk.Label(root, width=40, height=40, image=pixel, text=(y * 3 + x) + 1, relief="groove", compound="center"))
        label_list[-1].grid(row=x, column=y, sticky="nsew")

def font_resize(event=None):
    for lbl in label_list:
        x = lbl.winfo_width()
        y = lbl.winfo_height()
        if x < y:
            lbl.config(font=("Courier", (x-10)))
        else:
            lbl.config(font=("Courier", (y-10)))

root.bind( "<Configure>", font_resize)
root.mainloop()

Results:

No mater how you resize the window it should always have roughly the largest the font can be without exceeding the label size.

enter image description here enter image description here enter image description here

UPDATE:

I did change a few things in your code. I changed how you were creating your btn_ID to something less complicated and something we can use with the update method. Let me know if you have any questions.

To answer your changed question and your comment here is a reworked version of your new code to do what you want:

from tkinter import *
import tkinter.font as tkFont

grey = [0,1,2,6,7,8,9,10,11,15,16,17,18,19,20,24,25,26,30,31,32,39,40,41,48,49,50,54,55,56,60,61,62,63,64,65,69,70,71,72,73,74,78,79,80]

class GUI(Frame):
    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.pixel = PhotoImage(width=1, height=1)
        self.font = tkFont.Font(family="Helvetica")
        self.master.bind("<Configure>", self.font_resize)
        self.btn_list = []
        self.filled = []
        self.filled_ID = []

        self.screen_init()

        self.key = None
        self.keydetection = False
        self.detect()
        self.bind_all("<Key>", self.key_event)
        root.bind( "<Configure>", self.font_resize)

    def screen_init(self, master=None):
        btn_ndex = 0
        for x in range(9):
            for y in range(9):
                self.btn_list.append(Button(master, font=self.font, width=40, height=40, image=self.pixel, compound=CENTER, bg="#D3D3D3" if 9 * y + x in grey else "#FFFFFF", command=lambda c=btn_ndex: self.click(c)))
                self.btn_list[-1].grid(row=y, column=x, sticky="nsew")
                root.rowconfigure(y, weight=1)
                root.columnconfigure(x, weight=1)
                btn_ndex += 1

    def font_resize(self, event=None):
        for btn in self.btn_list:
            x = btn.winfo_width()
            y = btn.winfo_height()
            if x < y:
                self.font.configure(size=x-10)
            else:
                self.font.configure(size=y-10)

    def update(self, btn_ID, number=None, colour="#000000", master=None):
        print(btn_ID)
        y = btn_ID // 9
        x = btn_ID % 9
        self.btn_list[btn_ID].config(text=number, fg=colour, bg="#D3D3D3" if 9 * y + x in grey else "#FFFFFF", command=lambda c=9 * y + x: self.click(c))

    def detect(self):
        self.keydetection=not self.keydetection

    def key_event(self, event):
        try:
            self.key=int(event.keysym)
        except:
            print("Numbers Only!")

    def click(self, btn_ID):
        if btn_ID in self.filled:
            self.filled_ID.pop(self.filled.index(btn_ID))
            self.filled.remove(btn_ID)
            window.update(btn_ID)
        else:
            self.filled.append(btn_ID)
            self.filled_ID.append(self.key)
            window.update(btn_ID, self.key)


if __name__ == '__main__':
    root = Tk()
    root.title("Example")
    window = GUI(root)
    root.mainloop()
Sign up to request clarification or add additional context in comments.

Comments

1

Is that how do you want? I did not make any minor changes. In line 24, In the button, I added keyword text. 

btn_list.append(Button(master, width = 40, height = 40, image = pixel, text = (y * 3 + x) + 1, compound = CENTER, bg = "#D3D3D3" if 9 * y + x in grey else "#FFFFFF", command = lambda c = 9 * y + x: self.click(c)))

Result before:

enter image description here

After clicking the button to remove the number.

enter image description here

Comments

1

I have changed your code a bit simplifying it and adding the possibility to play around with the window and text size, resizing the text according to the current size of the window. By the way: you can achieve the functionality you are after also without using an image.

from tkinter import Tk, Frame, Button, Grid, GROOVE, N,E,S,W
root = Tk()
root.title("Example")

cellGeomWidth        = 240
cellGeomHeight    = 240
winTitleHeight        = 24
winBorderWidth    = 3
winWidth            = 3*cellGeomWidth+2*winBorderWidth
winHeight            = 3*cellGeomHeight+2*winBorderWidth+winTitleHeight
root.geometry(f"{winWidth}x{winHeight}")
fontSize             = 16

grey = [0,1,2,6,7,8,9,10,11,15,16,17,18,19,20,24,25,26,30,31,32,39,40,41,48,49,50,54,55,56,60,61,62,63,64,65,69,70,71,72,73,74,78,79,80]
btn_list = []

filled = []
filled_ID = []

class GUI(Frame):
    def __init__(self, master = None):
        Frame.__init__(self, master)

        self.screen_init()

        self.key = None
        self.keydetection = False
        self.detect()
        self.bind_all("<Key>", self.key_event)

    def screen_init(self, master = None):
        for x in range(9):
            for y in range(9):
                btn_list.append(Button(
                    master, 
                    text="  ",
                    font=("Courier", fontSize),
                    relief = GROOVE, 
                    bg = "#D3D3D3" if 9 * y + x in grey else "#FFFFFF", 
                    command = lambda c = 9 * x + y: self.click(c))
                ) 
                btn_list[-1].grid(row = y, column = x, sticky = N+E+S+W, )
                Grid.rowconfigure(root, y, weight = 1)
                Grid.columnconfigure(root, x, weight = 1)

    def update(self, btn_ID, number=None):
            btn_list[btn_ID].configure(text = number)

    def detect(self):
        self.keydetection = not self.keydetection

    def key_event(self, event):
        try:
            self.key = int(event.keysym)
        except:
            print("Numbers Only!")

    def click(self, btn_ID):
        if btn_ID in filled:
            filled_ID.pop(filled.index(btn_ID))
            filled.remove(btn_ID)
            window.update(btn_ID)
        else:
            filled.append(btn_ID)
            filled_ID.append(self.key)
            window.update(btn_ID, self.key)

def font_resize(e=None):
    if e.widget is root:
        newFontSize = int(min(e.width/winWidth, e.height/winHeight)*fontSize)
        print(f"{newFontSize=}")
        for btn in btn_list:
                btn.configure(font=(None, newFontSize))

if __name__ == '__main__':
    window = GUI(root)
    root.bind("<Configure>", font_resize)
    root.mainloop()

The reason for failing of your code based on the answer by Mike - SMT was lack of full understanding of the main code mechanism shown in the answer applying it the wrong way to your special case.

Comments

0

In my case I found this works while coding in tkinter in python:

Welcome.place(relx=0.5, rely=0.1,anchor-=CENTER)

Obviously, you can change the widget name, the x axis and the y axis

Comments

0

This solution only depends on the size of the widget itself.

from tkinter import *
from tkinter.font import Font as tkfont

def autosize(e=None):
    """Automatically sizes text given a widget with text."""
    if not e is None:
        widget:Widget = e.widget
        ffont = tkfont(widget.winfo_toplevel(), widget["font"])
        fw = ffont.measure(widget["text"])
        fh = ffont.metrics('linespace')
        w = widget.winfo_width() - 4
        h = widget.winfo_height() - 4
        _family = ffont.actual(option="family")
        _size = ffont.actual(option="size")
        _weight = ffont.actual(option="weight")
        while True: #make text larger than frame
            if fw < w and fh < h:
                _size = _size + 1
            else:
                break
            ffont = tkfont(widget.winfo_toplevel(), f"{_family} {_size} {_weight}")
            fh = ffont.metrics('linespace')
            fw = ffont.measure(widget["text"])

        while True:
            if fw <= w and fh <= h:
                break
            else:
                _size = _size - 1
            ffont = tkfont(widget.winfo_toplevel(), f"{_family} {_size} {_weight}")
            fh = ffont.metrics('linespace')
            fw = ffont.measure(widget["text"])

        widget.configure(font = f"{_family} {_size} {_weight}")

Just bind autosize to your widgets.

widget.bind("<Configure>",autosize)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.