Make two Frames occupy 50% of the available width each? - python

How can I make two Frames occupy 50% each of the available width of the window?
Right now I'm just letting the contents dictate the widths and using pack(side=Tkinter.LEFT), and obviously that doesn't make them both equal width.
I tried using grid() instead, but then I couldn't get them to resize when I resize the window.
EDIT
Note that I want to be able to resize the window and the frames should resize with it to always be 50% of the width.

You can use grid, using the uniform option. Put both halves in a "uniform group" by setting the uniform option to the same value for both, and they will be the same size. To get the columns to grow/shrink with the window, give them equal weight.
Example:
frame1 = tk.Frame(parent, ...)
frame2 = tk.Frame(parent, ...)
frame1.grid(row=0, column=0, sticky="nsew")
frame2.grid(row=0, column=1, sticky="nsew")
parent.grid_columnconfigure(0, weight=1, uniform="group1")
parent.grid_columnconfigure(1, weight=1, uniform="group1")
parent.grid_rowconfigure(0, weight=1)

You should use expand and fill if using the pack method
Example
import Tkinter as tk
root = tk.Tk()
left_frame = tk.Frame(root, bg = 'yellow')
left_frame.pack(side = tk.LEFT, expand = True, fill = tk.BOTH)
right_frame = tk.Frame(root, bg = 'lime')
right_frame.pack(side = tk.LEFT, expand = True, fill = tk.BOTH)
root.mainloop()

I finally came up with a hack that works correctly.
In the start, I create 2 frames, one of them has a label with (intentionally) a long text, and the other one a simple button. You can add as many widgets to both frames as you want.
The idea is to bind a callback method which takes in consideration the new dimensions of the main window (in case the user stretches or moves the window with the mouse) and resize the 2 frames consequently.
This is achieved using <Configure> event which provides the new dimensions to set to our 3 main widgets (the main window and the 2 frames).
The next step is to fix the problem you mentioned below #StevenSummers ' answer. To do that, we can rely on grid_propagate() which enables or disables the geometry propagation.
Program:
Let us put these ideas into action (a quick dirty example to implement the ideas; I did the same for the height, but you can ignore it if you do not need it):
import Tkinter as Tk
# Main window
master = Tk.Tk()
# Callback event that resizes the main components
def resize_it(event):
global master # Main Window
global a # Red Frame
global b # Blue Frame
#Reconfigure the main window's and frames' dimensions
a.configure(width=event.width/2, height=event.height/2)
b.configure(width=event.width/2, height=event.height/2)
master.configure(width=event.width, height=event.height)
#Create a red (first) Frame
a = Tk.Frame(master, bg='red')
a.pack(side='left', expand=True, fill=Tk.BOTH)
# Draw a button on frame a
a1 = Tk.Button(a, text='Button 1')
a1.grid(row=0, column=0)
# Disable geometry propagation
a.grid_propagate(0)
#Create a blue (second) Frame
b = Tk.Frame(master, bg='blue')
b.pack(side='left', expand=True, fill=Tk.BOTH)
# Draw a label on frame b
l1 = Tk.Label(b, text='Label 1Label 1Label 1Label 1Label 1Label 1Label 1Label 1Label 1Label 1Label 1Label 1Label 1Label 1Label 1Label 1')
l1.grid(row=0, column=0)
# Disable geometry propagation on frame b
# Note: always after adding ALL the desired widgets to b. Same note for frame b
b.grid_propagate(0)
master.bind('<Configure>', resize_it)
#Start program
master.mainloop()
Demo:
The output of the above program is always the same result you expect whenever you resize the main window using the mouse and whatever number of widgets you add to the 2 frames:

Related

How do I stop text from moving when I resize the screen in tkinter

I am creating a registration form, and I have coded labels to show next to the text box for someone's username and password. This is the code I am using to place the text boxes and labels:
usernamebx.place(relx=0.5, rely=0.5, width=225, height=25,
anchor= CENTER)
userbx_label.place(relx=0.1, rely=0.5, anchor=CENTER)
passwbx.place(relx=0.5, rely=0.6, width=225, height=25, anchor = CENTER)
passwbx_label.place(relx=0.1, rely=0.6, anchor=CENTER)
The code for usernamebx and passwbx means that the text boxes don't move when I resize the tkinter window. However, I have done the same with the labels for each but it doesn't work. Any help?
The code for usernamebx and passwbx means that the text boxes don't move when I resize the tkinter window.
Actually, they do move! If you put a widget at relx 0.5 in a window that is 200 pixels wide, that means the center of the widget will be 100 pixels from the left edge of the window. When you grow the window to 400 pixels wide, the center of the widget now will be 200 pixels from the left edge. It moved 100 pixels. You don't see it because it's symmetrical so it stays in the center.
The same happens with a widget that is at 0.1. on a 200 pixel wide window it's going to be 20 pixels from the left edge. When you make the window 400 pixels widget it's going to be 40 pixels from the edge.
This is the nature of relative coordinates -- they will always change when the window is resized.
It's hard to see what your actual requirement is, though I'm guessing you want the username label+entry and password label+entry to be co-aligned in the center of the window.
If that's the case, one simple solution is to put those widgets in a frame. Use grid internally since it appears that you are in fact creating a grid. Then, you can place the frame in the window as a separate step.
Here's an example of the technique. For illustrative purposes the frame has a visible border, but that's not strictly necessary. You can remove the border to make it blend in with the background.
This example uses place to put the frame in the center, though you can also use pack.
import tkinter as tk
root = tk.Tk()
root.geometry("400x400")
inner_frame = tk.Frame(root, bd=2, relief="groove")
usernamebx = tk.Entry(inner_frame)
userbx_label = tk.Label(inner_frame, text="Username:")
passwbx = tk.Entry(inner_frame)
passwbx_label = tk.Label(inner_frame, text="Password:")
inner_frame.grid_columnconfigure(0, weight=1)
userbx_label.grid(row=0, column=0, sticky="e")
usernamebx.grid(row=0,column=1, sticky="ew")
passwbx_label.grid(row=1, column=0, sticky="e")
passwbx.grid(row=1, column=1, sticky="ew")
inner_frame.place(relx=.5, rely=.5, anchor="center")
root.mainloop()
If you want to use pack rather than place, have the packer expand the allocated space to be the whole window, and the frame will automatically be centered. In this case the window will shrink to fit the frame plus the padding.
inner_frame.pack(side="top", expand=True, padx=10, pady=10)

How to get .grid_columnconfigure() working inside Frame?

I want to create a GUI with tkinter in python using grid-Method and grid_columnconfigure/grid_rowconfigure.
Unfortunately, this is not working inside a Frame.
How can I get this work?
from tkinter import *
master = Tk()
master.state('zoomed')
f = Frame(master, width=800, height=400)
Label1 = Label(f, text='Label 1')
Label2 = Label(f, text='Label 2')
f.grid_columnconfigure(0, weight=1)
f.grid_columnconfigure(2, weight=1)
f.grid_columnconfigure(4, weight=1)
Label1.grid(row=0, column=1)
Label2.grid(row=0, column=3)
f.pack()
master.mainloop()
ADDITIONAL QUESTION:
I got great answers, all is working fine with pack-Manager.
But how could I do this if using grid-Manager?
The grid_columnconfigure is working fine. The problem is that your frame will by default set its size to the smallest possible size to fit the labels. Since empty columns don't have a size, the frame will be just wide enough to hold the two labels.
This will be easy to visualize if you give frame a distinctive color during development. It also sometimes helps to give the frame a visual border so you can see its boundaries.
While I don't know what your ultimate goal is, you can see the spaces between the column if you have the frame fill the entire window:
f.pack(fill="both", expand=True)
If you want to use grid instead of pack, you have to do a bit more work. In short, put the frame in row 0 column 0, and give that row and column a non-zero weight so that grid will give all unused space to that row and column.
f.grid(row=0, column=0, sticky="nsew")
master.grid_rowconfigure(0, weight=1)
master.grid_columnconfigure(0, weight=1)
If you want to force the window to be a specific size, you can use the geometry method of the master window:
master.geometry("800x400")

tkinter sticky not working for some frames

I'm using tkinter to write a card game, and I'm having trouble with he grid layout manager 'sticky' configuration. I would like help fixing my code to make the frames display in the desired location. In my code and illustration below, there is a frame (b2) that contains two other (one green, b2a; and one red; b2b) frames. I would like to display frame b2 at the bottom of the parent frame (frame b). I've tried various combinations of N+S+E+W as arguments for 'sticky', for both frame b2 and the child frames b2a and b2b. However, I've been unable to make frame b2 (and more importantly b2a and b2b) appear in the desired location (the bottom image below with the correct placement was made in Illustrator).
In particular, it seems that sticky arguments in lines 27, 36 and 37 have no effect on the placement of frame b2, b2a and b2b inside of frame b.
from tkinter import *
from PIL import Image, ImageTk
def main(root):
cons = Frame(root)
cons.grid()
frameDict = setup_frames(cons)
populate_frames(frameDict)
def setup_frames(cons):
frame = {}
# Parental Frames
frame['a'] = Frame(cons, borderwidth=2, relief='groove')
frame['b'] = Frame(cons, borderwidth=2, relief='groove')
frame['c'] = Frame(cons, borderwidth=2, relief='groove')
frame['a'].grid(row=0, column=0, sticky=N+S+E+W)
frame['b'].grid(row=0, column=1, sticky=N+S+E+W)
frame['c'].grid(row=1, column=0, columnspan=2)
# Progeny 0 Frames:
frame['b1'] = Frame(frame['b'], borderwidth=2, relief='groove')
frame['b2'] = Frame(frame['b'], borderwidth=2, relief='groove')
frame['b1'].grid(row=0, column=0, sticky=N+S+E+W)
frame['b2'].grid(row=1, column=0, sticky=N+S+E+W)
# Progeny 1 Frames:
frame['b2a'] = Frame(frame['b2'], borderwidth=2, relief='groove',
background='green')
frame['b2b'] = Frame(frame['b2'], borderwidth=2, relief='groove',
background='red')
frame['b2a'].grid(row=0, column=0, sticky=S)
frame['b2b'].grid(row=0, column=1, sticky=SW)
return frame
def populate_frames(fr):
# Populating 'a' frame
aLab = Label(fr['a'], image=img[0])
aLab.grid()
# Populating b2a & b2b frames
bLab = Label(fr['b2a'], image=img[1])
bLab.grid(row=0, column=0)
bLab = Label(fr['b2b'], image=img[2])
bLab.grid(row=0, column=1)
# Populating c1 frame
cLab = Label(fr['c'], image=img[3])
cLab.grid()
if __name__ == '__main__':
root = Tk()
img = []
w = [40, 160, 80, 480]
h = [180, 60, 60, 60]
for i in range(4):
a = Image.new('RGBA', (w[i], h[i]))
b = ImageTk.PhotoImage(a)
img.append(b)
main(root)
The images below illustrate where the offending frames (green and red) are displaying (top) and where I would like them displayed (bottom).
Could someone please help me display frame b2 (and ultimately b2a and b2b) in the correct position (Edit: at the bottom of frame b, and spanning from the right side of frame a to the right side of frame c)?
Update:
I've solved both problems (vertical placement and horizontal justification of frame b2) using grid weights, as Bryan suggested. The solution to the vertical placement problem is straightforward, but I would not have predicted the solution to the horizontal justification issue.
I solved the vertical placement problem by giving weight=1 to row 0 in frame b (resulting in the upper panel of the figure below).
I solved the horizontal justification problem (wherein frames b1 and b2 were not stretching to fill frame b) by assigning weight=1 to column 0 in frame b. The frame outlines in the figure below show that frame b is already stretched from the right side of frame a to the right side of frame c. It's strange to me that giving weight to the only column in a frame would be required to allow child frames to fill horizontally. In any case, I've pasted my working code below. Lines 40 and 41 solved the issue I was having.
from tkinter import *
from PIL import Image, ImageTk
def main(root):
cons = Frame(root)
cons.grid()
frameDict = setup_frames(cons)
populate_frames(frameDict)
def setup_frames(cons):
frame = {}
# Parental Frames
frame['a'] = Frame(cons, borderwidth=2, relief='groove')
frame['b'] = Frame(cons, borderwidth=2, relief='groove')
frame['c'] = Frame(cons, borderwidth=2, relief='groove')
frame['a'].grid(row=0, column=0, sticky=N+S+E+W)
frame['b'].grid(row=0, column=1, sticky=N+S+E+W)
frame['c'].grid(row=1, column=0, columnspan=2)
# Progeny 0 Frames:
frame['b1'] = Frame(frame['b'], borderwidth=2, relief='groove')
frame['b2'] = Frame(frame['b'], borderwidth=2, relief='groove')
frame['b1'].grid(row=0, column=0, sticky=N+S+E+W)
frame['b2'].grid(row=1, column=0, sticky=N+S+E+W)
# Progeny 1 Frames:
frame['b2a'] = Frame(frame['b2'], borderwidth=2, relief='groove',
background='green')
frame['b2b'] = Frame(frame['b2'], borderwidth=2, relief='groove',
background='red')
frame['b2a'].grid(row=0, column=0, sticky=S)
frame['b2b'].grid(row=0, column=1, sticky=SW)
# Weighting
frame['b'].grid_rowconfigure(0, weight=1)
frame['b'].grid_columnconfigure(0, weight=1)
return frame
def populate_frames(fr):
# Populating 'a' frame
aLab = Label(fr['a'], image=img[0])
aLab.grid()
# Populating b2a & b2b frames
bLab = Label(fr['b2a'], image=img[1])
bLab.grid(row=0, column=0)
bLab = Label(fr['b2b'], image=img[2])
bLab.grid(row=0, column=1)
# Populating c1 frame
cLab = Label(fr['c'], image=img[3])
cLab.grid()
if __name__ == '__main__':
root = Tk()
img = []
w = [40, 160, 80, 480]
h = [180, 60, 60, 60]
for i in range(4):
a = Image.new('RGBA', (w[i], h[i]))
b = ImageTk.PhotoImage(a)
img.append(b)
main(root)
Consistent with Bryan's advice, it does seem to be a good rule of thumb to assign a weight to at least one column and one row in every container.
Here's before and after I fixed the horizontal justification problem:
Using Python 3.4, Yosemite
You must give some rows and columns a weight, so tkinter knows how to allocate extra space.
As a rule of thumb when using grid, every container using grid should give at least one row and one column weight.
What I would do is start over. Be methodical. Get the main three areas working first before tackling other problems. What is making this problem hard to solve is that nothing is behaving right, so you're trying to adjust many things at once. Focus on one area at a time, get it working just right, and then move on.
Given your diagram, pack seems like a much simpler solution than using grid for the children of the root window Using grid inside of frames inside of other frames using grid can be confusing.
It looks like frame C is a status bar of some sort that stretches across the bottom, so pack it first. Above that you have two areas - frame a is to the left and looks to be a fixed width, and frame c is to the right and takes up all of the extra space. Using pack, it would look like this:
frame['c'].pack(side="bottom", fill="x")
frame['a'].pack(side="left", fill="y")
frame['b'].pack(side="right", fill="both", expand=True)
Of course, you can get the exact same appearance with grid, but it will take a couple more lines of code since you have to give column 1 and row 1 a weight.
That should get the three main areas working just fine. Now all you have to worry about is the contents of frame B.
Your diagram shows that you want b2a and b2b at the bottom of frame b, with more widgets above it. Is that correct? If that's the case, you need to leave at least one extra row above it to fill the extra space.
The blank row with a positive weight will force all of the widgets to be moved toward the bottom of the area. They will take up only as much space as they need vertically, with the empty row with the non-zero weight taking up all the extra.
You then only have to worry about horizontal placement. It's unclear exactly what you expect, but the solution again revolves around giving columns weight. If you want both b2a and b2b to expand equally, give both columns an equal weight. If you want b2a to do all of the expanding, give only column zero a weight.

Is it possible to easily center side-stacked frames in a frame in tkinter?

I am faced with the problem to center side-stacked frames in a parent frame. I know how to center a single frame in a frame but I did not find a simple way to do this for several of them.
I get the following window
from the code below:
import Tkinter as tk
root = tk.Tk()
root.geometry("200x200")
# main frame
f = tk.Frame(root, background='black')
f.pack(expand=True, fill="both")
# two side-by-side frames inside, they fill up their space
f1 = tk.Frame(f, background='green')
f1.pack(side=tk.LEFT, expand=True, fill="both")
f2 = tk.Frame(f, background='red')
f2.pack(side=tk.LEFT, expand=True, fill="both")
# three fixed-size frames in the left frame above; I would like them to be centered in the frame
tk.Frame(f1, width=20, height=20, background="orange").pack(side=tk.LEFT, fill=None, expand=False)
tk.Frame(f1, width=20, height=20, background="white").pack(side=tk.LEFT, fill=None, expand=False)
tk.Frame(f1, width=20, height=20, background="gray50").pack(side=tk.LEFT, fill=None, expand=False)
root.mainloop()
I would like the three square frames to be centered in the green one. I had to use tk.LEFT to position them, otherwise they would have been stacked up by default.
In my complete program, the green frame is there to exclusively contain the three square frames.
What is the most standard way to center the three square frames in the green one?
While thinking about furas's comment I realized that I did not understand the true difference between expand and fill (it is still a bit vague). It is possible to center the three frames by changing the f1.pack() line to:
f1.pack(side=tk.LEFT, expand=True, fill=None)
The f1 frame is tight around the three square (fill=None) ones buts tries to take as much space as possible in all directions (expand=True), effectively being centered. Note that the green background is not visible, the frame being tight around its content.

Having trouble with resizing multiple grids

I have the following code. My problem is that I can't manage to resize properly the frames. When I run the program, everything is as expected. But when I resize it, I want to keep the original view.
from Tkinter import *
import os
import sys
ALL=N+S+E+W
class Application(Frame):
def __init__(self,master=None):
Frame.__init__(self,master)
self.master.rowconfigure(0,weight=1)
self.master.columnconfigure(0,weight=1)
self.grid(sticky=ALL)
self.rowconfigure(0,weight=1)
myframe1=Frame(self,bg='green')
myframe1.bind("<Button-1>",self.handler1)
myframe1.grid(row=0,column=0,rowspan=1,columnspan=2,sticky=ALL)
self.rowconfigure(1,weight=1)
myframe2=Frame(self,bg='blue')
myframe2.bind("<Button-1>",self.handler2)
myframe2.grid(row=1,column=0,rowspan=1,columnspan=2,sticky=ALL)
buttons=('Red','Blue','Green','Black')
button=[0]*4
for c in range(4):
self.rowconfigure(c+2,weight=1)
self.columnconfigure(c,weight=1)
button[c]=Button(self,text="{0}".format(buttons[c]),command=lambda x=buttons[c]:self.colors(x))
button[c].grid(row=2,column=c,sticky=E+W)
self.columnconfigure(4,weight=1)
self.rowconfigure(6,weight=1)
button1=Button(self,text='{0}'.format('Open'),command=self.content)
button1.grid(row=2,column=4,sticky=E+W)
f=Frame(self,bg='red')
self.myentry=Entry(f)
self.myentry.grid(row=0,column=4,sticky=ALL)
self.text=Text(f)
self.text.grid(row=1,column=4,sticky=ALL)
f.grid(row=0,column=2,rowspan=2,columnspan=3,sticky=ALL)
...
I tried many combinations of rowconfigure, columnconfigure, rowspan, columnspan, but I failed!
My original view is:
After resizing in one direction:
In another direction:
The white area is the Text widget which I want to be resizable (also the blue and green areas).
Your problem is that you seem to not quite understand how grid works. For example, you are putting only two widgets in the red frame (self.myentry and self.text) yet you are putting them in column 2 and 4. Are you aware that the columns are relative to their parent, not the GUI as a whole? You want them in column 0 of the red frame, then you want the red frame in the second column of it's parent.
The way to solve this is to divide and conquer. First, divide the main screen up into it's logical parts, and lay out those logical parts so they resize properly. Then, for anything inside each part, lather, rinse repeat. Using frames for organization is the way to go.
Here's how I would tackle your problem (though there's certainly more than one way to solve this problem). First, you have two major areas of the screen: the top portion which has the green, blue and red frames and their contents, and the bottom part which holds the buttons. The top area should grow and shrink in all directions, the bottom area only grows in the X direction. I would create two frames for this, one for each part, and use pack since pack is the simplest geometry manager. The top frame should be configured to fill both directions and expand. The bottom part (with the buttons) should only fill in the X direction.
You now have two areas that are independent of each other and have proper resize behavior: the "main" area and the "toolbar" area. You are free to arrange the inner contents of these frames however you wish without having to worry about how that affects the main layout.
In the bottom frame, if you want all the widgets to be the same size, use pack and have them all fill X and expand, and they will equally fill the area. If you want them to be different sizes, use grid so you can control each column separately.
For the top part, it has three sub-sections: the red, green and blue frames. Since they are not all arranged horizontally or vertically I would use grid. Place green in cell 0,0, blue in cell 0,1, and red in cell 1,1 spanning two rows. Give row 0 and column 1 a weight of 1 so it takes up all the slack.
As I wrote earlier, this isn't the only way to "divide and conquer" this specific problem. Instead of seeing the main app as two parts -- top and bottom, with the top part having three sub-parts, another choice is to see that your main window has four parts: green, blue, red and toolbar. The key isn't to pick the perfect definition, but to break the layout problem down into chunks working from the outside in.
Here is a working example:
from Tkinter import *
ALL=N+S+E+W
class Application(Frame):
def __init__(self,master=None):
Frame.__init__(self,master)
# the UI is made up of two major areas: a bottom row
# of buttons, and a top area that fills the result of
# UI
top_frame = Frame(self)
button_frame = Frame(self)
button_frame.pack(side="bottom", fill="x")
top_frame.pack(side="top", fill="both", expand=True)
# top frame is made up of three sections: two smaller
# regions on the left, and a larger region on the right
ul_frame = Frame(top_frame, background="green", width=200)
ll_frame = Frame(top_frame, background="blue", width=200)
right_frame = Frame(top_frame, background="red")
ul_frame.grid(row=0, column=0, sticky=ALL)
ll_frame.grid(row=1, column=0, sticky=ALL)
right_frame.grid(row=0, column=1, rowspan=2, sticky=ALL)
top_frame.columnconfigure(1, weight=1)
top_frame.rowconfigure(0, weight=1)
top_frame.rowconfigure(1, weight=1)
# the right frame is made up of two widgets, an entry
# on top and a text below
entry = Entry(right_frame)
text = Text(right_frame)
entry.pack(side="top", fill="x")
text.pack(side="top", fill="both", expand=True)
# the button frame has five equally spaced buttons
for color in ('Red', 'Blue', 'Green', 'Black'):
b = Button(button_frame, text=color)
b.pack(side="left", fill="x", expand=True)
quit_button = Button(button_frame, text="Quit")
quit_button.pack(side="left", fill="x", expand=True)
root = Tk()
app = Application(root)
app.pack(fill="both", expand=True)
root.mainloop()

Categories