Refresh Python tkinter TreeView - python

I'm creating a GUI that interacts with a Postgresql database.
The GUI displays all the contents of a certain table when the program launches.
I have a button programmed to add/remove entries.
The buttons work as when I checked the database, the entries are added to the database but I don't know how to refresh the TreeView in order for it to reflect the changes in the database.
My attempt to make it refresh is to clear (also to build) the TreeView is the _build_tree... code below:
def _build_tree(self):
for i in self.tree.get_children():
self.tree.delete(i)
for col in self.header:
self.tree.heading(col, text=col.title(),command=lambda c=col: self.sortby(self.tree, c, 0))
# adjust the column's width to the header string
self.tree.column(col,width=tkFont.Font().measure(col.title()))
for item in self.content:
self.tree.insert('', 'end', values=item)
# adjust column's width if necessary to fit each value
for ix, val in enumerate(item):
col_w = tkFont.Font().measure(val)
if self.tree.column(self.header[ix],width=None)<col_w:
self.tree.column(self.header[ix], width=col_w)
My complete code is below:
from tkinter import *
import tkinter.font as tkFont
import tkinter.ttk as ttk
import datetime
import psycopg2 as pg2
myFont = ('Impact',24)
myFontColor = 'red'
def check(): #to check if button works
print ("It works!")
class Database:
def __init__(self):
self.conn = pg2.connect(database='inventory', user='loremipsum', password='loremipsum')
self.cur = self.conn.cursor()
self.timeNow = datetime.datetime.now()
def view_rooms(self):
self.cur.execute('''
SELECT * FROM rooms
ORDER BY room_id;
''')
return self.cur.fetchall()
def add_room(self,roomID,roomName,floor):
addRoom = '''
INSERT INTO rooms (room_id,room_name,floor)
VALUES ({},'{}',{});
'''.format(roomID,roomName,floor)
self.cur.execute(addRoom)
self.conn.commit()
def del_room(self,roomID):
addRoom = '''
DELETE FROM rooms
WHERE room_id={};
'''.format(roomID)
self.cur.execute(addRoom)
self.conn.commit()
def __del__(self):
self.conn.close()
database = Database()
class Page(Frame):
def __init__(self, *args, **kwargs):
Frame.__init__(self, *args, **kwargs)
def show(self):
self.lift()
class RoomPage(Page):
def __init__(self, *args, **kwargs): #create widgets
Page.__init__(self, *args, **kwargs)
Label(self,text="ROOM",font=myFont,fg=myFontColor).grid(row=0,column=0, sticky=W)
Button(self,text="SEARCH",command=check).grid(row=0,column=1, sticky=W+E)
self.header = ['Room ID','Room','Floor']
self.content = database.view_rooms()
self.tree=ttk.Treeview(self,columns=self.header, show="headings")
vsb = ttk.Scrollbar(self,orient="vertical",command=self.tree.yview)
hsb = ttk.Scrollbar(self,orient="horizontal",command=self.tree.xview)
self.tree.configure(yscrollcommand=vsb.set,xscrollcommand=hsb.set)
self.tree.bind('<ButtonRelease-1>',self.get_selected_row)
self.tree.grid(column=0, row=1, columnspan=4, sticky='nsew')
vsb.grid(column=4, row=1, sticky='ns')
hsb.grid(column=0, row=2, columnspan=4, sticky='ew')
self._build_tree()
Button(self,text="ADD",command=AddRoom).grid(row=2,column=0,sticky=W+E)
Button(self,text="REMOVE",command=self.deleteRoom).grid(row=2,column=1, sticky=W+E)
Button(self,text="CLOSE",command=root.destroy).grid(row=2,column=3, sticky=W+E)
def _build_tree(self):
for i in self.tree.get_children():
self.tree.delete(i)
for col in self.header:
self.tree.heading(col, text=col.title(),command=lambda c=col: self.sortby(self.tree, c, 0))
# adjust the column's width to the header string
self.tree.column(col,width=tkFont.Font().measure(col.title()))
for item in self.content:
self.tree.insert('', 'end', values=item)
# adjust column's width if necessary to fit each value
for ix, val in enumerate(item):
col_w = tkFont.Font().measure(val)
if self.tree.column(self.header[ix],width=None)<col_w:
self.tree.column(self.header[ix], width=col_w)
def sortby(self,tree, col, descending):
"""sort tree contents when a column header is clicked on"""
# grab values to sort
data = [(tree.set(child, col), child) \
for child in tree.get_children('')]
# now sort the data in place
data.sort(reverse=descending)
for ix, item in enumerate(data):
tree.move(item[1], '', ix)
# switch the heading so it will sort in the opposite direction
tree.heading(col, command=lambda col=col: sortby(tree, col, \
int(not descending)))
def get_selected_row(self,event):
selection = self.tree.item(self.tree.selection())
self.selected_tuple=selection['values'][0]
def openRoom(self,event):
index=self.roomList.curselection()[0]
self.selected_tuple=self.roomList.get(index)[0]
print (self.selected_tuple)
def deleteRoom(self):
database.del_room(self.selected_tuple)
self.clear()
self.build()
class AddRoom:
def __init__(self, *args, **kwargs): #create widgets
window = Toplevel(root)
window.title("Room Details")
roomDetailsLabel = Label (window,text="Room Details",font=myFont,fg=myFontColor).grid(row=0,column=0, sticky=W,columnspan=2)
roomNumLabel = Label(window,text="Room ID").grid(row=1,column=0, sticky=E)
self.roomNumText = StringVar()
roomNameEntry = Entry(window,textvariable=self.roomNumText,width=30).grid(row=1,column=1,sticky=W)
roomNameLabel = Label(window,text="Room Name").grid(row=2,column=0, sticky=E)
self.roomNameText = StringVar()
roomNameEntry = Entry(window,textvariable=self.roomNameText,width=30).grid(row=2,column=1,sticky=W)
floorLabel = Label(window,text="Floor").grid(row=3,column=0, sticky=E)
self.floorText = StringVar()
floorEntry = Entry(window,textvariable=self.floorText,width=30).grid(row=3,column=1,sticky=W)
Button(window,text="SAVE",command=self.add_room).grid(row=4,column=0,sticky=W+E)
Button(window,text="CLOSE",command=window.destroy).grid(row=4,column=1,sticky=W+E)
def add_room(self):
database.add_room(self.roomNumText.get(),self.roomNameText.get(),self.floorText.get())
p1._build_tree()
class MainView(Frame):
def __init__(self, *args, **kwargs):
global p1
Frame.__init__(self, *args, **kwargs)
p1 = RoomPage(self)
buttonframe = Frame(self)
container = Frame(self)
buttonframe.pack(side="top", fill="x", expand=False)
container.pack(side="top", fill="both", expand=True)
p1.place(in_=container, x=0, y=0, relwidth=1, relheight=1)
p1.show()
if __name__ == "__main__": #main loop
root = Tk()
root.title('Inventory System')
root.configure(bg="#BDE9EB")
parent = Frame(root, padx=10, pady=10)
parent.pack(fill=BOTH, expand=True)
main = MainView(parent)
main.pack(side="top", fill="both", expand=True)
root.wm_geometry("600x350")
root.mainloop()
How do you refresh the TreeView to reflect the changes made in the database?

Hello seems like the problem you have encountered is like mine.
You are correct when you call the get.children() after that what you will do is call the database again.
Here is my code, i hope you will get this.
def _build_tree(self):
cursor=db.cursor(buffered=True)
cursor.execute("SELECT ")
result = cursor.fetchall()
for i in self.tree.get_children():
self.tree.delete(i)
for i in result:
self.tree.insert('', 'end', values=(i[0],i[2]..)

Related

Pass opened file name to function

I am building a small app where I am doing some calculations and want to save the variables and results into a *.csv file.
My desire is to open the *.csv file via a menu point in Tkinter and, after calculations done, save the results with a button to the *.csv file.
What I am not able to do is to save the data, as I am passing the file name in the wrong way.
I have tried to assign the file name to a variable, declare it as global but have not find any solution.
This is the code snippet requested, not just the 2 functions:
import tkinter as tk
from tkinter import *
# Used for styling the GUI
from tkinter import ttk
from math import *
import csv
from tkinter import filedialog as fd
from tkinter.filedialog import asksaveasfile
from tkinter.messagebox import showinfo
from datetime import datetime
# global database
class windows(tk.Tk):
def __init__(self, *args, **kwargs):
global database
tk.Tk.__init__(self, *args, **kwargs)
# Adding a title to the window
self.wm_title("calculator and database")
#
tire_menu = Menu(self)
self.config(menu=tire_menu)
def command_open():
filename = fd.askopenfilename(
title = 'Select database',
filetypes = [("CSV files", "*.csv")])
showinfo(
title = 'You have selected',
message = filename)
database = filename
return(database)
def command_new():
extensions = [("csv file(*.csv)", "*.csv")]
file = asksaveasfile(filetypes=extensions,
defaultextension=extensions)
headerlist = [
"Date",
"Track"]
database = file.name
with open(database, "w") as f:
writer = csv.writer(f)
writer.writerow(headerlist)
# create a menu item
file_menu = Menu(tire_menu)
db_menu = Menu(tire_menu)
tire_menu.add_cascade(label="File", menu = file_menu)
file_menu.add_command(label="Exit", command=self.quit)
tire_menu.add_cascade(label="Database", menu = db_menu)
db_menu.add_command(label="New DB", command=command_new)
db_menu.add_command(label="Open DB", command=command_open)
# creating a frame and assigning it to container
container = tk.Frame(self, height=600, width=800)
# specifying the region where the frame is packed in root
container.pack(side="top", fill="both", expand=True)
# configuring the location of the container using grid
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
# We will now create a dictionary of frames
self.frames = {}
# we'll create the frames themselves later but let's add the components to the dictionary.
for F in (MainPage, SidePage, CompletionScreen):
frame = F(container, self)
# the windows class acts as the root window for the frames.
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
# Using a method to switch frames
self.show_frame(MainPage)
def show_frame(self, cont):
frame = self.frames[cont]
# raises the current frame to the top
frame.tkraise()
class MainPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
title_main = tk.Label(
self,
text="calculator")
title_main.config(font=("Times 18 bold"), bg="white")
title_main.grid(row=0, column=0, columnspan=2, pady=5)
def savetodb():
data = [
datetime.today().strftime('%Y-%m-%d'),
]
with open(database, "w") as f:
writer = csv.writer(f)
writer.writerow(data)
## Create reference tire pressures frame
frame_reference = tk.LabelFrame(
self,
text = "1: Enter reference data: ")
frame_reference.grid(row=1, column=0, padx=5, pady=5, columnspan = 2)
lbl_track = Label(
frame_reference,
text = "Track: ",
font = 'Times 11',
justify = tk.CENTER)
lbl_track.grid(row = 0, column = 0)
# entries for database save
ent_track = Entry(
frame_reference,
justify = tk.CENTER,
width = 23).grid(row=0, column=1)
btn_savetodb = Button(
frame_reference,
text="Save to DB!",
font = 'Times 11 bold',
command=savetodb)
btn_savetodb.grid(row=2, column=0, columnspan=2, padx=10, pady=5)
class SidePage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="This is the Side Page")
label.pack(padx=10, pady=10)
switch_window_button = tk.Button(
self,
text="Go to the Completion Screen",
command=lambda: controller.show_frame(CompletionScreen),
)
switch_window_button.pack(side="bottom", fill=tk.X)
class CompletionScreen(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Completion Screen, we did it!")
label.pack(padx=10, pady=10)
switch_window_button = ttk.Button(
self, text="Return to menu", command=lambda: controller.show_frame(MainPage)
)
switch_window_button.pack(side="bottom", fill=tk.X)
if __name__ == "__main__":
testObj = windows()
testObj.mainloop()
Could you please provide some help?
You have declared global database in wrong place. It should be put inside command_open() as I said in my comment:
class windows(tk.Tk):
def __init__(self, *args, **kwargs):
...
def command_open():
global database
...
database = filename
return database
...
...
However I would suggest to use a class variable inside windows class instead of global variable, so that it can be accessed using controller.database inside MainPage class:
class windows(tk.Tk):
def __init__(self, *args, **kwargs):
...
self.database = None
...
def command_open():
...
self.database = filename
return self.database
...
...
...
class MainPage(tk.Frame):
def __init__(self, parent, controller):
...
def savetodb():
...
with open(controller.database, "w") as f:
...
...
...
Note that you have to change all occurrences of database to self.database inside windows class.

Tkinter Python: Grid &Entry problem. How to add and remove dynamicly Entry&delete Button wingets

Hi im trying to write simple app with posibility to add and delete Entry boxes and i cant get the expected result.
What i want : add button add a row with entry and delete this entry button
what i get :Entry is not created and delete button is not his column
here is my code:
from tkinter import *
class App():
def __init__(self, root):
self.root=root
self.all_entries = []
self.addboxButton = Button(root, text='<Add >', command=self.addBox)
self.addboxButton.grid(row=0, column=0)
self.rows =[]
def addBox(self):
print ("ADD")
new_row = Row(self,root)
self.rows.append(new_row)
self.render()
def render(self):
for i in range(len(self.rows)):
self.rows[i].grid(row=i + 1, column=1, sticky=W,columnspan=2, padx=(5, 0), pady=(5, 0))
class Row(Frame):
def __init__(self,parent, main_frame, **kwargs):
super(Row, self).__init__()
self.main_frame = main_frame
self.entryRowVar = StringVar()
self.entryRow= Entry(self.main_frame, textvariable=self.entryRowVar)
self.entryRow.grid(row=0, column=1, sticky=W,)
self.del_btn = Button(self, text='Delete')
self.del_btn.grid(row=0, column=2, sticky=W,)
if __name__ == "__main__":
root = Tk()
app = App(root)
root.mainloop()

How do I change a ttk Combobox values that was made in a for loop

I have this sample code I am running. I am creating a window, and when I load the template page and click the "Click me" button, it adds 20 boxes on the screen. 10 rows, 2 wide. Column 1 is Car makes, and column 2 is Models.
When I click the Make box in row 1, and change it from Ford to Toyota, I want the model combobox in row 1 to change to show the Toyota models. But it only works for the last row. Is it possible to get each row to work?
import tkinter as tk
from tkinter import font as tkfont, filedialog, messagebox
from tkinter.ttk import Combobox
class SLS_v1(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# Setting up the root window
self.title('Test App')
self.geometry("200x300")
self.resizable(False, False)
self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold", slant="italic")
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 = {}
self.frames["MenuPage"] = MenuPage(parent=container, controller=self)
self.frames["template"] = template(parent=container, controller=self)
self.frames["MenuPage"].grid(row=0, column=0, sticky="nsew")
self.frames["template"].grid(row=0, column=0, sticky="nsew")
self.show_frame("MenuPage")
def show_frame(self, page_name):
frame = self.frames[page_name]
frame.tkraise()
class MenuPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
template = tk.Button(self, text='Template', height=3, width=20, bg='white', font=('12'),
command=lambda: controller.show_frame('template'))
template.pack(pady=50)
class template(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.grid(columnspan=10, rowspan=10)
button = tk.Button(self, text='Click me', command= lambda: stored_functions().add_boxes(self))
button.grid(row=11, column=1)
class stored_functions():
make = ['Ford', 'Toyota', 'Honda']
models = [['F-150', 'Mustang', 'Explorer'], ['Camry', 'Corolla', 'Tacoma'], ['Civic', 'CRV', 'Accord']]
def add_boxes(self, main_window):
for row in range(10):
self.make_var = tk.StringVar(main_window)
self.make_options = self.make
self.make_var.set(self.make_options[0])
self.make_selection = tk.ttk.Combobox(main_window, value=self.make_options,
state='readonly', width=10)
self.make_selection.current(0)
self.make_selection.bind('<<ComboboxSelected>>', lambda event:
stored_functions.update_models(self, selection=self.make_selection))
self.make_selection.grid(row=row, column=1)
self.model_var = tk.StringVar(main_window)
self.model_options = self.models[0]
self.model_var.set(self.model_options[0])
self.model_selection = tk.ttk.Combobox(main_window, value=self.model_options,
state='readonly', width=10)
self.model_selection.current(0)
self.model_selection.grid(row=row, column=2)
def update_models(self, selection):
if selection.get() == 'Ford':
self.model_options = self.models[0]
if selection.get() == 'Toyota':
self.model_options = self.models[1]
if selection.get() == 'Honda':
self.model_options = self.models[2]
self.model_selection.config(values=self.model_options)
self.model_selection.current(0)
if __name__ == "__main__":
app = SLS_v1()
app.mainloop()
You have used same variables for car brands and models selection, so the variables refer the last set after the for loop.
You need to pass the model combobox to update_models() using default value of argument:
class stored_functions():
make = ['Ford', 'Toyota', 'Honda']
models = [['F-150', 'Mustang', 'Explorer'], ['Camry', 'Corolla', 'Tacoma'], ['Civic', 'CRV', 'Accord']]
def add_boxes(self, main_window):
for row in range(10):
self.make_var = tk.StringVar(main_window)
self.make_options = self.make
self.make_var.set(self.make_options[0])
self.make_selection = tk.ttk.Combobox(main_window, value=self.make_options,
state='readonly', width=10)
self.make_selection.current(0)
self.make_selection.grid(row=row, column=1)
self.model_var = tk.StringVar(main_window)
self.model_options = self.models[0]
self.model_var.set(self.model_options[0])
self.model_selection = tk.ttk.Combobox(main_window, value=self.model_options,
state='readonly', width=10)
self.model_selection.current(0)
self.model_selection.grid(row=row, column=2)
# pass the corresponding model combobox to bind function
self.make_selection.bind(
'<<ComboboxSelected>>',
lambda event, peer=self.model_selection: self.update_models(event.widget.get(), peer)
)
def update_models(self, selection, model_selection):
model_options = self.models[self.make.index(selection)]
model_selection.config(values=model_options)
model_selection.current(0)
Note that it is not necessary to use instance variables inside the for loop. Also those StringVars are not used at all, so the functions can be simplified as below:
class stored_functions():
make = ['Ford', 'Toyota', 'Honda']
models = [['F-150', 'Mustang', 'Explorer'], ['Camry', 'Corolla', 'Tacoma'], ['Civic', 'CRV', 'Accord']]
def add_boxes(self, main_window):
for row in range(10):
make_selection = tk.ttk.Combobox(main_window, value=self.make,
state='readonly', width=10)
make_selection.current(0)
make_selection.grid(row=row, column=1)
model_selection = tk.ttk.Combobox(main_window, value=self.models[0],
state='readonly', width=10)
model_selection.current(0)
model_selection.grid(row=row, column=2)
make_selection.bind(
'<<ComboboxSelected>>',
lambda event, peer=model_selection: self.update_models(event.widget.get(), peer)
)
def update_models(self, selection, model_selection):
model_options = self.models[self.make.index(selection)]
model_selection.config(values=model_options)
model_selection.current(0)

Iteration within tkinter Frame

I would like to use a tkinter GUI to iterate through a dictionary (for example) and allow the user to take actions on its values.
For example, my boss might want to iterate through departments and select which employees to fire. The below code works (mostly) for the first department, but I don't understand how to advance to the next department (self.advance below) .
This question is related but just updates values of existing widgets. The number of employees in each department varies, so I can't just update the names, and I also have to allow vertical scrolling.
The iteration occurs within a frame (innerFrame) and the rest of the UI is mostly static. Should I be destroying and recreating that innerFrame, or just all of the widgets inside it? Either way, how can I advance to the next iteration?
# Example data
emp = {'Sales':['Alice','Bryan','Cathy','Dave'],
'Product':['Elizabeth','Frank','Gordon','Heather',
'Irene','John','Kristof','Lauren'],
'Marketing':['Marvin'],
'Accounting':['Nancy','Oscar','Peter','Quentin',
'Rebecca','Sally','Trevor','Umberto',
'Victoria','Wally','Xavier','Yolanda',
'Zeus']}
import tkinter as tk
from tkinter import messagebox
class bossWidget(tk.Frame):
def __init__(self, root):
"""
Scrollbar code credit to Bryan Oakley:
https://stackoverflow.com/a/3092341/2573061
"""
super().__init__()
self.canvas = tk.Canvas(root, borderwidth=0)
self.frame = tk.Frame(self.canvas)
self.scroll = tk.Scrollbar(root, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.scroll.set)
self.scroll.pack(side="right", fill="y")
self.canvas.pack(side="left", fill="both", expand=True)
self.canvas.create_window((4,4), window=self.frame, anchor="nw",
tags="self.frame")
self.frame.bind("<Configure>", self.onFrameConfigure)
self.initUI()
def initUI(self):
"""
Creates the static UI content and the innerFrame that will hold the
dynamic UI content (i.e., the Checkbuttons for the copies)
"""
self.master.title("Boss Interface")
self.instructLabel = tk.Label( self.frame, justify='left',
text = "Select the employees you wish to FIRE")
self.skipButton = tk.Button( self.frame, text="Skip Department",
command = self.advance)
self.deleteButton = tk.Button( self.frame, text="Fire employees", fg = 'red',
command = self.executeSelection )
self.quitButton = tk.Button( self.frame, text="Exit", command=self.frame.quit)
self.innerFrame = tk.Frame( self.frame)
self.instructLabel.pack(anchor = 'nw', padx=5,pady=5)
self.innerFrame.pack(anchor='nw', padx=5, pady=20, expand=True)
self.deleteButton.pack(side='left', padx=5,pady=5)
self.skipButton.pack(side='left', padx=5,pady=5)
self.quitButton.pack(side='left', padx=5,pady=5)
def populateUI(self, title, labelList):
"""
Creates and packs a list of Checkbuttons (cbList) into the innerFrame
By default, the first Checkbutton will be unchecked, all others checked.
You should help the boss out by passing the best employee at the head of the list
"""
self.instructLabel.config(text = title + ' department:\nSelect the employees you wish to FIRE')
self.cbList = [None] * len(labelList)
self.cbValues = [tk.BooleanVar() for i in range(len(labelList))]
for i in range(len(labelList)):
self.cbList[i] = tk.Checkbutton( self.innerFrame,
text=labelList[i],
variable = self.cbValues[i])
if i: self.cbList[i].select() # Check subsequent buttons by default
self.cbList[i].pack(anchor = 'w', padx=5,pady=5)
def advance(self):
# -------------> this is what I don't understand how to do <-------------
self.innerFrame.destroy() # this destroys everything!
# how to advance to next iteration?
def querySelection(self):
return [x.get() for x in self.cbValues]
def executeSelection(self):
fired = self.querySelection()
if ( not all(x for x in fired) or
messagebox.askokcancel(message='Fire ALL the employees in the department?')
):
for i in range(len(self.cbList)):
empName = self.cbList[i].cget('text')
if fired[i]:
print('Sorry, '+ empName + ', but we have to let you go.', flush=True)
else:
print('See you Monday, '+ empName, flush=True)
self.advance()
def onFrameConfigure(self, event):
"""Reset the scroll region to encompass the inner frame"""
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def main():
root = tk.Tk()
root.geometry("400x250+250+100") # width x height + xOffset + yOffset
app = bossWidget(root)
while emp:
department, employees = emp.popitem()
app.pack(side='top',fill='both',expand=True)
app.populateUI(title = department, labelList = employees)
root.mainloop()
try:
root.destroy()
except tk.TclError:
pass # if run in my IDE, the root already is destroyed
if __name__ == '__main__':
main()
Here's a short rework of your code to handle updating the checkboxes on firing employees and switching frames to display the new employees from the department. I didn't handle advancing if all employees have been fired. There's also a small bug, but I'll leave that to you to figure out.
This could be a lot cleaner. I just didn't want to rewrite all of your code....
# Example data
emp = [['Sales', ['Alice','Bryan','Cathy','Dave']],
['Product', ['Elizabeth','Frank','Gordon','Heather',
'Irene','John','Kristof','Lauren']],
['Marketing', ['Marvin']],
['Accounting', ['Nancy','Oscar','Peter','Quentin',
'Rebecca','Sally','Trevor','Umberto',
'Victoria','Wally','Xavier','Yolanda',
'Zeus']]]
import tkinter as tk
from tkinter import messagebox
class bossWidget(tk.Frame):
def __init__(self, root):
"""
Scrollbar code credit to Bryan Oakley:
https://stackoverflow.com/a/3092341/2573061
"""
super().__init__()
self.cursor = 0
self.canvas = tk.Canvas(root, borderwidth=0)
self.frame = tk.Frame(self.canvas)
self.scroll = tk.Scrollbar(root, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.scroll.set)
self.scroll.pack(side="right", fill="y")
self.canvas.pack(side="left", fill="both", expand=True)
self.canvas.create_window((4,4), window=self.frame, anchor="nw",
tags="self.frame")
self.frame.bind("<Configure>", self.onFrameConfigure)
self.initUI()
def initUI(self):
"""
Creates the static UI content and the innerFrame that will hold the
dynamic UI content (i.e., the Checkbuttons for the copies)
"""
self.master.title("Boss Interface")
self.instructLabel = tk.Label( self.frame, justify='left',
text = "Select the employees you wish to FIRE")
self.skipButton = tk.Button( self.frame, text="Skip Department",
command = self.advance)
self.deleteButton = tk.Button( self.frame, text="Fire employees", fg = 'red',
command = self.executeSelection )
self.quitButton = tk.Button( self.frame, text="Exit", command=self.frame.quit)
self.innerFrame = tk.Frame(self.frame)
self.instructLabel.pack(anchor = 'nw', padx=5,pady=5)
self.innerFrame.pack(anchor = 'nw', padx=5,pady=5)
self.deleteButton.pack(side='left', padx=5,pady=5)
self.skipButton.pack(side='left', padx=5,pady=5)
self.quitButton.pack(side='left', padx=5,pady=5)
self.populateUI(*self.get_populate_items())
def get_populate_items(self):
return (emp[self.cursor][0], emp[self.cursor][1])
def populateUI(self, title, labelList):
"""
Creates and packs a list of Checkbuttons (cbList) into the innerFrame
By default, the first Checkbutton will be unchecked, all others checked.
You should help the boss out by passing the best employee at the head of the list
"""
for child in self.innerFrame.winfo_children():
child.destroy()
self.instructLabel.config(text = title + ' department:\nSelect the employees you wish to FIRE')
self.cbList = [None] * len(labelList)
self.cbValues = [tk.BooleanVar() for i in range(len(labelList))]
for i in range(len(labelList)):
self.cbList[i] = tk.Checkbutton( self.innerFrame,
text=labelList[i],
variable = self.cbValues[i])
if i: self.cbList[i].select() # Check subsequent buttons by default
self.cbList[i].pack(anchor = 'w', padx=5,pady=5)
def advance(self):
if (self.cursor < len(emp) - 1):
self.cursor += 1
else:
self.cursor = 0
self.populateUI(*self.get_populate_items())
def querySelection(self):
return [x.get() for x in self.cbValues]
def executeSelection(self):
fired = self.querySelection()
if ( not all(x for x in fired) or
messagebox.askokcancel(message='Fire ALL the employees in the department?')
):
for i in range(len(self.cbList)):
empName = self.cbList[i].cget('text')
if fired[i]:
emp[self.cursor][1].remove(empName)
print('Sorry, '+ empName + ', but we have to let you go.', flush=True)
else:
print('See you Monday, '+ empName, flush=True)
self.populateUI(*self.get_populate_items())
# self.advance()
def onFrameConfigure(self, event):
"""Reset the scroll region to encompass the inner frame"""
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def main():
root = tk.Tk()
root.geometry("400x250+250+100") # width x height + xOffset + yOffset
app = bossWidget(root)
root.mainloop()
# while emp:
# department, employees = emp.popitem()
# app.pack(side='top',fill='both',expand=True)
# app.populateUI(title = department, labelList = employees)
# root.mainloop()
# try:
# root.destroy()
# except tk.TclError:
# pass # if run in my IDE, the root already is destroyed
if __name__ == '__main__':
main()
The basic pattern is to have a class or a function for each frame. Each of these classes or functions creates a single Frame, and places all of its widgets in that frame.
Then, all you need to do to switch frames is delete the current frame, and call the function or object to create the new frame. It's as simple as that.
Some examples on this site:
Switching between frames in python with functions
Switch between two frames in tkinter
I accepted Pythonista's answer but eventually wound up doing the following:
the UI constructor gets the data as an argument (perhaps better practice than the global data variable)
the UI populator deletes any existing labels first (see accepted answer)
the UI populator then pops a record off (if remaining, otherwise terminate)
the execute button calls the UI populator after doing its other tasks
the skip button just calls the UI populator (thus the advance function could be removed entirely)
This is what I wound up using. As Pythonista said, it's messy, but we all have to start somewhere.
# Example data
emp = {'Sales':['Alice','Bryan','Cathy','Dave'],
'Product':['Elizabeth','Frank','Gordon','Heather',
'Irene','John','Kristof','Lauren'],
'Marketing':['Marvin'],
'Accounting':['Nancy','Oscar','Peter','Quentin',
'Rebecca','Sally','Trevor','Umberto',
'Victoria','Wally','Xavier','Yolanda',
'Zeus']}
import tkinter as tk
from tkinter import messagebox
class bossWidget(tk.Frame):
def __init__(self, root, data):
"""
Scrollbar code credit to Bryan Oakley:
https://stackoverflow.com/a/3092341/2573061
"""
super().__init__()
self.canvas = tk.Canvas(root, borderwidth=0)
self.frame = tk.Frame(self.canvas)
self.scroll = tk.Scrollbar(root, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.scroll.set)
self.scroll.pack(side="right", fill="y")
self.canvas.pack(side="left", fill="both", expand=True)
self.canvas.create_window((4,4), window=self.frame, anchor="nw",
tags="self.frame")
self.frame.bind("<Configure>", self.onFrameConfigure)
self.data = data
self.initUI()
def initUI(self):
"""
Creates the static UI content and the innerFrame that will hold the
dynamic UI content (i.e., the Checkbuttons for the copies)
"""
self.master.title("Boss Interface")
self.instructLabel = tk.Label( self.frame, justify='left',
text = "Select the employees you wish to FIRE")
self.skipButton = tk.Button( self.frame, text="Skip Department",
command = self.populateUI)
self.deleteButton = tk.Button( self.frame, text="Fire employees", fg = 'red',
command = self.executeSelection )
self.quitButton = tk.Button( self.frame, text="Exit", command=self.frame.quit)
self.innerFrame = tk.Frame( self.frame)
self.instructLabel.pack(anchor = 'nw', padx=5,pady=5)
self.innerFrame.pack(anchor='nw', padx=5, pady=20, expand=True)
self.deleteButton.pack(side='left', padx=5,pady=5)
self.skipButton.pack(side='left', padx=5,pady=5)
self.quitButton.pack(side='left', padx=5,pady=5)
self.populateUI()
def populateUI(self):
"""
Creates and packs a list of Checkbuttons (cbList) into the innerFrame
By default, the first Checkbutton will be unchecked, all others checked.
You should help the boss out by passing the best employee at the head of the list
"""
for child in self.innerFrame.winfo_children():
child.destroy()
try:
title, labelList = self.data.popitem()
self.instructLabel.config(text = title + ' department:\nSelect the employees you wish to FIRE')
self.cbList = [None] * len(labelList)
self.cbValues = [tk.BooleanVar() for i in range(len(labelList))]
for i in range(len(labelList)):
self.cbList[i] = tk.Checkbutton( self.innerFrame,
text=labelList[i],
variable = self.cbValues[i])
if i: self.cbList[i].select() # Check subsequent buttons by default
self.cbList[i].pack(anchor = 'w', padx=5,pady=5)
except KeyError:
messagebox.showinfo("All done", "You've purged all the departments. Good job, boss.")
self.frame.quit()
def querySelection(self):
return [x.get() for x in self.cbValues]
def executeSelection(self):
fired = self.querySelection()
if ( not all(x for x in fired) or
messagebox.askokcancel(message='Fire ALL the employees in the department?')
):
for i in range(len(self.cbList)):
empName = self.cbList[i].cget('text')
if fired[i]:
print('Sorry, '+ empName + ', but we have to let you go.', flush=True)
else:
print('See you Monday, '+ empName, flush=True)
self.populateUI()
def onFrameConfigure(self, event):
"""Reset the scroll region to encompass the inner frame"""
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def main():
root = tk.Tk()
root.geometry("400x250+250+100") # width x height + xOffset + yOffset
app = bossWidget(root, data=emp)
app.mainloop()
try:
root.destroy()
except tk.TclError:
pass # if run in my IDE, the root already is destroyed
if __name__ == '__main__':
main()

Python - Inheritance from a Tk class

I'm trying to create a modular class ( for some gui buttons ).
CoreButton should consist of most methods for a common button, including tk frame.
Goal is to inheret CoreButton - and to use its frame to build rest of button's GUI - it does not appear.
any help will be appriciated
class CoreButton(ttk.Frame):
def __init__(self, master,nickname, hw_in=[], hw_out=[],ip_in='', ip_out='', sched_vector=[]):
ttk.Frame.__init__(self, master)
self.master = master
if ip_in == '': ip_in = ip_out # in case remote input is not defined
self.grid()
#####Rest of code
and class that inherits:
class ToggleBut2(CoreButton):
def __init__(self, master, hw_in=[], hw_out=[],ip_in='', ip_out='', sched_vector=[]):
CoreButton.__init__(self, master, nickname="JOHM", hw_in=hw_in, hw_out=hw_out, ip_in=ip_in, ip_out=ip_out, sched_vector=sched_vector)
self.master = master
def build_gui(self, nickname='babe', height=3, width=13):
self.button = tk.Checkbutton(self, text=nickname, variable=self.but_var, indicatoron=0, height=height, width=width, command=self.sf_button_press)
self.button.grid(row=0, column=0)
I don't know what you try to do but I would do something like this
I don't use self.grid() inside class, so outside class I can use tb1.pack() or tb1.grid() depends on which layout manager I use in window.
In __init__ I execute self.build_gui() so I don't have to do it manually, but now all classes have to create self.build_gui() without arguments.
I add Label only for test - to display "selected"/"not selected". You don't need it.
import tkinter as tk
from tkinter import ttk
class CoreButton(ttk.Frame):
def __init__(self, master, nickname, hw_in=None, hw_out=None, ip_in=None, ip_out=None, sched_vector=None):
ttk.Frame.__init__(self, master)
self.nickname = nickname
self.hw_in = hw_in
if self.hw_in is None:
self.hw_in = []
#self.hw_in = hw_in or []
self.hw_out = hw_out
if self.hw_out is None:
self.hw_out = []
#self.hw_out = hw_out or []
self.ip_out = ip_out
self.ip_in = ip_in
if self.ip_in is None:
self.ip_in = self.ip_out # in case remote input is not defined
#self.ip_in = hw_in or self.ip_out
self.sched_vector = sched_vector
if sched_vector is None:
sched_vector = []
#self.sched_vector = sched_vector or []
self.build_gui() # <--- to build it automatically
def build_gui(self):
# you will overide it in child widgets
raise NotImplementedError('You have to override method build_gui()')
class ToggleBut2(CoreButton):
def __init__(self, master, hw_in=None, hw_out=None, ip_in=None, ip_out=None, sched_vector=None, height=3, width=13):
self.height = height
self.width = width
# `self.but_var` is used in `build_gui` so it has to be created before `__init__` which executes `build_gui`
# or create it directly in `build_gui`
#self.but_var = tk.StringVar()
CoreButton.__init__(self, master, "JOHM", hw_in, hw_out, ip_in, ip_out, sched_vector)
def build_gui(self, nickname='babe'):
self.but_var = tk.IntVar()
self.button = tk.Checkbutton(self, text=self.nickname, variable=self.but_var, indicatoron=0, height=self.height, width=self.width, command=self.sf_button_press)
self.button.grid(row=0, column=0)
self.label = tk.Label(self, text='[not selected]')
self.label.grid(row=1, column=0)
def sf_button_press(self):
print(self.but_var.get())
if self.but_var.get() == 0:
self.label['text'] = '[ not selected ]'
else:
self.label['text'] = '[ selected ]'
# --- main ---
root = tk.Tk()
tb1 = ToggleBut2(root, height=1, width=10)
tb1.pack()
tb2 = ToggleBut2(root, height=3, width=30)
tb2.pack()
tb2 = ToggleBut2(root, height=5, width=50)
tb2.pack()
root.mainloop()

Categories