Sizing the Canvas widget with other widgets on the GUI - python

I'm creating a GUI that has a spreadsheet-like interface which is wrapped with entry method to allow the person that uses the program to enter it as if it is on a spreadsheet. This allows me much greater control over all the data and manipulate it for data analysis later. However, I can't restrict the size of the canvas to allow the scrolling bar to take effect. I.E. if I change the number of rows, the canvas will resize to that (along with column(s) changes too). I have other widgets within the GUI that isn't shown in the code but I'm just focusing on trying to force a size on the Canvas widget itself.
Is there a way for me to force the Canvas to stay within "width and height" size without having the rows and columns controlling the size of the Canvas?
import tkinter as tk
from tkinter import *
from textwrap import fill
from datetime import date
#Instantiate the GUI
class StartApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
t1 = ExcelTable(self)
t1.grid(row = 0, columnspan=2, sticky = "N")
t2 = ProductWidget()
t2.grid(row=1, column=0,sticky="SW")
t3 = TotalTrucks()
t3.grid(row=1, column=1, sticky="nsew")
#Instantiate the layout for the excel-like table entry for the top
part of the GUI
class ExcelTable(tk.Frame):
def __init__(self, parent, width=500, height=500):
rows=20
columns=10
#label for each column in the table colHeaders = ["#", "Driver", "Tare", "Ticket #", "Location", "Product", "Gross", "Net", "Tons", "Date"]
# use black background so it "peeks through" to
# form grid lines
#tk.Frame.__init__(self, parent, background="black", width=150, height=200)
#attempt to create a scrollbar within a canvas
canvas = tk.Canvas(parent)
scrollbar = tk.Scrollbar(parent, orient="vertical", command=canvas.yview)
#contain the Frame within the canvas
tk.Frame.__init__(self, canvas)
#creates the widgets to behave like excel-like table for data entry
self._widgets = []
for row in range(rows):
current_row = []
for column in range(columns):
if row == 0: #create the headers for the spreadsheet widget, using the colHeaders array
if column == 0:
label = tk.Label(self, text = colHeaders[column], borderwidth=0, width = 4)
else:
label = tk.Label(self, text = colHeaders[column], borderwidth=0, width=10)
else:
if column == 0:
label = tk.Label(self, text = (row))
else:
label = tk.Entry(self, text="")
label.bind
label.grid(row=row, column=column, sticky="nsew", padx=1, pady=1)
current_row.append(label)
self._widgets.append(current_row)
for column in range(columns):
self.grid_columnconfigure(column, weight=1)
canvas.create_window(0,0,anchor='nw', window=self)
canvas.update_idletasks()
canvas.configure(scrollregion=parent.bbox('all'))
canvas.grid(row=0,column=0)
scrollbar.grid(row=0, column=1, sticky="ns")
def set(self, row, column, value):
widget = self._widgets[row][column]
widget.configure(text=value)
def key(event):
print ("key: {}".format(event.char))
#obtain and store values that are entered in the ExcelTable
def find_in_grid(frame, row, column):
for children in frame.children.values():
info = children.grid_info()
#note that rows and column numbers are stored as string
if info['row'] == str(row) and info['column'] == str(column):
return children
return None
class ProductWidget(tk.Frame):
def __init__(self):
tk.Frame.__init__(self, background="white")
self._widgets = []
label1 = tk.Label(self, text="Product")
label1.grid(row=0,column=0, sticky="nsew")
label2 = tk.Label(self, text="Total Tons")
label2.grid(row=0, column=1, sticky="nsew")
class TotalTrucks(tk.Frame):
def __init__(self):
tk.Frame.__init__(self, background="white" )
self._widgets = []
label1 = tk.Label(self, text="Total Trucks")
label1.grid(row=0, rowspan=2, column=0, sticky="nsew")
label2 = tk.Label(self, text="Today: ")
label2.grid(row=1, column=0, stick="nsew")
label3 = tk.Label(self, text="Last Week: ")
label3.grid(row=2, column=0, sticky="nsew")
label4 = tk.Label(self, text="Overall")
label4.grid(row=3, column=0, sticky="nsew")
if __name__ == "__main__":
currYear = date.today().year
startGUI = StartApp()
startGUI.title("Truck Log " + str(currYear))
startGUI.mainloop()

Since ExcelTable is the internal frame inside canvas and is already put inside canvas using canvas.create_window(...), so you should not call t1.grid(...) inside StartApp.__init__().
Also you set the scrollregion of canvas wrong in the following line:
canvas.configure(scrollregion=parent.bbox('all'))
It should be:
canvas.configure(scrollregion=canvas.bbox('all'))
Finally you forgot to configure yscrollcommand option of canvas.
Below is the changes required:
...
class StartApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
t1 = ExcelTable(self)
### should not call t1.grid(...) since t1 has already put in canvas using canvas.create_window(...)
#t1.grid(row = 0, columnspan=2, sticky = "N")
t2 = ProductWidget()
t2.grid(row=1, column=0,sticky="SW")
t3 = TotalTrucks()
t3.grid(row=1, column=1, sticky="nsew")
...
class ExcelTable(tk.Frame):
def __init__(self, parent, width=500, height=500):
...
#canvas.configure(scrollregion=parent.bbox('all'))
canvas.configure(scrollregion=canvas.bbox('all'), width=self.winfo_width(),
yscrollcommand=scrollbar.set)
...

Related

Is it possible to resize an input box (entry) when clicked with tkinter?

Reading through other stackoverflow questions, and other sources I do see that bind can be used to call a function. Currently I'm working on a program that will communicate with a database (most likely mongodb), and so far I've set up a frame that has 2 inputs per row (key-value). I haven't completely decided whether I want one row per document, or one row per field. Right now, if a user has a lot to type then it wouldn't be ideal for them because you can't see everything you write. So what I was thinking is that, if the user clicks on the entry widget, then the box would become bigger and show them everything they have written. My current line of thinking is that maybe I could create another frame for it and somehow pass onto the information to that?
This is what it currently looks like
Then what I'd ideally want it to look like
Here's the code if interested how I made it (Images are from the "CreatePage" section):
from tkinter import *
import tkinter as tk
class Database_Project(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
stack_frame_container = tk.Frame(self)
stack_frame_container.grid_columnconfigure(0, weight=1)
stack_frame_container.grid_rowconfigure(0, weight=1)
stack_frame_container.pack(side="top", fill="both", expand=True)
self.frameslist = {}
for frame in (MainPage, CreatePage):
frame_occurrence = frame.__name__
active_frame = frame(parent=stack_frame_container, controller=self)
self.frameslist[frame_occurrence] = active_frame
active_frame.grid(row=0, column=0, sticky="snew")
self.current_frame("MainPage")
def current_frame(self, frame_occurrence):
active_frame = self.frameslist[frame_occurrence]
active_frame.tkraise()
class MainPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label_create = tk.Label(self, text="Create and insert data").grid(row=0, column=0, padx=50, pady=(50,0))
create_button = tk.Button(self, text="CREATE", command=lambda: controller.current_frame("CreatePage")).grid(row=1, column=0)
label_read = tk.Label(self, text="Query over data").grid(row=0, column=1, padx=50, pady=(50,0))
read_button = tk.Button(self, text="READ").grid(row=1, column=1)
label_update = tk.Label(self, text="Modify existing data").grid(row=2, column=0, padx=50, pady=(50,0))
update_button = tk.Button(self, text="UPDATE").grid(row=3, column=0, pady=(0,50))
label_delete = tk.Label(self, text="Remove data").grid(row=2, column=1, padx=50, pady=(50,0))
delete_button = tk.Button(self, text="DELETE").grid(row=3, column=1, pady=(0,50))
class CreatePage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.inputlist = []
self.newinputlist = []
labels = [tk.Label(self, text="Enter unique field"), tk.Label(self, text="Enter corresponding the value/s")]
self.inputlist.append(labels[:])
for toplabels in range(1):
self.inputlist[toplabels][0].grid(row=toplabels, column=0, padx=10, pady=5)
self.inputlist[toplabels][1].grid(row=toplabels, column=1, padx=10, pady=5)
for entries in range(2):
for entrynum in range(0, 1):
print("column:", entries)
print("row", entrynum)
self.newinputlist.append(tk.Entry(self, borderwidth=5))
for x in range(len(self.newinputlist)):
self.newinputlist[x].grid(row=1, column=x, padx=10, pady=5)
self.inputlist.append(self.newinputlist[:])
button_input_1 = [tk.Button(self, text="ADD FIELD/VALUE", command=self.add_insert), tk.Button(self, text="BACK", command=lambda: controller.current_frame("MainPage"))]
self.inputlist.append(button_input_1[:])
button_input_2 = [tk.Button(self, text="IMPORT FILE"), tk.Button(self, text="SUBMIT DATA")]
self.inputlist.append(button_input_2[:])
for button in range(len(self.inputlist) - 2, len(self.inputlist)):
self.inputlist[button][0].grid(row=button, column=0, padx=10, pady=5)
self.inputlist[button][1].grid(row=button, column=1, padx=10, pady=5)
def add_insert(self):
add_input = [tk.Entry(self, borderwidth=5), tk.Entry(self, borderwidth=5)]
self.inputlist.insert(-2, add_input)
self.newinputlist.append(add_input)
for widget in self.children.values():
widget.grid_forget()
for index, widgets in enumerate(self.inputlist):
widget_one = widgets[0]
widget_two = widgets[1]
print(str(index), widget_one, widget_two)
widget_one.grid(row=index, column=0, padx=10, pady=5)
widget_two.grid(row=index, column=1, padx=10)
if __name__ == "__main__":
NoSQL_Project = Database_Project()
NoSQL_Project.title("NoSQL Database Project")
NoSQL_Project.mainloop()
It's pointless to resize an Entry widget since they can only ever hold a single line. I'll give an example using the Text widget instead, though the technique works with any widget.
There's really no trick, just bind to <FocusIn> and <FocusOut>. In the following example I've created two Text widgets that have this resize behavior:
import tkinter as tk
def resizer(event):
if event.widget == event.widget.focus_get():
event.widget.configure(height=8)
else:
event.widget.configure(height=1)
root = tk.Tk()
root.geometry("400x200")
text1 = tk.Text(root, height=1, width=20)
text2 = tk.Text(root, height=1, width=20)
text1.pack(side="left")
text2.pack(side="right")
for widget in (text1, text2):
widget.bind("<FocusIn>", resizer)
widget.bind("<FocusOut>", resizer)
root.mainloop()
The actual behavior depends on how you've laid out your widget. This could cause widgets to jump around or the window resize, but every app will be different so it's hard to give a solution that works everywhere.

How To Add Multiple Tables In One Tkinter Frame

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

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()

Python 3: Tkinter and updating frames

The problem I'm currently stuck with is that when I add a new shift I can't get the GUI to update properly. Currently the best I can seem to get is getting the updated information but it is displayed on top of the old information.
I did read about the whole mainloop() issue and tried to get use the after() method but I'm not sure if thats what I'm looking for:
class ViewShifts(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
buttonFrame = tk.Frame(self)
buttonFrame.pack(side='bottom', fill='both', expand=True)
#frame to display shifts
table = tk.Frame(self)
table.pack(side='top', fill='both', expand=True)
table.destroy()
#dictionary which will contain widgets that will display shifts (in viewNew method)
self.widgets = {}
button1= tk.Button(self, text="Home",
command=lambda: controller.showFrame(StartPage))
button1.pack(padx=0, pady=10, side='bottom')
button2 = tk.Button(self, text='Update',
command=lambda: deleteShiftFrame(self))
button2.pack(padx=0, pady=10, side='bottom')
text = tk.Label(self, text="All shifts", font=LARGEFONT)
text.pack(padx=10, side='top')
def deleteShiftFrame(self):
table.destroy()
viewNew(self)
def viewNew(self):
#frame to display shifts
table = tk.Frame(self)
table.pack(side='top', fill='both', expand=True)
row = 0
#make dictionary empty
self.widgets = {}
c.execute("SELECT * FROM shifts")
data = c.fetchall()
#create labels for each column
date_label = tk.Label(table, text="Date")
shift_label=tk.Label(table, text="Shift")
shop_label=tk.Label(table, text="Shop")
hours_label=tk.Label(table, text="Hours")
#add labels to grid
date_label.grid(row=0, column=0, sticky="nsw")
shift_label.grid(row=0, column=1, sticky="nsw")
shop_label.grid(row=0, column=2, sticky="nsw")
hours_label.grid(row=0, column=3, sticky="nsw")
#for each column create a tk label for each row within column with corresponding details
for id, date, shift, shop, hours in (data):
row+=1
self.widgets[id] = {
"id":tk.Label(table, text=id),
"date":tk.Label(table, text=date),
"shift":tk.Label(table, text=shift),
"shop":tk.Label(table, text=shop),
"hours":tk.Label(table, text=hours)
}
#add current row of column to grid
self.widgets[id]["date"].grid(row=row, column=0, sticky="nsw")
self.widgets[id]["shift"].grid(row=row, column=1, sticky="nsw")
self.widgets[id]["shop"].grid(row=row, column=2, sticky="nsw")
self.widgets[id]["hours"].grid(row=row, column=3, sticky="nsw")
#add blank column between each field for spacing
table.grid_columnconfigure(0, weight=1)
table.grid_columnconfigure(1, weight=1)
table.grid_columnconfigure(2, weight=1)
table.grid_columnconfigure(3, weight=1)
#add extra row for padding
table.grid_rowconfigure(row+1, weight=1)
viewNew(self)
class AddShifts(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
text = tk.Label(self, text = "Add shifts", font=LARGEFONT)
text.pack(padx=10, side = 'top')
#create frame for entry
AddShiftFrame = tk.Frame(self)
AddShiftFrame.pack(side="top", fill="both", expand=True)
#add column headers
dateLabel = tk.Label(AddShiftFrame, text="Date")
shiftLabel = tk.Label(AddShiftFrame, text="Shift")
shopLabel = tk.Label(AddShiftFrame, text='Shop')
dateLabel.grid(row=1, column=0, sticky="nsw")
shiftLabel.grid(row=1, column=1, sticky="nsw")
shopLabel.grid(row=1, column=2, sticky="nsw")
#create dictionary of widgets
self.widgets = {
"Date":tk.Entry(AddShiftFrame),
"Shift":tk.Entry(AddShiftFrame),
"Shop":tk.Entry(AddShiftFrame)
}
#add widgets to frame
self.widgets["Date"].grid(row=2, column=0, sticky="nsw")
self.widgets["Shift"].grid(row=2, column=1, sticky="nsw")
self.widgets["Shop"].grid(row=2, column=2, sticky="nsw")
#this method will submit the data (callback function)
def submit_data(self):
shift_data = [(self.widgets["Date"].get()), (self.widgets["Shift"].get()), (self.widgets["Shop"].get())]
c.execute("INSERT INTO shifts (date, shift, shop) VALUES(?,?,?)", (shift_data[0], shift_data[1], shift_data[2]))
conn.commit()
print("done")
#submit button
submit = tk.Button(self, text="Submit",
command= lambda: submit_data(self))
submit.pack(padx=10, pady=10, side="left")
#home button
button1 = tk.Button(self, text="Home",
command=lambda: controller.showFrame(StartPage))
button1.pack(padx=10, pady=10, side='left')
app = MainApplication()
app.after(1000, getShifts)
app.mainloop()

Categories