Multiple Key Event Bindings in Tkinter - “Control + E” “Command (apple) + E” etc

后端 未结 4 1167
鱼传尺愫
鱼传尺愫 2020-12-20 13:22

Mac OS X 10.6.6 - Tkinter

I want to bind multiple-key events, and while I have found an effbot article and the Tk man pages, I\'ve been unable to make this work corr

相关标签:
4条回答
  • 2020-12-20 13:38

    Option 1

    Something like this:

    # Status of control, shift and control+shift keys in Python
    import tkinter as tk
    
    ctrl = False
    shift = False
    ctrl_shift = False
    
    def key(event):
        global ctrl, shift, ctrl_shift
        #print(event.keycode, event.keysym, event.state)
        if ctrl_shift:
            print('<Ctrl>+<Shift>+{}'.format(event.keysym))
        elif ctrl:
            print('<Ctrl>+{}'.format(event.keysym))
        elif shift:
            print('<Shift>+{}'.format(event.keysym))
        ctrl = False
        shift = False
        ctrl_shift = False
    
    def control_key(state, event=None):
        ''' Controll button is pressed or released '''
        global ctrl
        ctrl = state
    
    def shift_key(state, event=None):
        ''' Controll button is pressed or released '''
        global shift
        shift = state
        control_shift(state)
    
    def control_shift(state):
        ''' <Ctrl>+<Shift> buttons are pressed or released '''
        global ctrl, ctrl_shift
        if ctrl == True and state == True:
            ctrl_shift = True
        else:
            ctrl_shift = False
    
    root = tk.Tk()
    root.geometry('256x256+0+0')
    
    root.event_add('<<ControlOn>>',  '<KeyPress-Control_L>',   '<KeyPress-Control_R>')
    root.event_add('<<ControlOff>>', '<KeyRelease-Control_L>', '<KeyRelease-Control_R>')
    root.event_add('<<ShiftOn>>',    '<KeyPress-Shift_L>',     '<KeyPress-Shift_R>')
    root.event_add('<<ShiftOff>>',   '<KeyRelease-Shift_L>',   '<KeyRelease-Shift_R>')
    
    root.bind('<<ControlOn>>', lambda e: control_key(True))
    root.bind('<<ControlOff>>', lambda e: control_key(False))
    root.bind('<<ShiftOn>>', lambda e: shift_key(True))
    root.bind('<<ShiftOff>>', lambda e: shift_key(False))
    root.bind('<Key>', key)
    
    root.mainloop()
    

    Option 2

    However, in the end, I decided to process keystrokes manually. You can se the example in this file. First, I set keycodes and shortcuts in two dictionaries self.keycode and self.__shortcuts:

    # List of shortcuts in the following format: [name, keycode, function]
    self.keycode = {}  # init key codes
    if os.name == 'nt':  # Windows OS
        self.keycode = {
            'o': 79,
            'w': 87,
            'r': 82,
            'q': 81,
            'h': 72,
            's': 83,
            'a': 65,
        }
    else:  # Linux OS
        self.keycode = {
            'o': 32,
            'w': 25,
            'r': 27,
            'q': 24,
            'h': 43,
            's': 39,
            'a': 38,
         }
    self.__shortcuts = [['Ctrl+O', self.keycode['o'], self.__open_image],   # 0 open image
                        ['Ctrl+W', self.keycode['w'], self.__close_image],  # 1 close image
                        ['Ctrl+R', self.keycode['r'], self.__roll],         # 2 rolling window
                        ['Ctrl+Q', self.keycode['q'], self.__toggle_poly],  # 3 toggle between roi/hole drawing
                        ['Ctrl+H', self.keycode['h'], self.__open_poly],    # 4 open polygons for the image
                        ['Ctrl+S', self.keycode['s'], self.__save_poly],    # 5 save polygons of the image
                        ['Ctrl+A', self.keycode['a'], self.__show_rect]]    # 6 show rolling window rectangle
    

    Then added self.__keystroke function to monitor <Ctrl> keystroke events. This function checks if <Ctrl> key is pressed or not:

    def __keystroke(self, event):
        """ Language independent handle events from the keyboard """
        #print(event.keycode, event.keysym, event.state)  # uncomment it for debug purposes
        if event.state - self.__previous_state == 4:  # check if <Control> key is pressed
            for shortcut in self.__shortcuts:
                if event.keycode == shortcut[1]:
                    shortcut[2]()
        else:  # remember previous state of the event
            self.__previous_state = event.state
    

    Finally, bind the self.__keystroke function to the master GUI window. Note that this function is bonded in the idle mode, because multiple keystrokes slow down the program on weak computers:

    # Handle keystrokes in the idle mode, because program slows down on a weak computers,
    # when too many key stroke events in the same time.
    self.master.bind('<Key>', lambda event: self.master.after_idle(self.__keystroke, event))
    
    0 讨论(0)
  • 2020-12-20 13:44

    With Tkinter, "Control-R" means Ctrl-Shift-R whereas "Control-r" means Ctrl-R. So make sure you're not mixing up uppercase and lowercase.

    0 讨论(0)
  • 2020-12-20 13:49

    This appears to be a bug in Tk. I get the same error with tcl/tk on the mac as well as with python/tkinter. You can bind <Command-e> to a widget (I tried with a text widget) but binding it to the root window or to "all" seems to cause the error you get.

    0 讨论(0)
  • 2020-12-20 13:54

    Enhanced to cover the Alt and Meta keys, aka Option and Command on macOS.

    # Original <https://StackOverflow.com/questions/6378556/
    #           multiple-key-event-bindings-in-tkinter-control-e-command-apple-e-etc>
    
    # Status of alt (ak option), control, meta (aka command)
    # and shift keys in Python tkinter
    
    # Note, tested only on macOS 10.13.6 with Python 3.7.4 and Tk 8.6.9
    
    import tkinter as tk
    import sys
    
    _macOS = sys.platform == 'darwin'
    _Alt   = 'Option' if _macOS else 'Alt'
    _Ctrl  = 'Control'
    _Meta  = 'Command' if _macOS else 'Meta'
    _Shift = 'Shift'
    
    alt = ctrl = meta = shift = ''
    
    
    def up_down(mod, down):
        print('<%s> %s' % (mod, 'down' if down else 'up'))
        return down
    
    
    def key(event):
        '''Other key pressed or released'''
        # print(event.keycode, event.keysym, event.down)
        global alt, ctrl, meta, shift
        t = [m for m in (alt, ctrl, shift, meta, str(event.keysym)) if m]
        print('+'.join(t))
    
    
    def alt_key(down, *unused):
        '''Alt (aka Option on macOS) key is pressed or released'''
        global alt
        alt = up_down(_Alt, down)
    
    
    def control_key(down, *unused):
        '''Control key is pressed or released'''
        global ctrl
        ctrl = up_down(_Ctrl, down)
    
    
    def meta_key(down, *unused):
        '''Meta (aka Command on macOS) key is pressed or released'''
        global meta
        meta = up_down(_Meta, down)
    
    
    def shift_key(down, *unused):
        '''Shift button is pressed or released'''
        global shift
        shift = up_down(_Shift, down)
    
    
    def modifier(root, mod, handler, down):
        '''Add events and handlers for key press and release'''
        root.event_add('<<%sOn>>' % (mod,), ' <KeyPress-%s_L>' % (mod,), '<KeyPress-%s_R>' % (mod,))
        root.bind(     '<<%sOn>>' % (mod,), lambda _: handler('<%s>' % (down,)))
    
        root.event_add('<<%sOff>>' % (mod,), '<KeyRelease-%s_L>' % (mod,), '<KeyRelease-%s_R>' % (mod,))
        root.bind(     '<<%sOff>>' % (mod,), lambda _: handler(''))
    
    
    root = tk.Tk()
    root.geometry('256x64+0+0')
    
    modifier(root, 'Alt',     alt_key,     _Alt)
    modifier(root, 'Control', control_key, _Ctrl)
    modifier(root, 'Meta',    meta_key,    _Meta)
    modifier(root, 'Shift',   shift_key,   _Shift)
    
    root.bind('<Key>', key)
    
    root.mainloop()
    
    0 讨论(0)
提交回复
热议问题