_tkinter.TclError: bad window path name when closing window - python

I've made a tk.Toplevel class to get a date from the user. After the user clicked the date, the window is closing and the date should return to the mainprocess.
When the tk.Toplevel is closed I've got the date, but also an error:
\_tkinter.TclError: bad window path name ".!kalender.!dateentry.!toplevel"
What did I do wrong?
class Kalender(tk.Toplevel):
def __init__(self, parent, date=''):
Toplevel.__init__(self, parent)
# Fenster mittig zentrieren
x = (self.winfo_screenwidth() // 2) - (100 // 2)
y = (self.winfo_screenheight() // 2) - (50 // 2)
self.grab_set()
self.geometry('{}x{}+{}+{}'.format(180, 90, x, y))
self.attributes('-toolwindow', True)
self.title('Datum auswählen')
self.resizable(width=False, height=False)
self.date = None
self.sel = StringVar()
self.cal = DateEntry(self, font="Arial 14", selectmode='day', locale='de_DE', date_pattern="dd.mm.y ",
textvariable=self.sel)
self.cal.bind("<<DateEntrySelected>>", self.close_window)
self.cal.set_date(date)
self.cal.grid(row=0, column=0, padx=10, pady=10, sticky=W+E)
self.focus_set()
def close_window(self, e):
self.date = self.cal.get()
self.destroy()
def show(self):
self.deiconify()
self.wm_protocol("WM_DELETE_WINDOW", self.close_window)
self.wait_window()
return self.date
cal = Kalender(main_window, d).show()
I've got the following error:
Exception in Tkinter callback
Traceback (most recent call last):
File "B:\Python 310\lib\tkinter\__init__.py", line 1921, in __call__
return self.func(*args)
File "B:\Python 310\lib\site-packages\tkcalendar\dateentry.py", line 301, in _select
self._top_cal.withdraw()
File "B:\Python 310\lib\tkinter\__init__.py", line 2269, in wm_withdraw
return self.tk.call('wm', 'withdraw', self._w)
_tkinter.TclError: bad window path name ".!kalender.!dateentry.!toplevel"
It seems, that tkinter tries to call the kalender.dateentry after it has been destroyed.

It is because when the user has selected a date in the pop-up calendar, the bind function self.close_window() will be executed and the toplevel is destroyed (so is the DateEntry widget). Then DateEntry widget closes the pop-up calendar which raises the exception.
To fix this, you can delay the execution of self.close_window() a bit so that it is executed after the pop-up calendar is closed using after():
self.cal.bind("<<DateEntrySelected>>", lambda e: self.after(10, self.close_window, None))

I am not 100% sure about this, but it seems the that the tkcalendar module has some trouble forcing a destroy on the parent widget through a bind on the DateEntry class. You can try using the withdraw command instead, which hides the window rather than destroying it
def close_window(self, e):
self.date = self.cal.get()
self.withdraw()

Related

Exception KeyError if call focus_get() when down arrow clicked on widget Combo

Try to view where the focus by following code, but get Exception KeyError if call focus_get() when down arrow clicked on widget Combo.
import tkinter as tk
from tkinter import ttk
def show_focus():
label.configure(text=f'Focus at {root.focus_get()}')
root.after(100, show_focus)
font = ('Courier New', 16)
root = tk.Tk()
label = tk.Label(root, text='Focus at', width=40, font=font, anchor='w')
label.pack(anchor='w', fill='x')
value1 = tk.StringVar()
value1.set("Entry")
entry = tk.Entry(root, textvariable=value1, width=40, font=font, bg='green', fg='black')
entry.pack(anchor='w', fill='x')
value2 = tk.StringVar()
value2.set("Male")
combo = ttk.Combobox(root, values=['Male', 'Female'], textvariable=value2, width=40, height=5, font=font)
combo.pack(anchor='w')
show_focus()
root.mainloop()
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Software\Python\lib\tkinter\__init__.py", line 1892, in __call__
return self.func(*args)
File "C:\Software\Python\lib\tkinter\__init__.py", line 814, in callit
func(*args)
File "D:\tkinter focus_get.py", line 5, in show_focus
label.configure(text=f'Focus at {root.focus_get()}')
File "C:\Software\Python\lib\tkinter\__init__.py", line 758, in focus_get
return self._nametowidget(name)
File "C:\Software\Python\lib\tkinter\__init__.py", line 1507, in nametowidget
w = w.children[n]
KeyError: 'popdown'
In method focus_get() of tkinter
...
name = self.tk.call('focus') #name: <string object: '.!combobox.popdown.f.l'>
...
self._nametowidget(name)
...
def nametowidget(self, name):
"""Return the Tkinter instance of a widget identified by
its Tcl name NAME."""
name = str(name).split('.')
w = self
if not name[0]:
w = w._root()
name = name[1:]
for n in name:
if not n:
break
w = w.children[n] # '!combobox' in root.children, but 'popdown' not in combo.children, so it get KeyError here.
return w
_nametowidget = nametowidget
My question is how can I get where the focus, but to avoid situation like this one. Maybe try except statement can skip exception, but it will get wrong focus at that moment.
[Update]
def show_focus():
try:
label.configure(text=f'Focus at {root.focus_get()}')
except:
# root.after(100, show_focus), if this statement put here, this function will stop if no exception
pass
root.after(100, show_focus) # statement here to keep update where the focus
You can get over the error by ignoring each press on the 'popdown' button.
def show_focus():
if str(root.tk.call(combo,'state')) != 'pressed':
label.configure(text=f'Focus at {root.focus_get()}')
else:
label.configure(text=f'Focus at {str(combo)}')
root.after(100, show_focus)
This is a way around the issue as 'popdown' button is not identified by nametowidget()(which is used to return the widget name when using focus_get()). I was able to find a similar bug at https://bugs.python.org/issue18686

Tkinter - Withdrawing previous window as opposed to destroying it causing issues in current window

I have a application which I initialize with a login window - once the user and password are verified, it opens the main menu window (Code for that window is below). Previously if the user had no option to logout from the main menu to go back to the login in. But now I have inserted a logout button which SHOULD kill the current window open the former login window.
Problem is that previously I didn't have to retain the state of the login window as I never planned to go back there, but now I do want the option to go back there - so I need to retain the state - therefore I need to uses root.withdraw() as opposed to root.destroy() which seems to the root of my problems (no pun intended)
If I execute the main_menu_window() function directly on my IDE - it works 100%, but if I were to call it from the LoginWindow (the "parent") in my below code I get the error as detailed below:
def main_menu_window(_parent, _user):
def open_user_man(event=None):
user_man_window(main_win)
main_menu_window(_user)
def open_prof_man(event=None):
prof_man_window(main_win, _user)
main_menu_window(_user)
def open_mon_leagues(event=None):
mon_leagues_window(main_win)
main_menu_window(_user)
def back_to_login():
main_win.destroy()
# if _parent is not None:
# _parent.deiconify()
# Hide Login Window
if _parent is not None:
_parent.withdraw()
# Window Setup
main_win = tk.Tk()
main_win.title("11Sixteen Database Management Controller - Main menu")
main_win.geometry("%dx%d+0+0" % (main_win.winfo_screenwidth(), main_win.winfo_screenheight()))
# Object setup
user_man = ImagedButtonWithText(main_win,
'C:\\Users\\rferreira\\GitHub\\11Sixteen\\DatabaseManagentController\\GlobalResources\\Images_Icons\\user_man_icon.png',
"LargeGroove", "User Management")
prof_man = ImagedButtonWithText(main_win,
'C:\\Users\\rferreira\\GitHub\\11Sixteen\\DatabaseManagentController\\GlobalResources\\Images_Icons\\profile_man_icon.png',
"LargeGroove", "Profile Management")
mon_leas = ImagedButtonWithText(main_win,
'C:\\Users\\rferreira\\GitHub\\11Sixteen\\DatabaseManagentController\\GlobalResources\\Images_Icons\\monitor_leagues_icon.png',
"LargeGroove", "Monitored Leagues")
back_to_mm_btn = ColourSchemedButton(main_win, "PaleGreen", "Logout", width=18)
# Object binding
user_man.btn.config(command=open_user_man)
prof_man.btn.config(command=open_prof_man)
mon_leas.btn.config(command=open_mon_leagues)
back_to_mm_btn.config(command=back_to_login)
# Object placement
back_to_mm_btn.grid(row=0, column=0, columnspan=2, padx=30, pady=20, sticky="nw")
user_man.frame.grid(row=1, column=0, padx=30, pady=20)
prof_man.frame.grid(row=1, column=1, padx=30, pady=20)
mon_leas.frame.grid(row=1, column=2, padx=30, pady=20)
main_win.mainloop()
if __name__ == "__main__":
user = User()
main_menu_window(None, user)
The error I am getting is as follows:
C:\Users\rferreira\AppData\Local\Programs\Python\Python39\python.exe C:/Users/rferreira/GitHub/11Sixteen/DatabaseManagentController/Login/LoginWindow.py
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\rferreira\AppData\Local\Programs\Python\Python39\lib\tkinter\__init__.py", line 1892, in __call__
return self.func(*args)
File "C:\Users\rferreira\GitHub\11Sixteen\DatabaseManagentController\Login\LoginWindow.py", line 19, in login_process
validate_login(login_win, email_entry, password_entry, message_label)
File "C:\Users\rferreira\GitHub\11Sixteen\DatabaseManagentController\Login\LoginValidation.py", line 54, in validate_login
login_attempt()
File "C:\Users\rferreira\GitHub\11Sixteen\DatabaseManagentController\Login\LoginValidation.py", line 42, in login_attempt
MwW.main_menu_window(parent, user)
File "C:\Users\rferreira\GitHub\11Sixteen\DatabaseManagentController\MainMenu\MainMenuWindow.py", line 41, in main_menu_window
user_man = ImagedButtonWithText(main_win,
File "C:\Users\rferreira\GitHub\11Sixteen\DatabaseManagentController\GlobalResources\GuiObjectsFactories.py", line 45, in __init__
self.btn = LargeGroove(self.frame, image=image)
File "C:\Users\rferreira\GitHub\11Sixteen\DatabaseManagentController\GlobalResources\GUIObjectsComponents.py", line 20, in __init__
super().__init__(master=parent, width=130, height=130, relief="groove", **kw)
File "C:\Users\rferreira\AppData\Local\Programs\Python\Python39\lib\tkinter\__init__.py", line 2650, in __init__
Widget.__init__(self, master, 'button', cnf, kw)
File "C:\Users\rferreira\AppData\Local\Programs\Python\Python39\lib\tkinter\__init__.py", line 2572, in __init__
self.tk.call(
_tkinter.TclError: image "pyimage4" doesn't exist
I would love any help or ideas on how to kill this bug. Please let me know if you need more details or further code extracts.
For better context below are further code extracts for the objects called where the errors are triggered
class ImagedButtonWithText(tk.Frame):
def __init__(self, parent, image_path, btn_type, text, **kw):
self.frame = tk.Frame(parent)
# Set up button
image = PhotoImageComp(image_path)
if btn_type == "MicroGroove":
self.btn = MicroGroove(self.frame, image=image)
if btn_type == "LargeGroove":
self.btn = LargeGroove(self.frame, image=image)
self.btn.image = image
self.btn.grid(row=0, column=0)
# Set up text
self.label = tk.Label(self.frame, text=text)
self.label.grid(row=1, column=0)
# --------------------- Buttons ---------------------------
class MicroGroove(tk.Button):
def __init__(self, parent, **kw):
super().__init__(master=parent, width=30, height=20, relief="groove", **kw)
class LargeGroove(tk.Button):
def __init__(self, parent, **kw):
super().__init__(master=parent, width=130, height=130, relief="groove", **kw)
Tkinter never forces you to pass in the master argument. You usually do it like in this:
self.label = tk.Label(self.frame, text=text)
self.frame is the master for the label. Similarly you should pass in the master parameter when creating PhotoImages like this:
image = PhotoImage(image_path, master=parent)
This is only necessary only if you have more than 1 Tk() instance, but I would advise always passing it.
If you don't pass it in, tkinter assumes that the master is the first instance of Tk() that you created. It is a problem because the different instances of Tk() can't talk to each other (most of the time) so the PhotoImage doesn't actually exist according to the second instance of Tk().

Frame object has no attribute

Part of a GUI program I'm building needs to be able to convert times given into seconds. The frame class in my GUI that does this is giving me some trouble. I created an instance variable of type combobox to hold the options for types of time period to convert. I bound the combobox selection to convert the input time into seconds. I want to tie entering values into the entry box to doing the same thing. I tried to call my conversion function in my validation command function for the entry box, but it's telling me that my frame object "PERIODIC" doesn't have an attribute "period_type". I'm confused because I named the combobox as an instance variable, and it should be accessible to everything in the class. "self.period_type" is right there in my init. Why can't I access this variable? Am I missing something painfully obvious?
The Traceback I'm getting is:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\4D_User\AppData\Local\Programs\Python\Python38-32\lib\tkinter\__init__.py", line 1883, in __call__
return self.func(*args)
File "C:/Users/PycharmProjects/LoggerProject/Scripts/ProblemExample.py", line 37, in ValidateTime
self.convert_time(self.period_type.get())
AttributeError: 'PERIODIC' object has no attribute 'period_type'<
This is my code:
from tkinter import ttk
import re
root = tk.Tk()
class PERIODIC(tk.Frame):
def __init__(self, container, **kwargs):
super().__init__(container, **kwargs)
self.time_unconverted = tk.DoubleVar()
self.time_converted = tk.DoubleVar()
self.periodic_label = tk.Label(self, text="PERIODIC")
self.periodic_label.grid(row=0, columnspan=2, sticky="NSEW")
trigger_option_label = ttk.Label(self, text="Trigger Every: ")
trigger_option_label.grid(row=1, column=0)
vcmd = (self.register(self.ValidateTime), '%P')
self.num_period = tk.Entry(self,textvariable=self.time_unconverted, validate="key", validatecommand=vcmd)
self.num_period.grid(row=1, column=1)
self.period_type = ttk.Combobox(self, values=["seconds", "minutes", "hours", "days"])
self.period_type.bind("<<ComboboxSelected>>", lambda y: self.convert_time(self.period_type.get()))
self.period_type.grid(row=1, column=2)
def convert_time(self, type):
if type == 'seconds':
self.time_converted.set(self.time_unconverted.get())
if type == 'minutes':
self.time_converted.set(self.time_unconverted.get() * 60)
if type == 'hours':
self.time_converted.set(self.time_unconverted.get() * 3600)
if type == 'days':
self.time_converted.set(self.time_unconverted.get() * 86400)
def ValidateTime(self, P):
test = re.compile('^[0-9]{1,3}?\.?[0-9]?$')
if test.match(P):
self.convert_time(self.period_type.get())
return True
else:
return False
frame = PERIODIC(root)
frame.grid(row=0,column=0)
root.mainloop()
Your validation command is being triggered when you create the entry, and it's using self.period_type which hasn't been defined yet.
The simple solution is to move the creation of the combobox prior to creating the entry.

Why can't the Tkinter Button configuration of a Class be modified?

I am constructing a simple hex editor in Python using Tkinter. I want to be able to indicate selection of a value (the "0000"s) in the GUI by changing the colors of a pressed button.
To implement this, I instantiated each button as a class inside a for loop and put each instance into a list to reference them later in the code. When I attempt to alter the Button API properties of the classes in the while loop, the IDLE prints an error when the hex editor is closed:
Traceback (most recent call last):
File "C:/Users/Administrator/Desktop/Files/Python/hex editor.py", line 64, in <module>
ml.button.configure(bg='#00FF00', fg='#000000')
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python36\lib\tkinter\__init__.py", line 1479, in configure
return self._configure('configure', cnf, kw)
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python36\lib\tkinter\__init__.py", line 1470, in _configure
self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
_tkinter.TclError: invalid command name ".!frame.!frame.!button"
The intended behavior of the button is that when any "0000" is clicked, the selected button will become green and remain so until another button is pressed. (i.e the pressed button will turn green and the previously selected button will turn black)
Source Code:
from tkinter import *
selected_i = 0
selected_j = 0
data_w = 16
address_w = 8
class mem_location:
def __init__(self, row, column, data):
self.row = row
self.column = column
self.data = data
self.button = Button(subframe,
text=self.data,
font=('Consolas',9),
bd=0,
command=self.select)
self.button.pack(side=LEFT)
self.button.configure(bg='#000000', fg='#00FF00')
def select(self):
selected_i = self.row
selected_j = self.column
root = Tk()
root.configure(bg="black")
root.title('Hex Editor')
frame = Frame(root,
padx=30,
pady=10,
bg='black')
frame.pack()
ml_list = []
for i in range((1<<address_w)>>4):
subframe = Frame(frame,
padx=10,
bg='black')
subframe.pack()
addr_label = Label(subframe,
text=hex(i<<4)+" ",
bg='black',
fg='#00FF00',
width=5)
addr_label.pack(side = LEFT)
for j in range(16):
ml_list.append(mem_location(i, j, '0000'))
root.mainloop()
while True:
for ml in ml_list:
if selected_i == ml.row and selected_j == ml.column:
ml.button.configure(bg='#00FF00', fg='#000000')
else:
ml.button.configure(bg='#000000', fg='#00FF00')
I am currently in the process of learning about Tkinter. Why can't I modify the class's Button configuration and how can this be fixed?
The code after root.mainloop will not run until the window is not closed, so you are trying to modify the buttons after the window has been closed.
Instead, you could move the code in the submit method like this:
from tkinter import *
# You don’t need selected_i and selected_j anymore
data_w = 16
address_w = 8
class MemLocation:
def __init__(self, data):
# Same code without self.row and self.column
def select(self):
for ml in [ml_ for ml_ in ml_list if ml_ is not self]: # All elements except this one
ml.button.config(bg='#000', fg='#0F0')
self.button.config(bg='#0F0', fg='#000')
# Same code here
root.mainloop()
#END
Note that I renamed the class according to PEP 8 but there are many other improvement possible like making MemLocation inherit from Frame or Button, or adding event listeners to make the code cleaner.

_tkinter.TclError: can't pack when trying to add ttkcalendar into tkinter GUI

I'm trying to add a ttk calendar into my Tkinter GUI. The problem is that it raises _tkinter.TclError: can't pack .34164128 inside .34161248.34161448.34161608
import Tkinter
import tkSimpleDialog
import ttkcalendar
class CalendarDialog(tkSimpleDialog.Dialog):
"""Dialog box that displays a calendar and returns the selected date"""
def body(self, master):
self.calendar = ttkcalendar.Calendar(master)
self.calendar.pack()
def apply(self):
self.result = self.calendar.selection
# Demo code:
def main():
root = Tkinter.Tk()
root.wm_title("CalendarDialog Demo")
def onclick():
print 'click'
cd = CalendarDialog(root)
button = Tkinter.Button(root, text="Click me to see a calendar!", command=onclick)
button.pack()
root.update()
root.mainloop()
if __name__ == "__main__":
main()
TRACEBACK:
File "C:/Users/Milano/PycharmProjects/MC/plots/ds.py", line 32, in <module>
main()
File "C:/Users/Milano/PycharmProjects/MC/plots/ds.py", line 23, in main
cd = CalendarDialog(root)
File "C:\Python27\lib\lib-tk\tkSimpleDialog.py", line 64, in __init__
self.initial_focus = self.body(body)
File "C:/Users/Milano/PycharmProjects/MC/plots/ds.py", line 9, in body
self.calendar = ttkcalendar.Calendar(master)
File "C:\Users\Milano\PycharmProjects\MC\plots\ttkcalendar.py", line 52, in __init__
self.__place_widgets() # pack/grid used widgets
File "C:\Users\Milano\PycharmProjects\MC\plots\ttkcalendar.py", line 110, in __place_widgets
self._calendar.pack(in_=self, expand=1, fill='both', side='bottom')
File "C:\Python27\lib\lib-tk\Tkinter.py", line 1940, in pack_configure
+ self._options(cnf, kw))
_tkinter.TclError: can't pack .34164128 inside .34161248.34161448.34161608
Do you know where is the problem?
The fault is that you don't have an __init__ method in the class CalendarDialog. So just rename the body method to __init__. Now you have initialized the instance every time one is made and a pack() method is defined.
I also encountered this problem putting a ttkCalendar into a Dialog box.
I suspect the author of this post "borrowed" the same code for building a calendar as I did:
https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=2&ved=2ahUKEwiWgYWKsJ3nAhVKl3IEHYrhCU8QFjABegQICBAB&url=https%3A%2F%2Fsvn.python.org%2Fprojects%2Fpython%2Ftrunk%2FDemo%2Ftkinter%2Fttk%2Fttkcalendar.py&usg=AOvVaw0ifTox4EI7CtBFWlRYD_m9
There are two problems I found using this code to create a Calendar object and placing it into a Dialog box.
The first one causes the traceback as shown in the post. The fix is to modify the ttkcalendar.py file to pack the calendar when it is created, not after it is created using the pack() function.
Here is the diff:
102c102
< self._calendar = ttk.Treeview(show='', selectmode='none', height=7)
---
> self._calendar = ttk.Treeview(self, show='', selectmode='none', height=7)
109c109
< self._calendar.pack(in_=self, expand=1, fill='both', side='bottom')
---
> self._calendar.pack(expand=1, fill='both', side='bottom')
Once you make this change, the calendar will appear in the dialog box. However, your problems are not yet done. Another exception occurs when trying to set the minimum size of the calendar:
Exception in Tkinter callback
Traceback (most recent call last):
File "/home/richawil/Applications/anaconda3/envs/TLM/lib/python3.7/tkinter/__init__.py", line 1705, in __call__
return self.func(*args)
File "/home/richawil/Documents/Programming/Apps/TLM/TLM/ttkcalendar.py", line 134, in __minsize
width, height = self._calendar.master.geometry().split('x')
AttributeError: 'Calendar' object has no attribute 'geometry'
I have not been able to fix this issue other than to comment out the call to self.__minsize.
63c62,63
< self._calendar.bind('<Map>', self.__minsize)
---
> # Commented out because _calendar object does not support geometry() function
> #self._calendar.bind('<Map>', self.__minsize)

Categories

Resources