Tkinter中冲突的默认和用户定义绑定之间的事件相关的优先级更改

huangapple go评论75阅读模式
英文:

Event dependent change of precedence between conflicting default and user-defined bindings in Tkinter

问题

I have a Python GUI with Tkinter. In a class, I have some Text widgets. These have some default bindings some of which I'd like to replace preferably with bind_class(). I managed to do it with all, except the ones with a capitalized hotkey, like Ctrl-E/A/B/F (i.e. Ctrl-Shift-e/a/b/f). These I can only change by applying the bind() method on every widget separately. I am wondering why is this and if there is a smarter way to do this with bind_class(), which otherwise seems to have no effect on the above mentioned default bindings.

Please consider the code below. After clicking on each text widget and trying all four hotkeys (Ctrl-b/B/y/Y), one can see that all of them work in both widgets as expected, except for Ctrl-B (i.e. Ctrl-Shift-b), which works only if it is bound to the widget with bind().

As I see this issue now: for a built-in function with a capitalized Key, the order of precedence is apparently the following: bind() > built-in function with a return 'break' at the end probably > bind_class(). This means, that bind_class() won't ever work and the built-in function only gets called, if there's no return 'break' at the end of the function referenced in bind(). At the same time, built-in hotkeys with lowercase keys (e.g. Ctrl-a/e/b/f) have somehow lower priority and even bind_class() can take precedence over them.

So my questions are:

  1. Is my above conclusion correct, or there's something else to this?
  2. Is this the intended functionality of bind() and bind_class()?
  3. Is it somehow still possible to overwrite a built-in function like Ctrl-B using only bind_class() for all the widgets at once instead of bind()-ing them all one-by-one?
英文:

I have a Python GUI with Tkinter. In a class, I have some Text widgets. These have some default bindings some of which I'd like to replace preferably with bind_class(). I managed to do it with all, except the ones with a capitalized hotkey, like Ctrl-E/A/B/F (i.e. Ctrl-Shift-e/a/b/f). These I can only change by applying the bind() method on every widget separately. I am wondering why is this and if there is a smarter way to do this with bind_class(), which otherwise seems to have no effect on the above mentioned default bindings.

Please consider the code below. After clicking on each text widget and trying all four hotkeys (Ctrl-b/B/y/Y), one can see that all of them work in both widgets as expected, except for Ctrl-B (i.e. Ctrl-Shift-b), which works only if it is bound to the widget with bind().

As I see this issue now: for a built-in function with a capitalized Key, the order of precedence is apparently the following: bind() > built-in function with a return 'break' at the end probably > bind_class(). This means, that bind_class() won't ever work and the built-in function only gets called, if there's no return 'break' at the end of the function referenced in bind(). At the same time, built-in hotkeys with lowercase keys (e.g. Ctrl-a/e/b/f) have somehow lower priority and even bind_class() can take precedence over them.

So my questions are:

  1. Is my above conclusion correct, or there's something else to this?
  2. Is this the intended functionality of bind() and bind_class()?
  3. Is it somehow still possible to overwrite a built-in function like Ctrl-B using only bind_class() for all the widgets at once instead of bind()-ing them all one-by-one?

Any insightful comment is welcome. Thank you!

from tkinter import *

class Test:
    def __init__(self):
        self.window = Toplevel(root)
        self.window.lift()
        
        self.t1 = Text(self.window)
        self.t1.insert(1.0, 'Initial text in Text widget #1')
        #self.t1.bind('<Control-B>', self.func)
        self.t1.pack()
        
        self.t2 = Text(self.window)
        self.t2.insert(1.0, 'Initial text in Text widget #2')
        self.t2.bind('<Control-B>', self.func)
        self.t2.pack()
        
        self.t1.bind_class('Text', '<Control-b>', self.func)
        self.t1.bind_class('Text', '<Control-B>', self.func)   # This line has no effect.
        
        self.t1.bind_class('Text', '<Control-y>', self.func)
        self.t1.bind_class('Text', '<Control-Y>', self.func)
    
    def func(self, event):
        obj = root.focus_get()
        obj.insert(END, '\n'+str(event))
        return 'break'

root = Tk()
t1 = Test()
root.lower()
root.mainloop()

答案1

得分: 1

以下是翻译好的部分:

优先级顺序不会改变,对于所有情况下的所有小部件都是相同的,并且始终由小部件的绑定标签定义。所以,回答你的前几个问题:不,你的结论是不正确的,是的,这是按设计工作的。

在这种情况下的问题是,至少在某些平台上,<Control-Shift-Key-B> 与虚拟事件 <<SelectPrevChar>> 关联(<Control-B><Control-Shift-Key-B> 相同)。当你按下 control+B 键时,事件在传递给小部件之前会被转换为 <<SelectPrevChar>> 虚拟事件。类绑定是在该事件上,而不是原始按键按下事件。换句话说,小部件永远不会看到 <Control-Shift-Key-B>,它只看到 <<SelectPrevChar>>

你可以通过以下两种方式解决这个问题:

  • 你可以移除虚拟事件,这样你的类绑定将起作用
  • 你可以更改虚拟事件的类绑定

要从虚拟事件中删除特定的原始事件,并为原始事件序列添加显式绑定,你可以这样做:

self.window.event_delete("<<SelectPrevChar>>", "<Control-Shift-Key-B>")
self.t1.bind_class("Text", "<Control-Shift-Key-B>", self.func)

要更改虚拟事件的类绑定,你可以使用 bind_class 绑定虚拟事件:

self.t1.bind_class("Text", "<<SelectPrevChar>>", self.func)

要查看所有虚拟事件及其绑定的键的列表,你可以运行以下代码片段,它使用没有参数的 event_info 命令获取虚拟事件列表,然后使用相同的方法获取每个事件的信息。

for event in self.window.event_info():
    info = self.window.event_info(event)
    print(f"{event:<20s} => {', '.join(info)}")
英文:

The order of precedence doesn't change, it is the same for all widgets under all circumstances, and is always defined by the bindtags for the widget. So, to answer your first couple of questions: no, your conclusion isn't correct, and yes, this is working as designed.

The problem in this case is that at least on some platforms, <Control-Shift-Key-B> is associated with the virtual event <<SelectPrevChar>> (and <Control-B> is the same as <Control-Shift-Key-B>). When you press control+B, the event gets translated into the <<SelectPrevChar>> virtual event before being passed to the widget. The class binding is on that event rather than the raw keypress. In other words, the widget never sees <Control-Shift-Key-B>, it only sees <<SelectPrevChar>>.

You can solve this problem in one of two ways:

  • you can remove the virtual event, in which case your class binding will work
  • you can change the class binding for the virtual event

To remove that specific raw event from the virtual event and add an explicit binding to the raw event sequence you can do this:

self.window.event_delete("<<SelectPrevChar>>", "<Control-Shift-Key-B>")
self.t1.bind_class("Text", "<Control-Shift-Key-B>", self.func)

To change the class binding for the virtual event you can use bind_class on the virtual event:

self.t1.bind_class("Text", "<<SelectPrevChar>>", self.func)

To see a list of all virtual events and the keys they are bound to you can run this little bit of code which uses the event_info command with no arguments to get a list of virtual events, and then uses the same method to get information about each event.

for event in self.window.event_info():
    info = self.window.event_info(event)
    print(f"{event:<20s} => {', '.join(info)}")

huangapple
  • 本文由 发表于 2023年4月7日 00:11:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/75951603.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定