5
\$\begingroup\$

I'm specifically asking about binding a keyboard keystroke to a button in a GUI. Originally I planned to bind the keystroke to the GUI button itself, but that seemed impossible, and potentially unwise after I considered it later.

Is this the proper (pythonic) way to bind a keyboard key and GUI button?

## start.py
print 'start'

#from tkinter import *
from Tkinter import *
import os
from PIL import ImageTk, Image

class MyClass:
    '''This is my class.'''

    imgpath = '/Users/user/Dropbox/Camera Uploads'
    imgfile = 'susshi.jpg'
    #imgfilepath = os.path.join(self.imgpath, self.imgfile)
    imgfilepath = os.path.join(imgpath, imgfile)

    def __init__(self,master):
        print 'in __init__'

        # Create and load a frame into the tk (tkinter) window.
        frame = Frame(master)
        frame.pack()

        # Create and load two buttons in to the above-created frame.
        self.button = Button(frame, text="QUIT", fg="red", command=quit)
        self.button.pack(side=LEFT)
        self.slogan = Button(frame, text="Hello", command = self.f)
        self.slogan.pack(side=RIGHT)

        # Bind a key to each button from above.
        master.bind('q', quit)
        master.bind('f', self.f)

        # Open the image, resize it...
        self.image = Image.open(self.imgfilepath)
        self.image.thumbnail((600,600))
        # Make a tkinter-friendly image object ...
        self.display_image = ImageTk.PhotoImage(self.image)
        # Make a tkinter canvas, plug in the image, and pack the canvas.
        self.canvas = Canvas(master, bg='red')
        self.canvas.create_image(0,0, image=self.display_image, anchor="nw")
        self.canvas.pack(fill=BOTH, expand=1)
        ### FIXME: Set canvas focus!

    def f(self, event=None):
        print 'in f()'
        return 'hello world'

    def get_image_list(self):
        pass

    def next_image(self):
        pass

    def previous_image(self):
        pass

    def load_image(self):
        #stub
        pass

    def update_image(self):
        pass

root = Tk()
abc = MyClass(root)
root.mainloop()

print 'abc has been defined'
print '"abc.f()" returns "%s"' % abc.f()
#print str(abc.f)
#print repr(abc.f)

print 'end'

The intention of the project is to make a simple image viewer in python, with buttons in the GUI for basic navigation functions, and keys mapped to those same functions.

\$\endgroup\$
  • \$\begingroup\$ This is the actual code I'm using. I haven't implemented the other functions yet, and I was hoping to just get the code implemented thus-far reviewed. In fact, the original question was just asking about reviewing one specific portion of the code - that is, the binding of the keystrokes in association with the buttons in the GUI. However, that specific-level of question was downvoted and given a recommendation to adhere to the guidance in the help center. I understand there may be a common issue of incomplete code segments posted, but I have truly included the entire module. \$\endgroup\$ – user3.1415927 Apr 7 '18 at 15:03
  • \$\begingroup\$ This question has been brought to attention on meta. \$\endgroup\$ – Simon Forsberg Apr 7 '18 at 15:53
  • \$\begingroup\$ On the last edit: There really isn't a GUI-agnostic way to ask this question. Each library will have its own way of handling button binding. An answer in PyQT would be useless for tkinter. \$\endgroup\$ – Stevoisiak May 1 '18 at 15:39
  • \$\begingroup\$ @StevenVascellaro interesting, I am unfamiliar with any other GUI options for python. In fact, I'd be open to suggestions with those other frameworks, if they are also pythonic - hence the leaving of the wording. Although I do understand the wording change suggestion (since my code was already using tkinter), I'd be open to the correction I see from time to time on SE of: 'Yes, it will work this way, but a better way is to XYZ.' I'm here to learn! :) \$\endgroup\$ – user3.1415927 May 1 '18 at 15:42
  • \$\begingroup\$ @user3.1415927 I'm using tkinter myself, but PyQT is a popular alternative. (See Graphic User Interface FAQ) \$\endgroup\$ – Stevoisiak May 1 '18 at 15:45
2
\$\begingroup\$

TL;DR: Yes, binding the keyboard key and button to the same function is a proper solution.

How to bind a keyboard key to a tkinter button

There are two ways to share a function between a bind keypress a command button.

Method 1: Use an optional event parameter

As you have already discovered yourself, you can use an optional event parameter that you don't depend on.

Since the parameter is unused, you can prefix it with an underscore (_event) to prevent warnings.

    self.button = Button(frame, text="Hello", command=self.func)
    master.bind('f', self.func)

def func(self, _event=None):
    print("Hello, world")

Full Example:

# from tkinter import Button, Frame, Tk  # Python 3
from Tkinter import Button, Frame, Tk    # Python 2

class MyClass:
    def __init__(self, master):
       frame = Frame(master)
       frame.pack()

       self.button = Button(frame, text="Hello", command=self.func)
       self.button.pack(side='left')

       master.bind('f', self.func)

    def func(self, _event=None):
        print("Hello, world")

root = Tk()
abc = MyClass(root)
root.mainloop()

Method 2: Use lambdas

If the event parameter isn't being used, you can discard it by binding your key to a lambda function.

    self.button = Button(frame, text="Hello", command=self.func)
    master.bind('f', lambda event: self.func())

def func(self):
    print("Hello, world")

Full Example:

# from tkinter import Button, Frame, Tk  # Python 3
from Tkinter import Button, Frame, Tk    # Python 2

class MyClass:
    def __init__(self, master):
        frame = Frame(master)
        frame.pack()

        self.button = Button(frame, text="Hello", command=self.func)
        self.button.pack(side='left')

        master.bind('f', lambda event: self.func())

    def func(self):
        print("Hello, world!")

root = Tk()
abc = MyClass(root)
root.mainloop()

(See How do I make Bind and Command do the same thing in tkinter?)


Miscellaneous tips

Avoid using wildcard imports

You should try to be explicit whenever you import in Python.

Yes

from Tkinter import Tk, Button, Frame

Yes

import Tkinter as tk

No

from Tkinter import *

(See Why is “import *” bad?)

Switch to Python 3

As coal_'s answer mentioned, development on Python 2 is scheduled to end in 2020. To ensure continued support, you should transition to Python 3 or later.

Use strings when packing constants

This is more personal preference than a rule, but I usually try to use strings when packing in tkinter. It means you don't have to import as many things.

Yes

self.button.pack(side="left")

No

self.button.pack(side=LEFT)
\$\endgroup\$
  • \$\begingroup\$ Unused variables are typically named _. Prefixing a name with an underscore is usually done to distinguish 'private' names from 'public' names (those listed in __all__, or at least those part of the API). \$\endgroup\$ – Daniel May 1 '18 at 15:33
  • \$\begingroup\$ @Coal_ I remember seeing unused _ arguments suggested as a PEP proposal. Was that proposal ever approved? \$\endgroup\$ – Stevoisiak May 1 '18 at 15:37
  • \$\begingroup\$ As a PEP on its own? I'm looking through the list of PEPs now, but can't find it. \$\endgroup\$ – Daniel May 1 '18 at 15:56
  • 1
    \$\begingroup\$ I may have been mistaken about PEP. I’ve found there isn’t much consensus on how to name unused arguments, but Pylint favors descriptive names prefixed with underscore. \$\endgroup\$ – Stevoisiak May 1 '18 at 16:00
0
\$\begingroup\$

Is this the proper(, pythonic) way to bind a keyboard key and GUI button?

Sure. It's hard to judge exactly what you mean by 'proper', but it works, and I don't see any major caveats with this approach. Binding the callback to the button and calling button.focus_set() works just as well, but your approach makes more sense. Think about it: Does the callback apply only to the button? At least in the case of the quit-callback, no; no matter what widget has focus, pressing 'q' should quit the application.

Some other remarks:

  1. Using wildcard imports (from <module> import *) is discouraged for a number of reasons. For one, using wildcard imports can cause conflicts if two symbols with the same name, defined in different namespaces, are imported into the global namespace. Secondly, for other developers, there's no way to quickly tell where an object was defined.

  2. PEP-8 proposes sorting import statements alphabetically.

  3. Unless you have compelling reasons to use Python 2, switch to Python 3! Version 2.7 will only be supported until 2020 (see PEP-373).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.