Issue when making new label on button press tkinter - python

import tkinter as tk
root = tk.Tk()
root.title("To Do")
root.geometry("500x750")
root.resizable(False, False)
class App:
def __init__(self, window):
#Button font
self.buttonFont = ("Courier", 13, "normal")
#Header
self.header = tk.Label(text = "To Do List\n", font = ("Helvetica", 26, "bold"))
self.header.grid()
#Add item button
self.addButton = tk.Button(text = "Add Item", font = self.buttonFont, bg = "green", command = self.OpenAddItem)
self.addButton.grid(row = 1, column = 0, )
#Del item button
self.delButton = tk.Button(text = "Delete Item", font = self.buttonFont, bg = "red")
self.delButton.grid(row = 1, column = 1, )
#items frame and stuff
self.itemsframe = tk.Frame()
self.itemsframe.grid(row = 2)
self.item = tk.Label(self.itemsframe, font = ("Helvetica", 11, "normal"))
def OpenAddItem(self):
#COnfiguring prompt win
self.addPromptWin = tk.Toplevel(root)
self.addPromptWin.title("Prompt")
self.addPromptWin.geometry("375x100")
self.addPromptWin.resizable(False,False)
#Entry label
self.entryLabel = tk.Label(self.addPromptWin,text = "Enter item name:", font = ("Courier", 11, "normal"))
self.entryLabel.grid(sticky = tk.E)
#Adding entry
self.entry = tk.Entry(self.addPromptWin, font = ("Courier", 11, "normal"))
self.entry.grid(row = 0, column = 1)
self.entry.bind("<Return>", lambda e: self.AddItem())
#Add new item button
self.addNewItemButton = tk.Button(self.addPromptWin, text = "Add", font = self.buttonFont, bg = "green", command = self.AddItem)
self.addNewItemButton.grid(row = 1, sticky = tk.W)
def AddItem(self):
self.entryValue = self.entry.get()
self.item.config(text = f"1. {self.entryValue}")
self.item.pack()
self.addPromptWin.destroy()
app = App(root)
root.mainloop()
Hey guys, I'm having an issue with my tkinter gui program.
When i call the addItem function, instead of creating a new label every time, it just replaces the current one. Any help would be much appreciated!

You made a Label named self.item and you keep overwriting it, expecting it to magically be a new Label. The fix is simple ~ don't create self.item. Create a list instead and push new Labels onto it as they are needed. This sets the stage for your delete method, as well ~ as you can just unpack everything in the cache, delete the desired item and repack whatever is left in the cache. My example is commented where it counts. I didn't write a delete method for you.
import tkinter as tk
root = tk.Tk()
root.title("To Do")
root.geometry("500x750")
root.resizable(False, False)
class App:
def __init__(self, window):
#Button font
self.buttonFont = ("Courier", 13, "normal")
#Header
self.header = tk.Label(text = "To Do List\n", font = ("Helvetica", 26, "bold"))
self.header.grid()
#Add item button
self.addButton = tk.Button(text = "Add Item", font = self.buttonFont, bg = "green", command = self.OpenAddItem)
self.addButton.grid(row = 1, column = 0, )
#Del item button
self.delButton = tk.Button(text = "Delete Item", font = self.buttonFont, bg = "red")
self.delButton.grid(row = 1, column = 1, )
#items frame and stuff
self.itemsframe = tk.Frame()
self.itemsframe.grid(row = 2)
''' you just keep overwriting this '''
#self.item = tk.Label(self.itemsframe, font = ("Helvetica", 11, "normal"))
#start a cache instead
self.items = []
def OpenAddItem(self):
#COnfiguring prompt win
self.addPromptWin = tk.Toplevel(root)
self.addPromptWin.title("Prompt")
self.addPromptWin.geometry("375x100")
self.addPromptWin.resizable(False,False)
#Entry label
self.entryLabel = tk.Label(self.addPromptWin,text = "Enter item name:", font = ("Courier", 11, "normal"))
self.entryLabel.grid(sticky = tk.E)
#Adding entry
self.entry = tk.Entry(self.addPromptWin, font = ("Courier", 11, "normal"))
self.entry.grid(row = 0, column = 1)
self.entry.bind("<Return>", lambda e: self.AddItem())
#Add new item button
self.addNewItemButton = tk.Button(self.addPromptWin, text = "Add", font = self.buttonFont, bg = "green", command = self.AddItem)
self.addNewItemButton.grid(row = 1, sticky = tk.W)
def AddItem(self):
self.entryValue = self.entry.get()
'''
you can keep configuring and repacking this for infinity
it's not going to change the fact that it's the same label every time
'''
#self.item.config(text = f"1. {self.entryValue}")
#self.item.pack()
#append a new label to the cache instead
self.items.append(tk.Label(self.itemsframe, font = ("Helvetica", 11, "normal"), anchor='w'))
#config and pack the last item in the cache
self.items[-1].config(text = f"{len(self.items)}. {self.entryValue}",)
self.items[-1].pack(anchor='w')
self.addPromptWin.destroy()
app = App(root)
root.mainloop()

Related

Back Page Button on Tkinter won't

I'm trying to incorporate a 'back button' in my Tkinter app in my 'second_home' to allow the user to return to the previous page - from second_home to home. I've incorporated a hide_frames() function to allow all the frames to disappear when called. I have this done on the second_home but I've commented out the function on the home function - the app won't run with the function called. I also have the home function called when launching the app.
When clicking on the back button titled "Previous Page" I receive the error 'TclError: bad window path name ".!labelframe.!canvas'
from tkinter import *
from tkinter import ttk
from tkinter.font import BOLD
win = Tk()
# Creating Frames
wrapper1 = LabelFrame(win, width = 900, height = 950)
wrapper2 = LabelFrame(win, width = 900, height = 950)
wrapper3 = LabelFrame(win, width = 900, height = 950)
wrapper4 = LabelFrame(win, width = 900, height = 950)
mycanvas = Canvas(wrapper1)
mycanvas.pack(side = LEFT, fill = "both", expand ="yes")
# Creating a Hide Frame Function
def hide_frames():
# Destroying the widgets in each frame
for widget in wrapper1.winfo_children():
widget.destroy()
for widget in wrapper2.winfo_children():
widget.destroy()
for widget in wrapper3.winfo_children():
widget.destroy()
for widget in wrapper4.winfo_children():
widget.destroy()
# Hiding all frames
wrapper1.pack_forget()
wrapper2.pack_forget()
wrapper3.pack_forget()
wrapper4.pack_forget()
def home():
#hide_menu_frames()
global myframe
myframe = Frame(mycanvas)
mycanvas.create_window((0,0), window = myframe, anchor = "nw")
wrapper1.pack(fill = "both", expand = "yes", padx = 10, pady = 10)
myframe.pack(fill = "both", expand = 1)
start_label = Label(myframe, text = "Choose Length of Loan Model", font = ("Helvetica", 19)).pack(pady = 100)
# Creating buttons
home_button1 = Button(myframe, text = "12 Month Model", bg='#ffffff', activeforeground='#4444ff', font = ("Helvetica", 16, BOLD), command = second_home).pack(pady = 10)
def second_home():
hide_frames()
mycanvas = Canvas(wrapper2)
mycanvas.pack(side = LEFT, fill = "both", expand ="yes")
yscrollbar = ttk.Scrollbar(wrapper2, orient = VERTICAL, command = mycanvas.yview)
yscrollbar.pack(side = RIGHT, fill = "y")
mycanvas.configure(yscrollcommand = yscrollbar.set)
mycanvas.bind('<Configure>',lambda e: mycanvas.configure(scrollregion = mycanvas.bbox('all')))
wrapper2.pack(fill = "both", expand = "yes", padx = 10, pady = 10)
myframe = Frame(mycanvas)
mycanvas.create_window((0,0), window = myframe, anchor = "nw")
second_label = Label(myframe, text = "Please Input Loan Parameters", font = ("Helvetica", 18), pady = 10, padx = 325).pack()
# Creating a Back Button
back_button = Button(myframe, text = "Previous Page", command = home)
back_button.pack(pady = 10)
# Defining a Main Menu
my_menu = Menu(win)
win.config(menu = my_menu)
# Creating Menu Items
app_menu = Menu(my_menu)
my_menu.add_cascade(label = "Options", menu = app_menu)
app_menu.add_command(label = "Home", command = home)
app_menu.add_separator()
app_menu.add_command(label = "Exit", command = win.quit)
win.geometry("900x950")
#win.resizable(False, False)
win.title("Loan Cash Flow Model Creator")
home()
win.mainloop()

tkinter throws error using a variable for button name to set state

I want to pass a button name to tkinter as a variable. This method works for Text fields, but not for buttons in my code.
I am building a gui app and I want to have a generic clear function that zaps a text entry field and resets a button from NORMAL to DiSABLED.
There are multiple buttons and fields, hence the desire to make this generic.
For the code I have, the buttons are present with the exception of clear all.
I am setting the variable w_button to the specific name of the (existing) button based on what is passed to the function.
def switch_clear(elem_type):
if elem_type == 'scn':
w_button = 'b_clear_scn'
clear_field = 'scn_file_entry'
print ('scenario')
elif elem_type == 'sol2':
w_button = 'b_clear_sol2'
clear_field = 'sol2_file_entry'
print ('sol2')
elif elem_type == 'mdl':
w_button = 'b_clear_mdlList'
clear_field = 'mdlList_file_entry'
print ('mdl')
elif elem_type == 'all':
print ('clear all TBD')
return()
if w_button["state"] == NORMAL:
clear_field.delete(0, END)
w_button["state"] = DISABLED
return()
Here's what happens:
C:\utils>my_frame3.py
scenario
Traceback (most recent call last):
File "C:\utils\my_frame3.py", line 127, in <module>
b_clear_scn = Button(first_frame, text = "Clear scenario", command = switch_clear('scn'), height = 2, state=DISABLED)
File "C:\utils\my_frame3.py", line 100, in switch_clear
if w_button["state"] == NORMAL:
TypeError: string indices must be integers
C:\utils>
I realize I could duplicate and push the clear operations into the if/elif statements, and I may have to live with that but - is it possible to reference buttons as variables? How can I do this?
Complete code as requested below. The preview is a mess despite using the code widget, but in my file lines are correctly formatted (notepad++). Tx
import os, sys
from tkinter import *
from tkinter import messagebox
from tkinter import filedialog
root2 = Tk()
root2.title('Model Processing')
root2.geometry('{}x{}'.format(512, 400))
# colors
color1 = 'light cyan'
color2 = 'gold'
color3 = 'RosyBrown1'
color4 = 'lavender'
color5 = 'linen'
bg_color = 'azure'
# number of items to process; until >1 OK is disabled; if >1 OK is enabled TBD
l_to_do = [] # items to process; scn, sol2, modelList, installed models
# functions/commands
def callback():
print ('A button was clicked.')
return;
def my_exit():
result = messagebox.askquestion("Cancel Model Processing", "Are you sure?", icon='warning')
if result == 'yes':
root2.quit()
else: # user changed mind
pass
def choose_import_file(str_ftype):
which_field = ''
which_clear_btn = ''
ftype = str_ftype
mdl_opts = {}
if ftype == 'scn':
title_msg = 'Choose a scenario file'
mdl_opts['filetypes'] = [('Supported types', ('.scn')),
('scenario files',('.scn'))]
which_field = scn_file_entry
which_clear_btn = 'b_clear_scn'
elif ftype == 'sol2':
title_msg = 'Choose a SOL2 file'
mdl_opts['filetypes'] = [('Supported types', ('.txt')),
('sol2 files',('.txt'))]
which_field = sol2_file_entry
elif ftype == 'mdllist':
title_msg = 'Choose a ModelList file'
print ('TBD: ModelList file')
file_input = filedialog.askopenfilename(title = title_msg, **mdl_opts)
if file_input == '':
print ('error or cancelled by user')
return
else:
f_inp_file = os.path.basename(file_input)
f_inp_file_base = str(file_input.split('.')[0])
f_inp_file_ext = str.lower(str(file_input.split('.')[1]))
f_inp_d_name = os.path.dirname(file_input)
print('File chosen:', f_inp_file_base)
# populate scenario file field
which_field.insert(INSERT,file_input)
which_clear_btn["state"] = NORMAL
# define appropriate clear button active
# define_clear_btn.configure(state = ACTIVE)
return
def switch_clear(elem_type):
if elem_type == 'scn':
if b_clear_scn["state"] == NORMAL:
scn_file_entry.delete(0, END)
b_clear_scn["state"] = DISABLED
elif elem_type == 'sol2':
f b_clear_sol2["state"] == NORMAL:
clear_field = 'sol2_file_entry'
b_clear_sol2["state"] = DISABLED
elif elem_type == 'mdl':
if b_clear_mdlList["state"] == NORMAL:
clear_field = 'mdlList_file_entry'
b_clear_mdlList["state"] = DISABLED
elif elem_type == 'all':
print ('clear all TBD')
return()
return()
# create all of the main containers
first_frame = Frame(root2, bg=color5, width = 512, height=90, pady=10)
second_frame = Frame(root2, bg=color5, width = 512, height=90, pady=10)
third_frame = Frame(root2, bg=color5, width=512, height=90, pady=10)
fourth_frame = Frame(root2, bg=color5, width = 512, height = 90, pady=10)
# layout all of the main containers
root2.grid_rowconfigure(3, weight=1)
root2.grid_rowconfigure(2, weight=1)
root2.grid_rowconfigure(1, weight=1)
root2.grid_columnconfigure(0, weight=1)
first_frame.grid(row=0, sticky="ew")
second_frame.grid(row=1, sticky="ew")
third_frame.grid(row=2, sticky="ew")
fourth_frame.grid(row = 3, sticky="e")
# create the widgets for the first frame
#scn_label = Label(first_frame, text = 'Scenario file')
scn_file_entry = Entry(first_frame, background=bg_color, width = 50)
b_choose_scn = Button(first_frame, text = "Choose a scenario..", command = lambda: choose_import_file('scn'), height = 2)
b_clear_scn = Button(first_frame, text = "Clear scenario", command = switch_clear('scn'), height = 2, state=DISABLED)
# layout the widgets in the first frame
#scn_label.grid(row = 0, column = 0, padx = (10,50), pady=5)
scn_file_entry.grid(row = 0, column = 1, padx = (10,10))
b_choose_scn.grid(row=0, column=0, padx = (10,10), sticky=W)
b_clear_scn.grid(row=2, column=0, padx = (10,10), sticky=W)
# second frame
# sol2_label = Label(second_frame, text = 'Sol2 file')
sol2_file_entry = Entry(second_frame, background=bg_color, width = 50)
b_choose_sol2 = Button(second_frame, text = "Choose SOL2 file..", command = lambda: choose_import_file('sol2'), height = 2)
b_clear_sol2 = Button(second_frame, text = "Clear SOL2", command = switch_clear('sol2'), height = 2, state=DISABLED)
# layout the widgets in the second frame
# sol2_label.grid(row = 0, column = 0, padx = (10,50), pady=5)
sol2_file_entry.grid(row = 0, column = 1, padx = (10,10), sticky=EW)
b_choose_sol2.grid(row=0, column=0, padx = (10,10), sticky=W)
b_clear_sol2.grid(row=2, column=0, padx = (10,10), sticky=W)
# third frame
# mdlList_label = Label(third_frame, text = 'ModelList.txt file')
mdlList_file_entry = Entry(third_frame, background=bg_color, width = 50)
b_choose_mdlList = Button(third_frame, text = "Choose ModelList.txt file..", command = callback, height = 2)
b_clear_mdlList = Button(third_frame, text = "Clear ModelList", command = callback, height = 2, state=DISABLED)
# layout the widgets in the third frame
#mdlList_label.grid(row = 0, column = 0, padx = (10,10), pady=5, sticky = 'ns')
mdlList_file_entry.grid(row = 0, column = 1, padx = (10,10), sticky=EW)
b_choose_mdlList.grid(row=0, column=0, padx = (10,10), sticky=W)
b_clear_mdlList.grid(row=2, column=0, padx = (10,10), sticky=W)
#####################################################################
# create bottom widgets
#####################################################################
clear_all = Button(fourth_frame, text='Clear All', padx = '5', command = callback, height = 2, state=DISABLED)
ok_btn = Button(fourth_frame, text='OK', padx = '5', command = callback, height = 2, state=DISABLED)
cancel_btn = Button(fourth_frame, text='Cancel', height = 2, padx = '12', command = my_exit)
#####################################################################
# layout the bottom widgets
#####################################################################
clear_all.grid(row = 0, column = 2, sticky = 'e')
ok_btn.grid(row = 0, column = 3, sticky = 'e')
cancel_btn.grid(row = 0, column = 4, sticky = 'e')
# commands/bindings
root2.mainloop()
I believe this is what you are trying to do. The below is fully commented with explanations. switch_clear becomes entirely unnecessary with this method. The buttons directly clear their corresponding entry in their command, and Entry validation takes care of the corresponding button state.
Note: I wrote all of this before you posted your full code
import tkinter as tk
root = tk.Tk()
root.geometry('400x400')
#toggle the state of buttons based on entry text length
def toggle_button(name, text):
global btn_switch
btn_switch[name]['state'] = 'normal' if len(text) else 'disabled'
return True
#to hold the buttons so they are easy to position
#just for this example
buttons = tk.Frame(root)
buttons.pack(side='top', anchor='nw')
#create a tcl wrapper for the validate command
vcmd = tk.Widget.register(root, toggle_button)
#mock-up of your entries ~ validate on key press. send widget name and full text to vcmd
scn_file_entry = tk.Entry(root, width=20)
scn_file_entry.configure(validate="key", validatecommand=(vcmd, '%W', '%P'))
scn_file_entry.pack(side='left', anchor='nw')
sol2_file_entry = tk.Entry(root, width=20)
sol2_file_entry.configure(validate="key", validatecommand=(vcmd, '%W', '%P'))
sol2_file_entry.pack(side='left', anchor='nw')
mdlList_file_entry = tk.Entry(root, width=20)
mdlList_file_entry.configure(validate="key", validatecommand=(vcmd, '%W', '%P'))
mdlList_file_entry.pack(side='left', anchor='nw')
#mock-up of your buttons ~ delete the entry text in a lambda and let entry validation handle the button state
b_clear_scn = tk.Button(buttons, text="scn", state='disabled')
b_clear_scn.configure(command=lambda: scn_file_entry.delete(0, 'end'))
b_clear_scn.pack(side='left', anchor='nw')
b_clear_sol2 = tk.Button(buttons, text="sol2", state='disabled')
b_clear_sol2.configure(command=lambda: sol2_file_entry.delete(0, 'end'))
b_clear_sol2.pack(side='left', anchor='nw')
b_clear_mdlList = tk.Button(buttons, text="mdl", state='disabled')
b_clear_mdlList.configure(command=lambda: mdlList_file_entry.delete(0, 'end'))
b_clear_mdlList.pack(side='left', anchor='nw')
#create a dictionary of 'widget name':corresponding button, for toggle_button to reference
btn_switch = {
f'{scn_file_entry}':b_clear_scn,
f'{sol2_file_entry}':b_clear_sol2,
f'{mdlList_file_entry}':b_clear_mdlList,
}
root.mainloop()

Why does the entry widget not work in this case i put it in a frame, tkinter-python

So this is a basic clock and alarm that i am creating, and in the process i want the user to type in what hour and minute they want to set for the alarm. But the entry widget here is not responding.
import time
import tkinter as tk
current_date, current_time = 0, 0
def current_info(timeinfo): #function to get the current time and date
global current_date, current_time
# current time
current_time = time.strftime('%H:%M:%S')
current_date = time.strftime(r'%m/%d/%Y')
clock.after(200, timeinfo)
#Initialise the window
clock = tk.Tk()
clock.title('Easy CLock')
clock.configure(bg='#121212')
clock.columnconfigure(0, weight = 1)
clock.columnconfigure(1, weight = 1)
clock.columnconfigure(2, weight = 1)
clock.columnconfigure(3, weight = 1)
border_effects = {
"flat": tk.FLAT,
"sunken": tk.SUNKEN,
"raised": tk.RAISED,
"groove": tk.GROOVE,
"ridge": tk.RIDGE,
}
#Logo will be under the main parent
logo = tk.PhotoImage(file = r'C:\Users\User\VSC\Alarm\Logo1.png')
logo_size = logo.subsample(5)
#Time and Date function
def time_date():
current_info(time_date)
#Displays the time
c_time = tk.Label(f_time, text = current_time, fg='white', bg='#121212', font=('Verdana', 30))
c_date = tk.Label(f_time, text = current_date, font=('Verdana', 10), fg='white', bg='#121212')
c_time.grid(column=0, row=0)
c_date.grid(column=0, row=1)
#alarm button command
def alarm_func():
current_info(alarm_func)
c_time = tk.Label(f_alarm, text = current_time, fg='white', bg='#121212', font=('Verdana', 10))
c_date = tk.Label(f_alarm, text = current_date, font=('Verdana', 10), fg='white', bg='#121212')
def pressed_enter(): #Command for the enter button
set_label = tk.Label(f_alarm, text = f'Alarm has been set for {time_set}', fg ='white', bg = '#121212', borderwidth = 1, relief = border_effects['sunken'])
set_label.grid(column = 4, row = 0, sticky = 'W')
# Set the time and date for the alarm
set_time = tk.StringVar()
alarm_entry = tk.Entry(clock, textvariable = set_time)
set_time.set('H : M')
time_set = alarm_entry.get()
#label and entry to set alarm / Enter Button
c_label = tk.Label(f_alarm, text = 'Set Alarm: ', font = ('Verdana', 10), fg= 'white', bg ='#121212' )
alarm_enter = tk.Button(f_alarm, text = 'Enter', font = ('Verdana', 7), width = 5, command = pressed_enter)
#Pack the widgets
c_time.grid(row = 0, column = 0)
c_date.grid(column = 1 , row = 0)
alarm_enter.grid(row = 2, column = 3)
c_label.grid(row = 2, sticky = 'W')
alarm_entry.grid(row = 2, column = 1)
#configure the empty columns
f_alarm.columnconfigure(2, minsize = 10)
def recall_frame(event):
if event == f_alarm:
event.grid_forget()
f_time.grid(column=0, row =1, columnspan = 4, sticky = 'N')
elif event == f_time:
event.grid_forget()
f_alarm.grid(column=0, row=1, columnspan = 4, sticky = 'W')
def back_func():
pass
#Creating Frames
f_time = tk.Frame(clock) #Clock Button
f_alarm = tk.Frame(clock) #Alarm Buttton
#configure the frames
f_time.configure(bg = '#121212')
f_alarm.configure(bg = '#121212')
#Setting label in the frame
f_lbl = tk.Label(clock, text= ' Simplistic Clock', image = logo_size, font=('Verdana', 30), fg='white', bg='#121212', compound = tk.LEFT, padx = 35)
time_but = tk.Button(clock, text='Clock', command= lambda :[time_date(), recall_frame(f_alarm)], bg='#f39c12', relief = border_effects['ridge'], pady = 7)
alarm_but = tk.Button(clock, text = 'Alarm', command = lambda :[alarm_func(), recall_frame(f_time)], bg='#f39c12', relief = border_effects['ridge'], pady = 7)
quit_but = tk.Button(clock, text='Exit', command = clock.quit, bg='#f39c12', relief = border_effects['ridge'], pady = 7)
back_but = tk.Button(clock, text = 'Back ', command = back_func, bg='#f39c12', relief = border_effects['ridge'], pady = 7)
f_lbl.config(borderwidth = 4, relief = border_effects['sunken'])
#Putting it on the frames
f_lbl.grid(column = 0, row = 0, columnspan = 5, sticky = 'EW')
time_but.grid(column = 0, row = 3, sticky = 'EW')
alarm_but.grid(column = 1, row = 3, sticky = 'EW')
quit_but.grid(column = 3, row = 3, sticky = 'EW')
back_but.grid(column = 2, row = 3, sticky = 'EW')
clock.mainloop()
i tried testing an entry widget outside the frame and the entry widget was able to work, is it because the frame f_alarm is not looping constantly in the background?
When someone clicks on your button which activates the pressed_enter() function, it will call that function again every time which will set the time to H:M and it will get that value as the set_time.get() is called immediately after.
You're also creating a new Entry every time the button is being clicked because you put alarm_entry = tk.Entry(clock, textvariable=set_time)
in there as well. You should only put the set_time.get inside of that button so that it gets the value that is currently filled in into the Entry. The other things like
set_time = tk.StringVar()
alarm_entry = tk.Entry(clock, textvariable=set_time)
set_time.set('H : M')
Should be put outside of that function so they don't get called every time someone clicks on the button.

OptionMenu in tkinter keeps crashing when placed in a frame

I am trying to place an OptionMenu widget inside of a frame, which itself is inside of a notebook. From what I've found online, the code for doing this is roughly:
# add a drop down menu
hops = range(0,6)
self.selectedHop = StringVar(frame2)
self.selectedHop.set(hops[0])
self.hopOptions = OptionMenu(frame2, self.selectedHop, *hops)
self.hopOptions.grid(row=0, column=2, sticky=EW)
However, when I place this in my code in the code below (the chunk above is placed towards the bottom of it, and is labeled "PROBLEMATIC CODE..."), my app just freezes and I have to force quit it, and I have no error message to debug with. Any help would be appreciated.
#!/usr/bin/python3
from tkinter import *
from tkinter import ttk
from tkinter import messagebox
import subprocess
import sys
class ReportGUI:
def __init__(self, master):
self.master = master
master.title('Reporting')
master.resizable(True, True)
master.configure(background = '#b3e6cc')
master.minsize(width=800,height=700)
textcolor = "#003399"
self.style = ttk.Style()
self.style.configure('TFrame', background = '#e1d8b9')
self.style.configure('TButton', background = '#e1d8b9')
self.style.configure('TLabel', background = '#e1d8b9', font = ('Arial', 40))
self.style.configure('Header.TLabel', font = ('Arial', 30, 'bold'))
# step 1 - create notebook
self.notebook = ttk.Notebook(master)
self.notebook.pack()
# step 2 - create first frame to add to notebook
self.frame_logon = ttk.Frame(self.notebook)
# step 3 - add first frame to notebook and style it
self.notebook.add(self.frame_logon, text = 'Login')
self.frame_logon.config(padding = (20, 20, 20))
self.frame_logon.config(relief = RIDGE)
######### --- (1) LOGIN TAB ---- #########
label = ttk.Label(self.frame_logon, text = 'Administrative Reporting',
foreground=textcolor, style = 'Header.TLabel')
label.grid(row = 1, columnspan = 2)
# widget: username and password Label()
label2 = ttk.Label(self.frame_logon, text = 'Username:',
font = ('Arial', 17),foreground=textcolor)
label2.grid(row = 3, column = 0, padx = 5, sticky = 'sw')
label3 = ttk.Label(self.frame_logon, text = 'Password:',
font = ('Arial', 17),foreground=textcolor)
label3.grid(row = 3, column = 1, padx = 5, sticky = 'sw')
# widget: entry boxes Entry()
self.entry_name = ttk.Entry(self.frame_logon, width = 20, font = ('Arial', 15))
self.entry_pw = ttk.Entry(self.frame_logon, width = 20, font = ('Arial', 15))
# place the widgets
self.entry_name.grid(row = 4, column = 0, padx = 5)
self.entry_pw.grid(row = 4, column = 1, padx = 5)
self.entry_pw.config(show = '*') # make password not show
# widget: connect button Button()
self.loginButton = ttk.Button(self.frame_logon, text = 'Connect',
command = self.login)
self.loginButton.grid(row = 5, column = 1, columnspan = 2, padx = 1, pady = 1, sticky = 'e')
### COMMAND FUNCTIONS
def login(self):
# Make connections (TBA)
# 1) log into app
# 2) ssh into server port
# 3) connect to database
# if successful login and connection, launch reports tab
self.reportTab()
self.notebook.select(1) # switch tabs to reports tab
self.loginButton.state(['disabled']) # disable login button
# TAB 2: reporting tab
def reportTab(self):
# create report frame and add to notebook
self.frame_report = ttk.Frame(self.notebook)
self.notebook.add(self.frame_report, text = 'Report Options')
######### --- REPORT TAB ---- #########
#--------- FILTER 1: -----------
frame = ttk.Frame(self.frame_report)
frame.grid(row=1,column=0)
frame.config(height = 100, width = 200)
frame.config(relief = RIDGE)
ttk.LabelFrame(frame, height=100,width = 200,text = 'FILTER 1').pack()
#--------- FILTER 2: -----------
frame2 = ttk.Frame(self.frame_report)
frame2.grid(row=1,column=1)
frame2.config(height = 100, width = 200)
frame2.config(relief = RIDGE)
ttk.LabelFrame(frame2, height=100,width = 200,text = 'FILTER 2').pack()
#---------- PROBLEMATIC CODE: trying to add a drop down menu ----
hops = range(0,6)
self.selectedHop = StringVar(frame2)
self.selectedHop.set(hops[0])
self.hopOptions = OptionMenu(frame2, self.selectedHop, *hops)
self.hopOptions.grid(row=0, column=2, sticky=EW)
#----------------------------------------------------------------
#--------- FILTER 3: -----------
frame3 = ttk.Frame(self.frame_report)
frame3.grid(row=2,column=0)
frame3.config(height = 100, width = 200)
frame3.config(relief = RIDGE)
lbf3 = ttk.LabelFrame(frame3, height=100,width = 200,text = 'FILTER 3')
lbf3.pack()
#--------- FILTER 4: -----------
frame4 = ttk.Frame(self.frame_report)
frame4.grid(row=2,column=1)
frame4.config(height = 100, width = 200)
frame4.config(relief = RIDGE)
ttk.LabelFrame(frame4, height=100,width = 200,text = 'FILTER 4').pack()
# code for calling queries TBA
# launch results tab if queries successful
# self.resultsTab()
def func(self,value):
print(value)
# TAB 3: results tab
def resultsTab(self):
# create results frame and add to notebook
self.frame_results = ttk.Frame(self.notebook)
self.notebook.add(self.frame_results, text = 'Results')
def clear(self):
self.entry_name.delete(0, 'end')
self.entry_pw.delete(0, 'end')
self.text_comments.delete(1.0, 'end')
def main():
root = Tk()
hccwgui = ReportGUI(root)
root.mainloop()
if __name__ == "__main__": main()
Thanks in advance!
# ...
ttk.LabelFrame(frame2, height=100,width = 200,text = 'FILTER 2').pack()
# ...
self.hopOptions = OptionMenu(frame2, self.selectedHop, *hops)
self.hopOptions.grid(row=0, column=2, sticky=EW)
This is your problem. You're packing a LabelFrame in frame2, and then trying to grid something in frame2. A given container can only use one of grid() or pack()- using both won't cause an error, but the two managers will negotiate on how to place things for the rest of your lifetime.
The solution is to make sure that a given container's children are either packed or gridded, but never both in the same container (though you could have a structure parent->child->grandchild, where child is packed in parent and grandchild is gridded in child).

Python tkinter: access child widgets of a widget

I have a string 'currentMessage' and a Label to display it.
I have a Toplevel widget, which contains a Text widget to provide new value for 'currentMessage':
from tkinter import *
from tkinter import ttk
root = Tk()
mainFrame = ttk.Frame(root)
mainFrame.grid()
currentMessage = 'current Message'
ttk.Label(mainFrame, text = currentMessage).grid(padx = 10, pady = 10)
def updateCurrentMessage(popupWindow):
currentMessage = popupWindow.textBox.get(0.0, END)
def changeValues():
popup = Toplevel(mainFrame)
popup.grid()
textBox = Text(popup, width = 20, height = 5)
textBox.grid(column = 0, row = 0)
textBox.insert(END, 'new message here')
b = ttk.Button(popup, command = lambda: updateCurrentMessage(popup))
b.grid(column = 0, row = 1, padx = 5, pady = 5)
b['text'] = 'Update'
theButton = ttk.Button(mainFrame, command = changeValues, text = 'Click')
theButton.grid(padx = 10, pady = 10)
mainFrame.mainloop()
I tried to get the content of 'textBox' Text widget of the Toplevel by using this function:
def updateCurrentMessage(popupWindow):
currentMessage = popupWindow.textBox.get(0.0, END)
But I got an error
'Toplevel' object has no attribute 'textBox'
So how do I access content of the widget 'textBox', which is a child widget of 'popup' (this Toplevel widget is only created when function changeValues() is called)?
I think probably this is what you are looking for -- although I'm just guessing, because you are asking for a solution for a specific problem you think you have, however if I were you I would rethink what exactly do I want to do:
from tkinter import *
from tkinter import ttk
# Create Tk Interface root
root = Tk()
# Initialize mainFrame
mainFrame = ttk.Frame( root )
mainFrame.grid()
# Initialize label of mainframe
theLabel = ttk.Label( mainFrame, text='Current Message' )
theLabel.grid( padx=10, pady=10 )
def createPopup():
# Initialize popup window
popup = Toplevel( mainFrame )
popup.grid()
# Initialize text box of popup window
textBox = Text( popup, width=20, height=5 )
textBox.grid( column = 0, row = 0 )
textBox.insert( END, 'New Message Here' )
# Initialize button of popup window
button = ttk.Button( master = popup,
command = lambda: theLabel.config(text=textBox.get(0.0, END)),
text = 'Update')
button.grid( column=0, row=1, padx=5, pady=5 )
# Initialize button of main frame
theButton = ttk.Button( mainFrame, command=createPopup, text='Click' )
theButton.grid( padx=10, pady=10 )
# Enter event loop
mainFrame.mainloop()
There is a way indeed, like this:
def updateCurrentMessage(popupWindow):
currentMessage = popupWindow.nametowidget('textBox').get(0.0, END)
def changeValues():
popup = Toplevel(mainFrame)
popup.grid()
textBox = Text(popup, width = 20, height = 5, name = 'textBox')
textBox.grid(column = 0, row = 0)
textBox.insert(END, 'new message here')
b = ttk.Button(popup, command = lambda: updateCurrentMessage(popup))
b.grid(column = 0, row = 1, padx = 5, pady = 5)
b['text'] = 'Update'
You can choose whatever you want for the 'name'.

Categories