Related
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!
Working on an internal app and trying to get a scrollbar to "FGCs" labelFrame only, in addition, I need to scale the frame to the app size.
I'll be happy if anyone could assist me in refactoring this and create the scrollbar.
I know that I need to create a canvas or something, but do not know how.
This is a first time for me with Tkinter.
from tkinter import *
import os, subprocess
from tkinter import ttk
managment = ["MAIN","NAV","OUTPUT","OUTPUTSPARE","GATEWAY","Sync1","Sync2"]
servers = ["recon01","recon02","recon03","recon04","recon05","recon06","recon07","recon08","recon09","recon10","recon11","recon12","render01","render02","storinator"]
FGCs =["fgc01","fgc02","fgc03","fgc04","fgc05","fgc06","fgc07","fgc08","fgc09","fgc10","fgc11","fgc12","fgc13","fgc14","fgc15","fgc16","fgc17","fgc18","fgc19","fgc20","fgc21","fgc22","fgc23","fgc24","fgc25","fgc26","fgc27","fgc28","fgc29","fgc30","fgc31","fgc32","fgc33","fgc34","fgc35","fgc36","fgc37","fgc38","fgcspare1","fgcspare2","fgcspare3","fgcspare4"]
tests = ["Network", "GPU", "Time", "Gidel", "Snapshot", "Disks"]
testbuttons = []
serverbuttons = []
managebuttons = []
fgcbuttons = []
class mainwindow:
global test
global testbuttons
global managment
global managebuttons
global FGCs
def __init__(self,master):
self.master = master
master.title("System Report Tool")
master.geometry("1000x500")
self.Tests = ttk.LabelFrame(root, text="Tests")
self.Tests.pack(expand="no",anchor=W,pady=20)
for test in tests:
testcheck = ttk.Checkbutton(self.Tests, text=test)
testcheck.pack(side=LEFT, anchor="n")
testbuttons.append(testcheck)
def select_tests():
for test in testbuttons:
test.invoke()
selecttests = ttk.Checkbutton(root, text="Select All",command=select_tests)
selecttests.place(x=260,y=16)
self.Managment = ttk.LabelFrame(root, text="Managment")
self.Managment.configure(width=10, height=20)
self.Managment.pack(expand="no",anchor=W)
for manage in managment:
managecheck = ttk.Checkbutton(self.Managment, text=manage)
managecheck.pack(side=TOP, anchor="w")
managebuttons.append(managecheck)
def select_manage():
for manage in managebuttons:
manage.invoke()
selectmanage = ttk.Checkbutton(self.Managment, text="Select All",command=select_manage)
selectmanage.pack()
self.FGCs = ttk.LabelFrame(root, text="FGCs")
self.FGCs.configure(width=10, height=20)
self.FGCs.place(x=120,y=80)
for fgc in FGCs:
fgccheck = ttk.Checkbutton(self.FGCs, text=fgc)
fgccheck.pack(side=TOP, anchor="w")
fgcbuttons.append(fgccheck)
def select_fgc():
for fgc in fgcbuttons:
fgc.invoke()
selectfgc = ttk.Checkbutton(self.FGCs, text="Select All",command=select_fgc)
selectfgc.pack()
root = Tk()
Window = mainwindow(root)
root.mainloop()
Thanks in advance.
Copy code with class ScrolledFrame from scrolled-frame-canvas
Create ScrolledFrame inside LabelFrame self.FGCs
self.sf = ScrolledFrame(self.FGCs)
self.sf.pack()
And then put widgets in inner element in ScrolledFrame instead of putting them in self.FGCs like
fgccheck = ttk.Checkbutton(self.sf.inner, text=fgc)
Working code
import os
import subprocess
import tkinter as tk
from tkinter import *
from tkinter import ttk
class ScrolledFrame(tk.Frame):
def __init__(self, parent, vertical=True, horizontal=False):
super().__init__(parent)
# canvas for inner frame
self._canvas = tk.Canvas(self)
self._canvas.grid(row=0, column=0, sticky='news') # changed
# create right scrollbar and connect to canvas Y
self._vertical_bar = tk.Scrollbar(self, orient='vertical', command=self._canvas.yview)
if vertical:
self._vertical_bar.grid(row=0, column=1, sticky='ns')
self._canvas.configure(yscrollcommand=self._vertical_bar.set)
# create bottom scrollbar and connect to canvas X
self._horizontal_bar = tk.Scrollbar(self, orient='horizontal', command=self._canvas.xview)
if horizontal:
self._horizontal_bar.grid(row=1, column=0, sticky='we')
self._canvas.configure(xscrollcommand=self._horizontal_bar.set)
# inner frame for widgets
self.inner = tk.Frame(self._canvas)
self._window = self._canvas.create_window((0, 0), window=self.inner, anchor='nw')
# autoresize inner frame
self.columnconfigure(0, weight=1) # changed
self.rowconfigure(0, weight=1) # changed
# resize when configure changed
self.inner.bind('<Configure>', self.resize)
# resize inner frame to canvas size
self.resize_width = False
self.resize_height = False
self._canvas.bind('<Configure>', self.inner_resize)
def resize(self, event=None):
self._canvas.configure(scrollregion=self._canvas.bbox('all'))
def inner_resize(self, event):
# resize inner frame to canvas size
if self.resize_width:
self._canvas.itemconfig(self._window, width=event.width)
if self.resize_height:
self._canvas.itemconfig(self._window, height=event.height)
managment = ["MAIN","NAV","OUTPUT","OUTPUTSPARE","GATEWAY","Sync1","Sync2"]
servers = ["recon01","recon02","recon03","recon04","recon05","recon06","recon07","recon08","recon09","recon10","recon11","recon12","render01","render02","storinator"]
FGCs =["fgc01","fgc02","fgc03","fgc04","fgc05","fgc06","fgc07","fgc08","fgc09","fgc10","fgc11","fgc12","fgc13","fgc14","fgc15","fgc16","fgc17","fgc18","fgc19","fgc20","fgc21","fgc22","fgc23","fgc24","fgc25","fgc26","fgc27","fgc28","fgc29","fgc30","fgc31","fgc32","fgc33","fgc34","fgc35","fgc36","fgc37","fgc38","fgcspare1","fgcspare2","fgcspare3","fgcspare4"]
tests = ["Network", "GPU", "Time", "Gidel", "Snapshot", "Disks"]
testbuttons = []
serverbuttons = []
managebuttons = []
fgcbuttons = []
class mainwindow:
global test
global testbuttons
global managment
global managebuttons
global FGCs
def __init__(self,master):
self.master = master
master.title("System Report Tool")
master.geometry("1000x500")
self.Tests = ttk.LabelFrame(root, text="Tests")
self.Tests.pack(expand="no", anchor=W, pady=20)
for test in tests:
testcheck = ttk.Checkbutton(self.Tests, text=test)
testcheck.pack(side=LEFT, anchor="n")
testbuttons.append(testcheck)
def select_tests():
for test in testbuttons:
test.invoke()
selecttests = ttk.Checkbutton(root, text="Select All",command=select_tests)
selecttests.place(x=260,y=16)
self.Managment = ttk.LabelFrame(root, text="Managment")
self.Managment.configure(width=10, height=20)
self.Managment.pack(expand="no",anchor=W)
for manage in managment:
managecheck = ttk.Checkbutton(self.Managment, text=manage)
managecheck.pack(side=TOP, anchor="w")
managebuttons.append(managecheck)
def select_manage():
for manage in managebuttons:
manage.invoke()
selectmanage = ttk.Checkbutton(self.Managment, text="Select All",command=select_manage)
selectmanage.pack()
self.FGCs = ttk.LabelFrame(root, text="FGCs")
self.FGCs.configure(width=10, height=20)
self.FGCs.place(x=120, y=80)
self.sf = ScrolledFrame(self.FGCs)
self.sf.pack()
for fgc in FGCs:
fgccheck = ttk.Checkbutton(self.sf.inner, text=fgc)
fgccheck.pack(side=TOP, anchor="w")
fgcbuttons.append(fgccheck)
def select_fgc():
for fgc in fgcbuttons:
fgc.invoke()
selectfgc = ttk.Checkbutton(self.sf.inner, text="Select All",command=select_fgc)
selectfgc.pack()
root = Tk()
Window = mainwindow(root)
root.mainloop()
EDIT: This code uses pack() with some options to resize widgets when window is resized
import os
import subprocess
import tkinter as tk
from tkinter import *
from tkinter import ttk
class ScrolledFrame(tk.Frame):
def __init__(self, parent, vertical=True, horizontal=False):
super().__init__(parent)
# canvas for inner frame
self._canvas = tk.Canvas(self)
self._canvas.grid(row=0, column=0, sticky='news') # changed
# create right scrollbar and connect to canvas Y
self._vertical_bar = tk.Scrollbar(self, orient='vertical', command=self._canvas.yview)
if vertical:
self._vertical_bar.grid(row=0, column=1, sticky='ns')
self._canvas.configure(yscrollcommand=self._vertical_bar.set)
# create bottom scrollbar and connect to canvas X
self._horizontal_bar = tk.Scrollbar(self, orient='horizontal', command=self._canvas.xview)
if horizontal:
self._horizontal_bar.grid(row=1, column=0, sticky='we')
self._canvas.configure(xscrollcommand=self._horizontal_bar.set)
# inner frame for widgets
self.inner = tk.Frame(self._canvas)
self._window = self._canvas.create_window((0, 0), window=self.inner, anchor='nw')
# autoresize inner frame
self.columnconfigure(0, weight=1) # changed
self.rowconfigure(0, weight=1) # changed
# resize when configure changed
self.inner.bind('<Configure>', self.resize)
# resize inner frame to canvas size
self.resize_width = False
self.resize_height = False
self._canvas.bind('<Configure>', self.inner_resize)
def resize(self, event=None):
self._canvas.configure(scrollregion=self._canvas.bbox('all'))
def inner_resize(self, event):
# resize inner frame to canvas size
if self.resize_width:
self._canvas.itemconfig(self._window, width=event.width)
if self.resize_height:
self._canvas.itemconfig(self._window, height=event.height)
managment = ["MAIN","NAV","OUTPUT","OUTPUTSPARE","GATEWAY","Sync1","Sync2"]
servers = ["recon01","recon02","recon03","recon04","recon05","recon06","recon07","recon08","recon09","recon10","recon11","recon12","render01","render02","storinator"]
FGCs =["fgc01","fgc02","fgc03","fgc04","fgc05","fgc06","fgc07","fgc08","fgc09","fgc10","fgc11","fgc12","fgc13","fgc14","fgc15","fgc16","fgc17","fgc18","fgc19","fgc20","fgc21","fgc22","fgc23","fgc24","fgc25","fgc26","fgc27","fgc28","fgc29","fgc30","fgc31","fgc32","fgc33","fgc34","fgc35","fgc36","fgc37","fgc38","fgcspare1","fgcspare2","fgcspare3","fgcspare4"]
tests = ["Network", "GPU", "Time", "Gidel", "Snapshot", "Disks"]
testbuttons = []
serverbuttons = []
managebuttons = []
fgcbuttons = []
class mainwindow:
global test
global testbuttons
global managment
global managebuttons
global FGCs
def __init__(self,master):
self.master = master
master.title("System Report Tool")
master.geometry("1000x500")
# --- top ---
self.Tests = ttk.LabelFrame(root, text="Tests")
self.Tests.pack(anchor='w', pady=20)
for test in tests:
testcheck = ttk.Checkbutton(self.Tests, text=test)
testcheck.pack(side='left', anchor="n")
testbuttons.append(testcheck)
def select_tests():
for test in testbuttons:
test.invoke()
selecttests = ttk.Checkbutton(root, text="Select All",command=select_tests)
selecttests.place(x=260,y=16)
# --- middle ---
self.Managment = ttk.LabelFrame(root, text="Managment")
self.Managment.configure(width=10, height=20)
self.Managment.pack(anchor='nw', side='left')
for manage in managment:
managecheck = ttk.Checkbutton(self.Managment, text=manage)
managecheck.pack(anchor="w")
managebuttons.append(managecheck)
def select_manage():
for manage in managebuttons:
manage.invoke()
selectmanage = ttk.Checkbutton(self.Managment, text="Select All",command=select_manage)
selectmanage.pack()
self.FGCs = ttk.LabelFrame(root, text="FGCs")
self.FGCs.configure(width=10, height=20)
self.FGCs.pack(fill='y', expand=True, side='left', anchor='w')
self.sf = ScrolledFrame(self.FGCs)
self.sf.pack(fill='y', expand=True)
for fgc in FGCs:
fgccheck = ttk.Checkbutton(self.sf.inner, text=fgc)
fgccheck.pack(anchor="w")
fgcbuttons.append(fgccheck)
def select_fgc():
for fgc in fgcbuttons:
fgc.invoke()
selectfgc = ttk.Checkbutton(self.sf.inner, text="Select All", command=select_fgc)
selectfgc.pack()
root = Tk()
Window = mainwindow(root)
root.mainloop()
I am currently writing a simple logbook application for my sailing holidays. Therefore I wanted to create a small gui for entering data in lines for the logbook.
I am using grid layout with frames and canvas for the main frame, to show a scroll bar, when there are to many lines.
Everything is looking fine, until the command
self.canvasCenter.configure(scrollregion=self.canvasCenter.bbox("all"))
is called. If I comment this line out, the canvas is using the full frame. If this line is active - the canvas is using only 50% of the available screen and date is cut off.
I tried to strip down the code to the necessary things (not using any additional files, configs, classes, etc.). So that it is executable stand alone:
#!/usr/bin/python
# -*- coding: iso-8859-1 -*-
import sys
if sys.version_info.major == 2:
# We are using Python 2.x
import Tkinter as tk
elif sys.version_info.major == 3:
# We are using Python 3.x
import tkinter as tk
#from lib import logBookData
#from config import *
#Titel of the application shown in title bar
appTitle = "My APP"
#Layout geometry (size of the window)
lWidth = 768
lHeight = 900
layoutGeometry = '900x768'
#Colours for foreground and background for labels and entry fields
#headerBG = "Light Blue"
headerBG = "white"
labelBG = "white"
labelFG = "black"
#Number of columns (keep in Sync with headers and width list below!)
cNumber = 14
#Column width for number of columns defined above
cWidth = [5,7,5,5,5,5,5,5,5,5,5,5,5,10]
cHeaders = ["C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "C10", "C11", "C12", "C13", "C14" ]
class logBookGUI(tk.Tk):
def __init__(self,parent):
tk.Tk.__init__(self,parent)
self.parent = parent
self.lineCount = 0
self.initialize()
def initialize(self):
#create a masterframe
self.masterFrame = tk.Frame(self, bg=headerBG, bd=4, relief="groove") #, width = lWidth, height = lHeight)
self.masterFrame.grid(sticky=tk.NSEW)
self.masterFrame.columnconfigure(cNumber, weight=1)
#create Headerframe for Standard Info
self.headerFrame = tk.Frame(self.masterFrame, bg=headerBG, bd=2, relief="groove")
self.headerFrame.grid(row=0, column=0, sticky=tk.NSEW)
self.headerFrame.columnconfigure(cNumber, weight=1)
#Label Bootsname
boatNameLabel = tk.Label(self.headerFrame, text="Bootsname", anchor="w", fg=labelFG,bg=labelBG)
boatNameLabel.grid(column=0, row=0, sticky=tk.NSEW)
#Field Bootsname
self.boatName = tk.StringVar()
self.boatNameEntry = tk.Entry(self.headerFrame,textvariable=self.boatName, width = cWidth[0])
self.boatNameEntry.grid(column=1,row=0, sticky=tk.NSEW)
self.boatName.set(u"Solva")
for i in xrange(cNumber-2):
button = tk.Button(self.headerFrame,text=u"T %s" %i, command=self.OnButtonClick)
button.grid(column=i,row=0, sticky=tk.NSEW)
button = tk.Button(self.headerFrame,text=u"TEST", command=self.OnButtonClick)
button.grid(column=cNumber-2,row=0, sticky=tk.NSEW)
button = tk.Button(self.headerFrame,text=u"Neue Zeile", command=self.newLineClick)
button.grid(column=cNumber-1,row=1, sticky=tk.NSEW)
#create center frame
self.centerFrame = tk.Frame(self.masterFrame, bg=headerBG, bd=2, relief="groove")
self.centerFrame.grid(row=1, column=0, sticky=tk.NSEW)
self.centerFrame.columnconfigure(cNumber, weight=1)
#create a canvas for center frame
self.canvasCenter = tk.Canvas(self.centerFrame)
self.canvasCenter.grid(row=0, column=0, sticky=tk.NSEW)
self.canvasCenter.columnconfigure(cNumber, weight=1)
# Create a vertical scrollbar linked to the canvas.
vsbar = tk.Scrollbar(self.centerFrame, orient=tk.VERTICAL, command=self.canvasCenter.yview)
vsbar.grid(row=0, column=cNumber, sticky=tk.NSEW)
self.canvasCenter.configure(yscrollcommand=vsbar.set)
# Create a frame on the canvas to contain the content.
self.contentFrame = tk.Frame(self.canvasCenter, bg="Red", bd=2, relief="groove")
self.contentFrame.grid(row=0, column=0, sticky=tk.NSEW)
self.contentFrame.columnconfigure(cNumber, weight=1)
column = 0
for header in cHeaders:
label = tk.Label(self.contentFrame, text=header, anchor="w", fg=labelFG,bg=labelBG,borderwidth=2,relief="groove", width = cWidth[column])
label.grid(column = column, row=1, sticky="EW")
column += 1
self.addLine()
# Create canvas window to hold the centerframe.
self.canvasCenter.create_window((0,0), window=self.contentFrame, anchor=tk.NW)
# Update buttons frames idle tasks to let tkinter calculate buttons sizes
self.contentFrame.update_idletasks()
self.canvasCenter.configure(scrollregion=self.canvasCenter.bbox("all"))
self.grid_columnconfigure(cNumber,weight=1)
self.resizable(True,False)
self.update()
self.geometry("%sx%s" % (lHeight, lWidth))
def newLineClick(self):
self.addLine()
def OnButtonClick(self):
pass
def OnPressEnter(self,event):
pass
def addLine(self):
tkLine = []
try:
for i in xrange(cNumber):
self.entryVar = tk.StringVar()
self.entry = tk.Entry(self.contentFrame,textvariable=self.entryVar,width=cWidth[i])
self.entry.grid(column=i, row = self.lineCount + 3, sticky='EW')
tkLine.append(self.entryVar)
self.canvasCenter.configure(scrollregion=self.canvasCenter.bbox("all"))
except IndexError:
print ("Error in config file. Number of columns and given columns do not match - check file config.py")
sys.exit()
self.update()
self.lineCount += 1
if __name__ == "__main__":
app = logBookGUI(None)
app.title(appTitle)
app.mainloop()
Could someone give me an advice how to use scrollregion, so that the whole size of the frame is used?
Many thanks,
Gernot
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()
I found this introductory tutorial online, but when I run it, I get just a blank window. Any idea what's going wrong?
# A more complex example showing the use of classes in GUI programming
from tkinter import *
class GUIExample:
def __init__(self, master):
self.master = master # for showInfo()
# Create frame to hold widgets
frame = Frame(master)
# Create label for header
self.headLbl = Label(frame, text="Widget Demo Program", relief=RIDGE)
self.headLbl.pack(side=TOP, fill=X)
# Dummy frame for border
spacerFrame = Frame(frame, borderwidth=10)
# Create frame to hold center part of the form
centerFrame = Frame(spacerFrame)
leftColumn = Frame(centerFrame, relief=GROOVE, borderwidth=10)
rightColumn = Frame(centerFrame, relief=GROOVE, borderwidth=10)
# Some colorful widgets
self.colorLabel = Label(rightColumn, text="Select a color")
self.colorLabel.pack(expand=YES, fill=X)
entryText = StringVar(master)
entryText.set("Select a color")
self.colorEntry = Entry(rightColumn, textvariable=entryText)
self.colorEntry.pack(expand=YES, fill=X)
# Create some radio buttons
self.radioBtns = []
self.radioVal = StringVar(master)
btnList = ("black", "red", "green", "blue", "white", "yellow")
for color in btnList:
self.radioBtns.append(Radiobutton(leftColumn, text=color, value=color, \
indicatoron=TRUE, variable=self.radioVal, command=self.updateColor))
else:
if (len(btnList) > 0):
self.radioVal.set(btnList[0])
self.updateColor()
for btn in self.radioBtns:
btn.pack(anchor=W)
# Make the frames visible
leftColumn.pack(side=LEFT, expand=YES, fill=Y)
rightColumn.pack(side=LEFT, expand=YES, fill=BOTH)
centerFrame.pack(side=TOP, expand=YES, fill=BOTH)
# Create an indicator checkbutton
self.indicVal = BooleanVar(master)
self.indicVal.set(TRUE)
self.updateIndic()
Checkbutton(spacerFrame, text="Show indicator", command=self.updateIndic, \
variable=self.indicVal).pack(side=TOP, fill=X)
# Create the Info button
Button(spacerFrame, text="Info", command=self.showInfo).pack(side=TOP, fill=X)
# Create the Quit button
Button(spacerFrame, text="Quit", command=self.quit).pack(side=TOP, fill=X)
def quit(self):
import sys
sys.exit()
def updateColor(self):
self.colorLabel.configure(fg=self.radioVal.get())
self.colorEntry.configure(fg=self.radioVal.get())
def updateIndic(self):
for btn in self.radioBtns:
btn.configure(indicatoron=self.indicVal.get())
def updateColorPrev(self):
if (self.colorprevVal.get()):
for btn in self.radioBtns:
color = btn.cget("text")
btn.configure(fg=color)
else:
for btn in self.radioBtns:
btn.configure(fg="black")
def showInfo(self):
toplevel = Toplevel(self.master, bg="white")
toplevel.transient = self.master
toplevel.title("Program info")
Label(toplevel, text="A simple Tkinter demo", fg="navy", bg="white").pack(pady=20)
Label(toplevel, text="Written by Bruno Dufour", bg = "white").pack()
Label(toplevel, text="http://www.cs.mcgill.ca/ Ėbdufou1/", bg="white").pack()
Button(toplevel, text="Close", command=toplevel.withdraw).pack(pady=30)
root = Tk()
ex = GUIExample(root)
root.title("A simple widget demo")
root.mainloop()
They forgot
frame.pack()
and
spacerFrame.pack()
I didn't test rest of code.
EDIT: BTW: In place of
def quit(self):
import sys
sys.exit()
I would use something more natural - and less drastic
def quit(self):
self.master.destroy()