tk.Label with image not resizing correctly - python

I have the following tkinter example:
from PIL import Image, ImageTk
import tkinter as tk
root = tk.Toplevel()
container_frame = tk.Frame(master=root)
container_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
top_frame = tk.Frame(container_frame, background='red')
middle_frame = tk.Frame(container_frame, background='green')
bottom_frame = tk.Frame(container_frame, background='blue')
top_frame.grid(row=0, column=0, sticky='NSEW')
middle_frame.grid(row=1, column=0, sticky='NSEW')
bottom_frame.grid(row=2, column=0, sticky='NSEW')
container_frame.grid_columnconfigure(0, weight=1)
container_frame.grid_rowconfigure(0, weight=1, uniform='container_frame')
container_frame.grid_rowconfigure(1, weight=7, uniform='container_frame')
container_frame.grid_rowconfigure(2, weight=2, uniform='container_frame')
image = Image.open('some_image.png')
photo_image = ImageTk.PhotoImage(image)
label = tk.Label(top_frame, image=photo_image, background='yellow', anchor='w')
label.image = photo_image
label.pack(expand=1, side='left')
root.geometry('1280x720')
root.mainloop()
Unfortunately the image within the label has not been shrunk to fit the top_frame correctly. Why is this?
Furthermore, when I adjust the size of the window the label (and thus image) is not resized at all. Again, why is this?
Thanks for any help.

Unfortunately the image within the label has not been shrunk to fit the top_frame correctly. Why is this?
It is because that is not how tkinter works. It will not automatically grow and shrink images for you. It will grow and shrink the label, but not the image inside. You will have to write code to do that using an external library such as Pillow.
Furthermore, when I adjust the size of the window the label (and thus image) is not resized at all. Again, why is this?
As I wrote earlier, the image will never change. As for why the label doesn't change, it's because you've configured it not to change.
Consider this line of code:
label.pack(expand=1, side='left')
The expand option says that any extra space is given to the label, which tkinter does. However, by leaving the fill option as the default value, you've instructed tkinter to not expand the label to fill the space allocated to it.
If you want the label to grow, add fill='both'.

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()

Tkinter fix size of Frame to not overlap Canvas border with scrollbar

I'm working on a GUI with tkinter and i have a problem.
When i add a scrollbar to my app, the frame on my canvas overlaps the outlines (see image)
Here is the code:
from tkinter import *
window = Tk()
window.geometry("400x225")
scrollbar1 = Scrollbar(window, orient=VERTICAL)
canvas1 = Canvas(window, bg="#003333", yscrollcommand=scrollbar1.set)
frame1 = Frame(canvas1, bg="#003333")
scrollbar1.config(command=canvas1.yview)
scrollbar1.pack(side=RIGHT, fill=Y)
canvas1.pack(fill=BOTH, expand=True)
canvas1.create_window((0, 0), window=frame1, anchor="nw")
for x in range(20):
string = "line " + str(x)
label1 = Label(frame1, fg="white", bg="#003333", text=string, font=("Calibri Bold", 14))
label1.pack(pady=5)
window.update()
canvas1.config(scrollregion=canvas1.bbox("all"))
window.mainloop()
I don't know if it's possible but i want the frame to fit within the canvas and keeping the outlines as well.
I hope you get my problem and can probably help me out! Thanks in advance.
The highlightthickness
Specifies a non-negative value indicating the width of the highlight rectangle to draw around the outside of the widget when it has the input focus.
So, this is not really the "border" that you want. It is a part of the drawing space within the canvas, when you use window_create to draw a window, the parent of that window is the canvas, which begins before the highlight and so the window slides over it.
A solution, as also suggested by #martineau would be to make this 0 by specifying highlightthickness=0 and as you suggested that you need the "border" around the whole thing, you can either create a container frame and specify the bd parameter, or just set the bd of the window window.config(bd=2).

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.

Resize entry box in tkinter

The following MWE is for a window with horizontal and vertical scrollbars. The window contains an entry box in which the current working directory is displayed. However, the text in the entry box cannot all be seen as the box is too small. I would like to be able to display more of this text as the user enlarges the window. How can I adapt the following example so that the Entry box (defined in UserFileInput) resizes with the window? I have tried using window.grid_columnconfigure (see below), however this doesn't have any effect. It seems to be a problem with using the canvas, as previously I was able to get the Entry boxes to resize, however I need the canvas in order to place the horizontal and vertical scrollbars on the window.
window.grid(row=0, column=0, sticky='ew')
window.grid_columnconfigure(0, weight=1)
(and also with column = 1) but this doesn't have an effect.
import Tkinter as tk
import tkFileDialog
import os
class AutoScrollbar(tk.Scrollbar):
def set(self, lo, hi):
if float(lo) <= 0.0 and float(hi) >= 1.0:
# grid_remove is currently missing from Tkinter!
self.tk.call("grid", "remove", self)
else:
self.grid()
tk.Scrollbar.set(self, lo, hi)
class Window(tk.Frame):
def UserFileInput(self,status,name):
row = self.row
optionLabel = tk.Label(self)
optionLabel.grid(row=row, column=0, sticky='w')
optionLabel["text"] = name
text = status#str(dirname) if dirname else status
var = tk.StringVar(root)
var.set(text)
w = tk.Entry(self, textvariable= var)
w.grid(row=row, column=1, sticky='ew')
w.grid_columnconfigure(1,weight=1)
self.row += 1
return w, var
def __init__(self,parent):
tk.Frame.__init__(self,parent)
self.row = 0
currentDirectory = os.getcwd()
directory,var = self.UserFileInput(currentDirectory, "Directory")
if __name__ == "__main__":
root = tk.Tk()
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(0,weight=1)
vscrollbar = AutoScrollbar(root,orient=tk.VERTICAL)
vscrollbar.grid(row=0, column=1, sticky='ns')
hscrollbar = AutoScrollbar(root, orient=tk.HORIZONTAL)
hscrollbar.grid(row=1, column=0, sticky='ew')
canvas=tk.Canvas(root,yscrollcommand=vscrollbar.set,xscrollcommand=hscrollbar.set)
canvas.grid(row=0, column=0, sticky='nsew')
vscrollbar.config(command=canvas.yview)
hscrollbar.config(command=canvas.xview)
window = Window(canvas)
canvas.create_window(0, 0, anchor=tk.NW, window=window)
window.update_idletasks()
canvas.config(scrollregion=canvas.bbox("all"))
root.mainloop()
You have several problems in your code that are getting in your way. The biggest obstacle is that you're putting a frame inside a canvas. That's rarely necessary, and it makes your code more complex than it needs to be? Is there a reason you're using a canvas, and is there a reason you're using classes for part of your code but not for everything?
Regardless, you have two problems:
the frame isn't growing when the window grows, so the contents inside the window can't grow
the label won't grow because you're using grid_columnconfigure incorrectly.
Hints for visualizing the problem
When trying to solve layout problems, a really helpful technique is to temporarily give each containing widget a unique color so you can see where each widget is. For example, coloring the canvas pink and the Window frame blue will make it clear that the Window frame is also not resizing.
Resizing the frame
Because you're choosing to embed your widget in a canvas, you are going to have to manually adjust the width of the frame when the containing canvas changes size. You can do that by setting a binding on the canvas to call a function whenever it resizes. The event you use for this is <Configure>. Note: the configure binding fires for more than just size changes, but you can safely ignore that fact.
The function needs to compute the width of the canvas, and thus the desired width of the frame (minus any padding you want). You'll then need to configure the frame to have that width. To facilitate that, you'll need to either keep a reference to the canvas id of the frame, or give the frame a unique tag.
Here is a function that assumes the frame has the tag "frame":
def on_canvas_resize(event):
padding = 8
width = canvas.winfo_width() - padding
canvas.itemconfigure("frame", width=width)
You'll need to adjust how you create the canvas item to include the tag:
canvas.create_window(..., tags=["frame"])
Finally, set a binding to fire when the widget changes size:
canvas.bind("<Configure>", on_canvas_resize)
Using grid_columnconfigure to get the label to resize
You need to use grid_columnconfigure on the containing widget. You want the columns inside the frame to grow and shrink, not the columns inside the label.
You need to change this line:
w.grid_columnconfigure(...)
to this:
self.grid_columnconfigure(...)

Categories

Resources