How is my Tkinter widget requesting more width than is available? - python

I am using Tkinter to code a GUI and I am running into a strange issue. I have specified my Root widget to be about 1/3rd of the screen width. However, when I create a Text widget within the root and specify its width to be the same value, it somehow takes more space than the available screen width. I know this because I resized the window and checked and also because title.winfo_reqscreen_width() is outputting 4130. That does not make sense since my screen resolution is 1440x900.
Here is the code I am using:
from tkinter import *
root = Tk()
app_width = int(root.winfo_screenwidth()/3.5)
root.geometry(f"{app_width}x{app_height}+0+0")
root.resizable(False, False)
root.pack_propagate(0)
title = Text(root, height=1, width=app_width, padx=10, pady=10, font=('NewComputerModern08', 18))
title.insert('1.0', 'Title')
title.bind("<Leave>", leave)
#title.grid(column=0, row=0, padx=5, pady=5)
title.pack()
I have also tried variations with pack_propagate(), grid(), fill=X, fill=BOTH, expand=True, and not adding a width argument to the widget etc. However, I am still facing the same issue.

The width parameter of a Text widget (and Entry and Label) is used to specify the width in characters not pixels. 1440/3.5 is 380, so you're requesting that the window be 380 characters wide. On my machine the default font is 7 pixels or so, which would result in a window of around 2,660 pixels.

Related

Python TKinter Set absolute Label size

I am looking to set the Label Widget width to an exact value - width=100 should be 100 pixels.
Is there a way to achieve this or should I be looking at using a different widget?
When using the TKinter Label, the width and height parameters refer to the text size - height=2 will set the label large enough for two lines of text, not 2 pixels as I would expect.
I believe this post might help with your issue if you absolutely need a solution to do sizes pixel-perfect on your widget.
There is however no easy way to do it straight on the widget itself.
Specify the dimensions of a Tkinter text box in pixels
You can assign a blank image to the label, then you can specify width option in pixels:
import tkinter as tk
root = tk.Tk()
blank = tk.PhotoImage()
tk.Label(root, image=blank, text="Hello", width=200, height=50, compound="c", bg="yellow", bd=0, padx=0).pack(padx=100, pady=10)
tk.Label(root, image=blank, text="World", width=200, height=30, compound="c", bg="cyan", bd=0, padx=0).pack(padx=100, pady=10)
root.mainloop()
Result:

How do I let my Tkinter-Canvas have a fixed width/height ratio when resizing the window

I have the following chessboard:
And I want the canvas which holds the chessboard to always remain a square, no matter how I resize the Window, any help is appreciated!
This can get complicated if you have multiple items in a window and want to constrain a single widget. The correct answer will depend on what else is going on in the window and what sort of resize behavior you've defined.
A simple method is to make sure that the canvas is in a region that will not resize. For example:
import tkinter as tk
root = tk.Tk()
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(1, weight=1)
canvas = tk.Canvas(root, width=400, height=400, bg="bisque")
canvas.grid(row=0, column=1, sticky="nsew")
root.mainloop()
You can also constrain the window as a whole to a specific aspect ratio with wm_aspect. This lets you resize the window, but the window as a whole will retain a specific aspect ratio.
For example, to force the root window to be a square you can do this:
root = tk.Tk()
root.wm_aspect(1, 1, 1, 1)
From the tcl/tk man pages (and translated to python syntax):
window.wm_aspect(?minNumer minDenom maxNumer maxDenom?) - If minNumer, minDenom, maxNumer, and maxDenom are all specified, then they will be passed to the window manager and the window manager should use them to enforce a range of acceptable aspect ratios for window. The aspect ratio of window (width/length) will be constrained to lie between minNumer/minDenom and maxNumer/maxDenom. If minNumer etc. are all specified as empty strings, then any existing aspect ratio restrictions are removed. If minNumer etc. are specified, then the command returns an empty string. Otherwise, it returns a tuple containing four elements, which are the current values of minNumer, minDenom, maxNumer, and maxDenom (if no aspect restrictions are in effect, then an empty string is returned).
There are many solutions to your problem. Here is one with grid manager and Canvas of fixed size. If you want to resize the Canvas, you must use a bind to <Configure> event to keep its size square:
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
b = ttk.Button(text='test')
b.grid(column=0, row=0, sticky='ew')
root.columnconfigure(0, weight=1)
root.rowconfigure(1, weight=1)
canvas = tk.Canvas(root, width=500, height=500, bg="bisque")
canvas.grid(row=0, column=1, rowspan=2, sticky='nw')
root.mainloop()

Is it possible to reduce a button size in tkinter?

I'm working on a little text editor and currently making the buttons, on the picture bellow the 'B' button for bold. I would like to reduce his size in order to make him fit his true size, like a square box, and get rid of the extra-height.
How could I go below this size that seems to be the default minimum? Both the frame and button height are set to 1 and expecting an integer, so I'm not allowed to go below with something like 0.5.
top_menu = tk.Frame(root)
bold_button = tk.Button(top_menu, text='B', font=('EB Garamond ExtraBold',)) #here a tuple because tkinter needs it when the font name have multiple spaces
bold_button.pack(side='left', padx=4)
The width and height attributes are in units of characters (eg: 1 means the size of one average character). You can specify the width and height in units of pixels if the button has an image.
A button can have both an image and text, so a simple trick is to add a one-pixel transparent pixel as an image, which will then allow you to specify the size in pixels.
The padx and pady options also contributes to the size of the button. If you are trying to get a precise size, you need to set those to zero.
Here is a simple example:
import tkinter as tk
root = tk.Tk()
root.geometry("200x100")
pixel = tk.PhotoImage(width=1, height=1)
toolbar = tk.Frame(root)
toolbar.pack(side="top")
for size in (12, 16, 24, 32):
button = tk.Button(toolbar, text=str(size), width=size, height=size,
image=pixel, compound="center", padx=0, pady=0)
button.pack(side="left")
root.mainloop()
I think you could try to use a ttk.Button and give it a ttk.Style and set the font size:
s = ttk.Style()
s.configure('my_button', font=('Helvetica', 12))
b = ttk.Button(..., style='my_button')
Configuring a button (or any widget) in Tkinter is done by calling a configure method "config"
To change the size of a button called button1 you simple call
button1.config( height = WHATEVER, width = WHATEVER2 )
If you know what size you want at initialization these options can be added to the constructor.
button1 = Button(self, text = "Send", command = self.response1, height = 100, width = 100)

Change font size without messing with Tkinter button size

I am having trouble changing the font size of a button in Tkinter, when I attempt to do it the button also expands/contracts based on the size of the text. Is there a way I can alter the text size with the button's size anchored in place?
I ran across this while designing a tic-tac-toe application, however to save you the trouble, here is a very minimal example of the problem in practice:
import Tkinter as tk
MyWindow = tk.Tk()
MyWindow.geometry("500x550")
button = tk.Button(MyWindow,text="Hello!",width=17,height=10,font=('Helvetica', '20'))
button.grid(row=1, column=1)
MyWindow.mainloop()
The most important part here is font=('Helvetica', '15') or more specifically, the number 15. If you change that number and run this again, not only will the text be bigger/smaller, but so will the button! How do I get around this?
It's probably a really simple problem. I've just gotten started with Tkinter. Thanks in advance for any help I receive!
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.
I found solution of this problem. I was trying to solve a similar problem: I want to put image on label. I set the image size equal to label size. When I have been trying to put it with command label.config(image=img) the label size grow. The image have the size I set to it, so it didn't cover label completely. I was using grid manager. All size were not entered in advanced but calculated by Tkinter. I was using grid_columnconfigure and grid_rowconfigure. The solution I found is to put this label with image (or button in Your case) to LabelFrame and set grid_propagate to False.
Code example:
MyWindow = tk.Tk()
MyWindow.geometry("500x550")
#create LabelFrame (200x200)
label = tk.LabelFrame(MyWindow, width=200, height=200)
#grid manager to set label localization
labelk.grid(row=0, column=0)
#label row and column configure: first argument is col or row id
label.grid_rowconfigure(0, weight=1)
label.grid_columnconfigure(0, weight=1)
#cancel propagation
label.grid_propagate(False)
#Create button and set it localization. You can change it font without changing size of button, but if You set too big not whole will be visible
button = t.Button(label, text="Hello!", font=('Helvetica', '20'))
#Use sticky to button took up the whole label area
button.grid(row=0, column=0, sticky='nesw')
MyWindow.mainloop()
Result for font size 40 and 20:
Example for creating button with dynamic size by grid manager:
MyWindow = tk.Tk()
MyWindow.geometry("500x550")
#Divide frame on 3x3 regions
for col in range(3):
MyWindow.grid_columnconfigure(col, weight=1)
for row in range(3):
MyWindow.grid_rowconfigure(row, weight=1)
label = tk.LabelFrame(MyWindow)
#Put label in the middle
label.grid(row=1, column=1, sticky='nesw')
label.grid_propagate(False)
label.grid_rowconfigure(0, weight=1)
label.grid_columnconfigure(0, weight=1)
button = tk.Button(label, text="Hello!", font=('Helvetica', '30'))
button.grid(row=0, column=0, sticky='nesw')
MyWindow.mainloop()
It is late answer, but maybe it will help someone.

Avoid the status bar (footer) from disappearing in a GUI when reducing the size of the screen

I have developed a quite large GUI using tkinter.
Everything is working great when resizing, except for the case of reducing a lot the height of the window. All the frames are placed using 'pack()'.
The basic vertical structure of the GUI is (ordered from top to bottom):
Toolbar frame
self.frame_Toolbar=Frame(self.root, bg=colorBackground)
self.frame_Toolbar.pack(side=TOP, fill=X)
Middle frame containing all the basic information
self.frame_Middle=Frame(self.root)
self.frame_Middle.pack(side=TOP, fill=BOTH, expand=YES)
Status bar
self.frame_Status=Frame(self.root, bg=colorStatus, bd=1, relief=SUNKEN)
self.frame_Status.pack(side=BOTTOM, fill=X)
The middle frame is enlarged if the window is resized, which is great since it is what I want.
The issue is that when reducing a lot the height of the window, the status bar disappears because the middle frame adapts to the internal widgets. However, I would like that both the status bar and toolbar would be maintained and the middle frame would reduce as much as needed even if hiding some widgets.
To sum up, I would like that both the toolbar and status bar have like a minimum height.
Is that possible?
Edit: I add a functional MCVE code with the same error
# Import graphical interface
from tkinter import *
# Fixed window
root_fw=Tk()
# Toolbar
frame_Toolbar=Frame(root_fw, bg='red', height=50)
frame_Toolbar.pack(side=TOP, fill=X)
# Middle
frame_Middle=Frame(root_fw, bg='blue', height=300)
frame_Middle.pack(side=TOP, fill=BOTH, expand=YES)
# Status bar
frame_Status=Frame(root_fw, bg='green', bd=1, relief=SUNKEN, height=20)
frame_Status.pack(side=BOTTOM, fill=X)
root_fw.mainloop()
Once all of the widgets are at their minimum size, when you shrink the window tkinter must start reducing the size of one or more widgets. It does this in the order that widgets appear in the stacking order. Simply put, it starts removing space from the last widget that was packed.
So, the short answer is to pack the statusbar before you pack the middle widget. Generally speaking, a GUI will have one "hero" widget that takes up most of the space; it is this window that should be packed last so that the other widgets will not be clipped when the window is resized.
My recommendation is to always separate widget creation from widget layout, and to group layout together. I find this practice makes layout problems much easier to solve. I would therefore modify your code to look like this:
from tkinter import *
root_fw=Tk()
frame_Toolbar=Frame(root_fw, bg='red', height=50)
frame_Middle=Frame(root_fw, bg='blue', height=300)
frame_Status=Frame(root_fw, bg='green', bd=1, relief=SUNKEN, height=20)
frame_Toolbar.pack(side=TOP, fill=X)
frame_Status.pack(side=BOTTOM, fill=X)
frame_Middle.pack(side=TOP, fill=BOTH, expand=YES)
root_fw.mainloop()
I suggest you to switch to the Grid layout because you can configure rows and columns. The minimum height of a row can be set with the minsize option of the rowconfigure method.
In addition, you need to configure the middle row to resize with the window by setting its weight to 1 and finally you can set a minimum size for the widow to be at least big enough to show both bars.
from tkinter import *
# Fixed window
root_fw = Tk()
# resize row 1 and column 0 with window
root_fw.rowconfigure(1, weight=1)
root_fw.columnconfigure(0, weight=1)
# set minimum height for row 0 and 2
root_fw.rowconfigure(0, minsize=50)
root_fw.rowconfigure(2, minsize=20)
# set window min size
root_fw.minsize(70, 70)
# Toolbar
frame_Toolbar = Frame(root_fw, bg='red', height=50, width=200)
frame_Toolbar.grid(row=0, sticky="ew")
# Middle
frame_Middle = Frame(root_fw, bg='blue', height=300, width=200)
frame_Middle.grid(row=1, sticky="ewsn")
# Status bar
frame_Status = Frame(root_fw, bg='green', bd=1, relief="sunken", height=20, width=200)
frame_Status.grid(row=2, sticky="ew")
root_fw.mainloop()

Categories

Resources