How To Add Multiple Tables In One Tkinter Frame - python

I am using a library called tkintertable in python which essentially gives me the capability to efficiently add tables to my Tkinter application. I am trying to setup 6 different tables with 6 charts to go with each table in this frame(my home page essentially) but am relitvely new to tkinter so having some trouble organizing it, currently my code looks like the following:
import tkinter as tk
from tkintertable import TableCanvas, TableModel
class MainApplication(tk.Tk):
"""Main application class"""
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self)
self._frame = None
self.switch_frame(TickerInput, None)
def switch_frame(self, frame_class, ticker):
"""Destroys current frame and replaces it with a new one."""
if ticker is not None:
new_frame = frame_class(self, ticker)
else:
new_frame = frame_class(self)
if self._frame is not None:
self._frame.destroy()
self._frame = new_frame
self._frame.grid()
class TickerInput(tk.Frame):
"""Ticker input page that allows input of ticker and redirects to main indicator page"""
def __init__(self, master):
tk.Frame.__init__(self, master, background="#212020")
# Centers the frame
self.master.columnconfigure(0, weight=1)
self.master.rowconfigure(0, weight=1)
ticker_label = tk.Label(self, text="Enter ticker..")
ticker_label.grid(row=0, column=1, columnspan=2)
ticker_input = tk.Entry(self, width=35)
ticker_input.grid(row=0, column=3)
button = tk.Button(self, text="Search", command=lambda: master.switch_frame(Indicator, ticker_input.get()))
button.grid(row=1, column=1, columnspan=3)
class Indicator(tk.Frame):
"""Indicator page that shows the indicators and whether its a buy or sell"""
def __init__(self, master, ticker):
tk.Frame.__init__(self, master)
self.ticker = ticker
self.master.columnconfigure(0, weight=0)
self.master.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
button = tk.Button(self, text="Go Back", command=lambda: master.switch_frame(TickerInput, None))
button.grid(column=0, row=0)
ticker_label = tk.Label(self, text=("Current Ticker: " + self.ticker))
ticker_label.grid(column=1, row=0)
tableOne = Table(self)
tableOne.grid(column=0, row=1)
tableOne.createTable()
# tableTwo = Table(self)
# tableTwo.createTable()
class Table(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
def createTable(self):
data = {'rec1': {'col1': 99.88, 'col2': 108.79, 'label': 'rec1'},
'rec2': {'col1': 99.88, 'col2': 108.79, 'label': 'rec2'}
}
model = TableModel()
table = TableCanvas(self.master, model=model, data=data)
table.show()
return table
if __name__ == "__main__":
app = MainApplication()
app.title("Indicator Trading Confirmation Tool")
app.geometry("1920x1080")
app.config(background="#121212")
app.mainloop()
and here is the GUI
Why is my Go Back Button stuck on the table if I have it on a different row and have my weight set to 1 which should seperate them 50% correct? And also what is the best way to organize this both grid/code related to make sure that the GUI has 6 equal tables/charts, 3 on the left and 3 on the right? Thank you!

Okay, lets go step by step,
having replaced your Table class with just a colored Frame there is nothing overlapping for me, so this is probably just due to the Layout of the table itself - quick fix for that could be adding some padding
the weight option is used for distribution of extra space when resizing the window, for more information you can take a look here, there is also a nice visualization found here
in order to use weight for controlling actual used width or height of your widgets you might want to take a look at the sticky option of grid
see for a very nice overview you can also check this which is very detailed for both
for your general layout you can just go the easy route of combining weight and sticky options like this:
self.l1 = tk.Frame(self, background="red")
self.l2 = tk.Frame(self, background="orange")
self.l3 = tk.Frame(self, background="green")
self.l4 = tk.Frame(self, background="blue")
self.l5 = tk.Frame(self, background="brown")
self.l6 = tk.Frame(self, background="yellow")
self.rowconfigure(0, weight = 1)
self.rowconfigure(1, weight = 1)
self.rowconfigure(2, weight = 1)
self.columnconfigure(0, weight = 1)
self.columnconfigure(1, weight = 1)
self.l1.grid(row=0, column=0, sticky="nsew")
self.l2.grid(row=1, column=0, sticky="nsew")
self.l3.grid(row=2, column=0, sticky="nsew")
self.l4.grid(row=0, column=1, sticky="nsew")
self.l5.grid(row=1, column=1, sticky="nsew")
self.l6.grid(row=2, column=1, sticky="nsew")
for a 50:50 separation in width and 33:33:33 in height.
If you then later on want to add something like a menubar within your frame you might want to bundle your table frames within another Frame in order to have them resize equally

Related

tkinter problem in displaying two frames using tkraise

I'm trying to display two frames in a way such that one frame has a button to display the other frame and vice versa. I'm trying to use tkinter frame function of tkraise(). But they are getting simultaneously displayed over each other. Please you can ignore the import board file because it has some helper functions that I'm using to display some data on the GUI.
Here is my code:
import tkinter as tk
import tkinter.scrolledtext as st
from board import *
FONT = ("Helvetica", 20)
attributes = {'padx': 5, 'pady': 5}
class App(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.title('DAS')
self.geometry('500x325')
container = tk.Frame(self)
container.pack(fill=tk.BOTH, expand=True)
self.frames = {}
self.frames[0] = Display(container, self)
self.frames[0].grid(row=0, column=0)
self.frames[1] = DataStored(container, self)
self.frames[1].grid(row=0, column=0)
self.show_frame(1)
def show_frame(self, frame):
frame = self.frames[frame]
frame.tkraise()
class Display(tk.Frame):
def __init__(self, container, controller):
super().__init__(container)
name = tk.Label(self, text='Data Acquistion System', font=FONT)
name.pack(fill='x')
left_wrapper = tk.Frame(self)
left_wrapper.pack(side='left')
right_wrapper = tk.Frame(self)
right_wrapper.pack(side='left')
port_label = tk.Label(left_wrapper, text='Port:', font=FONT)
port_label.grid(row=0, column=0)
port_option = tk.StringVar(left_wrapper, value='None')
ports_availiable = get_arduino_ports()
port_value = tk.OptionMenu(left_wrapper, port_option, *ports_availiable)
port_value.grid(row=0, column=1)
baud_label = tk.Label(left_wrapper, text='Baudrate:', font=FONT)
baud_label.grid(row=1, column=0)
baud_value = tk.Entry(left_wrapper)
baud_value.grid(row=1, column=1)
baud_value.insert(0, 9600)
connect_button = tk.Button(left_wrapper, text='Connect', command=lambda: connect_to_port(port_option.get(), baud_value.get()))
connect_button.grid(row=2, column=1)
playback_button = tk.Button(left_wrapper, text='Playback', command=lambda: controller.show_frame(1))
playback_button.grid(row=3, column=1)
data_display = st.ScrolledText(right_wrapper)
data_display.pack()
class DataStored(tk.Frame):
def __init__(self, container, controller):
super().__init__(container)
tk.Label(self, text='Playback Data').pack(fill=tk.X)
top_frame = tk.Frame(self)
top_frame.pack(fill=tk.X)
bottom_frame = tk.Frame(self)
bottom_frame.pack(fill=tk.X)
canvas = st.ScrolledText(top_frame)
canvas.pack()
update = tk.Button(bottom_frame, text='Update', command=self.update_playback)
update.grid(row=0, column=0)
back = tk.Button(bottom_frame, text='Back', command=lambda: controller.show_frame(0))
back.grid(row=0, column=1)
def update_playback(self):
with open("data.txt", "r") as file:
data = file.read()
self.data.insert(tk.END, data)
self.data.config(state="disabled")
if __name__ == "__main__":
window = App()
window.mainloop()
There is a simple solution by changing the way in which you use your show_frame function:
def show_frame(self, frame_num):
for frame in self.frames.values():
frame.grid_remove()
frame = self.frames[frame_num]
frame.grid()
The reason tkraise does not work is that your two frames are of different sizes, if they were the same rise you would be entirely right with your code. Because they are of different sizes you can still see the bigger frame when looking at the smaller one.
grid_remove removes a frame from being loaded on the window, and then the following grid command places the frame back where it was before, removing any issues with overlap and such.
As the two frames have different sizes, you need to add sticky="nsew" in .grid(...) to make both frames to occupy the available space:
self.frames[0] = Display(container, self)
self.frames[0].grid(row=0, column=0, sticky="nsew")
self.frames[1] = DataStored(container, self)
self.frames[1].grid(row=0, column=0, sticky="nsew")

Cannot get tkinter grid to layout my GUI as I want

sorry for the simple question. Relatively new to python and especially Object Oriented and Class systems. Basically, I want my GUI to have a couple of rows across the top of the window (across full width). Beneath, I want 4 equal sized columns. I don't want this to impact my plots (on a different page). Any help is much appreciated. I cannot workout how to use tkinter to get this to work.
Thanks
__author__ = "dev"
# https://pythonprogramming.net/object-oriented-programming-crash-course-tkinter/?completed=/tkinter-depth-tutorial-making-actual-program/
import tkinter as tk
from tkinter import ttk
import yfinance as yf
import datetime as dt
import matplotlib
from matplotlib import style
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from matplotlib import pyplot as plt
import matplotlib.dates as mdates
LargeFont = ("Verdana", 16)
style.use("ggplot")
def yfinance_get(index):
tkr = yf.Ticker(index)
today = dt.datetime.today().isoformat()
m_ago = dt.datetime.today() - dt.timedelta(days=30)
h_px = tkr.history(period="1d", start=m_ago, end=today[:10])
return h_px
class DailySumm(tk.Tk):
def __init__(self, *args, **kwargs): # initialise - runs as soon as the class is called
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self) # frame creates the window of our GUI
container.pack(side="top", expand=True) # pack is similar to grid, but less accurate
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
# creates an empty dictionary called named self.frames, this will hold the diff "pages" in the Gui
for f in (StartPage, Equities): # for each page defined, this tuple needs to be updated to include
# this loop will add the pages to the self.frames dict
frame = f(container, self)
self.frames[f] = frame # enters a new entry into the dictionary, the StartPage page
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage) # this calls the show_frame method with cont = StartPage
def show_frame(self, cont):
frame = self.frames[cont] # cont = dict key to frames (dict)
frame.tkraise() # this raises the "frame" to the front of the window (i.e. shows it to the user)
class StartPage(tk.Frame): # this creates the StartPage page which inherits the tk.Frame functionality (ie imports)
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent) # parent is the parent class, in this case DailySumm
label = tk.Label(self, text="News and Market Data", font=LargeFont).grid(row=0, column=0, columnspan=4)
label2 = tk.Label(self, text="Equities", font=LargeFont).grid(row=1, column=0, padx=10)
label3 = tk.Label(self, text="Credit", font=LargeFont).grid(row=1, column=1, padx=10)
label4 = tk.Label(self, text="Currencies", font=LargeFont).grid(row=1, column=2, padx=10)
label5 = tk.Label(self, text="Commodities", font=LargeFont).grid(row=1, column=3, padx=10)
button_equ = ttk.Button(self, text="Equities",
command=lambda: controller.show_frame(Equities)).grid(row=2, column=0)
# lambda stops the function being called on inception, only when the button is pressed
class Equities(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Equities", font=LargeFont)
label.pack(pady=10, padx=10)
button_home = ttk.Button(self, text="Back to Home",
command=lambda: controller.show_frame(StartPage))
button_home.pack()
index = [['FTSE 100', '^FTSE'], ['S&P 500', '^GSPC'], ["Nikkei 225", "^N225"], ["MSCI EM", "EEM"]]
fig = Figure(dpi=100)
for i in range(0, 3+1):
ax = fig.add_subplot(2, 2, i+1)
plot_data = yfinance_get(index[i][1])
ax.plot(plot_data['Close'], color='r', label=index[i][0])
ax.grid(which="major", color='k', linestyle='-.', linewidth=0.3)
ax.legend(loc=2)
ax.xaxis.set_major_locator(mdates.WeekdayLocator(byweekday=mdates.MONDAY))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%d-%m"))
canvas = FigureCanvasTkAgg(fig, self)
canvas.draw()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)
app = DailySumm()
app.geometry("2560x1600")
app.mainloop()
One way to do this is to create a frame for the rows at the top, and another frame to hold the four columns below. You can use pack to add these frames to the window, and use grid in the lower frame to manage the columns. To get the columns to be the same size you can use the uniform option for each column.
Here's an example of the technique:
import tkinter as tk
root = tk.Tk()
top_frame = tk.Frame(bd=1, relief="raised", height=50)
bottom_frame = tk.Frame(bd=1, relief="raised")
top_frame.pack(side="top", fill="x")
bottom_frame.pack(side="bottom", fill="both", expand=True)
# add labels to the columns so that we can visualize the columns
labels = (
tk.Label(bottom_frame, bd=1, relief="raised", text="Column 1", height=8, width=10),
tk.Label(bottom_frame, bd=1, relief="raised", text="Column 2", height=8, width=10),
tk.Label(bottom_frame, bd=1, relief="raised", text="Column 3", height=8, width=10),
tk.Label(bottom_frame, bd=1, relief="raised", text="Column 4", height=8, width=10)
)
# add the labels to the columns.
labels[0].grid(row=0, column=0, sticky="nsew", ipadx=4)
labels[1].grid(row=0, column=1, sticky="nsew", ipadx=4)
labels[2].grid(row=0, column=2, sticky="nsew", ipadx=4)
labels[3].grid(row=0, column=3, sticky="nsew", ipadx=4)
# allocate all extra horizontal space to each column evenly
# the critical piece is both the weight and uniform options
bottom_frame.grid_columnconfigure(0, weight=1, uniform="column")
bottom_frame.grid_columnconfigure(1, weight=1, uniform="column")
bottom_frame.grid_columnconfigure(2, weight=1, uniform="column")
bottom_frame.grid_columnconfigure(3, weight=1, uniform="column")
# allocate all extra vertical space to row 0
bottom_frame.grid_rowconfigure(0, weight=1)
root.mainloop()

Accessing a tkinter list-box selection when using frames

I am trying to use frames in a tkinter window to change the layout when I user selects a range of options - in this case "Open".
I want the frame to update but I also need to capture the selection of the listbox. I have tried to access the selection from the method "openMat".
I have simplified the code as much as i can.
i have tried to solve this issue for a while, tried looking online for a solution and have finally resorted clicking the "ask a question" button.
import tkinter as tk
LARGE_FONT = ("Verdana", 12) # font's family is Verdana, font's size is 12
class MainWindow(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# text for all windows
label2 = tk.Label(self, text='title', font=LARGE_FONT)
label2.pack(pady=10, padx=10) # center alignment
# this container contains all the pages
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1) # make the cell in grid cover the entire window
container.grid_columnconfigure(0,weight=1) # make the cell in grid cover the entire window
self.frames = {} # these are pages we want to navigate to
for F in (StartPage, Page2): # for each page
frame = F(container, self) # create the page
self.frames[F] = frame # store into frames
frame.grid(row=0, column=0, sticky="nsew") # grid it to container
self.show_frame(StartPage) # let the first page is StartPage
def show_frame(self, name):
frame = self.frames[name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
ltbox = tk.Listbox(self)
label = tk.Label(self, text='Menu', font=LARGE_FONT)
label.grid(row=0, column = 0)
#label.pack(pady=10, padx=10) # center alignment
button1 = tk.Button(self, text='Open', width = 12, # when click on this button, call the show_frame method to make PageOne appear
command=self.openMat)
button1.grid(row=1, column = 0)
#button1.pack() # pack it in
#Insert data in listbox
ltbox.insert( 1, "Option 1")
ltbox.insert( 2, "Option 2")
ltbox.insert( 3, "Option 3")
ltbox.insert( 4, "Option 4")
ltbox.grid(row=1, column = 4, rowspan=100, pady=0, padx=50)
print (ltbox.curselection())
def openMat(self):
#This function prints the option selected and changes the frame
print (ltbox.curselection())
app.show_frame(Page2)
class Page2(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text='Page Two', font=LARGE_FONT)
label.pack(pady=10, padx=10)
button1 = tk.Button(self, text='Back to Home', # likewise StartPage
command=lambda : controller.show_frame(StartPage))
button1.pack()
if __name__ == '__main__':
app = MainWindow()
app.mainloop()
This gives the error:
NameError: name 'ltbox' is not defined
thank you for reading my question - any help is much appreciated!
Your issue is of Scope.
ltbox is defined and hence can be used only inside the __init__ function of the class StartPage. If you want it to be accessible to all the functions of a class, you have to make it an instance attribute of the class, which is done by using self. So wherever you have used ltbox, just change it to self.ltbox.

Tkinter: Code stops and window doesn't show up

I am working on a very basic interface on Python with Tkinter, that displays two input boxes and a button to login. I try to do it by creating different frames and change the frame when the user is logged. It was working nearly fine but then the code started to execute itself not entirely sometimes and entirely but without the Tkinter window. I looked into it and saw nothing shocking but I am not an expert so I am looking for help.
This is the code to run my class that implement Tkinter window:
print 1
app = Skeleton("HomePage")
print 2
app.mainloop()
print 3
The skeleton Class that implement the Tkinter window:
class Skeleton(Tk):
def __init__(self, f,*args, **kwags):
Tk.__init__(self,*args, **kwags)
self.title(f)
container = Frame(self, width=512, height=512)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
frameName = {"home","upload","retrieve","deconnected"}
self.frames["HomePage"] = HomePage(parent= container, controller=self)
self.frames["HomePage"].grid(row=0, column=0, sticky="nsew")
print 321
self.show_frame("HomePage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
print "Je vais te montrer mon frame"
frame = self.frames[page_name]
frame.tkraise()
And the code of the Home Page frame:
class HomePage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.parent = parent
self.controller = controller
#print ("Construction de la page dáccueil")
#LABEL
self.username = Label(self, text="Username:")
self.username.grid(row =0,column =0)
self.username.pack()
#ENTRY
self.username_txb = Entry( self)
self.username_txb.focus_set()
self.username_txb.grid(row =0,column =1)
self.username_txb.pack(side=LEFT)
#LABEL
self.pass_lbl = Label(self, text="Password:")
self.pass_lbl.grid(row =0,column =2)
#ENTRY
self.password_txb = Entry( self, text="Password", show = "*")
self.password_txb.grid(row =0,column =3)
self.password_txb.pack(side=LEFT)
#LOGIN BUTTON
self.login_btn = Button(self, text="Login", command=lambda: controller.show_frame("UploadPage"))
self.login_btn.grid(row =0,column =4)
self.login_btn.pack(side=LEFT)
self.info_pane = PanedWindow()
self.info_pane.grid(row =1,column =0)
self.info_pane.pack(fill="none", expand=True, side=BOTTOM)
self.info_lbl = Label(self, text="More information about access:", fg="blue", cursor="hand2")
self.contact_lbl = Label(self, text="Contact us", fg="blue", cursor="hand2")
self.contact_lbl.grid(row =2,column =0)
self.contact_lbl.pack()
self.contact_lbl.bind("<Button-1>", self.callback)
print ("123Construction de la page dáccueil")
#self.parent.update()
def callback(self, event):
pass
def connect(self,controller ):
login = self.username_txb.get()
pwd = self.password_txb.get()
if(login == "a" and pwd == "a"):
print "Valid account"
self.controller.show_frame("UploadPage")
#UploadPage frame is implemented
The output everytime I execute the code is as following:
1
123Construction de la page dáccueil
Thank you in advance for the help. Hope this will help other people.
First lets address your use of pack() and grid().
Due to how tkinter is set up you cannot use both pack() and grid() on the same widget in a frame or window at one time.
You may use for example pack() to pack the main frame and grid() on the widgets inside that frame but you cannot use both in side the frame.
If one of your issues is where each widget is located and if it is expanding with the window you can manage all that inside of grid() so we can just use grid() here as its what I prefer when writing up a GUI.
Next we need to look at your call to show_frame as you are attempting to show a frame that does not exist in self.frames in the code you have presented us.
I have created a new class so your program can be tested with this line of code:
self.controller.show_frame("UploadPage")
The new class just makes a basic frame with a label in it showing that the frame does rise properly with tkrise().
I did some general clean up as your show_frame method was taking unnecessary steps to raise the frame, your method of importing tkinter is not the best option and some other quality corrections.
Instead of using:
frame = self.frames[page_name]
frame.tkraise()
We can simplify this method with just one line like this:
self.frames[page_name].tkraise()
I have also changed how you are importing tkinter as importing with * can sometimes cause problems if you inadvertently override build in methods. The best option is to import tkinter like this:
import tkinter as tk
Take a look at the below code and let me know if you have any questions. It should provide the info you need to allow the HomePage frame and UploadPage frame to work as intended.
import tkinter as tk
class Skeleton(tk.Tk):
def __init__(self, f,*args, **kwags):
tk.Tk.__init__(self,*args, **kwags)
self.title(f)
self.container = tk.Frame(self, width=512, height=512)
self.container.grid(row=0, column=0, sticky="nsew")
self.container.grid_rowconfigure(0, weight=1)
self.container.grid_columnconfigure(0, weight=1)
self.frames = {}
self.frames["HomePage"] = HomePage(parent=self.container, controller=self)
self.frames["HomePage"].grid(row=0, column=0, sticky="nsew")
self.frames["UploadPage"] = UploadPage(parent=self.container)
self.frames["UploadPage"].grid(row=0, column=0, sticky="nsew")
self.show_frame("HomePage")
def show_frame(self, page_name):
self.frames[page_name].tkraise()
class HomePage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.parent = parent
self.controller = controller
self.username = tk.Label(self, text="Username:")
self.username.grid(row =0,column =0)
self.username_txb = tk.Entry(self)
self.username_txb.focus_set()
self.username_txb.grid(row=0, column=1)
self.pass_lbl = tk.Label(self, text="Password:")
self.pass_lbl.grid(row =0,column =2)
self.password_txb = tk.Entry(self, text="Password", show="*")
self.password_txb.grid(row =0,column =3)
self.login_btn = tk.Button(self, text="Login", command=self.connect)
self.login_btn.grid(row=0, column=4)
self.info_pane = tk.PanedWindow()
self.info_pane.grid(row=1, column=0)
self.info_lbl = tk.Label(self, text="More information about access:", fg="blue", cursor="hand2")
self.contact_lbl = tk.Label(self, text="Contact us", fg="blue", cursor="hand2")
self.contact_lbl.grid(row=2, column=0)
self.contact_lbl.bind("<Button-1>", self.callback)
def callback(self, event):
pass
# webbrowser.open_new("https://www.tno.nl/nl/")
# I do not have the import for this webbrowser so I disabled it for testing.
def connect(self):
login = self.username_txb.get()
pwd = self.password_txb.get()
if(login == "a" and pwd == "a"):
self.controller.show_frame("UploadPage")
class UploadPage(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
tk.Label(self, text="This upload frame is a test to see if your code is working").grid(row=0, column=0)
if __name__ == "__main__":
app = Skeleton("HomePage")
app.mainloop()

TKinter GUI, how do I get Frames to correct size?

So here's the issue. I am programming a GUI using Python's TKinter, and I need the window to be a specific size. The release version of the program will have the screen settings as root.attributes("-fullscreen", True) so that it's fullscreen and the user won't be able to access any menus. Said screen will be an 800x480 tablet screen.
Obviously I am programming on a screen much bigger than 800x480, so when I create my tkinter window I am setting root.minsize("800x480") to simulate the environment the program will be in.
As for what is on the GUI, I'll have a series of Buttons and other things. The window itself is going to be split into two Frames: A Button Frame, and a Visual Frame. The Button Frame, as the name implies will be a Frame consisting entirely of Buttons for user input, whereas the Visual Frame, again obviously, will just contain visual outputs for the user.
So here is my problem. I am currently working on the Button Frame, and I am having an issue where the Frame is not sized properly. Since I don't have the programming done for the Visual Frame yet, I've just been creating two Button Frames and placing them into the root window. Both the Frames should take up the whole screen, but they aren't. Here is my code:
import tkinter as tk
from tkinter import ttk
class ButtonManager(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.config(background = 'green')
self.columnconfigure(0, weight = 1)
self.rowconfigure(0, weight = 1)
lblFrame = LabelFrame(self, controller)
lblFrame.grid(column = 0, row = 0, sticky = "nsew")
lblFrame.tkraise()
btnFrame = ButtonFrame(self, controller)
btnFrame.grid(column = 0, row = 1, sticky = "nsew")
btnFrame.tkraise()
for child in self.winfo_children():
child.grid_configure(padx = 5, pady = 5)
class LabelFrame(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.config(background = 'blue')
lblTitleBar = ttk.Label(self, text = 'TITLE BAR', background = 'grey', font = ("Arial", 20, "bold"))
lblTitleBar.grid(column = 1, row = 1, columnspan = 4, sticky = "nsew")
lblTextBar = ttk.Label(self, text = 'test text', background = 'grey', font = ("Arial", 16, "bold"))
lblTextBar.grid(column = 1, row = 2, columnspan = 4, sticky = "nsew")
class ButtonFrame(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.config(background = 'red')
btn1 = ttk.Button(self, text = '1', command = lambda : print("1"))
btn1.grid(column = 1, row = 1, columnspan = 2, rowspan = 2, sticky = "nsew")
#Not gonna type out the code for all the buttons, but I have 2 columns, and 6 rows.
#The buttons on the 4th and 6th row span the columns of both rows.
#In total there are 10 buttons
for child in self.winfo_children():
child.grid_configure(padx = 5, pady = 5)
class Interface(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.grid(column = 0, row = 0, sticky = "nsew")
container.grid_rowconfigure(0, weight = 1)
container.grid_columnconfigure(0, weight = 1)
bMan1 = ButtonManager(container, self)
bMan1.grid(column = 0, row = 0, sticky = "nsew")
bMan2 = ButtonManager(container, self)
bMan2.grid(column = 1, row = 0, sticky = "nsew")
interface = Interface()
interface.minsize(800, 480)
interface.mainloop()
As I said above my issue is that I need each of the ButtonManager objects to take up half the screen each width wise. However I instead get 2 small-ish boxes and a large grey area.
The random colours are for testing purposes btw :P
EDIT: Had a few copy/paste errors in my code, and it should work as a single file now. Apologies.
You are trying to solve too many problems at once. When first starting out with tkinter, or when laying out a new GUI for the first time, it really helps to be methodical and only solve one layout problem at a time.
Step 1 - The main window
You've elected to create a "container" for everything in your GUI. So, the first step is to get that working before trying to get anything else to work. If it's too small then it will cause everything inside of it to be too small, and that's part of the problem with your original code.
Since it's the only widget directly in the root, I recommend using pack. You can use grid, but that takes three lines of code instead of one, since with grid you have to remember to configure the weight of at least one row and column.
Start with the following (and only the following):
class Interface(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self, background="bisque")
container.pack(fill="both", expand=True)
interface = Interface()
interface.minsize(800, 480)
interface.mainloop()
How does it look? It looks good to me -- the blue completely fills the 800x480 window. We now no longer have to worry about the container.
If you want to use grid, remove the one line that calls pack, and replace it with these three lines:
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
container.grid(row=0, column=0, sticky="nsew")
Step 2 - Children of container
It looks like the container will contain two children, right? A button frame on the left and a main area on the right. For now let's use a couple of empty frames, and get them working before continuing.
You didn't specify the size of these areas, so in this example the column on the left will be 1/3 the screen size, and the right will be 2/3. If you prefer, you could use a fixed pixel width for the left, and let the right take up the rest.
If you need a very specific width, you could use place here, but for now we'll stick with using grid.
Add the following to the bottom of Interface.__init__:
bman = ButtonManager(container, controller=self)
main = Main(container, controller=self)
container.grid_columnconfigure(0, weight=1)
container.grid_columnconfigure(1, weight=2)
container.grid_rowconfigure(0, weight=1)
bman.grid(row=0, column=0, sticky="nsew")
main.grid(row=0, column=1, sticky="nsew")
We also need to define stubs for ButtonManager and Main:
class ButtonManager(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.configure(background="green")
class Main(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.configure(background="yellow")
That's enough coding. Stop and run the code, and make sure that the pink window takes up 1/3 of the blue area, and the yellow window takes up the other 2/3.
This might be a good time to play around with different values for the columnconfigure attributes (minsize, weight, pad), or switch to using place to see how it works.
Step 3 - The button manager
We just need to continue this process, going one level deeper. The nice thing is, we only have to focus on what's happening inside the button manager. Unless we add a widget that is too large to fit, we can't change the overall layout of the window as a whole.
It looks like ButtonManager is made up of a title and an area for buttons. So, let's add that. Since there are only two widgets in this frame, we can again save a couple lines of code and some headache by using pack, since pack excels at stacking things top-to-bottom.
In the following code I have the label stick to the top, and let the button frame fill the rest of the window.
Change ButtonManager to look like this:
class ButtonManager(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.config(background = 'green')
lblFrame = LabelFrame(self, controller)
btnFrame = ButtonFrame(self, controller)
lblFrame.pack(side="top", fill="x", expand=False)
btnFrame.pack(side="top", fill="both", expand=True)
Step 4: the LabelFrame
This is just a frame with a couple of labels. Again, since it has only a couple widgets, pack will save a little coding. You can use grid if you want, just remember that you have to configure the weight of rows and columns.
class LabelFrame(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.configure(background="blue")
lblTitleBar = ttk.Label(self, text = 'TITLE BAR', background = 'grey', font = ("Arial", 20, "bold"))
lblTextBar = ttk.Label(self, text = 'test text', background = 'grey', font = ("Arial", 16, "bold"))
lblTitleBar.pack(side="top", fill="x")
lblTextBar.pack(side="top", fill="x")
Step 5 - ButtonFrame
Finally, the button frame. There will be a grid of buttons. By now the process should be familiar - create the widgets, and use grid or pack to lay them out. In this case grid makes the most sense.
class ButtonFrame(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.configure(background="red")
for row in range(7):
self.grid_rowconfigure(row, weight=1)
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(1, weight=1)
b1 = tk.Button(self, text="Button 1")
b2 = tk.Button(self, text="Button 2")
b3 = tk.Button(self, text="Button 3")
b4 = tk.Button(self, text="Button 4")
b5 = tk.Button(self, text="Button 5")
b6 = tk.Button(self, text="Button 6")
b7 = tk.Button(self, text="Button 7")
b8 = tk.Button(self, text="Button 8")
b9 = tk.Button(self, text="Button 9")
b10 = tk.Button(self, text="Button 10")
b1.grid(row=0, column=0, sticky="nsew")
b2.grid(row=0, column=1, sticky="nsew")
b3.grid(row=1, column=0, sticky="nsew")
b4.grid(row=1, column=1, sticky="nsew")
b5.grid(row=2, column=0, sticky="nsew")
b6.grid(row=2, column=1, sticky="nsew")
b7.grid(row=3, column=0, columnspan=2, sticky="nsew")
b8.grid(row=4, column=0, sticky="nsew")
b9.grid(row=4, column=1, sticky="nsew")
b10.grid(row=5, column=0, columnspan=2, sticky="nsew")
Summary
If you take a methodical approach to your GUI layout, problems become much easier to solve since you're only solving for one area at a time. if you've followed all of the above closely, you'll end up with a GUI that works quite well even if you grow or shrink the window.
I know you have a requirement for a very specific size, but it's good to get in the habit of writing guis that responsive to changes in fonts, resolutions, and window sizes.

Categories