繁体   English   中英

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

[英]How to configure default Double Mouse Click behavior in Tkinter text widget?

在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. 如果出现分隔符,我们可以依赖RegEx 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' break'-string阻止了默认行为,所以代码可以简化为一个回调,不再需要<<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'[%s\s]' % 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'[%s\s]' % 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'
...

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM