I'm displaying two windows. The first one for saving my game with Entry widget. And the second one which is my game.
When I close the widget with .destroy() function it works. But then as I want to leave the game I do fenetre.destroy() but nothing happens. And I got a message error when I close the window manually:
_tkinter.TclError: can't invoke "destroy" command: application has been destroyed
This is my code:
def game_quit():
global name
if askyesno("Quit game ?","Are you sure? :("):
if askyesno("Save ?","Do you want to save your game? "):
ask_name()
save_scoreG(grid,lenght)
fenetre.destroy()
def ask_name():
global entry, master
master = Toplevel()
master.title("Your Name")
button=Button(master, text='Input your name and click here', command = get_name, bg= "yellow" )
usertext= StringVar()
entry = Entry(master, textvariable=usertext)
entry.pack()
button.pack()
master.mainloop()
def get_name():
global name, entry, master
name = str(entry.get())
master.destroy()
def save_scoreG(grid,lenght):
global name
with open('score','a') as s:
s.write(str(lenght)+':' + name +':'+ str(score(grid,lenght))+'\n')
I can't simplify this to get the error in shell:
>>> from tkinter import Toplevel, Tk
>>> fenetre = Tk()
>>> w = Toplevel()
>>> w.destroy()
>>> fenetre.destroy()
>>> fenetre.destroy()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "Z:\py34-64\lib\tkinter\__init__.py", line 1842, in destroy
self.tk.call('destroy', self._w)
_tkinter.TclError: can't invoke "destroy" command: application has been destroyed
I'm really not a fan of using global variables, especially in a GUI program, where everything should be in classes inheriting from the base widgets.
I would like to propose a function that will do what I think you are trying to achieve, in -in my opinion- a much clearer way.
So here is a function that opens a pop-up, asks a name, and returns it.
def ask_name():
toplevel = tk.Toplevel()
label = tk.Label(toplevel, text="What's your name?")
entry = tk.Entry(toplevel)
button = tk.Button(toplevel, text="OK", command=toplevel.quit)
toplevel.pack(label)
toplevel.pack(entry)
toplevel.pack(button)
toplevel.mainloop()
return entry.get()
This function allows you not to use global variables. Besides, it takes no parameter. I like this style, because this function could almost be integrated into a utils.py module, since it is absolutely independent of any context.
Though this might not directly solve your problem, this philosophy of avoiding global variables will help keep your code clean of weird dependencies, and much easier to understand and debug.
Related
New to tkinter, I want a short program to:
create a window to take user input, by means of an entry widget and a submit button
capture the input and return it to main, so that I can do something else with it.
The following code creates the window and captures input, but breaks at the next-to-last line.
from tkinter import *
from tkinter import ttk
class TxtEntryWindow:
def __init__(self):
self.root = Tk()
self.frame = ttk.Frame(self.root).pack()
self.box = ttk.Entry(self.frame)
self.box.pack()
self.but = ttk.Button(self.frame, text='Submit', command=self.capture_txt)
self.but.pack()
self.root.mainloop()
def capture_txt(self):
txt = self.box.get()
return txt
win = TxtEntryWindow()
user_input = win.capture_txt()
print(user_input)
Here's a copy of the error message:
Traceback (most recent call last):
File "C:...\wclass.py", line 22, in
user_input = win.capture_txt()
File "C:...\wclass.py", line 17, in capture_txt
txt = self.box.get()
File "C:...\Python\Python310\lib\tkinter_init_.py", line 3072, in get
return self.tk.call(self._w, 'get')
_tkinter.TclError: invalid command name ".!entry"
I have no idea what this means. I suspect that dependence of "txt" on the button event in the class prevents "win.capture_txt()" in main() at the bottom from fetching the user input.
Can anyone shed light?
Consulted Python's official documentation (unintelligible), Tkinter's official tutorial and command reference. Searched numerous analogies on StackOverflow and on youtube. Googled the error message itself. I've tried to strike out on my own and rewrite the critical command about twenty times. Blind intuition leads nowhere. Stabbing in the dark.
The error means that the entry widget (self.box) has been destroyed when the line user_input = win.capture_txt() is executed.
You can use an instance variable to store the input text instead and access this instance variable after the window is destroyed:
from tkinter import *
from tkinter import ttk
class TxtEntryWindow:
def __init__(self):
self.text = "" # initialize the instance variable
self.root = Tk()
self.frame = ttk.Frame(self.root).pack()
self.box = ttk.Entry(self.frame)
self.box.pack()
self.but = ttk.Button(self.frame, text='Submit', command=self.capture_txt)
self.but.pack()
self.root.mainloop()
def capture_txt(self):
# save the user input into the instance variable
self.text = self.box.get()
self.root.destroy()
win = TxtEntryWindow()
print(win.text) # show the content of the instance variable
I am trying to create a popup menu that opens only when right-clicked inside of certain widgets (Text and Entry, in this case) but nowhere else
inside the root window.
When a user right-clicks inside one of the widgets and selects "copy", the text selection inside that widget
should be copied to the clipboard.
As is, the code below only works when explicitly referring to a certain widget but I want to generalize the copyToClipboard function
to copy the text selection from the widget that the user right-clicked inside.
Instead, running the commented out lines from the code below gives the following error:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\...\...\lib\tkinter\__init__.py", line 1702, in __call__
return self.func(*args)
TypeError: <lambda>() missing 1 required positional argument: 'e'
How do I access the appropriate (right-clicked) widget within the copyToClipboard function?
def copyToClipboard():
#def copyToClipboard(event):
#value = event.widget.get(SEL_FIRST,SEL_LAST)
value = inputText.get(SEL_FIRST,SEL_LAST)
pyperclip.copy(value)
print(value)
def showMenu(event):
popup.post(event.x_root, event.y_root)
inputEntry = ttk.Entry(root)
inputText = Text(root)
popup = Menu(root)
popup.add_command(label="Copy", command=copyToClipboard)
#popup.add_command(label="Copy", command=lambda e: copyToClipboard(e))
inputText.bind("<Button-3>", showMenu)
inputEntry.bind("<Button-3>", showMenu)
inputText.pack()
inputEntry.pack()
mainloop()
I've added a solution below. Storing event.widget as a global variable seemed to help as per acw's suggestion. I got rid of pyperclip because it kept giving me chinese chars and other random chars when mixing clicking-to-copy with Ctrl-V.
EDIT: It is worth noting that the Entry widget doesn't seem to handle line breaks well when they are pasted with Ctrl-V into the entry widget. Unfortunately, I haven't found an effective way to override the hotkey's default paste command to remove the line breaks prior to pasting.
from tkinter import *
from tkinter import ttk
root = Tk()
def copyToClipboard():
val = clickedWidget.selection_get()
root.clipboard_clear()
root.clipboard_append(val)
def showMenu(event):
global clickedWidget
clickedWidget = event.widget
popup.post(event.x_root, event.y_root)
inputEntry = ttk.Entry(root)
inputText = Text(root)
popup = Menu(root)
popup.add_command(label="Copy", command=copyToClipboard)
inputText.bind("<Button-3>", showMenu)
inputEntry.bind("<Button-3>", showMenu)
inputText.pack()
inputEntry.pack()
mainloop()
I would love some help, thanks!
I am trying to use lambdas to assign functionality to window events. It already worked for assigning the "enter" button to a function.
But, for some reason, it doesn't work for the default exit button on the window.
As you can see in the create_entry_window function, I used a lambda twice, and it worked only for the "Return".
The problem occurs with this line:
root.protocol("WM_DELETE_WINDOW", (lambda event: exit_entry(root)))
Here is the code:
from tkinter import Tk, Frame, BOTTOM, Entry, Text, END
def clear_and_get_entry(root, entry):
"""
"""
global entered_text
entry_text = entry.get()
entry.delete(0, END)
entry.insert(0, "")
entered_text = entry_text
root.destroy()
def exit_entry(root):
"""
"""
global entered_text
entered_text = False
print "here at exit"
root.destroy()
def create_entry_window(text):
"""
"""
root = Tk()
root.title("One picture is worth a thousand sounds!")
root.geometry("500x200")
root.resizable(width=False, height=False)
bottom_frame = Frame(root)
bottom_frame.pack(side=BOTTOM)
entry = Entry(root)
entry.pack(side=BOTTOM)
description_text = Text(root, height=50, width=100)
description_text.insert(END, text)
description_text.tag_configure("center", justify='center')
description_text.tag_add("center", "1.0", "end")
description_text.pack()
root.protocol("WM_DELETE_WINDOW", (lambda event: exit_entry(root)))
entry.bind("<Return>", (lambda event: clear_and_get_entry(root, entry)))
return root
if __name__ == '__main__':
root = create_entry_window("Some text")
root.mainloop()
When trying to exit the window, I get this error:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Heights\PortableApps\PortablePython2.7.6.1\App\lib\lib-tk\Tkinter.py", line 1470, in __call__
return self.func(*args)
TypeError: <lambda>() takes exactly 1 argument (0 given)
The quick-fix to your problem is quite easy:
Change the line reading root.protocol("WM_DELETE_WINDOW", (lambda event: exit_entry(root))) into
root.protocol("WM_DELETE_WINDOW", lambda: exit_entry(root))
The root.protocol does not provide an argument when triggered. But your lambda expects one. Removing the argument event fixes the problem for me.
Please note that there are some other issues with your code.
You are still using Python 2. Unless there are very compelling reasons to do so, I suggest you move forward to Python 3. I had not problems running your code with Python 3. All I needed to do was to add brackets to the print statement:
print "here at exit" -> print("here at exit")
Secondly: You are using global in order for your functions to communicate with each other. This is considered bad-practice. It leads to confusing code that is very hard to debug. I suggest that you have a closer look at some tkinter examples and how they are using an object oriented approach to deal with this issue. A possible starting point could be Introduction_to_GUI_Programming. The Calculator class looks like a good starting-point to me.
I'm a beginner in python and I'm creating an interface where I can click on a button to open a new window and then fill a form.
My problem here is about to get the value of the entry after the customer pressed the button print.
First as you can see I created a window.
This is example of what i want to do.
def return_nom(*args):
return ent1.get()
def save():
print("your name is", return_nom())
def new_win():
top = Toplevel(fen)
top.title("new window")
strvar = StringVar()
strvar.trace("w", return_nom)
ent1 = Entry(top, textvariable=strvar)
bouton1 = Button(top, text='print', command=save)
bouton1.pack()
ent1.pack()
top.mainloop()
fen = Tk()
lab = Label(fen)
lab.pack()
bouton = Button(fen, text='new window', command=new_win)
bouton.pack()
fen.mainloop()
If someone can tell me why it doesn't work and explain me why this technique works when I use this it for an entry on the main interface.
Thanks everybody ! ;)
The main issue you have here is scope, since you're trying to access the Entry and StringVar from functions that don't have access to it.
The name ent1 defined inside new_win() will not be accessible from return_nom().
Furthermore, it seems to me that what you actually want to query is the StringVar and not the Entry widget itself.
How you can pass the StringVar to the called functions, there are several ways:
Make the strvar a global variable, that way the other functions will have access to it. This is probably the worst possible solution to this problem, since the point of having functions is to avoid namespace pollution of a global namespace.
Make the return_nom() and save() functions inner functions of new_win(), which allows them to access the local variables in new_win() as a closure. This is slightly better.
Use an object-oriented interface, where your StringVar is an instance member and your save() is a method. State such as the StringVar is available to the whole instance. This is probably the best here, it's how Tkinter is actually intended to be used, it most naturally fits an object-oriented approach.
An example of using inner functions would be:
def new_win():
top = Toplevel(fen)
top.title("new window")
strvar = StringVar()
def return_nom(*args):
return strvar.get()
def save():
print("your name is", return_nom())
ent1 = Entry(top, textvariable=strvar)
bouton1 = Button(top, text='print', command=save)
bouton1.pack()
ent1.pack()
top.mainloop()
An example of an object-oriented approach would be:
class MyDialog:
def __init__(self, fen):
self.fen = fen
self.strvar = StringVar()
def return_nom(self, *args):
return self.strvar.get()
def save():
print("your name is", self.return_nom())
def new_win(self):
top = Toplevel(self.fen)
top.title("new window")
ent1 = Entry(top, textvariable=self.strvar)
bouton1 = Button(top, text='print', command=self.save)
bouton1.pack()
ent1.pack()
top.mainloop()
And then at the top-level:
my_dialog = MyDialog(fen)
bouton = Button(fen, text='new window', command=my_dialog.new_win)
But the above is still not the best approach, which would actually be to create subclasses of Tkinter.Frame for your windows, connect them together through methods and have your top-level code only instantiate a main Application class and call app.mainloop() on it, letting it drive the whole application flow through events connected to methods and other frames.
See a simple Hello World program in the Python tkinter documentation on the Python standard library to get a somewhat better idea of how tkinter is intended to be used.
That documentation also has pointers to many other resources on tkinter and on Tk itself that you can follow to get more in-depth knowledge of that toolkit library.
Check what the traceback is telling you when you start typing:
Exception in Tkinter callback
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/tkinter/__init__.py", line 1883, in __call__
return self.func(*args)
File "tktest.py", line 3, in return_nom
return ent1.get()
NameError: name 'ent1' is not defined
When you define a variable in a function, it's not automatically defined in other functions. That's called "scope of a variable". Either you define ent1 in the new_win() function with the global keyword or you make it a function attribute:
new_win.ent1 = Entry(top, textvariable=strvar)
...
new_win.ent1.pack()
and call it like that:
def return_nom(*args):
return new_win.ent1.get()
Happy programming!
Hi I am using Python27 on Window7 OS, i am trying to create a Tk GUI with button, when the button is press a file directory will appear. But the following code won't do anything. Did i miss out something?
import webbrowser
import Tkinter as Tk
def action(self):
webbrowser.open ('C:\AgmPlots')
win = Tk.Toplevel()
frame = Tk.Frame(master=win).grid(row=1, column=1)
button = Tk.Button(master=frame, text='press', command= lambda: action())
You've got three big problems.
First, you never start the GUI. You need something like win.mainloop() at the end to actually do anything.
Second, your button isn't actually laid out within the frame, so you won't see it. You need something like button.pack().
Finally, your command is a function that calls action(), with no arguments. But you've defined it to require a parameter. So, all that will happen when you click it is that Tk will log a traceback that looks like this:
Exception in Tkinter callback
Traceback (most recent call last):
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk/Tkinter.py", line 1470, in __call__
return self.func(*args)
File "tkt.py", line 8, in <lambda>
button = Tk.Button(master=frame, text='press', command= lambda: action())
TypeError: action() takes exactly 1 argument (0 given)
To fix that, either don't add the unnecessary self parameter to action (this is a function, not a method), or explicitly pass some dummy to match it in your lambda.
While we're at it, lambda: action() does exactly the same thing as action itself, except more verbose, harder to read, and slower. You should never use unescaped backslashes in non-raw string literals. And we might as well remove the stray spaces and PEP8-ify everything to make it consistent.
So, putting it all together:
import webbrowser
import Tkinter as Tk
def action():
webbrowser.open(r'C:\AgmPlots')
win = Tk.Toplevel()
frame = Tk.Frame(master=win).grid(row=1, column=1)
button = Tk.Button(master=frame, text='press', command=action)
button.pack()
win.mainloop()