Can't destroy inherited tkinter.Tk() window - python-3.x

I'm having a ton of trouble killing a tkinter window created using the below fashion. I'm getting the error shown below. I'm pretty new to Python, so any help would be greatly appreciated.
class InspectWindow(tk.Frame):
def __init__(self, sender_email, recipient_email, email_body,
master = None):
super().__init__(master)
self.create_widgets()
def create_widgets(self):
self.yes = tk.Button(self)
self.yes['text'] = 'send me!'
self.yes['command'] = self.send_email()
def send_email(self):
root.destroy()
root = tk.Tk()
popup = InspectWindow(sender_email, recipient_email, email_body,
master=root)
popup.mainloop()
Traceback (most recent call last):
File "spam.py", line 108, in <module>
master=root)
File "spam.py", line 16, in __init__
self.create_widgets()
File "AutomateFellowshipEmails.py", line 23, in create_widgets
self.yes['command'] = self.send_email()
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/tkinter/__init__.py", line 1486, in __setitem__
self.configure({key: value})
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/tkinter/__init__.py", line 1479, in configure
return self._configure('configure', cnf, kw)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/tkinter/__init__.py", line 1470, in _configure
self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
_tkinter.TclError: invalid command name ".!inspectwindow.!button"

The problem is this line of code:
self.yes['command'] = self.send_email()
It is the exact same as if you did this:
result = self.send_email()
self.yes['command'] = reuslt
Since self.send_email() destroys the root window and then returns None, it's the same as doing this:
root.destroy()
self.yes['command'] = None
Once you destroy the root widget -- which causes all other widgets to be destroyed -- you will get an error anytime you try to call a method on a widget. When you try to configure self.yes, self.yes no longer exists so you get an error.
The solution is to pass a reference to the function of a button so that you don't immediately call it. You do it like this:
self.yes['command'] = self.send_email
Notice the lack of parenthesis on self.send_email. Instead of calling the function immediately, you are telling tk "here is the name of a function that I want you to call when the user clicks the button".

you must keep a reference of master in order to call methods on it.
(and a few other errors too)
something like this (I removed undefined variables mail, etc...)
import tkinter as tk
class InspectWindow(tk.Frame): # if this is a popup, you may like to inherit from tk.TopLevel?
def __init__(self, master = None): # removed undefined variables, you can put them back in
self.master = master # keep a reference of master
super().__init__(self.master)
self.create_widgets()
self.pack() # pack into parent window
def create_widgets(self):
self.yes = tk.Button(self)
self.yes['text'] = 'send me!'
self.yes['command'] = self.send_email # removed () call, this takes a method, not a method call
self.yes.pack() # pack into self
def send_email(self):
self.master.destroy() # call destroy on self.master
# maybe you want to call on self instead, depending what you want to destroy
root = tk.Tk()
popup = InspectWindow(master=root) # removed undefined variables, you can put them back in
popup.mainloop()

Related

Pygtk Liststore most recent call last after Liststore update

I created simple code in PyGtk that displays a list of names. After clicking the Update button, the list of names will be updated. Unfortunately, I can't get rid of the "most recent call last" error that appears after clicking the Update button. Can anyone advise me on the problem? This is probably a problem with a line of code under "# select and row".
EDIT:
Script returns error message:
Traceback (most recent call last):
File "/home/radek/Desktop/stackowr1.py", line 18, in choiceRow
line = model[treeiter][0]
File "/usr/lib/python3/dist-packages/gi/overrides/Gtk.py", line 849, in __getitem__
aiter = self._getiter(key)
File "/usr/lib/python3/dist-packages/gi/overrides/Gtk.py", line 837, in _getiter
aiter = self.get_iter(key)
File "/usr/lib/python3/dist-packages/gi/overrides/Gtk.py", line 871, in get_iter
path = self._coerce_path(path)
File "/usr/lib/python3/dist-packages/gi/overrides/Gtk.py", line 846, in _coerce_path
return TreePath(path)
File "/usr/lib/python3/dist-packages/gi/overrides/Gtk.py", line 1210, in __new__
path = ":".join(str(val) for val in path)
TypeError: 'NoneType' object is not iterable
My code:
```python
# -*- coding: utf-8 -*-
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
class MyWindow(Gtk.Window):
def __init__(self):
super(MyWindow, self).__init__()
self.set_border_width(3)
self.set_default_size(800, 600)
self.name1 = "John"
self.name2 = "George"
def choiceRow(selection):
model, treeiter = selection.get_selected()
line = model[treeiter][0]
print(line)
def update_name(self):
self.name1 = "Jeane"
self.name2 = "Margot"
print(self.name1, self.name2)
win.liststore.clear()
win.liststore.append([self.name1])
win.liststore.append([self.name2])
button = Gtk.Button(label = "Update")
button.connect("clicked", update_name)
self.layout = Gtk.Layout()
self.tree = Gtk.TreeView()
self.liststore = Gtk.ListStore(str)
self.tree.set_model(self.liststore)
self.liststore.append([self.name1])
self.liststore.append([self.name2])
render = Gtk.CellRendererText()
self.column = Gtk.TreeViewColumn("ID", render, text=0)
self.tree.append_column(self.column)
# select a row
selectetRow = self.tree.get_selection()
selectetRow.connect("changed", choiceRow)
self.layout.put(self.tree, 0,0)
self.layout.put(button, 0,100)
self.add(self.layout)
win = MyWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
Gtk.TreeSelection.get_selected returns None when nothing is selected, after the update call there's nothing selected so you get None for treeiter and you try to access model[None][0] which obviously must fail. You need to check whether the returned iter is valid before trying to use it so just change your choiceRow function to
def choiceRow(selection):
model, treeiter = selection.get_selected()
if treeiter:
line = model[treeiter][0]
print(line)

RuntimeError: Queue objects should only be shared between processes through inheritance

I'm having some trouble with ProcessPoolExecutor.
The following code is trying to find the shortest path in a WikiRace game, it gets 2 titles and navigates between one to another.
Here is my code:
class AsyncSearch:
def __init__(self, start, end):
self.start = start
self.end = end
# self.manager = multiprocessing.Manager()
self.q = multiprocessing.Queue()
# self.q = self.manager.Queue()
def _add_starting_node_page_to_queue(self):
start_page = WikiGateway().page(self.start)
return self._check_page(start_page)
def _is_direct_path_to_end(self, page):
return (page.title == self.end) or (page.links.get(self.end) is not None)
def _add_tasks_to_queue(self, pages):
for page in pages:
self.q.put(page)
def _check_page(self, page):
global PATH_WAS_FOUND_FLAG
logger.info('Checking page "{}"'.format(page.title))
if self._is_direct_path_to_end(page):
logger.info('##########\n\tFound a path!!!\n##########')
PATH_WAS_FOUND_FLAG = True
return True
else:
links = page.links
logger.info("Couldn't find a direct path form \"{}\", "
"adding {} pages to the queue.".format(page.title, len(links)))
self._add_tasks_to_queue(links.values())
return "Couldn't find a direct path form " + page.title
def start_search(self):
global PATH_WAS_FOUND_FLAG
threads = []
logger.debug(f'Running with concurrent processes!')
if self._add_starting_node_page_to_queue() is True:
return True
with concurrent.futures.ProcessPoolExecutor(max_workers=AsyncConsts.PROCESSES) as executor:
threads.append(executor.submit(self._check_page, self.q.get()))
I'm getting the following exception:
Traceback (most recent call last):
File "c:\users\tomer smadja\appdata\local\programs\python\python36-32\lib\multiprocessing\queues.py", line 241, in _feed
obj = _ForkingPickler.dumps(obj)
File "c:\users\tomer smadja\appdata\local\programs\python\python36-32\lib\multiprocessing\reduction.py", line 51, in dumps
cls(buf, protocol).dump(obj)
File "c:\users\tomer smadja\appdata\local\programs\python\python36-32\lib\multiprocessing\queues.py", line 58, in __getstate__
context.assert_spawning(self)
File "c:\users\tomer smadja\appdata\local\programs\python\python36-32\lib\multiprocessing\context.py", line 356, in assert_spawning
' through inheritance' % type(obj).__name__
RuntimeError: Queue objects should only be shared between processes through inheritance
It's weird since I'm using multiprocessing.Queue() that should be shared between the processes as mentioned by the exception.
I found this similar question but couldn't found the answer there.
I tried to use self.q = multiprocessing.Manager().Queue() instead of self.q = multiprocessing.Queue(), I'm not sure if this takes me anywhere but the exception I'm getting is different:
Traceback (most recent call last):
File "c:\users\tomer smadja\appdata\local\programs\python\python36-32\lib\multiprocessing\queues.py", line 241, in _feed
obj = _ForkingPickler.dumps(obj)
File "c:\users\tomer smadja\appdata\local\programs\python\python36-32\lib\multiprocessing\reduction.py", line 51, in dumps
cls(buf, protocol).dump(obj)
File "c:\users\tomer smadja\appdata\local\programs\python\python36-32\lib\multiprocessing\process.py", line 282, in __reduce__
'Pickling an AuthenticationString object is '
TypeError: Pickling an AuthenticationString object is disallowed for security reasons
Also, when I'm trying to use multiprocessing.Process() instead of ProcessPoolExecutor, I'm unable to finish the process once I do find a path. I set up a global variable to stop PATH_WAS_FOUND_FLAG to stop the process initiation but still with no success. What I'm missing here?
ProcessPoolExecutor.submit(...) will not pickle multiprocessing.Queue instances as well other shared multiprocessing.* class instances. You can do two things: One is to use SyncManager, or you can initialize the worker with the multiprocessing.Queue instance at ProcessPoolExecutor construction time. Both are shown below.
Following is your original variation with a couple of fixes applied (see note at end)... with this variation, multiprocessing.Queue operations are slightly faster than below SyncManager variation...
global_page_queue = multiprocessing.Queue()
def set_global_queue(q):
global global_page_queue
global_page_queue = q
class AsyncSearch:
def __init__(self, start, end):
self.start = start
self.end = end
#self.q = multiprocessing.Queue()
...
def _add_tasks_to_queue(self, pages):
for page in pages:
#self.q.put(page)
global_page_queue.put(page)
#staticmethod
def _check_page(self, page):
...
def start_search(self):
...
print(f'Running with concurrent processes!')
with concurrent.futures.ProcessPoolExecutor(
max_workers=5,
initializer=set_global_queue,
initargs=(global_page_queue,)) as executor:
f = executor.submit(AsyncSearch._check_page, self, global_page_queue.get())
r = f.result()
print(f"result={r}")
Following is SyncManager variation where queue operations are slightly slower than above multiprocessing.Queue variation...
import multiprocessing
import concurrent.futures
class AsyncSearch:
def __init__(self, start, end):
self.start = start
self.end = end
self.q = multiprocessing.Manager().Queue()
...
#staticmethod
def _check_page(self, page):
...
def start_search(self):
global PATH_WAS_FOUND_FLAG
worker_process_futures = []
print(f'Running with concurrent processes!')
with concurrent.futures.ProcessPoolExecutor(max_workers=5) as executor:
worker_process_futures.append(executor.submit(AsyncSearch._check_page, self, self.q.get()))
r = worker_process_futures[0].result()
print(f"result={r}")
Note, for some shared objects, SyncManager can be anywhere from slightly to noticeably slower compared to multiprocessing.* variations. For example, a multiprocessing.Value is in shared memory whereas a SyncManager.Value is in the sync manager processes, requiring overhead to interact with it.
An aside, unrelated to your ask, your original code was passing _check_page with incorrect parameters, where you were passing dequeued item to self, leaving the page parameter None. I resolved this by changing _check_page to a static method and passing self.

NameError: name 'Ptime' is not defined using tkinter scale scroller

I'm trying to create a function to modify the timecode shift of a script for instant replay, but when I create the function including the variable mentioned by Tkinter, I'm getting a name not defined error. I used a software called "PAGE" to create the GUI so I really have a very basic knowledge of tkinter. I'm not quite sure what code I need to include so I'm just going to paste all of the function definition and the entire support script generated by PAGE
I moved all of my function definitions into the support script but to no avail
def set_Tk_var():
global Ptime
Ptime = tk.DoubleVar()
global Pspeed
Pspeed = tk.StringVar()
text = "play: timecode: 00:00:0" + str(Ptime) + ";00 \\nc"
def IRplay():
print('testguiPAGE_support.IRplay')
session.write(b"stop\n")
session.write( text.encode() )
sys.stdout.flush()
in the main script the only place it's being referred to is:
def vp_start_gui():
global val, w, root
root = tk.Tk()
testguiPAGE_support.set_Tk_var()
top = Toplevel1 (root)
testguiPAGE_support.init(root, top)
root.mainloop()
w = None
def create_Toplevel1(root, *args, **kwargs):
global w, w_win, rt
rt = root
w = tk.Toplevel (root)
testguiPAGE_support.set_Tk_var()
top = Toplevel1 (w)
testguiPAGE_support.init(w, top, *args, **kwargs)
return (w, top)
getting error
Traceback (most recent call last):
File "C:\Users\InstSmart\Desktop\Rplay\Outputs\testguiPAGE.py", line 23, in <module>
import testguiPAGE_support
File "C:\Users\InstSmart\Desktop\Rplay\Outputs\testguiPAGE_support.py", line 40, in <module>
text = "play: timecode: 00:00:0" + str(Ptime) + ";00 \\nc"
NameError: name 'Ptime' is not defined
if there is anymore info you need, comment it, and I will add to the question

Bug appears when writing file from tkinter module

import tkinter as tk
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.create_widgets()
def create_widgets(self):
self.hi_there = tk.Button(self)
self.hi_there["text"] = "Done!\n(click me)"
self.hi_there["command"] = self.say_hi
self.hi_there.pack(side="top")
self.entrythingy = tk.Entry()
self.entrythingy2 = tk.Entry()
self.entrythingy.pack()
self.entrythingy2.pack()
# here is the application variable
self.contents = tk.StringVar()
self.contents2 = tk.StringVar()
# set it to some value
self.contents.set("stdio")
self.contents2.set("script name")
# tell the entry widget to watch this variable
self.entrythingy["textvariable"] = self.contents
self.entrythingy2["textvariable"] = self.contents2
self.text = tk.Text()
self.text.pack()
# and here we get a callback when the user hits return.
# we will have the program print out the value of the
# application variable when the user hits return
self.entrythingy.bind('<Key-Return>',
self.print_contents)
self.quit = tk.Button(self, text="QUIT", fg="red",
command=root.destroy)
self.quit.pack(side="bottom")
def say_hi(self):
#print("hi there, everyone!")
self.fn = self.contents2.get()
self.body = self.text.get(1.0, tk.END).split('\n')
#print('Self.body:\n',self.body)
self.libs = self.contents.get().split(' ')
self.make_c()
def make_c(self):
lib_text = ''
for i in self.libs:
lib_text += "#include <lib.h>\n".replace('lib', i)
body_text = "int main() {\n\t"+"\n\t".join(self.body)+"return 0\n}"
print(lib_text+body_text)
with open(self.fn+'.c', 'w+') as f:
f.write(lib_text+body_text)
print('File written!')
from subprocess import call
call(['gcc',self.fn+'.c', '-o', self.fn])
def print_contents(self, event):
print("hi. contents of entry is now ---->",
self.contents.get())
#self.contents.set("")
#def
root = tk.Tk()
app = Application(master=root)
app.mainloop()
Those are the my code, which tries to make a c file and convert it. The problem is, when I convert it once, it is working fine, but when I change the content of the text box, the file doesn't change, and I don't understand why. I am sure that I put in the new file content, because it prints before it writes. Also, it appears that when I try to write files independent from tkinter, it works just the way I want it to.
I think there is some mechanism that I am not aware of in TK, or there is a bug. Please help me out, thanks.
I solved it. It doesn't compile again due to the error in it when I added return 0, without semicolon. So, when I click the executable file, it shows the old program. I added the semicolon, and now it is fine. Thx everyone!

tkinter listbox insert error "invalid command name ".50054760.50055432"

I have a database of objects and you can view the items in the database in a listbox and there's a button to remove an item and to create an item. Creating an item opens a dialog window for the item class and then the item's data is stored in the database. I have reproduced the problem with a very simple duplicate of my set-up (see code below).
Every time I add a new item, the addition is successful (it's there the next time I open up the database dialog), but the listbox doesn't insert the item, and when I close the database dialog I get the following error:
Exception in Tkinter callback Traceback (most recent call last):
File "C:\Python33\lib\tkinter__init__.py", line 1442, in call
return self.func(*args) File "", line 21, in addRecord File "C:\Python33\lib\tkinter__init__.py", line 2604, in insert
self.tk.call((self._w, 'insert', index) + elements)
_tkinter.TclError: invalid command name ".50054760.50055432"
The same problem doesn't come up if I just try to create the object and populate its values without invoking its inputs GUI (which is necessary for the process of inserting things into my database). I've seen a similar error in another thread (sorry, but I can't seem to find it again), where the problem was with multithreading. I'm not aware of any threading that I'm doing and don't want to download yet another package to handle tkinter threading. Any ideas? Workarounds? I'm using Python v3.3 and 64-bit Windows 7, if that helps.
Here's my simplified database code:
import tkinter
import traceback
# Test =========================================================================
class Test:
def __init__(self):
"""A database of objects' IDs and values."""
self.data = {1: 'a', 2: 'b', 3: 'c'}
#---------------------------------------------------------------------------
def addRecord(self):
"""Opens up a new item for editing and saves that ability to the
database."""
print('hi0')
newItem = OtherObject()
newItem.create(self.root)
print('hi1')
self.data[newItem.ID] = newItem.value
print('hi2')
self.listbox.insert(tkinter.END, self.formatItem(newItem.ID))
print('hi3')
#---------------------------------------------------------------------------
def delRecord(self):
"""Removes selected item from the database."""
try:
index = self.listbox.curselection()[0]
selection = self.listbox.get(index)
except:
return
ID = int(selection.split(':')[0])
self.data.pop(ID)
self.listbox.delete(index)
#---------------------------------------------------------------------------
def dataframe(self, master):
"""Assembles a tkinter frame with a scrollbar to view database objects.
(Returns Frame, Scrollbar widget, Listbox widget)
master: (Tk or Toplevel) tkinter master widget."""
frame = tkinter.Frame(master)
# scrollbar
scrollbar = tkinter.Scrollbar(frame)
scrollbar.pack(side=tkinter.LEFT, fill=tkinter.Y)
# listbox
listbox = tkinter.Listbox(frame, yscrollcommand=scrollbar.set)
listbox.pack(side=tkinter.LEFT, fill=tkinter.BOTH)
# fill listbox
for ID in self.data:
listbox.insert(tkinter.END, self.formatItem(ID))
return (frame, listbox, scrollbar)
#---------------------------------------------------------------------------
def destroyLB(self, e):
for line in traceback.format_stack():
print(line.strip())
#---------------------------------------------------------------------------
def formatItem(self, ID):
"""Creates a nice string representation of an item in the database."""
return '{0}:{1}'.format(ID, self.data[ID])
#---------------------------------------------------------------------------
def listboxSelect(self, e):
"""Manages events when the selection changes in the database interface.
e: (Event) tkinter event."""
try:
selection = self.listbox.get(self.listbox.curselection()[0])
except:
return
# set description label
ID = int(selection.split(':')[0])
self.lblstr.set(self.data[ID])
#---------------------------------------------------------------------------
def view(self):
"""Displays database interface."""
self.root = tkinter.Tk()
# listbox frame
self.frame, self.listbox, self.scrollbar = self.dataframe(self.root)
self.frame.grid(column=0, row=0)
self.listbox.bind('<<ListboxSelect>>', self.listboxSelect)
self.listbox.bind('<Destroy>', self.destroyLB)
# record display frame
self.lblstr = tkinter.StringVar()
self.lbl = tkinter.Label(self.root, textvariable=self.lblstr)
self.lbl.grid(column=1, row=0, sticky=tkinter.N)
# buttons frame
self.frame_btn = tkinter.Frame(self.root)
self.frame_btn.grid(row=1, columnspan=2, sticky=tkinter.E+tkinter.W)
# 'create new' button
self.btn_new = tkinter.Button(
self.frame_btn, text='+', command=self.addRecord)
self.btn_new.grid(row=0, column=0)
# 'delete record' button
self.btn_del = tkinter.Button(
self.frame_btn, text='-', command=self.delRecord)
self.btn_del.grid(row=0, column=1)
# display
self.root.mainloop()
# Test =========================================================================
# OtherObject ==================================================================
class OtherObject:
"""An object with an ID and value."""
def __init__ (self):
self.ID = 0
self.value = ''
#---------------------------------------------------------------------------
def create(self, master=None):
"""open a dialog for the user to entry a new object ID and value.
master: (Tk or Toplevel) tkinter master widget."""
self.stuff = tkinter.Toplevel(master)
# ID
tkinter.Label(self.stuff, text='ID: ').grid(row=0, column=0)
self.IDvar = tkinter.StringVar(self.stuff)
self.IDvar.set(self.ID)
IDwidget = tkinter.Entry(self.stuff, textvariable=self.IDvar)
IDwidget.grid(row=0, column=1)
# value
tkinter.Label(self.stuff, text='Value: ').grid(row=1, column=0)
self.valueVar = tkinter.StringVar(self.stuff)
self.valueVar.set(self.value)
valueWidget = tkinter.Entry(self.stuff, textvariable=self.valueVar)
valueWidget.grid(row=1, column=1)
# OK button
tkinter.Button(self.stuff, text='OK', command=self.OK).grid(row=2)
self.stuff.mainloop()
#---------------------------------------------------------------------------
def OK(self):
try: self.ID = int(self.IDvar.get())
except: self.ID = 0
self.value = self.valueVar.get()
self.stuff.destroy()
# OtherObject ==================================================================
Thanks in advance
You are creating more than one instance of Tk. Tkinter is not designed to work like that and you will get unexpected behavior. You need to refactor your code so that you create an instance of Tk only once. If you need multiple windows, create instances of Toplevel.
... time passes ... the code in the question gets updated ...
In the updated version of your question you now are creating one instance of Tk, and then instances of Toplevel. This is good. However, you are also calling mainloop more than once which is a problem. Worse, you're redefining self.root which no doubt is part of the problem. You must call mainloop exactly once over the entirety of your program.
Bryan Oakley guided me to the exact problem that I was encountering:
(1) I created a second Tk object in my OtherObject class.
(2) I called mainloop in my OtherObject class.
Only one Tk object should exist and mainloop should only ever be called once, no matter how many windows are to be displayed.

Resources