如何在 Tkinter 文本小部件中配置默认的鼠标双击行为?



在 tkinter 文本小部件上,双击的默认行为是选择鼠标下方的文本。

该事件将选择"(空格)字符之间的所有字符。

所以 - 假设文本小部件具有:1111111 222222双击第一个单词(所有1)将仅选择它(双击2单词将选择它)

我希望有类似的行为,但添加额外的字符作为工作分隔符(例如,.()) 目前,如果文本有111111.222222- 双击文本上的任意位置将突出显示所有字符(不会用.分隔单词)

有没有办法做到这一点?

更改什么是"单词">

双击定义为选择光标下方的"单词"。如果你想改变所有文本小部件的默认行为,tkinter有办法告诉它什么是"单词"字符。如果您更改 tkinter 认为是"单词"的内容,则可以通过双击更改选择的内容。这需要我们直接调用 tkinter 所基于的内置 tcl 解释器。

注意:这也会影响小部件的其他方面,例如用于将光标移动到单词开头或结尾的键绑定。

下面是一个示例:

import tkinter as tk
def set_word_boundaries(root):
# this first statement triggers tcl to autoload the library
# that defines the variables we want to override.  
root.tk.call('tcl_wordBreakAfter', '', 0) 
# this defines what tcl considers to be a "word". For more
# information see http://www.tcl.tk/man/tcl8.5/TclCmd/library.htm#M19
root.tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_.,]')
root.tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_.,]')
root = tk.Tk()
set_word_boundaries(root)
text = tk.Text(root)
text.pack(fill="both", expand=True)
text.insert("end", "foo 123.45,678 bar")
root.mainloop()

自定义键绑定

如果您不想影响除一个小部件之外的任何小部件,或者不想影响 tkinter 依赖于"单词"定义的其他方面,您可以创建自己的绑定来选择您想要的任何内容。

要记住的重要一点是,绑定应返回字符串"break"以防止双击的默认行为:

def handle_double_click(event):
<your code for selecting whatever you want>
return "break"
...
text.bind("<Double-1>", handle_double_click)

为了方便这一点,文本小部件有一个search方法,可以在文本中向后和向前搜索给定的字符串或正则表达式。

有没有办法做到?

当然,甚至不是一种方法。但无论如何 - 我们需要为我们的Text小部件定制一个自定义类,所以让我们开始:

class CustomText(tk.Text):
def __init__(self, parent, delimiters=[]):
tk.Text.__init__(self, parent)
#   test text
self.insert('1.0', '1111111 222222'
'n'
'1111111.222222'
'n'
'1111111.222222,333333'
'n'
'444444444444444444')
#   binds
self.bind('<Double-1>', self.on_dbl_click)
self.bind('<<Selection>>', self.handle_selection)
#   our delimiters
self.delimiters = ''.join(delimiters)
#   stat dictionary for double-click event
self.dbl_click_stat = {'clicked': False,
'current': '',
'start': '',
'end': ''
}

可选delimiters有两个选项:

  1. 如果提供分隔符,我们可以依靠正则表达式search作为分隔符。

  2. 如果省略了分隔符,我们可以依赖内置表达式,尤其是这两个(类似正则表达式的单词边界):wordstartwordend。根据文档:

    wordstartwordend将索引移动到当前单词的开头(结尾)。单词是字母、数字和下划线的序列,或单个非空格字符。

逻辑很简单 - 当双击发生时 - 我们跟踪此事件并将索引存储在字典中。之后,我们处理选择的更改,并相应地对选择的选项进行操作(见上文)。

这是一个完整的片段:

try:
import tkinter as tk
except ImportError:
import Tkinter as tk

class CustomText(tk.Text):
def __init__(self, parent, delimiters=[]):
tk.Text.__init__(self, parent)
#   test text
self.insert('1.0', '1111111 222222'
'n'
'1111111.222222'
'n'
'1111111.222222,333333'
'n'
'444444444444444444')
#   binds
self.bind('<Double-1>', self.on_dbl_click)
self.bind('<<Selection>>', self.handle_selection)
#   our delimiters
self.delimiters = ''.join(delimiters)
#   stat dictionary for double-click event
self.dbl_click_stat = {'clicked': False,
'current': '',
'start': '',
'end': ''
}
def on_dbl_click(self, event):
#   store stats on dbl-click
self.dbl_click_stat['clicked'] = True
#   clicked position
self.dbl_click_stat['current'] = self.index('@%s,%s' % (event.x, event.y))
#   start boundary
self.dbl_click_stat['start'] = self.index('@%s,%s wordstart' % (event.x, event.y))
#   end boundary
self.dbl_click_stat['end'] = self.index('@%s,%s wordend' % (event.x, event.y))

def handle_selection(self, event):
if self.dbl_click_stat['clicked']:
#   False to prevent a loop
self.dbl_click_stat['clicked'] = False
if self.delimiters:
#   Preserve "default" selection
start = self.index('sel.first')
end = self.index('sel.last')
#   Remove "default" selection
self.tag_remove('sel', '1.0', 'end')
#   search for occurrences
occurrence_forward = self.search(r'[%s]' % self.delimiters, index=self.dbl_click_stat['current'],
stopindex=end, regexp=True)
occurrence_backward = self.search(r'[%s]' % self.delimiters, index=self.dbl_click_stat['current'],
stopindex=start, backwards=True, regexp=True)
boundary_one = occurrence_backward + '+1c' if occurrence_backward else start
boundary_two = occurrence_forward if occurrence_forward else end
#   Add selection by boundaries
self.tag_add('sel', boundary_one, boundary_two)
else:
#   Remove "default" selection
self.tag_remove('sel', '1.0', 'end')
#   Add selection by boundaries
self.tag_add('sel', self.dbl_click_stat['start'], self.dbl_click_stat['end'])

root = tk.Tk()
text = CustomText(root)
text.pack()
root.mainloop()

总之,如果您并不真正关心分隔符,而是关心单词 - 第二个选项是可以的,否则 - 第一个。

更新:

非常感谢 @Bryan Oakley 指出'break'字符串可以防止默认行为,因此代码可以缩短为只有一个回调,不再需要<<Selection>>

...
def on_dbl_click(self, event):
if self.delimiters:
#   click position
current_idx = self.index('@%s,%s' % (event.x, event.y))
#   start boundary
start_idx = self.search(r'[%ss]' % self.delimiters, index=current_idx,
stopindex='1.0', backwards=True, regexp=True)
#   quick fix for first word
start_idx = start_idx + '+1c' if start_idx else '1.0'
#   end boundary
end_idx = self.search(r'[%ss]' % self.delimiters, index=current_idx,
stopindex='end', regexp=True)
else:
#   start boundary
start_idx = self.index('@%s,%s wordstart' % (event.x, event.y))
#   end boundary
end_idx = self.index('@%s,%s wordend' % (event.x, event.y))
self.tag_add('sel', start_idx, end_idx)
return 'break'
...

相关内容

最新更新