Do frames in Tkinter contain local grid systems? - python

Very simple question that I cannot find the answer to on Stack Exchange. (Only this misleading thread: Python3 Tkinter: Grids Within Grids? Frames Alongside Other Frames? which sadly does not answer the question).
I am using tkinter with Python to attempt a program with a GUI. Unfortunately, I completely do not understand the grid system.
I was under the impression that each frame contains its own grid system, where 0,0 is the top-left of that frame.
I created this code:
root = Tk()
main_frame = Frame(root, width='1520', height='1080', bg='#a1a1a1').grid(row=0, column=1)
side_frame = Frame(root, width='400', height='1080', bg='#757575').grid(row=0, column=0)
header_label = Label(main_frame, text='Heading', font=('Agency', 48), bg='#a1a1a1', fg='#ffffff').grid(row=0, column=0)
And hoped that because the label is set to (0,0) in the main frame, that the label would appear in the main frame. However, it didn't. I find that a bit odd and upsetting. Below is a picture of what is instead happening - the header label is appearing in the 'side_frame'.
Faulty layout picture
Could someone please explain to me how the grid system works? I wasn't really having trouble until I tried to add a scrollbar to the main_frame - I then realised that there's some serious awkwardness embedded within tkinter and I'd like to properly understand it.
Thanks!

The problem is that you're setting main_frame and side_frame to None, so passing that as the parent for any other widget will make that widget a child of the root widget.
That is because in python x=y().z() sets the value of x to z(). Thus, when you do main_frame = Frame(...).grid(...) sets main_frame to the result of .grid(...), and grid always returns None.
You should separate widget creation from widget layout, and this is one of the reasons why. The other big reason is that it makes the code easier to read and visualize.
main_frame = Frame(root, width='1520', height='1080', bg='#a1a1a1')
side_frame = Frame(root, width='400', height='1080', bg='#757575')
main_frame.grid(row=0, column=1)
side_frame.grid(row=0, column=0)

Related

tkinter - understand pack and grid - simple layout. (always confused...)

I'm trying to configure a text box and below to the text box 3 buttons in a row centered
I don't want to expand them to fill all the area. just be at the center to stay in their original size.
I was trying to do it with pack or grid. but I'm really get confused. I also trying to to put the text box and the buttons on different frame so maybe it will separate the widgets and let me configure it without messing up things (because everything is relative to the other..) ... but I came with nothing that looks good.
I also want to learn how to use the grid in the correct way if I have all kinds of widgets and buttons one below the other without "columnspan" or adjust the text length inside the buttons as well to match the widgets above them...
In this example. How I can center the buttons? I have to use side=tkinter.LEFT in order to put them one after one in a row. but the problem that they also stick to the left...
import tkinter
window = tkinter.Tk()
frame1 = tkinter.Frame(window).pack()
textbox1 = tkinter.Text(frame1, width=70, height=15).pack(side=tkinter.TOP)
button1 = tkinter.Button(frame1, text="button1").pack(side=tkinter.LEFT)
button2 = tkinter.Button(frame1, text="button2").pack(side=tkinter.LEFT)
button3 = tkinter.Button(frame1, text="button3").pack(side=tkinter.LEFT)
window.mainloop()
in this example if I set another frame to do separation between the widgets ...
It's not get to the center either....
import tkinter
window = tkinter.Tk()
frame1 = tkinter.Frame(window).pack(side=tkinter.TOP)
textbox1 = tkinter.Text(frame1, width=70, height=15).pack(side=tkinter.TOP)
frame2 = tkinter.Frame(window).pack(side=tkinter.TOP)
button1 = tkinter.Button(frame2, text="button1").pack(side=tkinter.LEFT)
button2 = tkinter.Button(frame2, text="button2").pack(side=tkinter.LEFT)
button3 = tkinter.Button(frame2, text="button3").pack(side=tkinter.LEFT)
window.mainloop()
And in this example. with grid, if I'm using different frames the button just jump on the text box and messed up everything....
import tkinter
window = tkinter.Tk()
frame0 = tkinter.Frame(window).grid(row=0, column=0)
frame1 = tkinter.Frame(window).grid(row=1, column=0)
textbox = tkinter.Text(frame0, width=70, height=15).grid(row=0, column=0)
button1 = tkinter.Button(frame1, text="button1").grid(row=0, column=0)
button2 = tkinter.Button(frame1, text="button2").grid(row=0, column=1)
button3 = tkinter.Button(frame1, text="button3").grid(row=0, column=2)
window.mainloop()
Can someone explain to me please in which way it's better to use and how to understand it better...?
it's always confusing me...
thanks in advance,
eliran
I was trying to do it with pack or grid. but I'm really get confused.
I also trying to to put the text box and the buttons on different
frame so maybe it will separate the widgets and let me configure it
without messing up things (because everything is relative to the
other..) ... but I came with nothing that looks good.
Your second example is fairly close to working, but it has a fatal flaw. If you add some debugging statements you'll see that frame1 and frame2 are None. Thus, any widgets with those as a parent actually end up in the root window.
This is because foo().bar() always returns the result of .bar(). In tkinter, .grid(...) always returns None, so Frame(...).grid(...) will always return None.
The best practice is to always separate widget creation from widget layout. For example:
frame1 = tkinter.Frame(window)
frame2 = tkinter.Frame(window)
frame1.pack(side="top")
frame2.pack(side="top")
With that, frame1 and frame2 are properly set to the frames. And when that happens, the rest of the code in your second example works as you expect and the buttons are centered.
And in this example. with grid, if I'm using different frames the button just jump on the text box and messed up everything....
That happens for the same reason as mentioned above: you think you're using separate frames, but everything is going in the root window. Because they are all in the root window, and you put the text widget and a button in the same row and column, they overlap.
I also want to learn how to use the grid in the correct way if I have
all kinds of widgets and buttons one below the other without
"columnspan" or adjust the text length inside the buttons as well to
match the widgets above them...
grid is not the right choice in this specific case, since you aren't actually creating a grid. You can use it, but it requires more code than using pack. grid is the right choice if you're creating an actual grid. In this case you aren't.
Using grid in this case requires a little creativity. While it's not the only solution, I would recommend that you divide the bottom frame into five columns - an empty column on the left and right, and three columns in the middle for the buttons. The empty columns can be used to take up all extra space, forcing the middle columns to all be centered.
A best practice for using grid is that every window that uses grid to manage its children needs at least one row and one column with a non-zero weight. That lets tkinter know where to allocate any extra space, such as when the user resizes the window.
Here's a complete solution using grid:
import tkinter
window = tkinter.Tk()
frame0 = tkinter.Frame(window)
frame1 = tkinter.Frame(window)
window.grid_rowconfigure(0, weight=1)
window.grid_columnconfigure(0, weight=1)
frame0.grid(row=0, column=0, sticky="nsew")
frame1.grid(row=1, column=0, sticky="nsew")
frame0.grid_rowconfigure(0, weight=1)
frame0.grid_columnconfigure(0, weight=1)
textbox = tkinter.Text(frame0, width=70, height=15)
textbox.grid(row=0, column=0, sticky="nsew")
button1 = tkinter.Button(frame1, text="button1")
button2 = tkinter.Button(frame1, text="button2")
button3 = tkinter.Button(frame1, text="button3")
frame1.grid_rowconfigure(0, weight=1)
frame1.grid_columnconfigure((0,4), weight=1)
button1.grid(row=0, column=1)
button2.grid(row=0, column=2)
button3.grid(row=0, column=3)
window.mainloop()
Can someone explain to me please in which way it's better to use and how to understand it better...?
In summary, your instinct to use separate frames is the right place to start. You should divide your UI into logical groups, and use separate frames for each group. Then, you are free to pick either grid or pack for each group separately. However, you need to be diligent with grid to make sure that the sticky option is used correctly, and that you've set weights for all of the right columns.
And finally, you have to start with the proper practice of separating widget creation from widget layout.
I have had these kinds of problems before. Even though the .pack() and .grid() systems are excellent, when things are getting hectic you can use the .place() system. .place() allows you to exactly pin-point your tkinter and ttk widgets using x-y axis coordinates.
The coordinates (0,0) are not at the center but at the topmost left corner of your tkinter window.
Eg:
some_widget_name = Button(root, text="Click me!"....)
some_widget_name.place(x=100, y=50)
This will make your widget move right 100 pixels and move down 50 pixels from the topmost left corner.
However, sometimes when you really want to make the location of the widgets precise, you may have to do some trial-and-error to make it visually pleasing.

Tkinter: How to make a fixed canvas size with scroll bars that resize to the window

I am attempting to create fixed-size canvas widget with scroll bars. The canvas in question could be quite large, and will almost definitely be much larger than the frame containing it. I would like to keep the canvas at its fixed size, but be able to resize the window containing it. The issue I am having is I don't know how to bind the scroll bars to the edge of the window.
I have tried both .pack and .grid. The obvious issue with .grid is that it will simply place the scroll bars next to the canvas. Unfortunately, the canvas must have a fixed size that will always be larger than the window. Whenever I .pack, the canvas appears to resize with the window, even when I explicitly disable expand and set fill to None.
I have made set the background to black for the purpose of clearly seeing the canvas area.
import tkinter as tk
from tkinter import *
class DialogueCreation(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.xbar = tk.Scrollbar(parent, orient=HORIZONTAL)
self.xbar.pack(side=BOTTOM, fill=X)
self.ybar = tk.Scrollbar(parent)
self.ybar.pack(side=RIGHT, fill=Y)
self.item_canvas = tk.Canvas(parent, width=5000, height=5000, xscrollcommand=self.xbar.set, yscrollcommand=self.ybar.set)
self.item_canvas.pack(side=LEFT, expand=FALSE, fill=None)
self.item_canvas.configure(background='black')
if __name__ == '__main__':
root = tk.Tk()
DialogueCreation(root)
root.title("Editor")
root.mainloop()
The canvas is a massive 5000x5000, so I should definitely be able to scroll when the window is small. I want the scrollbars to remain flush with the edges of the window, without resizing my canvas. The scrollbars remain dormant no matter how large or small the window is. I'm assuming the canvas is resizing with the window, which is definitely not the desired result.
Eventually this canvas will have several images displayed on it, and the location of those images must not change on the canvas. I do not believe the issue is with how I bound the scrollbars (I checked several other posts on this website to make sure), but it would not be uncharacteristic if I missed something obvious like that.
When you say you want to create a fixed size canvas, I'm assuming that you mean you want the drawable area to be a fixed size, rather than have a fixed size for the viewable portion of the canvas.
To do that, you need to set the scrollregion attribute to the drawable area. You use the width and height attributes to set the size of the visible portion of the canvas.
Also, hooking up scrollbars is a two way street: you've got to configure the canvas to update the scrollbars, and configure the scrollbars to scroll the canvas.
Note: you made DialogCreation a Frame, yet you put all of the widgets directly in the parent. That's very unusual, and not the best way to do it. I recommend inheriting from Frame like you do, but then all of the widgets should go in self rather than parent.
When you do it this way, you need to make sure you call pack on the instance of DialogCreation, eg:
dr = DialogueCreation(root)
dr.pack(fill="both", expand=True)
Using pack
With pack, you should set the expand option to True if you want the visible portion to grow or shrink when the user resizes the window.
My personal experience is that code is easier to understand and easier to maintain if you separate widget creation from widget layout. The following code shows how I would rewrite your code using pack. Notice the additional lines for configuring xbar and ybar, as well as setting the scrollregion.
class DialogueCreation(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.xbar = tk.Scrollbar(self, orient=HORIZONTAL)
self.ybar = tk.Scrollbar(self)
self.item_canvas = tk.Canvas(self, width=400, height=400,
xscrollcommand=self.xbar.set,
yscrollcommand=self.ybar.set)
self.xbar.configure(command=self.item_canvas.xview)
self.ybar.configure(command=self.item_canvas.yview)
self.item_canvas.configure(scrollregion=(0,0,4999,4999))
self.item_canvas.configure(background='black')
self.xbar.pack(side=BOTTOM, fill=X)
self.ybar.pack(side=RIGHT, fill=Y)
self.item_canvas.pack(side=LEFT, expand=TRUE, fill=BOTH)
Using grid
The obvious issue with .grid is that it will simply place the scroll bars next to the canvas.
I don't see that as obvious at all. grid has no such restriction. You can put the scrollbar anywhere you want.
The important thing to remember with grid is that rows and columns do not automatically grow or shrink when the window as a whole changes size. You have to explicitly tell tkinter which rows and columns to grow and shrink.
To achieve the same effect as with using pack, you need to configure row zero, column zero to be given all extra space. You do that by giving them a weight that is greater than zero.
To use grid instead of pack, replace the last three lines of the above example with the following six lines:
self.item_canvas.grid(row=0, column=0, sticky="nsew")
self.xbar.grid(row=1, column=0, sticky="ew")
self.ybar.grid(row=0, column=1, sticky="ns")
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)

Can't understand pack and grid geometry with tkinter

Hi I didn't really understand how furas made the below code work. Why didn't he get an error message about grid and pack on the same root when he added a box? In the addbox function he sets a frame to the root which is pack already and even uses the pack inside the function and then uses the grid.
Can someone please explain to me how this "magic" works?
a link to the his answer:
Creating new entry boxes with button Tkinter
from Tkinter import *
#------------------------------------
def addBox():
print "ADD"
frame = Frame(root)
frame.pack()
Label(frame, text='From').grid(row=0, column=0)
ent1 = Entry(frame)
ent1.grid(row=1, column=0)
Label(frame, text='To').grid(row=0, column=1)
ent2 = Entry(frame)
ent2.grid(row=1, column=1)
all_entries.append( (ent1, ent2) )
#------------------------------------
def showEntries():
for number, (ent1, ent2) in enumerate(all_entries):
print number, ent1.get(), ent2.get()
#------------------------------------
all_entries = []
root = Tk()
showButton = Button(root, text='Show all text', command=showEntries)
showButton.pack()
Thanks
There's no magic, it's just working as designed. The code uses pack in the root window, and uses grid inside a frame. Each widget that acts as a container for other widgets can use either grid or pack. You just can't use both grid and pack together for widgets that have the same master.
not really an answer but I think you will be helped by the link.
tkinter and it's layout is indeed a bit hard to understand.
I never understood how to deal with it until I stumbled over this presentation which explained the layout particulars in a way where I finally could get the hang of it.
Just putting it out there for others to find as well.
tkinter tutorial by beazley
I think you miss out on what pack and grid actually are. Consider such code:
import tkinter as tk
root = tk.Tk()
myFrame = tk.Frame(root)
myFrame.pack()
myButton1 = tk.Button(myFrame, text='This is button 1')
myButton2 = tk.Button(myFrame, text='This is button 2')
myButton1.grid(row=0, column=0)
myButton2.grid(row=1, column=0)
root.mainloop()
By creating root we create a new window. In this window we will put everything else. Then we create myFrame. Note, that the actual "thing" (in more adequate terms - widget) is created in line myFrame = tk.Frame(root). Note, that we have to specify where we are going to put this widget in brackets and we've written that it is going to be root - our main window. Blank frame probably isn't the best example since you can not see it being placed (not unless you use some more specifications at least), but still. We have created it, but not placed it in our user interface. The we use .pack() to place it. Now you refer to widgets as being used as packs or grids. That is not true though. Pack and grid are just the set of rules, on which the widgets are being placed inside some kind of window. Because of that, if you want to add something more to the root in our case, you will have to use .pack() again. Why? If you will give two sets of rules on how to place things on the screen for your computer - they will most likely conflict with each other. However, if we go one more level down and now want to place something inside our myFrame, we can again choose which set of rules to use. It is because it does not matter, where our frame is going to end up inside root, we now just want to specify where our Buttons 1 and 2 are going to end up inside the frame. Therefore we can again use .pack() or switch to .grid().
To conclude: .pack(), .grid() and .place() are sets of rules on how place widgets inside other widgets. In more general terms though these are rules on how place boxes in other boxes. One boxes in which we arrange other boxes can only have one set of rules.
I hope this example helps.

Tkinter Scrollbar

If I call text_area.pack() before scrollbar.pack() (i.e switch them), the scrollbar doesn't show. Why is that? If I am going to create a larger program, I would have absolutely no chance to find out where the problem is.
from tkinter import *
import tkinter.filedialog
root = Tk()
root.geometry("200x100")
frame = Frame(root,width=150, height=90)
frame.pack()
scrollbar = Scrollbar(frame)
text_area = Text(frame, width=200, height=50,yscrollcommand=scrollbar.set)
scrollbar.config(command=text_area.yview)
scrollbar.pack(side="right", fill="y")
text_area.pack()
root.mainloop()
The reason the scrollbar doesn't show is because there's simply no room for it. You're specifying a window size of 200x100 pixels and an inner frame size of 150x90 pixels, but you are trying to put a much larger text widget in that space. You're specifying a size of 200x50 characters (roughly 1400x750, depending on the fonts you're using) which is much too wide for the available space.
The way pack works is that it looks at the available space, puts the widget in that space, and subtracts the spaced needed for that widget from the space available for the next widget. Because you put the text widget first, and it requested more than the available space, it used up all of the available space. Then, when you call pack on the scrollbar, there's simply nowhere to put it.
When you reverse the order, the scrollbar takes up only a fraction of the available space, so there's room to fit the text widget in.
The best solution is to change the order in which you call pack. In general, it's best to call pack so that the last widget you call pack on is the "hero" -- the one that takes up all remaining space and grows or shrinks as the window grows and shrinks. Usually that's a text widget or a canvas widget, or a frame that itself contains many widgets.
The key to success with tkinter is to not try to force a tkinter widget or window to be a particular size in pixels (except, perhaps, for the canvas). Instead, either let a widget use it's default size (particularly with buttons and scrollbars), or pick a sensible default (number of rows and/or columns). Tkinter will then compute the right size to fit everything in based on the font, the screen resolution, and other factors.
The other 2 answers are really good - I thought I would add an example. Personally, I don't like to use .pack - I like to place things instead like this: self.set_label_logpage.place(x=175, y=100)
Example code:
faultlogframe_logs = tk.Label(self, textvariable=logging_screen_label, font=Roboto_Normal_Font,
height=180, width=400, background='white', foreground='black')
faultlogframe_logs.place(x=605, y=600)
self.scrollbar = Scrollbar(self, orient=VERTICAL, elementborderwidth=4, width=32)
self.scrollbar.pack(pady=60,padx=0, ipady=4, ipadx=4, side=RIGHT, fill=Y)
self.scrollbar_y = Scrollbar(self, orient=HORIZONTAL, width=12, takefocus=1)
self.scrollbar_y.pack(expand=TRUE, ipady=9, ipadx=9, padx=0, pady=0, side=BOTTOM, fill=X, anchor=S)
self.set_label_logpage = tk.Listbox(self, yscrollcommand=self.scrollbar.set, xscrollcommand=self.scrollbar_y.set)
self.set_label_logpage.config(font=Roboto_Normal_Font, background='white', foreground='black', height=16, width=55) #textvariable=self.label_to_display_log
self.set_label_logpage.place(x=175, y=100)
self.scrollbar_y.config(command=self.set_label_logpage.xview)
self.scrollbar.config(command=self.set_label_logpage.yview)
When you switch them, scrollbar.pack() simply is unaware that it needs to go top, instead it goes next place from top to bottom, and right. You can see that when you expand window size.
You can resolve the issue by replacing:
text_area.pack()
with:
text_area.pack(side="left")
When you want to design more complex geometry structures I'd suggest you use grid instead of pack.

Tk Canvas Usage in Python

There is a post of this code, posted by mgilson, that I don't understand. All up to the reference to big_widget makes sense. But, what does the reference to big_widget contribute? The particular post is here. I am trying to refine my code and, I think, use the Frame object. But, the reference to Canvas threw me. I tried to comment but I need 50+ reputation to do so. Not there yet.
import Tkinter as Tk
root = Tk.Tk()
f = Tk.Frame(root)
f.grid(row=0,column=0)
#place buttons on the *frame*
b1 = Tk.Button(f,text="Button1")
b1.grid(row=0,column=0)
b2 = Tk.Button(f,text="Button2")
b2.grid(row=0,column=1)
big_widget = Tk.Canvas(root)
big_widget.grid(row=1,column=0) #don't need columnspan any more.
big_widget = Tk.Canvas(root)
--> A Canvas object is initialized, anchored to root and assigned to big_widget
The next line places the canvas (big_widget) at row=1 column=0 on the grid
(in the same way, a frame (f) was previously initialized and placed at row=0 column=0 on the grid inside root)

Categories