Tkinter pack() geometry manager confusion - python

Just when I thought I understood the pack() manager, I came upon the following problem:
I created two frames (each with a Button) and want to pack them horizontally in another frame (which is the 'main' frame). For some reason, they appear vertically. If I replace the first frame by a simple button, the button + second frame are packed horizontally, using the same pack() instructions.
Here's a distilled (but working) version of the program:
from Tkinter import *
class QuitBtn1(Button):
def __init__(self):
Button.__init__(self)
self["text"] = "Quit"
self["fg"] = "red"
self["command"] = self.quit
class QuitBtn(Frame):
def __init__(self):
Frame.__init__(self)
b = Button(text = "Quit", fg = "red", command = self.quit)
b.pack()
class HiBtn(Frame):
def __init__(self):
Frame.__init__(self)
b = Button(text = "Hi there", fg = "blue", command = self.quit)
b.pack()
class App(Frame):
def __init__(self, master = None):
Frame.__init__(self, master)
self.pack()
self.createWidgets()
def say_hi(self):
print "hi there, everyone!"
def createWidgets(self):
self.QUIT = QuitBtn().pack(side = LEFT)
self.hi_there = HiBtn().pack(side = LEFT)
root = Tk()
app = App(master = root)
app.mainloop()
root.destroy()
As run, it produces two, vertically packed, frames. Just switching the names of QuitBtn1 and QuitBtn (which changes the frame into a simple button), changes the packing to horizontal.
I've looked at tens of examples, which seem to somehow avoid this exact structure. Am I doing something wrong here?

The problem is that you aren't giving any of your widgets a parent, so they all default to the root window. Even though you think the buttons are inside the frames, they are not.
Because the default for pack is to put things along the top, when you call pack() on the buttons, they are being stacked in the root window. Your two frames, because nothing is in them, have a size of 1x1 so you can't see them. The are being packed on the left, you just can't see them.
The solution is to properly nest your widgets. For example:
class QuitBtn(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
b = Button(self, text = "Quit", fg = "red", command = self.quit)
b.pack()
def createWidgets(self):
self.QUIT = QuitBtn(self)
...
self.QUIT.pack(side = LEFT)
...
The above creates a button widget inside the QuitBtn frame, and packs it to the top of that frame. Later, that frame (and it's contents) will be packed to the left side of the main frame.
It's also important to separate the creation of your widgets from the layout of your widgets. When you do something like self.QUIT = QuitBtn(...).pack(...), self.QUIT will be set to None because that is what pack(...) returns. Plus, in my experience, moving all of your layout for a given containing frame into a separate block makes it much easier to manage your layouts.

One way is to tell pack what you want to pack each widget in, using the in_= parameter. So:
from Tkinter import *
class QuitBtn(Frame):
def __init__(self):
Frame.__init__(self)
b = Button(text = "Quit", fg = "red", command = self.quit)
b.pack(in_=self) # Here
class HiBtn(Frame):
def __init__(self):
Frame.__init__(self)
b = Button(text = "Hi there", fg = "blue", command = self.quit)
b.pack(in_=self) # Here
class App(Frame):
def __init__(self, master = None):
Frame.__init__(self, master)
self.pack()
self.createWidgets()
def say_hi(self):
print "hi there, everyone!"
def createWidgets(self):
self.QUIT = QuitBtn().pack(side = LEFT)
self.hi_there = HiBtn().pack(side = LEFT)
root = Tk()
app = App(master = root)
app.mainloop()
root.destroy()
Produces a horizontal arrangement of buttons within frames (each within the App frame).
I don't think I've ever needed this myself, but I don't remember ever declaring widgets using top-level classes either.
As a side note, thank you so much for supplying a distilled, version of your code that demonstrates the same issue! I wish this were more common.

Related

Passing values from child window to parent in tkinter (python3): unexpected behavior

I am trying to write a python GUI with tkinter that, among other things, will work with values entered into pop-up 'child' window. Here is a very stripped down version of the GUI:
from minimalChild import Child
import tkinter as tk
class exGui(tk.Tk):
def update(self):
self.amount = Child(self)
print(self.amount)
def __init__(self):
tk.Tk.__init__(self)
frame = tk.Frame(self)
frame.grid()
self.amount = 0
self.button = tk.Button(frame, text="pay", command=self.update).grid(row=0, column=0)
self.button = tk.Button(frame, text="Quit", fg="red", command=frame.quit).grid(row=0, column=1)
def main():
exGui().mainloop()
if __name__ == '__main__':
main()
and a minimal version of the Child class:
import tkinter as tk
class Child(tk.Toplevel):
def submitFunction(self):
amount = 2
self.parent.amount= amount
self.destroy()
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.parent = parent
self.frame = tk.Frame(self)
self.frame.grid()
submit = tk.Button(self.frame, text='Submit', command = self.submitFunction).grid(row=0, column=0)
Evidently I am not understanding the flow in the update method, since print(self.amount) does not give the expected value '2'. Rather, it gives some float number that appears before I click the 'submit' button and is different every time I run the code. Is there some way to tell the main window to wait for Child to return a value? Also, can anyone explain what that decimal number is? I would have expected the print statement to at least return 0?
I have looked at this thread Pass user input from child to parent window python tkinter and still could not understand how to solve the problem.
Any help, criticism or pointers to references would be appreciated!
Welcome to Stackoverflow. Thanks for the clear concise question.
When you assign Child to self.amount in your update method you print it out immediately. On my console I get .!Child printed. The method completes it doesn't wait for any action in Child. It creates the window, prints the value of amount and then waits for user interaction with either of the windows. In the update method you have set amount to be the object created by Child().
What you wrote probably does set parent.amount to 2 but it does it after you printed the result.
I've added a result label to your main window, which initially shows 0. Submitting from Child sets it to 2.
import tkinter as tk
class Child(tk.Toplevel):
def submitFunction(self):
amount = 2
self.parent.amount.set(str( amount)) # Set parent amount.
self.destroy()
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.parent = parent
self.frame = tk.Frame(self)
self.frame.grid()
submit = tk.Button(self.frame, text='Submit', command = self.submitFunction).grid(row=0, column=0)
class exGui(tk.Tk):
def update(self):
self.kid = Child(self)
print(self.amount, self.kid, self.amount.get())
def __init__(self):
tk.Tk.__init__(self)
frame = tk.Frame(self)
frame.grid()
self.amount = tk.StringVar() # Make amount a StringVar
self.amount.set('0') # Set it to 0
self.button = tk.Button(frame, text="pay", command=self.update).grid(row=0, column=0)
self.button = tk.Button(frame, text="Quit", fg="red", command=frame.quit).grid(row=0, column=1)
self.result = tk.Label(frame, textvariable = self.amount).grid(row=0, column=2) # Add a result label.
# This is linked to the self.amount StringVar.
def main():
exGui().mainloop()
I hope this is clear enough.

tkinter elements not resizing with window, using pack

I have the current code below for some basic parameter entry into an AI assignment. It is just there to st the starting parameters and display the outpit of the different algorithms implemented, however the box that contains the output will not resize? I think I am doing something wrong with maybe the parent-child structure but I can't figure out what.
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
self.create_widgets()
def create_widgets(self):
self.mainframe= tk.Frame(master=self, width=768, height=576)
self.mainframe.pack(fill=tk.BOTH, expand=1)
self.xsizelabel = tk.Label(self.mainframe, text="Size (X)")
self.xsizelabel.pack(side="top")
self.xsize = tk.Entry(self.mainframe)
self.xsize.insert(0, 2)
self.xsize.pack(side="top")
self.ysizelabel = tk.Label(self.mainframe, text="Size (Y)")
self.ysizelabel.pack(side="top")
self.ysize = tk.Entry(self.mainframe)
self.ysize.insert(0, 1)
self.ysize.pack(side="top")
self.xstartlabel = tk.Label(self.mainframe, text="Starting Position (X)")
self.xstartlabel.pack(side="top")
self.xStart = tk.Entry(self.mainframe)
self.xStart.insert(0, 0)
self.xStart.pack(side="top")
self.ystartlabel = tk.Label(self.mainframe, text="Starting Position (Y)")
self.ystartlabel.pack(side="top")
self.yStart = tk.Entry(self.mainframe)
self.yStart.insert(0, 0)
self.yStart.pack(side="top")
self.outputstartlabel = tk.Label(self.mainframe, text="Output")
self.outputstartlabel.pack(side="top")
self.separator = tk.Frame(master=self.mainframe, width=768, height=576, bd=1)
self.separator.pack(fill=tk.BOTH, padx=5, pady=5)
self.output = tk.Scrollbar(self.separator)
self.output.pack(side=tk.RIGHT, fill=tk.Y)
self.listbox = tk.Listbox(self.separator, yscrollcommand=self.output.set)
self.listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
self.run_button = tk.Button(self.mainframe)
self.run_button["text"] = "Run with these settings"
self.run_button["command"] = self.runAlgorithm
self.run_button.pack(side="top")
self.quit = tk.Button(self.mainframe, text="QUIT", fg="red",
command=self.master.destroy)
self.quit.pack(side="bottom")
but the resulting window looks like this:
default
expanded
nothing expands when I expand the window, dispite setting the autofill and expand options. what am I doing wrong?
I can't run your program because you didn't present the whole thing. I see that you have set the fill and expand options on self.mainframe, but you didn't set those options in the constructor. Therefore the base window, which contains self.mainframe, will not expand to fill its available space. You need to make all the parent windows expandable, because when you drag the edges of the main window you are acting on the top level frame.

Python tkinter 2 frames merge together

Hey guys I have to classes that both create a Frame. The first one contains a button that is supposed to close its frame. The second frame simply contains a Label. My code should first create the frame with the button and when the button is pressed the second window should show up. What happens is that when pressing the button a "merged" window is created that contains the button and the label.
import tkinter as tk
class Window1(tk.Frame):
def __init__(self):
tk.Frame.__init__(self)
self.grid()
self.btn = tk.Button(self,text = "button",command = self.run)
self.btn.grid(row = 0,column = 0)
def run(self):
tk.Frame.quit(self)
class Window2(tk.Frame):
def __init__(self):
tk.Frame.__init__(self)
self.grid()
self.label = tk.Label(self,text = "label ")
self.label.grid(row = 0,column = 0)
w = Window1()
w.mainloop()
v = Window2()
v.mainloop()
The first picture is before you press the button, the next one after you pressed the button. The problem seems that tk.Frame.quit(self) doesn't work correctly. I tried similar ways to close the window such as:
tk.Frame.destroy(self)
but that doesn't help either.
edit: I solved it by inheriting the class from tk.TK instead of tk.Frame
Frame doesn't create window - it only group elements. Tk() creates window.
To close window you have to destroy() object create by Tk(). But you don't creat it manually root = tk.Tk() so tkinter create it automatically, but you have no access to this root to close it.
If widget doesn't have parent then it uses root and your Frame does it too.
import tkinter as tk
class Window1(tk.Frame):
def __init__(self, master):
# send `root` to `Frame` as its parent
tk.Frame.__init__(self, master)
# `Frame` will keep `master as `self.master`
# so we don't have to do `self.master = master` manually
self.grid()
self.btn = tk.Button(self, text="Hello Button", command=self.run)
self.btn.grid(row=0, column=0)
def run(self):
# use `master` (`root`) to destroy it
self.master.destroy()
class Window2(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.grid()
self.label = tk.Label(self, text="Hello Label")
self.label.grid(row=0, column=0)
root = tk.Tk() # create main window as `root`
Window1(root) # send `root` to `Window1` and later to `Frame`
root.mainloop()
root = tk.Tk()
Window2(root)
root.mainloop()

Having some trouble clearing a textbox - tkinter - Python

I've read a few threads all over the internet regarding clearing a text box on tkinter. Basically everyone says it's simple:
text.delete("1.0", END)
However, perhaps it has something to do with the way I structured it, or the way I'm calling it, but for some reason, this does not work for me. It simply does nothing.
I've tried re-positioning the def, and re-writing the text.delete("1.0", END) in a number of ways, most of which lead me to other errors, but I cannot seem to get this to work.
Ultimately, what I'm trying to accomplish is that when I click a button, the text box will clear, before populating with new information.
Below is my code.
from tkinter import *
from PIL import Image, ImageTk
import functions
class MainWindow(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("pyTicket")
# TOOLBAR ####################################################
toolbar = Frame(self.parent, bd=1, relief=RAISED)
self.img = Image.open("Icons\startupcheck.png")
eimg = ImageTk.PhotoImage(self.img)
startupButton = Button(toolbar, text="Re-Check ", image=eimg, compound="left", relief=RAISED, command=self.StartUpChecker)
startupButton.image = eimg
startupButton.pack(side=RIGHT, padx=2, pady=2)
toolbar.pack(side=TOP, fill=X)
self.parent.config(menu=menubar)
self.pack(anchor=N, side=TOP, fill=X, expand=False)
# TOOLBAR ####################################################
# TEXTBOX ####################################################
self.textbox = Text(self, wrap="word", height=5)
self.textbox.pack(side="bottom", fill="both", expand=True)
self.textbox.tag_configure("TextBox", foreground="#b22222")
self.pack(anchor=S, side=BOTTOM, fill=BOTH, expand=True)
# TEXTBOX ####################################################
# Functions ###################################################
def StartUpChecker(self):
self.clear_text()
functions.StartUpChecker()
def clear_text(self):
self.textbox.delete("1.0", END)
class TextRedirector(object):
def __init__(self, widget, tag="stdout"):
self.widget = widget
self.tag = tag
def write(self, str):
self.widget.configure(state="normal")
self.widget.insert("end", str, (self.tag,))
self.widget.configure(state="disabled")
def main():
root = Tk()
#Width X Height
root.geometry("500x300+300+300")
root.update()
root.minsize(400,200)
app = MainWindow(root)
root.mainloop()
if __name__ == '__main__':
main()
You don't appear to actually use the TextRedirector class in the code you posted, but if you're using it in your actual code, note that its .write() method leaves the textbox in a disabled state - which prevents ALL modifications, even those resulting from code instead of direct user action. Your .clear_text() method needs to temporarily enable the textbox so that you can modify it, exactly as .write() does.

Can't Properly "connect" a class with its objects

I wanted to make a class that would be the template of a "custom widget" for a "game" I'm trying to make. The problem is that I can only get it to work if when I call the class, I specify the Frame object that holds everything together. I've been searching my whole afternoon and couldn't find a concrete answer...
The following code works but instead of just needing to write StatusButton().grid() I have to use StatusButton().frame.grid()
from tkinter import *
from tkinter.ttk import Progressbar
class StatusButton(Frame):
def __init__(self, master):
super(StatusButton, self).__init__()
self.frame = Frame(master, padx = 10, pady = 10, bd= 5, relief = RAISED)
self.label = Label(self.frame, text = "Hunger Bar")
self.pgbar = Progressbar(self.frame)
self.button = Button(self.frame, text = "Eat")
self.label.pack()
self.pgbar.pack()
self.button.pack(pady = 5, ipadx = 15)
return
root = Tk()
buttonslist = [StatusButton(root) for x in range(16)]
for r in range(4):
for c in range(4):
StatusButton(root).frame.grid(row = r, column = c)
root.mainloop()
I'm guessing I'm not properly "conecting" the frame object to the class, even though the former is inside the latter, because when the previous code gets executed, but with StatusButton().grid() instead, the TKinter window pops up normally, but without any content, like if i was "gridding" an empty Frame object. How can I fix this, so that when StatusButton().grid() is run, my "custom widget thing" appears?
Sorry if this is a noob error, it's my first week in programming
The problem is that you aren't taking advantage of your subclass. You initialize Frame with no arguments and then create a new one with arguments. You then create widgets with self.frame as a parent instead of self. Change it to this:
class StatusButton(Frame):
def __init__(self, master):
super(StatusButton, self).__init__(master, padx=10, pady=10, bd=5, relief=RAISED)
self.label = Label(self, text="Hunger Bar")
self.pgbar = Progressbar(self)
self.button = Button(self, text="Eat")
self.label.pack()
self.pgbar.pack()
self.button.pack(pady=5, ipadx=15)

Categories

Resources