Tuesday, December 29, 2015

Tkinter binding Return key to an Entry widget

For my GUI for my project, I wanted to pop up a text entry widget to get a parameter for the user, and have it disappear and return whatever was entered when they pressed the "Return" key. Several examples of using the Entry widget had an "Enter" button, but that would be too much extra mouse motion for my customers (it makes sense for a form with several parameters). I'm also still not to the point of programming an entire GUI, I'm just trying to pop up some windows whenever my program needs input, so in that regard I'm already not doing this right. However, I copied the example methodology that I used to make the button window earlier, which was to define a class for the general type of dialog I wanted, and then a method that instantiated an object of that class, ran it, destroyed it, and returned the result. On my windows version of python, things were a little bit different than in some examples that I found, and the big trick was getting the "Return" key binding to work right.

Here are the top two links that I found for using Entry widgets. They don't directly show using a class (both have the call to mainloop() right in line with the rest of the code).

This link is slightly lighter in example code than the next, but it shows use of get(), focus_set(), delete(), and insert(). Like the next link, its example uses a button to call get(). This link has the example of how to set the width using entry.config(width=width).

http://effbot.org/tkinterbook/entry.htm


This one shows how to insert default text into entry widgets and how to use the get() method. It also shows the delete() method for clearing an entry widget before inserting default text, but the examples use some kind of 'END' keyword that didn't work for me, and for me the widgets started empty anyhow. It then has some examples of nice formatting, but I actually just wanted to have the text entry frame and have the user prompt about what to enter in the title bar:

http://www.python-course.eu/tkinter_entry_widgets.php


Both of the above links seemed to show the Entry widget being imported from Tkinter, but in my case, it turned out that for me for some reason I couldn't find Entry in the Tkinter library, instead it was in the ttk libary. I seem to have lost the link that gave me the clue to try that.

Here's the example of how to use the root window .title property to make the text I wanted appear in the title bar of the entry window:

https://bytes.com/topic/python/answers/34607-how-change-text-title-bar-tkinter-windows



The next few links had clues about binding the Return key to a callback. However none of them were using the Entry widget in a class like I was, so the way that they had the callback contain references to the entry object didn't work for me.

Here's a really basic example showing binding, but doesn't show using the get() function:

http://stackoverflow.com/questions/16996432/cant-figure-out-how-to-bind-the-enter-key-to-a-function-in-tkinter


This one showed a callback, but I didn't notice until much later that it's doing something interesting by passing a "textvariable" parameter into the definition of the Entry widget that the callback is then accessing. This is the use of a "StringVar" type that one of the previous links disparaged and which I was trying to do without.

http://www.java2s.com/Code/Python/GUI-Tk/BindenterkeytoEntry.htm



The following super basic example turned out to have the solution, in that it showed the callback bound to the Return key using the event argument to the callback to find a pointer the entry window so that it's get() function could be called:

https://mail.python.org/pipermail/tkinter-discuss/2008-June/001447.html


So, here is the first version of the code that seemed to work for me:

class NoButtonEntry(object):
def __init__(self, title, default):
root = self.root = Tkinter.Tk()
root.title(title)

e = ttk.Entry(root)
e.config(width=(len(title)+20))
if (default):
e.insert(0,default)
e.bind('',self.return_it)
e.pack()
e.focus_set()

root.protocol("WM_DELETE_WINDOW", self.close_thing)
root.deiconify()
root.lift()
root.call('wm', 'attributes', '.', '-topmost', True)
root.call('wm', 'attributes', '.', '-topmost', False)
def return_it(self, event=None):
self.returning = event.widget.get()
self.root.quit()
def close_thing(self):
self.returning = None
self.root.quit()
def mentry(title,default=None):
newentry = NoButtonEntry(title,default)
newentry.root.mainloop()
newentry.root.destroy()
return newentry.returning
serial = mentry(title='Enter text or return to accept default',default='0')

Update: So, later I went on to try to add an "accept" button to my pop-up and found myself not able to make that work either. The solution turned out to be so simple, after looking at example after example. If I defined the entry object as an object within my class, by saying something like self.e = ttk.Entry(root) instead of e = ttk.Entry(root), then the callback for my button could find the entry object! Doing it the other way made the declaration of the entry object local only and not accessible to other objects in the class In the end, I learned to just always make objects in my GUI class members of that class so that they could all access each other.

Here is just a great general tutorial on how use Tkinter including how to define entry and button objects, with solid examples:

http://python-textbok.readthedocs.org/en/latest/Introduction_to_GUI_Programming.html


Here is the example that probably helped me figure it out, because the OP shows doing it the way I was trying to do it, and somebody answers with an example of doing it correctly:

http://stackoverflow.com/questions/9355742/python-tkinter-button-entry-combination


Oh, and here's more info on how to set the focus to the entry widget. Not sure why I didn't use this except that the answer seems to be for a Tkinter Entry widget and I'm using a ttk Entry widget:

http://stackoverflow.com/questions/13626406/setting-focus-to-specific-tkinter-entry-widget


So now my code looks like this:

class NoButtonEntry(object):
def __init__(self, title, default):
root = self.root = Tkinter.Tk()
root.title(title)

self.e = ttk.Entry(root)
self.e.config(width=(len(title)+20))
if (default):
self.e.insert(0,default)
self.e.bind('',self.return_it)
self.e.pack(side="left")
self.e.focus_set()

self.b = Tkinter.Button(root,text="Accept",command=self.return_it)
self.b.pack(side="left")

root.protocol("WM_DELETE_WINDOW", self.close_thing)
root.deiconify()
root.lift()
root.call('wm', 'attributes', '.', '-topmost', True)
root.call('wm', 'attributes', '.', '-topmost', False)
def return_it(self, event=None):
# Note I no longer have to care about whether it was from an event
self.returning = self.e.get()
self.root.quit()
def close_thing(self):
self.returning = None
self.root.quit()
def mentry(title,default=None):
newentry = NoButtonEntry(title,default)
newentry.root.mainloop()
newentry.root.destroy()
return newentry.returning
serial = mentry(title='Enter text or return to accept default',default='0')