Python Tkinter grid functions ignored - python

I feel like I am missing something stupidly obvious here - surely the widgets should just be positioned along the top of the root window, but they completely ignore the .grid() function.
When I printed the grid_size(), it returned (0,0). Why?!
Any help greatly appreciated :)
title = tk.Label(root, text="Enter a city below")
title.grid(row=0, column=0)
title.pack()
e = tk.Entry(root)
e.grid(row = 0, column = 1)
e.pack()
current_clicker = ttk.Button(root, text = "Current forecast", command=current)
current_clicker.grid(row=0, column=2)
current_clicker.pack()
hourly_clicker = ttk.Button(root, text = "By hour", command=hourly)
hourly_clicker.grid(row=0, column=3)
hourly_clicker.pack()
minute_clicker = ttk.Button(root, text = "By minute", command=minute)
minute_clicker.grid(row=0, column=4)
minute_clicker.pack()
daily_clicker = ttk.Button(root, text = "Daily", command=daily)
daily_clicker.grid(row=0, column=5)
print(daily_clicker.grid_size())
daily_clicker.pack()

I believe all you have to do is remove either pack() or grid(..) method. It is not recommended to mix pack() and grid(), as it might erase the effect of the other and lead to such errors.
I recommend to get rid of pack() as grid(...) is a more orderly way of managing widgets, compared to pack().
Tiny example:
hourly_clicker = ttk.Button(root, text = "By hour", command=hourly)
hourly_clicker.grid(row=0, column=3)
minute_clicker = ttk.Button(root, text = "By minute", command=minute)
minute_clicker.grid(row=0, column=4)
Hope it solved your doubts, if any more errors, do let me know
Cheers

Only one geometry manager can manage a widget at a time. So exactly like the title of the question is saying, when you call .pack() after calling .grid(...), the effects of grid(...) are ignored.
For any given widget, you must only use one, and you need to be consistent with all widgets that have the same parent.

pack, grid and place are the 3 methods available for placing a widget. tkinter refers to these as "geometry managers". Each of these provides a unique way to position and scale widgets.
pack will dock your widget to a defined or available edge
grid behaves like a table
place allows you to define an arbitrary position (and size)
An individual widget cannot have more than one geometry manager attached to it, and parents cannot contain widgets that alternate between pack and grid. This means (for example), if you have a Frame full of widgets that use pack, none of those widgets can use grid, and vise-versa. place is not subject to this limitation and can be used anywhere.

Related

Python tkinter - how to send StringVar() content to Scrolled Text box using grid() and using procedural code

Aspects of this question have most certainly been asked before, but not altogether.
[using Python 3.7.1]
Most of it works fine, but the display of scrolling textboxes is just not working... they expand southwards, and the scrollbar sliders never appear.
Never mind that the boxes and labels are too spaced apart.
Whilst it would probably be better to start again, with classes and use pack(), for the sake of understanding better, I would like to see if there's a way to make this work with grid() in a procedural way.
I can't quite get it over the line...
rtmiss = StringVar()
...
# COMMENT: Scrolled Text boxes
scrolledtext1_box=ScrolledText(framemain,width=30,height=10)
scrolledtext1_box.grid(sticky='n',row=5,column=0,rowspan=1,columnspan=1)
ttk.Label(scrolledtext1_box, anchor='w', width=20, text='placeholder', style='TLabel').grid(column=1, row=0, padx=0, pady=0)
tdiff_label = ttk.Entry(scrolledtext1_box, width=20, textvariable=tdiff)
scrolledtext1_box.insert(1.0, chars="anything")
tdiff_label.grid(column=0, row=5, columnspan=1, padx=1, pady=1)
no idea why this is so difficult. It's not the same as just inserting some free text into a textbox and adding a scrollbar.
tdiff_labelframe = ttk.LabelFrame(framemain, text='Title')
tdiff_labelframe.grid(column=0, row=5, padx=8, pady=4)
scrol_w = 30
scrol_h = 3
scrolledtext1_box = scrolledtext.ScrolledText(tdiff_labelframe, width=scrol_w, height=scrol_h, wrap=tk.WORD)
scrolledtext1_box.grid(sticky='nw',row=5,column=0,rowspan=1,columnspan=1)
tdiff_labelframe = ttk.Entry(framemain, width=20, textvariable=tdiff)
tdiff_entry = ttk.Entry(scrolledtext1_box, width=12, textvariable=tdiff)
tdiff_entry.grid(column=0, row=5)
I looked inside the Class Entry(Widget, XView) stuff...
def insert(self, index, string):
"""Insert STRING at INDEX."""
You can insert a string, as most examples will show you, as "my string", and I am able to get the contents of a StringVar into the text box, but only as a contiguous string, with no scrolling.
In the form in the codesnippet above...
tk.Tk()= myframe(root)
my_stringvar = stringvar()
my_labelframe = ttk.LabelFrame(my_frame, text='My Text')
my_labelframe = Entry(my_frame, textvariable = my_stringvar)
The scrolled stuff seems to be on a different "layer". The two blocks of code seem to be unconnected: apart from the scrolledtext function variable referring to the labelframe, which just sticks it onto that layer.
my_scrollheight = 20
my_scrollwidth = 10
my_scrolledtext = scrolledtext.ScrolledText(my_labelframe, width=my_scrollwidth, height=my_scrollheight, wrap=tk.WORD)
my_scrolledtext.grid(row=0,column=0,sticky='news')
So is the StringVar just being inserted into the Frame, above the LabelFrame layer, and thus not being "seen" by the scrolledtext part?
I have a partial answer to this, that I happened upon myself, but it creates a new problem. Before I could print the filepath, but not get the file contents to print and scroll; now it's the other way round.
I started off with this code:
Button errors in Tkinter - Selecting files, adding their paths
...trimmed it down to bare bones and one of each thing; then added my own junk, and eventually I accidentally made it work.
The solution to getting the text into a scrolled text widget, as in the question "how to send StringVar() content to Scrolled Text box using grid() and using procedural code?" is this:
def forkscroll():
mylabelframe = tk.LabelFrame(myframe, text='My Long Text:').pack()
scroll_w = 30
scroll_h = 10
myscrolledtext = scrolledtext.ScrolledText(mylabelframe, width=scroll_w, height=scroll_h, wrap=tk.WORD)
myscrolledtext.vbar.config(width=20)
myscrolledtext.grid(sticky='news', row=6, column=0, rowspan=1, columnspan=1, padx=5, pady=5)
# Open a dialogue; select and load a text file; read the text file directly into the scrolledtext widget (box)
fork = open(filedialog.askopenfilename(), 'r') # it's the same as "infile" above; but it's not a string
myscrolledtext.insert(tk.END, fork.read()) ### This reads the content of "fork" into the scrolledtext widget
...mainly those bottom two lines.
I then go on to put the file path of the selected text file into an Entry widget, but I can't get it to work... the best I could do was to keep the browsefunc and open the file twice... once for the path, and once for the contents... which is obviously a rubbish bodge.
forkit = StringVar()
forkit.set(fork.get())
mynormaltext = Text(mylabelframe, width = 10, height = 1, wrap=tk.WORD)
mynormaltext.grid(sticky='news', row=5, column=0, rowspan=1, columnspan=1, padx=5, pady=5)
mynormaltext.insert(tk.END, forkit.get())
# Even though it does the same thing in one line as it does in three, the outcome is not the same for scrolledtext
# forkname = filedialog.askopenfilename()
# forkfile = open(forkname, 'r')
# fork = forkfile.read()
# above... "fork" is a textIO, but Text can read it...
The best I can do, is get the content of the text file to fill the scrolledtext widget, and to print "<_IO.TextWrapper", as in the attached image. That's a bit better than just printing "PYVAR4" or something.
I have tried converting the "TextIO" or "PathLike" file into a string to inset it; I've tried doing set()/get(), and read() and lambda and tried putting things in different functions, and inputting and returning things, all sorts to try and get the filepath out again, but not really getting anywhere.
At least I've become more familiar with what some things do, and how, but I think I've hit a wall with the total solution, which is:
present 2 labels with 2 buttons each with a field: one as a text widget field; and scrolledtext widget field.
to click a button to browse to a text file, and read it into memory, and print its path into the text widget field
to click the other button, and print the contents of the same file into the scrolledtext field, and have it present within the frame, with a nice long scrollbar.

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.

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.

Why does my button not go to the far right of my GUI window?

The column value on my button is set to 500, but the button doesn't seem to move that far to the right.
This is my code:
from tkinter import *
root = Tk()
root.geometry("500x500")
logo = Label(root,text = "New File Finder")
new_folder = Button(text = "browse new folder")
old_folder = Button(text = "browse old folder")
logo.grid(row=0, column=8)
new_folder.grid(row=1,column=1)
old_folder.grid(row=1,column=500)
root.mainloop()
As Novel mentions, empty columns have a width of 0, so your column=8 has the same effect as column=2, and column=500 as column=3. And of course your column 0 has a width of zero. You could use .pack instead of .grid, as Novel suggests, but don't mix .pack and .grid into the same container widget, which is the root window in this case. However, you can achieve what you want via the .grid sticky arg, but you also need to set a column weight for the column containing the old_folder Button. Eg:
import tkinter as tk
root = tk.Tk()
root.geometry("500x500")
root.columnconfigure(2, weight=1)
logo = tk.Label(root, text="New File Finder")
new_folder = tk.Button(root, text="browse new folder")
old_folder = tk.Button(root, text="browse old folder")
logo.grid(row=0, column=1)
new_folder.grid(row=1,column=0)
old_folder.grid(row=1,column=2, sticky="e")
root.mainloop()
I also changed the "star" import, since that dumps 130 Tkinter names into your namespace, which is messy, a waste of space, and can cause name collisions, especially if you do other star imports.
As Bryan mentions, .pack(side=RIGHT) doesn't actually force the widget to the right side of the container, it merely tells .pack to pack the widget towards that side. So if you pack a bunch of widgets with .pack(side=RIGHT) they'll be packed from right to left instead of from the default top to bottom. You can control exactly where inside the widget's allotted space that you want the widget to be placed using the anchor arg. Also take a look at the various pad args. See the Pack docs for details.

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.

Categories