I have this code.
class App(object):
def __init__(self):
self.root = Tk()
self.root.attributes('-zoomed', True)
self.root.grid_rowconfigure(0, weight=1)
self.root.grid_columnconfigure(0, weight=1)
self.root.grid_columnconfigure(1, weight=1)
f1 = Frame(self.root, bd=1, bg="green")
f3 = Frame(self.root, bd=1, bg="blue")
self.image = Image.open("default.png")
self.photo = ImageTk.PhotoImage(self.image)
self.label = Label(image=self.photo)
self.label.grid(row=0, column=1, sticky="nsew")
f1.grid(row=0, column=0, sticky="nsew")
f3.grid(row=1, column=0, columnspan=2, sticky="ew")
app = App()
app.root.mainloop()
and the output is
How can I make image occupy equally to the left frame?
Before providing a solution, there are few remarks I would like to highlight:
You did not use the frame f3for anything. It is not even displayed when running your program. So I will simply ignore it in my solution below.
You set the attribute -zoomed for the main frame' attributes. I do not know which goal you are going to fulfill by using it. Anyway, there are many threads that state it is working only Windows and some Debian distributions like Ubuntu. I did not experience this problem though, however, as you can read on the first link I provide below, those arguments are platform specific. So for the sake of portability of your code, you may use -fullscreen instead. But here again, especially for your case, it will affect even the taskbar of your GUI which will not be displayed and thus you can not close your program by clicking on the close button. So in my solution I will rather use winfo_screenheight() and winfo_screenwidth() to set self.root to the size of the screen of your machine and call them inside the geometry() method.
Let us divide your problem into smaller ones:
First problem you need to resolve is how to set the first frame f1 and self.label to equal widths?. This is done by simply setting the width option of each one of them to the half of the width of your screen: width = self.root.winfo_screenwidth()/2
Once this is done, you will need to resolve the second problem you clarified in your comment. For that, you will need to resize self.image to fit self.label size by applying resize() method on the image, and pass to it the dimensions of the image which must be the ones of your label along with the constant PIL.Image.ANTIALIAS.
Full program
So here is the full program:
'''
Created on May 5, 2016
#author: billal begueradj
'''
import Tkinter as Tk
import PIL.Image
import PIL.ImageTk
class App(object):
def __init__(self):
self.root = Tk.Tk()
self.root.grid_rowconfigure(0, weight=1)
self.root.grid_columnconfigure(0, weight=1)
self.root.grid_columnconfigure(1, weight=1)
# Get width and height of the screen
self.h= self.root.winfo_screenheight()
self.w= self.root.winfo_screenwidth()
# Set the GUI's dimensions to the screen size
self.root.geometry(str(self.w) + "x" + str(self.h))
# Set the width of the frame 'f1' to the half of the screen width
self.f1 = Tk.Frame(self.root, bd=1, bg="green", width= self.w/2)
self.f1.grid(row=0, column=0, sticky="nsew")
# Load the image
self.image = PIL.Image.open("/home/Begueradj/mahomet_pedophile.jpg")
# Resize the image to fit self.label width
self.photo = PIL.ImageTk.PhotoImage(self.image)
self.label = Tk.Label(self.root, image=self.photo, width= self.w/2)
self.label.grid(row=0, column=1, sticky="nsew")
# Launch the main program
if __name__ == '__main__':
app = App()
app.root.mainloop()
Demo
Nota Bene
There is a little imprecision through your comment. It does not say weither you want the whole picture to appear of the same size as the frame f1 or just its label in which it is appended (self.label). If this is what you want to do, then just ignore this line in the previous program:
self.image = self.image.resize((self.w/2, self.h), PIL.Image.ANTIALIAS)
Demo
The output will then look like this (the image is simply positioned in the center of the label):
Related
Python beginner. I placed a scrollbar widget in window and that works, but no matter what I do I can't get the scrollbox widget to change size. Could go with a larger scrollbox or for it to resize when the window resizes, but can't figure out how to force either to happen. Tried lots of different solutions, but feels like the grid and canvas are defaulting to a size and can't figure out how to change that. Help would be appreciated. Code is below:
import tkinter as tk
from tkinter import ttk
import os
import subprocess
class Scrollable(tk.Frame):
"""
Make a frame scrollable with scrollbar on the right.
After adding or removing widgets to the scrollable frame,
call the update() method to refresh the scrollable area.
"""
def __init__(self, frame, width=16):
scrollbar = tk.Scrollbar(frame, width=width)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y, expand=True)
self.canvas = tk.Canvas(frame, yscrollcommand=scrollbar.set)
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.config(command=self.canvas.yview)
self.canvas.bind('<Configure>', self.__fill_canvas)
# base class initialization
tk.Frame.__init__(self, frame)
# assign this obj (the inner frame) to the windows item of the canvas
self.windows_item = self.canvas.create_window(0,0, window=self, anchor=tk.NW)
def __fill_canvas(self, event):
"Enlarge the windows item to the canvas width"
canvas_width = event.width
self.canvas.itemconfig(self.windows_item, width = canvas_width)
def update(self):
"Update the canvas and the scrollregion"
self.update_idletasks()
self.canvas.config(scrollregion=self.canvas.bbox(self.windows_item))
root = tk.Tk()
root.title("application")
root.geometry('750x800')
dbEnvs = ['a','b']
x = 1
header = ttk.Frame(root)
body = ttk.Frame(root)
footer = ttk.Frame(root)
header.pack(side = "top")
body.pack()
footer.pack(side = "top")
#setup Environment selection
envLabel = tk.Label(header, text="Environment:")
envLabel.grid(row=0,column=0,sticky='nw')
dbselection = tk.StringVar()
scrollable_body = Scrollable(body, width=20)
x = 1
for row in range(50):
checkboxVar = tk.IntVar()
checkbox = ttk.Checkbutton(scrollable_body, text=row, variable=checkboxVar)
checkbox.var = checkboxVar # SAVE VARIABLE
checkbox.grid(row=x, column=1, sticky='w')
x += 1
scrollable_body.update()
#setup buttons on the bottom
pullBtn = tk.Button(footer, text='Pull')
pullBtn.grid(row=x, column=2, sticky='ew')
buildBtn = tk.Button(footer, text='Build')
buildBtn.grid(row=x, column=3, sticky='ew')
compBtn = tk.Button(footer, text='Compare')
compBtn.grid(row=x, column=4, sticky='ew')
root.mainloop()
have tried anchoring, changing the window base size and multiple other things (8 or 19 different items, plus reading lots of posts), but they normally involve packing and since I used grids that normally and ends with more frustration and nothing changed.
If you want the whole scrollbox to expand to fill the body frame, you must instruct pack to do that using the expand and fill options:
body.pack(side="top", fill="both", expand=True)
Another problem is that you're setting expand to True for the scrollbar. That's probably not something you want to do since it means the skinny scrollbar will be allocated more space than is needed. So, remove that attribute or set it to False.
scrollbar.pack(side=tk.RIGHT, fill=tk.Y, expand=False)
tip: when debugging layout problems, the problems are easier to visualize when you temporarily give each widget a unique color. For example, set the canvas to one color, body to another, the instance of Scrollable to another, etc. This will let you see which parts are visible, which are growing or shrinking, which are inside the borders of others, etc.
The size of Tkinter windows can be controlled via the following methods:
.minsize()
.maxsize()
.resizable()
Are there equivalent ways to control the size of Tkinter or ttk Frames?
#Bryan: I changed your frame1.pack code to the following:
frame1.pack(fill='both', expand=True)
frame1.bind( '<Configure>', maxsize )
And I added this event handler:
# attempt to prevent frame from growing past a certain size
def maxsize( event=None ):
print frame1.winfo_width()
if frame1.winfo_width() > 200:
print 'frame1 wider than 200 pixels'
frame1.pack_propagate(0)
frame1.config( width=200 )
return 'break'
The above event handler detects that a frame's width is too big, but is unable to prevent the increase in size from happening. Is this a limitation of Tkinter or have I misunderstood your explanation?
There is no single magic function to force a frame to a minimum or fixed size. However, you can certainly force the size of a frame by giving the frame a width and height. You then have to do potentially two more things: when you put this window in a container you need to make sure the geometry manager doesn't shrink or expand the window. Two, if the frame is a container for other widget, turn grid or pack propagation off so that the frame doesn't shrink or expand to fit its own contents.
Note, however, that this won't prevent you from resizing a window to be smaller than an internal frame. In that case the frame will just be clipped.
import Tkinter as tk
root = tk.Tk()
frame1 = tk.Frame(root, width=100, height=100, background="bisque")
frame2 = tk.Frame(root, width=50, height = 50, background="#b22222")
frame1.pack(fill=None, expand=False)
frame2.place(relx=.5, rely=.5, anchor="c")
root.mainloop()
A workaround - at least for the minimum size: You can use grid to manage the frames contained in root and make them follow the grid size by setting sticky='nsew'. Then you can use root.grid_rowconfigure and root.grid_columnconfigure to set values for minsize like so:
from tkinter import Frame, Tk
class MyApp():
def __init__(self):
self.root = Tk()
self.my_frame_red = Frame(self.root, bg='red')
self.my_frame_red.grid(row=0, column=0, sticky='nsew')
self.my_frame_blue = Frame(self.root, bg='blue')
self.my_frame_blue.grid(row=0, column=1, sticky='nsew')
self.root.grid_rowconfigure(0, minsize=200, weight=1)
self.root.grid_columnconfigure(0, minsize=200, weight=1)
self.root.grid_columnconfigure(1, weight=1)
self.root.mainloop()
if __name__ == '__main__':
app = MyApp()
But as Brian wrote (in 2010 :D) you can still resize the window to be smaller than the frame if you don't limit its minsize.
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.
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(...)
The size of Tkinter windows can be controlled via the following methods:
.minsize()
.maxsize()
.resizable()
Are there equivalent ways to control the size of Tkinter or ttk Frames?
#Bryan: I changed your frame1.pack code to the following:
frame1.pack(fill='both', expand=True)
frame1.bind( '<Configure>', maxsize )
And I added this event handler:
# attempt to prevent frame from growing past a certain size
def maxsize( event=None ):
print frame1.winfo_width()
if frame1.winfo_width() > 200:
print 'frame1 wider than 200 pixels'
frame1.pack_propagate(0)
frame1.config( width=200 )
return 'break'
The above event handler detects that a frame's width is too big, but is unable to prevent the increase in size from happening. Is this a limitation of Tkinter or have I misunderstood your explanation?
There is no single magic function to force a frame to a minimum or fixed size. However, you can certainly force the size of a frame by giving the frame a width and height. You then have to do potentially two more things: when you put this window in a container you need to make sure the geometry manager doesn't shrink or expand the window. Two, if the frame is a container for other widget, turn grid or pack propagation off so that the frame doesn't shrink or expand to fit its own contents.
Note, however, that this won't prevent you from resizing a window to be smaller than an internal frame. In that case the frame will just be clipped.
import Tkinter as tk
root = tk.Tk()
frame1 = tk.Frame(root, width=100, height=100, background="bisque")
frame2 = tk.Frame(root, width=50, height = 50, background="#b22222")
frame1.pack(fill=None, expand=False)
frame2.place(relx=.5, rely=.5, anchor="c")
root.mainloop()
A workaround - at least for the minimum size: You can use grid to manage the frames contained in root and make them follow the grid size by setting sticky='nsew'. Then you can use root.grid_rowconfigure and root.grid_columnconfigure to set values for minsize like so:
from tkinter import Frame, Tk
class MyApp():
def __init__(self):
self.root = Tk()
self.my_frame_red = Frame(self.root, bg='red')
self.my_frame_red.grid(row=0, column=0, sticky='nsew')
self.my_frame_blue = Frame(self.root, bg='blue')
self.my_frame_blue.grid(row=0, column=1, sticky='nsew')
self.root.grid_rowconfigure(0, minsize=200, weight=1)
self.root.grid_columnconfigure(0, minsize=200, weight=1)
self.root.grid_columnconfigure(1, weight=1)
self.root.mainloop()
if __name__ == '__main__':
app = MyApp()
But as Brian wrote (in 2010 :D) you can still resize the window to be smaller than the frame if you don't limit its minsize.