在 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
有两个选项:
如果提供分隔符,我们可以依靠正则表达式
search
作为分隔符。如果省略了分隔符,我们可以依赖内置表达式,尤其是这两个(类似正则表达式的单词边界):
wordstart
和wordend
。根据文档:wordstart
和wordend
将索引移动到当前单词的开头(结尾)。单词是字母、数字和下划线的序列,或单个非空格字符。
逻辑很简单 - 当双击发生时 - 我们跟踪此事件并将索引存储在字典中。之后,我们处理选择的更改,并相应地对选择的选项进行操作(见上文)。
这是一个完整的片段:
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'
...