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()
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:
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()
I accepted Pythonista's answer but eventually wound up doing the following:
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()
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.