【问题标题】:Multiple Key Event Bindings in Tkinter - "Control + E" "Command (apple) + E" etcTkinter 中的多个键事件绑定 - “Control + E” “Command (apple) + E” 等
【发布时间】:2011-09-16 17:44:03
【问题描述】:

Mac OS X 10.6.6 - Tkinter

我想绑定多键事件,虽然我找到了一篇 effbot 文章和 Tk 手册页,但我无法使其正常工作。我是新来的。

我取得了喜忧参半的成功。我已经能够获得 Shift + 字母键,但不能获得 Control 或 Command(Apple 键)。我真正想做的是 Command + letter 和 Control + letter 键,所以理论上它可以在 Windows 和 OS X 中工作。

我希望它在窗口级别工作,所以我使用的是 root。也许有更好的方法。以下是我尝试过的:

root.bind('<Shift-E>', self.pressedCmdE)   # Works
root.bind('e', self.pressedCmdE)           # Works
root.bind('<Command-E>', self.pressedCmdE) # Does Not Work
#root.bind('<Mod1-E>', self.pressedCmdE)   #   # Do Mod1, M1, and
#root.bind('<M1-E>', self.pressedCmdE)     #   # Command mean the same thing?

奇怪的是,当我按下 alt/option +(E、N 或其他)时,它会产生错误。是否与 PythonLauncher 交互?

2011-06-16 16:19:22.618 Python[1546:d07] An uncaught exception was raised
2011-06-16 16:19:22.621 Python[1546:d07] *** -[NSCFString characterAtIndex:]: Range or index out of bounds
2011-06-16 16:19:22.622 Python[1546:d07] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSCFString characterAtIndex:]: Range or index out of bounds'
*** Call stack at first throw:
(
    0   CoreFoundation                      0x00007fff85b397b4 __exceptionPreprocess + 180
    1   libobjc.A.dylib                     0x00007fff848b90f3 objc_exception_throw + 45
    2   CoreFoundation                      0x00007fff85b395d7 +[NSException raise:format:arguments:] + 103
    3   CoreFoundation                      0x00007fff85b39564 +[NSException raise:format:] + 148
    4   Foundation                          0x00007fff866eb5e1 -[NSCFString characterAtIndex:] + 97
    5   Tk                                  0x0000000100759bcf Tk_SetCaretPos + 663
    6   Tk                                  0x000000010075fd94 Tk_MacOSXSetupTkNotifier + 699
    7   Tcl                                 0x000000010061d2ae Tcl_DoOneEvent + 297
    8   _tkinter.so                         0x00000001001d9be9 init_tkinter + 1132
    9   Python                              0x0000000100089187 PyEval_EvalFrameEx + 15317
    10  Python                              0x000000010008acce PyEval_EvalCodeEx + 1803
    11  Python                              0x000000010008935e PyEval_EvalFrameEx + 15788
    12  Python                              0x000000010008acce PyEval_EvalCodeEx + 1803
    13  Python                              0x000000010008ad61 PyEval_EvalCode + 54
    14  Python                              0x00000001000a265a Py_CompileString + 78
    15  Python                              0x00000001000a2723 PyRun_FileExFlags + 150
    16  Python                              0x00000001000a423d PyRun_SimpleFileExFlags + 704
    17  Python                              0x00000001000b0286 Py_Main + 2718
    18  Python                              0x0000000100000e6c start + 52
)
terminate called after throwing an instance of 'NSException'
Abort trap

【问题讨论】:

    标签: events binding command key tkinter


    【解决方案1】:

    选项 1

    类似这样的:

    # 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()
    

    选项 2

    但是,最后,我决定手动处理击键。您可以在this file 中查看示例。首先,我在self.keycodeself.__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
    

    然后添加self.__keystroke函数来监控&lt;Ctrl&gt;按键事件。这个函数检查&lt;Ctrl&gt;键是否被按下:

    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
    

    最后,将self.__keystroke 函数绑定到主GUI 窗口。请注意,此功能绑定空闲模式,因为多次击键会减慢弱计算机上的程序:

    # 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))
    

    【讨论】:

      【解决方案2】:

      已增强以覆盖 macOS 上的 AltMeta 键,即 OptionCommand

      # 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()
      

      【讨论】:

        【解决方案3】:

        对于 Tkinter,“Control-R”表示 Ctrl-Shift-R 而“Control-r”表示 Ctrl-R。所以请确保您没有混淆大写和小写。

        【讨论】:

        • 这是一个非常重要的说明。但在我看来,你只是特别正确。一切都取决于字母大小写。 "&lt;Control-R&gt;" 表示 Ctrl-Shift-R 仅在 Caps Lock 禁用时,否则等于 Ctrl-R。而“”绑定意味着 Ctrl-R 仅在 Caps Lock 被禁用时,而 Ctrl-Shift-R 在 Caps Lock 被启用时。
        【解决方案4】:

        这似乎是 Tk 中的一个错误。我在 mac 上使用 tcl/tk 以及使用 python/tkinter 时遇到相同的错误。您可以将&lt;Command-e&gt; 绑定到小部件(我尝试使用文本小部件),但将其绑定到根窗口或"all" 似乎会导致您收到错误。

        【讨论】:

        • 谢谢,我没有经验知道这是错误还是我的错误……通常是我的错误。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多