Tkinter 中的多个按键事件绑定 - “控制” E” “命令(苹果)” E” ETC

发布于 2024-11-15 20:19:05 字数 2894 浏览 4 评论 0原文

Mac OS X 10.6.6 - Tkinter

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

我的成功有好有坏。我可以使用 Shift + 字母键,但不能使用 Control 或 Command(Apple 键)。我真正想做的是 Command + 字母和 Control + 字母键,这样理论上它可以在 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

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 correctly. I'm new here.

I've had mixed success. I've been able to get Shift + letter key, but not Control or Command (Apple key). What I really want to do is Command + letter and Control + letter key so it would theoretically work in Windows and OS X.

I want it to work at window-level, so I'm using root. Perhaps there is a better way. Below is what I've tried:

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?

Strangely, when I press alt/option + (E, N, or others) it creates an error. Is it interacting with 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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

陌伤ぢ 2024-11-22 20:19:05

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

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.

梦屿孤独相伴 2024-11-22 20:19:05

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

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.

温柔嚣张 2024-11-22 20:19:05

增强功能涵盖了 AltMeta 键,即 macOS 上的 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()

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()
ら栖息 2024-11-22 20:19:05

选项 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

但是,最终,我决定手动处理击键。您可以在此文件中查看示例。首先,我在两个字典 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.__keytrip 函数来监控 击键事件。该函数检查 键是否被按下:

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.__keyrinkle 函数绑定到主 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))

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))
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文