Scrollbar with tkinter messing up - python

The code below was originally developed to understand how to render a list of urls in a frame and access them by clicking on them. I also had a few buttons to score them. In my project, I have now a long number of urls and need a scrollbar to access them. I have added one, but the urls are not rendered in the part of the frame where the scollbar can be used. In fact, I would like the entire bottomframe to be linked to the scrollbar and used to render the list of urls. I do not understand what I have done wrong. Could anyone help me please?
EDIT: following the answer below, the code below is now working
import tkinter as tk
import webbrowser
class Pierre:
"""This class holds all the objects, data and functions for a single line"""
def __init__(self, master, url):
self.url = url
self.counter = 0
_, i = master.grid_size() # get the current row number
lbl = tk.Label(master, text=url, fg="blue", cursor="hand2")
lbl.grid(row=i, column=0)
lbl.bind("<Button-1>", self.callback)
self.DisplayButton = tk.Button(master, text = self.counter)
self.DisplayButton.grid(row=i, column=1)
self.DisplayButton.config(height = 1, width = 1 )
self.Plus1Button = tk.Button(master, text = "+1", command=self.plus1, bg="green")
self.Plus1Button.grid(row=i, column=2)
self.Plus1Button.config(height = 1, width = 1 )
self.Neg1Button = tk.Button(master, text = "-1", command=self.neg1, bg="green")
self.Neg1Button.grid(row=i, column=3)
self.Neg1Button.config(height = 1, width = 1 )
master.update_idletasks()
def plus1(self):
self.counter += 1
self.DisplayButton["text"]=str(self.counter)
def neg1(self):
self.counter -= 1
self.DisplayButton["text"]=str(self.counter)
def callback(self, event):
webbrowser.open_new(self.url)
class TestClass(tk.Tk):
def __init__(self, **kwargs):
tk.Tk.__init__(self, **kwargs)
self.title('Test')
self.topframe = tk.Frame(self)
self.topframe.pack( side = tk.TOP, pady=30)
self.bottomframe = tk.Frame(self, width=250, height=190, bg="#EBEBEB")
self.bottomframe.pack( side = tk.BOTTOM )
self.canvas = tk.Canvas(self.bottomframe, width=250, height=190,
scrollregion=(0, 0, 1200, 800))
self.canvas.grid()
self.vscrollbar = tk.Scrollbar(self.bottomframe, orient=tk.VERTICAL,
command=self.canvas.yview)
self.vscrollbar.grid(row=0, column=5, sticky=tk.N+tk.S)
self.canvas['yscrollcommand'] = self.vscrollbar.set
self.frameCanvas=tk.Frame(self.canvas)
self.canvas.create_window((0,0),window=self.frameCanvas,anchor='nw')
#self.canvas.configure(scrollregion=self.canvas.bbox("all"))
self.button = tk.Button(self.topframe, text='Click', command = self.output_value)
self.button.pack(side="left", fill="both", expand=True)
####define the function that the submit button will do
def output_value(self):
urls = ["http://www.google.com", "http://www.facebook.com","http://www.google.com", "http://www.facebook.com", "http://www.google.com", "http://www.facebook.com"]
for url in urls:
Pierre(self.frameCanvas, url)
if __name__ == "__main__":
root = TestClass()
root.mainloop()

Frames do not have scrolling ability. The usual way is to use a canvas and then to place widgets on the canvas with create_window().

Related

Tkinter: drag and drop app only drop if the dragged text is selected in not under the cursor and show error for all widget instead of one

I'm working on a tkinter drag and drop app, my code is working just fine but only at one condition:
that the dragged text widget, when selected to be moved, is not under the cursor (so if the text widget is dragged by a word it doesn't work).
I've tried to fix the issue by adding some negative value to the y axis to make the selection always a little bit above, but it kinda create a weird distance if the text widget is dragged by the bottom.
So I was wondering if there is a better way to add some space.
Also I'd like to show the "Not empty" error only when the text is dragged in the self.dad_text
and is not empty, while right now it appear for every text widget. Is there a way to fix that?
This is my code
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.first_list = ["11:00", "03:43", "22:04", "17:21", "22:35", "07:01", "16:11", "09:00"]
self.second_list = ["morning\nmorning\nmorning", "afternoon\nafternoon\nafternoon", "evening\nevening\nevening", "nigth\nnight\nnight"]
self.w_list = []
self.first_frame = tk.Frame(root)
self.first_frame.pack(padx=15, pady=15, fill="both", expand="true", side="left", anchor="w")
self.second_frame = tk.Frame(root)
self.second_frame.pack(padx=15, pady=15, fill="both", expand="true", side="left")
self.up()
def up(self):
for n, hour in enumerate(self.first_list):
self.list_one_frame = tk.LabelFrame(self.first_frame)
self.list_one_frame.grid(row=n, column=0, padx=10, pady=10)
self.one_text = tk.Text(self.list_one_frame, width=10, height=2)
self.one_text.grid(row=0, column=0,padx=10, pady=10)
self.one_text.insert("end", hour)
self.one_text.config(state="disabled")
self.dad_text = tk.Text(self.list_one_frame, width=20, height=1)
self.dad_text.grid(row=0, column=1, padx=10, pady=10, ipady=7)
for n, day in enumerate(self.second_list):
self.second_text = tk.Text(self.second_frame, width=9, height=3)
self.second_text.grid(row=n, column=1, pady=10, padx=10)
self.second_text.insert("end", day)
self.second_text.bind('<Button-1>', self.click)
self.second_text.bind('<B1-Motion>',self.drag)
self.second_text.bind('<ButtonRelease-1>',self.release)
def click(self, event):
self.widget_check = event.widget
self.text = event.widget.get("1.0",'end-1c')
root.clipboard_clear()
root.clipboard_append(self.text)
self.top = tk.Toplevel(root)
self.top.attributes('-alpha', 0.7)
self.top.overrideredirect(True)
self.top._offsetx = event.x
self.top._offsety = event.y
x = self.top.winfo_pointerx() - self.top._offsetx
y = self.top.winfo_pointery() - self.top._offsety - 50
self.top.geometry('+{x}+{y}'.format(x=x,y=y))
label = tk.Label(self.top, text=self.text)
label.grid(row=0, column=0)
def drag(self,event):
widget = event.widget.winfo_containing(event.x_root, event.y_root)
x = self.top.winfo_pointerx() - self.top._offsetx
y = self.top.winfo_pointery() - self.top._offsety - 50
self.top.geometry(f"+{x}+{y}")
def release(self, event):
widget = event.widget.winfo_containing(event.x_root, event.y_root)
try:
if widget.get('1.0', 'end-1c') != "":
messagebox.showerror("Error", "Not empty")
else:
reference_clipboard = root.clipboard_get()
widget.config(state="normal")
widget.insert("end", f"{reference_clipboard}")
widget.config(state="disabled")
self.top.destroy()
except:
self.top.destroy()
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root).pack(side="top", fill="both", expand=True)
root.mainloop()
Thank you!

Tkinter: How to insert text into text widget that's part of a Frame?

I need help inserting text in a Text widget that's part of a Frame.
class Test_frame(Frame):
def __init__(self):
Frame.__init__(self)
self.config(width = 700, height = 450)
self.logs_frame = LabelFrame(self, height = 402, width = 348, text = 'Logs')
self.logs_frame.grid(column = 1, row = 0, pady=10, sticky = N)
self.logs_frame.grid_propagate(0)
self.text_box = Text(self.logs_frame, width=40, pady=10, height=22)
self.text_box.pack(side="left")
self.scroll_y = Scrollbar(self.logs_frame, orient="vertical", command=self.text_box.yview)
self.scroll_y.pack(side="left", expand=True, fill="y")
self.text_box.configure(yscrollcommand=self.scroll_y.set)
self.text_box.insert (?????.END, "Sample Text")
I have no idea how to access that Text widget now.
Test_frame is part of a Notebook:
class App(Tk):
def __init__(self):
Tk.__init__(self)
self.my_notebook = ttk.Notebook(self)
self.my_notebook.pack(pady = 5)
self.frames = {}
for F in (Test_frame, Final_flash_frame):
frame = F()
self.frames[F] = frame
frame.pack(fill = 'both', expand = 1)
self.my_notebook.add(self.frames[Test_frame], text = "Testing")
I created a minimal source, but I guess you solved your problem.
I added a little button to add text, and added some text from outside the class.
Here it is, anyway:
import tkinter as Tk
import tkinter.ttk as ttk
from tkinter import *
def BumpText(textbox):
textbox.insert(END,", MORE TEXT")
class Test_frame(Frame):
def __init__(self):
Frame.__init__(self)
self.config(width = 700, height = 450)
self.logs_frame = LabelFrame(self, height = 402, width = 348, text = 'Logs')
self.logs_frame.grid(column = 1, row = 0, pady=10, sticky = N)
self.logs_frame.grid_propagate(0)
self.text_box = Text(self.logs_frame, width=40, pady=10, height=22)
self.text_box.pack(side="left")
self.scroll_y = Scrollbar(self.logs_frame, orient="vertical", command=self.text_box.yview)
self.scroll_y.pack(side="left", expand=True, fill="y")
self.text_box.configure(yscrollcommand=self.scroll_y.set)
self.text_box.insert(END, "Sample Text")
self.button = Button(self.logs_frame, text="Add\nText", command=lambda:self.text_box.insert(END, ", More Text"))
self.button.pack(side="bottom")
class App(Tk):
def __init__(self):
Tk.__init__(self)
self.my_notebook = ttk.Notebook(self)
self.my_notebook.pack(pady = 5)
self.frames = {}
for F in (Test_frame,): # , Final_flash_frame):
frame = F()
self.frames[F] = frame
frame.pack(fill = 'both', expand = 1)
self.my_notebook.add(self.frames[Test_frame], text = "Testing")
for F,f in self.frames.items():
try:
f.text_box.insert(END," (Added by App)")
except Exception as e:
pass # ignore frames without text_box
if __name__ == "__main__":
App().mainloop()
Ok, so apparently just using:
self.text_box.insert (END, "Sample Text")
works
Cheers,
A

Why do my grid weights not affect the layout of window

I am writing some code for a program with a GUI, and part of this is this code:
self.chatbox = Text(self.chatframe)
self.messagebox = Entry(self.sendframe, textvariable=self.message)
self.sendbutton = Button(self.sendframe, text="Send", font=self.font(12))
self.chatframe.grid_rowconfigure(0, weight=1)
self.sendframe.grid_columnconfigure(0, weight=19)
self.sendframe.grid_columnconfigure(1, weight=1)
self.chatbox.grid(row=0, column=0)
self.messagebox.grid(row=0, column=0)
self.sendbutton.grid(row=0, column=1)
self.sendframe.grid(row=1, column=0)
self.mainframe.grid_columnconfigure(0, weight=1)
self.mainframe.grid_columnconfigure(1, weight=9)
self.mainframe.grid_rowconfigure(0, weight=1)
self.menu.grid(row=0, column=0)
self.chatframe.grid(row=0, column=1)
However when I run it, it always ends up only taking up some space and not filling the screen as I would expect. Any help appreciated.
Full Code:
from tkinter import *
import tkinter.messagebox
import os
class ServerInfo():
def __init__(self):
self.network = ""
self.host = False
self.name = StringVar()
self.name.set("")
class App(Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.master.state("zoomed")
#self.master.minsize(1200, 600)
self.master.title("Chatroom App")
self.serverinfo = ServerInfo()
self.buffer_length = 2048
self.message = StringVar()
self.message.set("")
self.mainmenu = Frame(self.master)
self.localframe = Frame(self.master)
self.publicframe = Frame(self.master)
self.mainframe = Frame(self.master)
self.chatframe = Frame(self.mainframe)
self.sendframe = Frame(self.chatframe)
self.choiceframe = Frame(self.master)
self.inputframe = Frame(self.master)
self.create_widgets()
def font(self, size):
return ("Alef", size)
def create_widgets(self):
self.title = Label(self.mainmenu, text="The Chatroom App", font=self.font(40))
self.localbutton = Button(self.mainmenu, text="Local Chatrooms", font=self.font(16), command=self.go_to_local)
self.publicbutton = Button(self.mainmenu, text="Public Chatrooms", font=self.font(16), command=self.go_to_public)
self.exitbutton = Button(self.mainmenu, text="Exit", font=self.font(16), command=self.master.destroy)
self.title.pack(fill=BOTH)
self.localbutton.pack(pady=20, fill=BOTH)
self.publicbutton.pack(pady=20, fill=BOTH)
self.exitbutton.pack(side=BOTTOM, fill=BOTH)
self.instruction = Label(self.choiceframe, text="Would you like to host a server or search for available servers", font=self.font(26))
self.hostbutton = Button(self.choiceframe, text="Host server", font=self.font(32), command=self.host_input)
self.joinbutton = Button(self.choiceframe, text="Join server", font=self.font(32), command=self.join_input)
self.backbutton = Button(self.choiceframe, text="Back", font=self.font(32), command=self.back_from_choice)
self.instruction.pack(pady=20, fill=BOTH)
self.hostbutton.pack(pady=10, fill=BOTH)
self.joinbutton.pack(pady=10, fill=BOTH)
self.backbutton.pack(pady=20, fill=BOTH)
self.instruction2 = Label(self.inputframe, text="", font=self.font(18))
self.server_input = Entry(self.inputframe, textvariable=self.serverinfo.name)
self.continuebutton = Button(self.inputframe, text="", font=self.font(28), command=self.take_input)
self.backbutton2 = Button(self.inputframe, text="Back", font=self.font(28), command=self.back_from_input)
self.instruction2.pack(pady=10, fill=BOTH)
self.server_input.pack(pady=40, fill=BOTH)
self.continuebutton.pack(pady=10, fill=BOTH)
self.backbutton2.pack(pady=20, fill=BOTH)
self.menu = Canvas(self.mainframe, bg="Black")
self.chatbox = Text(self.chatframe)
self.messagebox = Entry(self.sendframe, textvariable=self.message)
self.sendbutton = Button(self.sendframe, text="Send", font=self.font(12))
self.chatframe.grid_rowconfigure(0, weight=1)
self.sendframe.grid_columnconfigure(0, weight=19)
self.sendframe.grid_columnconfigure(1, weight=1)
self.chatbox.grid(row=0, column=0)
self.messagebox.grid(row=0, column=0)
self.sendbutton.grid(row=0, column=1)
self.sendframe.grid(row=1, column=0)
self.mainframe.grid_columnconfigure(0, weight=1)
self.mainframe.grid_columnconfigure(1, weight=9)
self.mainframe.grid_rowconfigure(0, weight=1)
self.menu.grid(row=0, column=0)
self.chatframe.grid(row=0, column=1)
self.mainmenu.pack()
def back_from_choice(self):
self.choiceframe.forget()
self.mainmenu.pack()
window.update()
def back_from_input(self):
self.inputframe.forget()
self.choiceframe.pack()
def take_input(self):
self.inputframe.forget()
self.mainframe.pack(fill=BOTH)
def go_to_local(self):
self.serverinfo.network = "local"
self.mainmenu.forget()
self.choiceframe.pack()
window.update()
def go_to_public(self):
self.serverinfo.network = "public"
tkinter.messagebox.showinfo("Message from the developer", "This feature is still under development")
def host_input(self):
self.serverinfo.host = True
self.instruction2.config(text="Type in the name of your server. When the server is created, a server ID will show in the top left. Share this to people who want to join the server")
self.continuebutton.config(text="Host Server")
self.choiceframe.forget()
self.inputframe.pack()
def join_input(self):
self.serverinfo.host = False
self.instruction2.config(text="Type in the server ID of the server you want to join")
self.continuebutton.config(text="Join Server")
self.choiceframe.forget()
self.inputframe.pack()
def host_server(self):
pass
def join_server(self):
pass
def write_message_to_screen(self, data):
print(data)
def encode_id(self, server_id):
return server_id
def decode_id(self, server_id):
return server_id
Weights only affect how grid allocates extra space once everything has been laid out, it doesn't describe the overall relative size of widgets.
You also don't seem to be using the sticky attribute, so space may be allocated to widgets but you aren't requesting that they stretch to fill the space given to them.

How to replace an existing image instead of adding additional images?

This is my first time here, and I would really appreciate some help with this.
So I have some code which runs a Tkinter tab and shows 2 buttons. If you click the first one, a picture of a cat appears.
However, if you click the button again, the same picture appears again at the bottom, making there 2.
If I click the other button, titled N/A, a different picture appears. But if you click the button again, the picture duplicates.
I want to make it so that when each button is pressed, the image is replaced, not duplicated.
Here is what I have so far.
from tkinter import *
root = Tk()
class HomeClass(object):
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.WelcomeLabel = Label(root, text="Welcome to the game!",
bg="Black", fg="White")
self.WelcomeLabel.pack(fill=X)
self.FirstButton = Button(root, text="Start", bg="RED", fg="White",
command=self.FirstClick)
self.FirstButton.pack(side=LEFT, fill=X)
self.SecondButton = Button(root, text="N/A", bg="Blue", fg="White",
command=self.SecondClick)
self.SecondButton.pack(side=LEFT, fill=X)
def FirstClick(self):
FirstPhoto = PhotoImage(file="keyboardcat.gif")
FiLabel = Label(root, image=FirstPhoto)
FiLabel.img = FirstPhoto
FiLabel.pack()
def SecondClick(self):
FirstPhoto = PhotoImage(file="donald.gif")
FiLabel = Label(root, image=FirstPhoto)
FiLabel.img = FirstPhoto
FiLabel.pack()
k = HomeClass(root)
root.mainloop()
That's becouse every time you click a button, you're calling FirstClick method which in turn creates new instance of PhotoImage class. I think it would be better to store FirstPhoto and in every FirstClick method call check if it is already has value or not.
class HomeClass(object):
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.WelcomeLabel = Label(root, text="Welcome to the game!",
bg="Black", fg="White")
self.WelcomeLabel.pack(fill=X)
self.FirstButton = Button(root, text="Start", bg="RED", fg="White",
command=self.FirstClick)
self.FirstButton.pack(side=LEFT, fill=X)
self.SecondButton = Button(root, text="N/A", bg="Blue", fg="White",
command=self.SecondClick)
self.SecondButton.pack(side=LEFT, fill=X)
self.FirstPhoto = None
def FirstClick(self):
if self.FirstPhoto is None:
self.FirstPhoto = PhotoImage(file="ksiazka.png")
self.FiLabel = Label(root, image=self.FirstPhoto)
self.FiLabel.img = self.FirstPhoto
self.FiLabel.pack()
Try this to change SecondPhoto
def SecondClick(self):
if self.SecondPhoto is None:
self.SecondPhoto = PhotoImage(file="ksiazka.png")
self.SecondPhotoLabel = Label(root, image=self.FirstPhoto)
self.SecondPhotoLabel.img = self.SecondPhoto
self.SecondPhotoLabel.pack()
Else:
self.SecondPhotoLabel.config(image='newimage')
self.SecondPhotoLabel.update()
Note - you can declare the newImage before as you have to read it with PhotoImage and then just put the image name in the .config
In this example you have two methods FirstClick, SecondClick to display an image and two methods to clear an first and second image accordingly: clearFirstImage, clearSecondImage. You just have to add two buttons to trigger those clear methods :)
from tkinter import *
from tkFileDialog import askopenfilename
root = Tk()
class HomeClass(object):
def __init__(self, master):
self.master = master
self.frame = Frame(master)
self.WelcomeLabel = Label(root, text="Welcome to the game!",
bg="Black", fg="White")
self.WelcomeLabel.pack(fill=X)
self.FirstButton = Button(root, text="Start", bg="RED", fg="White",
command=self.FirstClick)
self.FirstButton.pack(side=LEFT, fill=X)
self.SecondButton = Button(root, text="N/A", bg="Blue", fg="White",
command=self.SecondClick)
self.SecondButton.pack(side=LEFT, fill=X)
self.ToggleButtonText = "Show image"
self.ToggleButton = Button(root, text=self.ToggleButtonText, bg="Grey", fg="White",
command=self.ToggleClick)
self.ToggleButton.pack(side=LEFT, fill=X)
self.FirstPhoto = None
self.FiLabel = None
self.SecondPhoto = None
self.SecondPhotoLabel = None
self.ToggleButtonPhoto = None
self.ToggleButtonPhotoLabel = None
self.frame.pack()
def FirstClick(self):
if self.FirstPhoto is None:
self.FirstPhoto = PhotoImage(file="ksiazka.png")
self.FiLabel = Label(root, image=self.FirstPhoto)
self.FiLabel.img = self.FirstPhoto
self.FiLabel.pack()
def ToggleClick(self):
if self.ToggleButtonPhoto is None:
self.ToggleButtonPhoto = PhotoImage(file="ksiazka.png")
self.ToggleButtonPhotoLabel = Label(self.frame, image=self.ToggleButtonPhoto)
self.ToggleButtonPhotoLabel.img = self.ToggleButtonPhoto
self.ToggleButtonPhotoLabel.pack()
# and set label
self.ToggleButton.config(text="Hide image")
else:
self.ToggleButton.config(text="Show image")
self.ToggleButtonPhotoLabel.destroy()
self.ToggleButtonPhotoLabel.img = None
self.ToggleButtonPhotoLabel = None
self.ToggleButtonPhoto = None
self.frame.pack()
def SecondClick(self):
filename = askopenfilename()
allowed_extensions = ['jpg', 'png']
if len(filename) > 0 and filename.split('.')[-1] in allowed_extensions:
self.SecondPhoto = PhotoImage(file=filename)
self.SecondPhotoLabel = Label(root, image=self.SecondPhoto)
self.SecondPhotoLabel.img = self.SecondPhoto
self.SecondPhotoLabel.pack()
def clearFirstImage(self):
self.FirstPhoto = None
self.FiLabel = None
def clearSecondImage(self):
self.SecondPhoto = None
self.SecondPhotoLabel = None
k = HomeClass(root)
root.mainloop()
If you want to replace an existing image, first create the label the image is displayed in, and simply reconfigure only its image option with each click. Below is an example that does that:
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
except ImportError:
import Tkinter as tk
def download_images():
# In order to fetch the image online
try:
import urllib.request as url
except ImportError:
import urllib as url
url.urlretrieve("https://i.stack.imgur.com/57uJJ.gif", "13.gif")
url.urlretrieve("https://i.stack.imgur.com/8LThi.gif", "8.gif")
class ImageFrame(tk.Frame):
def __init__(self, master, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self._create_widgets()
self._display_widgets()
def _create_widgets(self):
def __create_image_label():
def ___load_images():
self.label.images = list()
self.label.images.append(tk.PhotoImage(file="8.gif"))
self.label.images.append(tk.PhotoImage(file="13.gif"))
self.label = tk.Label(self)
___load_images()
def __create_buttons():
self.buttons = list()
for i in range(2):
self.buttons.append(tk.Button(self, text=i,
command=lambda i=i: self.replace_image(i)))
__create_image_label()
__create_buttons()
def replace_image(self, button_index):
"""
Replaces the image in label attribute based on the index of the
button pressed.
"""
self.label['image'] = self.label.images[button_index]
def _display_widgets(self):
self.label.pack()
for i in range(2):
self.buttons[i].pack(fill='x', expand=True)
if __name__ == '__main__':
#download_images() # comment out after initial run
root = tk.Tk()
frame = ImageFrame(root)
frame.pack()
tk.mainloop()

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

Categories