I want the buttons to be on opposite sides of the screen, but I'm unsure as to why they keep positioning themselves in the middle. The code is set up like this because I plan on having multiple overlays that I switch between with the show_frame method. The class InputWindow is one of those overlays. When I created a basic script with only window and buttons, I was able to get the buttons positioned properly, but I'm unsure as to what I'm doing incorrectly here that's preventing me from distancing the buttons properly.
import tkinter as tk
class GuiController(tk.Tk):
def __init__(self, *args , **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side = "top", fill = "both", expand = True)
container.grid_rowconfigure(0, weight = 1)
container.grid_columnconfigure(0, weight = 1)
self.frames = {}
frame = InputWindow(container, self)
self.frames[InputWindow] = frame
frame.pack()
self.show_frame(InputWindow)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class InputWindow(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
controller.geometry("650x500")
button_defaultGame = tk.Button(self, text = "Default Game")
button_defaultGame.grid(row = 0, column = 0, sticky = "W")
button_test = tk.Button(self, text = "Test")
button_test.grid(row = 0, column = 1, sticky = "E")
self.columnconfigure(0, weight=1)
self.columnconfigure(1, weight=2)
app = GuiController()
app.mainloop()
When struggling with layout problems it helps to give your frames distinctive colors. Otherwise it can be difficult to see where one frame ends and another begins.
For example, give the container a color like this:
container = tk.Frame(self, bg="bisque")
Next, give your InputWindow a different color like this:
class InputWindow(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent, bg="pink")
When you run the code you'll see something like this:
This immediately makes it clear that InputWindow is not filling the container window. Looking through your code we can see that you're doing frame.pack() to add the instance of InputWindow to container.
Instead, you need to request that InputWindow fills the container window. You do that by changing your call to pack to look like this:
frame.pack(side="top", fill="both", expand=True)
Now we can see that the instance of InputWindow fills the container, and your buttons do indeed sit on the edges of the window.
Related
I am making a tkinter app with multiple pages. The simple version of the codes I used is shown below. I added the menu to the root of the app and gave one option of File with sub-option of "Reload Defaults" and "Exit".
When I run this code then I do get the menu but not on the GUI but the main menu bar of Mac. And the menu is unresponsive. I am not sure what I am doing wrong!
import tkinter as tk
from tkinter import ttk
from tkinter import RAISED
LARGEFONT =("Verdana", 35)
class tkinterApp(tk.Tk):
# __init__ function for class tkinterApp
def __init__(self, *args, **kwargs):
# __init__ function for class Tk
tk.Tk.__init__(self, *args, **kwargs)
# creating a container
container = tk.Frame(self)
container.pack(side = "top", fill = "both", expand = True)
container.grid_rowconfigure(0, weight = 1)
container.grid_columnconfigure(0, weight = 1)
container2 = tk.Frame(self, relief=RAISED, borderwidth=2)
container2.pack(side="bottom",fill="both", expand=True)
container2.grid_rowconfigure(0, weight = 1)
container2.grid_columnconfigure(0, weight = 1)
def runProg():
print("response from runProg")
#get all entry value from page 2
runButton = ttk.Button(container2, text="Run", command=runProg)
runButton.pack(side="left", padx=5, pady=5)
######################################################
## Menu items
progMenu = tk.Menu(self,tearoff=0)
self.config(menu=progMenu)
#create File menu
def reload_command():
pass
fileMenu = tk.Menu(progMenu)
progMenu.add_cascade(label="File", menu=fileMenu)
fileMenu.add_command(label="Reload Defaults", command=reload_command)
fileMenu.add_command(label="Exit", command=reload_command)
######################################################
# initializing frames to an empty array
self.frames = {}
frame = StartPage(container, self)
self.frames[StartPage] = frame
frame.grid(row = 0, column = 0, sticky ="nsew")
self.show_frame(StartPage)
# to display the current frame passed as
# parameter
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
# first window frame startpage
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
# label of frame Layout 2
label = ttk.Label(self, text ="Startpage", font = LARGEFONT)
# putting the grid in its place by using
# grid
label.grid(row = 0, column = 2, padx = 10, pady = 10)
app = tkinterApp()
app.mainloop()
Any help would be greatly appreciated!
You might want to change the menu to be buttons at the top of the grid as on a Mac you will get this bug, but on windows it works perfectly fine.
In tkinter I have step up multiple pages, each page should have a unique Listbox that will be populated with unique information.
My problem is the 'listbox' shows the information from my initial page on the other pages. Even if I completely remove the List box from the other pages, the 'Listbox` from my first page still shows up.
This is basically the first time I have used classes with anything, so I am not sure why this isn't working. This is all basically copied form YouTube how to's, and I am trying to bend it to what I need.
class Uploader(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs) #sets up tk window stuff
container = tk.Frame(self)
container.grid(row = 0, column = 0)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (Gr7, Gr8, Gr9): #put new pages on this
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column = 0, sticky = "nsew")
self.show_frame(Gr7)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class Gr7(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self, parent)
lb = tk.Listbox(width=30, height=15)
lb.insert('end', *homelist)
lb.grid(row = 1, column = 0)
class Gr8(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self, parent)
lb1 = tk.Listbox(width=30, height=15)
lb1.insert('end', *homelist1)
lb1.grid(row = 1, column = 0)
class Gr9(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self, parent)
lb2 = tk.Listbox(width=30, height=15)
lb2.insert('end', *homelist2)
lb2.grid(row = 1, column = 0)
You are not specifying which widget should contain the listbox, so all of your listboxes are given the root window as its master. Because you are putting them all in the same row and column, you only see one listbox.
To fix this -- and as a good general rule of thumb -- you should always explicitly provide the master when creating widgets:
lb = tk.Listbox(self, width=30, height=15)
I am creating a program with two frames in one window. The first has input fields, the second will create a graph.
I found a way to create input fields dynamically from a list and get their values accordingly, but I can't get them to show on the window. When I run the program it shows an empty window.
What should I do to get the label and input widgets to show on the first frame (InputPage)? I tried changing 'parent' to 'self' but it made no difference. I don't really understand the structure of widgets in multiple frame applications.
Here is my code:
from tkinter import *
namesInput = ["first", "second", "third", "fourth", "fifth"]
entryInput = {}
labelInput = {}
root = Tk()
class ZorgplanGrafiek(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
container = Frame(self)
container.pack(side="top", fill="both", expand = True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (InputPage, GraphPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(InputPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class InputPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self,parent)
label = Label(self, text="Zorgplan input")
label.pack(pady=10,padx=10)
i = 0
for name in namesInput:
e = Entry(self)
entryInput[name] = e
lb = Label(self, text=name)
labelInput[name] = lb
i += 1
#def print_all_entries():
# for name in namesInput:
# print( entryInput[name].get())
class GraphPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
label = Label(self, text="The graph will show here")
label.pack(pady=10,padx=10)
button = Button(self, text="Back to Home",
command=lambda: controller.show_frame(InputPage))
button.pack()
app = ZorgplanGrafiek()
app.mainloop()
Firstly delete root = Tk() at the top of your code you are creating 2 windows. Secondly, your loop to create the entry and label widgets is not correct therefore they are not displayed on the frame, so that is your answer for why they wont show.
Try this:
class InputPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self,parent)
label = Label(self, text="Zorgplan input")
label.grid(row=0, column=0, sticky ='n', columnspan =2)
# i brought your variable in the class for example sake
namesInput = ["First:", "second:", "Third:", "Fourth:", "Fifth:"]
self.entryWidgets = [] # we want to call this in another function so we assign it as self.variableName
labelWidgets = []
#LOOP TO CREATE WIDGETS
for i in range(0, len(namesInput)):
labelWidgets.append(Label(self, text = namesInput[i]))
self.entryWidgets.append(Entry(self))
labelWidgets[-1].grid(row= i+1, column =0, sticky='e')
self.entryWidgets[-1].grid(row= i+1, column = 1, sticky='w')
submit = Button(self, text = "Submit", command = self.getEntries)
submit.grid(row = 6, column =0, columnspan =2)
def getEntries(self):
results = []
for x in self.entryWidgets: # i.e for each widget in entryWidget list
results.append(x.get())
print(results)
Code explanation:
We are iteratively creating widgets to the number of elements within namesInput list. Each time we create a widget we add it to their respective list. E.g for entry widgets we created a list called entryWidgets. We append them to a list so that we can reference them individually later on when we want to do something with them.
Furthermore, i changed pack() to grid(). The grid method is much cleaner and gives us more control over the layout of our window in my opinion.
Note - If you're struggling to understand how i 'grided' the widgets in the way i did, i just drew up a quick sketch of the widgets with co-ordinates representing their row and column and then from there its fairly easy to see how to manipulate the grid settings in the for loop.
Screenshot:
I'm struggling with tkraise not hiding the 'bottom' frame in my app.
I have two frames, one contains a Listbox and is packed to the left and the other will display options for each item in the listbox and is packed to the right.
My problem is that I can see the Future page when I select General and vise versa. I copied and modified it from my working main app but I don't know what I did wrong to break it for this one.
# All settings windows and forms labels are built in here
import tkinter as tk
# from main import WinSize
from tkinter import Listbox, END, ttk
class Settings(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# create frame for listbox and parameters area
self.list_container = ttk.Frame(self, relief='sunken')
self.list_container.pack(side='left', fill='y', expand=False)
self.param_container = ttk.Frame(self)
self.param_container.pack(side='top', fill='both', expand=True)
self.options_list = Listbox(self.list_container, selectmode='single')
for choice in ['General', 'Future']:
self.options_list.insert(END, choice)
self.okbutton = ttk.Button(self.param_container, text="OK", command= self.destroy)
self.okbutton.grid_rowconfigure(0, weight=1)
self.okbutton.grid_columnconfigure(0, weight=1)
self.okbutton.grid(row=2, column=2, sticky='nsew')
# Grid layout for Settings window
self.options_list.grid(row=0, column=0, sticky='nsew')
self.list_container.grid_rowconfigure(1, weight=1)
self.list_container.grid_columnconfigure(1, weight=1)
self.param_container.grid_rowconfigure(1, weight=1)
self.param_container.grid_columnconfigure(1, weight=1)
# create empty TUPLE for future frames
self.frames = {}
# generate calls for frames
for F in (General, Future):
self.page_name = F.__name__
self.frame = F(parent=self.param_container, controller=self)
self.frames[self.page_name] = self.frame
self.options_list.select_set(0)
self.options_list.bind("<<ListboxSelect>>", self.onselect)
self.options_list.event_generate("<<ListboxSelect>>")
# grab value of listbox selection and call show_frame
def onselect(self, event):
self.widget = event.widget
self.value = self.widget.get(self.widget.curselection())
print(self.value)
self.show_frame(self.value)
# show corresponding frame based on listbox selection
def show_frame(self, page_name):
# show a frame for the given page name
self.frame = self.frames[page_name]
self.frame.grid(row=0, column=1, sticky='nsew')
self.frame.tkraise()
print("Show Frame")
class General(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.optiontitle = ttk.Label(parent, text='General')
self.optiontitle.grid(row=0, column=0, sticky='nsew')
self.dirlabel = ttk.Label(parent, text='Default Save Directory')
self.dirlabel.grid(row=1, column=1, sticky='s')
class Future(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
test1 = ttk.Label(self, text='Future')
test1.pack()
app=Settings()
app.mainloop()
I want to say it may be something to do with my grid layout but it doesn't make sense since the two 'pages' are not coupled (or supposed to be) with each other.
I solved this issue through another problem that I was able to work out with some help from others. Refer to this → Frames not stacking on top of each other in tkinter question as it has two great answers which easily allow to incorporate grid_forget() and/or pack_forget() as suggested by #jasonharper.
You aren't doing anything to actually hide the other page; you're just layering the new page on top of it, which isn't going to look right unless they occupy exactly the same screen area.
Even if they did, this still isn't a workable approach, since the widgets in the hidden page are still active. In particular, if it had any Entry or Text fields, they could still have (or gain) keyboard focus, so anything the user types might mysteriously end up in a field they can't even see at the moment.
You should call .grid_forget() on the previous page when showing the new one. Or, perhaps easier, call .grid_forget() on all pages before calling .grid() on the new one (it doesn't hurt to call this on a widget that isn't currently shown).
The two frames in my program, ViewSubjects and AddSubjects both contain frames and widgets that are not stretching to fill the screen. Why are they doing this and how would I go about fixing it?
ViewSubjects Frame
AddSubjects Frame
Here is my code:
import tkinter as tk
from tkinter import ttk
import tkinter.scrolledtext as tks
class Program(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.iconbitmap(self, default = "")
tk.Tk.wm_title(self, "")
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (SubjectHome, ViewSubject, AddSubject):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row = 0, column = 0, sticky = "nsew")
self.show_frame(SubjectHome)
def show_frame(self,cont):
frame = self.frames[cont]
frame.tkraise()
class SubjectHome(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
ttk.Style().configure("TButton", padding=6, relief="flat", background="#ccc")
name = tk.Label(self, text = "User: FirstName + LastName")
name.pack(anchor="ne")
pagename = tk.Label(self, text = "Subject Menu")
pagename.pack(anchor="n")
self.innerFrame = tk.Frame(self, bg="red")
self.innerFrame.place(relx=.5, rely=.5, anchor="c")
view = ttk.Button(self.innerFrame, text = "View Subjects", command = lambda: controller.show_frame(ViewSubject))
view.grid(row=0, sticky="W"+"E")
add = ttk.Button(self.innerFrame, text = "Add Subjects", command = lambda: controller.show_frame(AddSubject))
add.grid(row=1, sticky="W"+"E")
class ViewSubject(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
innerFrame = tk.Frame(self)
innerFrame.place(relx=.5, rely=.5, anchor="c")
firstFrame = tk.Frame(innerFrame)
firstFrame.grid(row=0, sticky="WE")
secondFrame = tk.Frame(innerFrame)
secondFrame.grid(row=1, sticky="WE")
self.text = tks.ScrolledText(firstFrame)
self.text.grid(rowspan=3, columnspan=3 ,sticky="E")
scrollbar = tk.Scrollbar(secondFrame, orient="vertical")
lb = tk.Listbox(secondFrame, yscrollcommand=scrollbar.set)
scrollbar.config(command=lb.yview)
scrollbar.pack(side="right", fill="y")
lb.pack(side="left",fill="both", expand=True)
for x in range(15):
lb.insert("end", x)
back = ttk.Button(innerFrame, text = "Back", command = lambda: controller.show_frame(SubjectHome))
back.grid(row=2, sticky="W")
next = ttk.Button(innerFrame, text = "Next")
next.grid(row=2, sticky="E")
class AddSubject(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
name = tk.Label(self, text = "User: FirstName + LastName")
name.pack(anchor="ne")
pagename = tk.Label(self, text = "Add Subjects")
pagename.pack(anchor="n")
self.innerFrame = tk.Frame(self)
self.innerFrame.place(relx=.5, rely=.5, anchor="c")
canvas = tk.Canvas(self.innerFrame)
self.firstFrame = tk.Frame(canvas)
self.firstFrame.pack(anchor="n")
info = tk.Label(self.innerFrame, text = "Information...\n Information....")
info.pack()
for x in range(5):
pagename = tk.Label(self.firstFrame, text = "Unit Name")
pagename.grid(row=0, column=x)
self.text = tks.ScrolledText(self.firstFrame, width=50)
self.text.grid(row=1, column=x ,sticky="E")
scrollbar = tk.Scrollbar(self.innerFrame, orient="horizontal", command=canvas.xview)
canvas.configure(xscrollcommand=scrollbar.set)
scrollbar.pack(side="bottom", fill="x")
canvas.pack(side="left", fill="both", expand=True)
back = ttk.Button(self.innerFrame, text = "Back", command = lambda: controller.show_frame(SubjectHome))
back.pack(anchor="sw")
next = ttk.Button(self.innerFrame, text = "Next")
next.pack(anchor="se")
app = Program()
app.state('zoomed')
app.mainloop()
Most of your problems boil down to three common mistakes:
First, you are using place, and place by default doesn't make windows grow or shrink. It assumes you created widgets to be the exact size you want them to be. You need to explicitly tell tkinter how you want it to handle extra space.
In your case you probably want to set the relative width and relative height of the inner frames to 1.0 (ie: 100% of the width and height of the containing window) with the relwidth and relheight options. This will force the inner frame to always be exactly as tall and wide as its parent.
self.innerFrame.place(..., relwidth=1.0, relheight=1.0)
Second, you are using grid within the inner frames, but you are failing to give any rows or columns a weight. The weight tells tkinter how to allocate extra space. By default, extra space goes unused. Since you want your widgets to fill the extra space, you need to give at least one row and one column a positive weight.
Third, you're trying to solve too many problems at once. My advice is to remove all of the widgets except one frame. Get it to fill, grow and shrink the way you want. Then, add its immediate children and do likewise. Then, add any grandchildren, and continue on, one group of widgets at a time, until you're satisfied with that frame. Only then should you move on and fix another frame.
protip: give each frame a unique color during development. This makes it easy to see which frames are growing or shrinking, and which ones are not.