简体   繁体   English

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

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

On a tkinter text widget, the default behavior of double click will be to select the text under the mouse. 在tkinter文本小部件上,双击的默认行为是选择鼠标下的文本。

The event will select all characters between " " (space) char. 该事件将选择“”(空格)字符之间的所有字符。

So - assume the text widget has: 1111111 222222 double click on over the first word (all 1 ) will select only it (and double clicking on 2 word will select it) 所以 - 假设文本小部件有: 1111111 222222双击第一个单词(全部1 )将只选择它(并双击2单词将选择它)

I would like to have a similar behavior, but add additional char as work seperators (eg, . , ( , ) ) currently, if the text has 111111.222222 - double click anywhere over the text will highlight all characters (won't separate the words by . ) 我想有一个类似的行为,但添加额外的字符作为工作分隔符(例如, .() )当前,如果文本有111111.222222 - 双击文本上的任何地方将突出显示所有字符(不会分隔单词通过.

Is there a way to do it? 有办法吗?

Changing what is a 'word' 改变什么是'字'

The double click is defined to select the 'word' under the cursor. 双击定义为选择光标下的“单词”。 If you are wanting to change the default behavior for all text widgets, tkinter has a way to tell it what is a "word" character. 如果您想要更改所有文本小部件的默认行为,tkinter可以告诉它什么是“单词”字符。 If you change what tkinter thinks is a "word", you change what gets selected with a double-click. 如果您更改tkinter认为是“单词”的内容,则可以通过双击更改所选内容。 This requires that we directly call the built-in tcl interpreter upon which tkinter is based. 这要求我们直接调用tkinter所基于的内置tcl解释器。

Note: this will affect other aspects of the widget as well, such as key bindings for moving the cursor to the beginning or ending of a word. 注意:这也会影响窗口小部件的其他方面,例如用于将光标移动到单词的开头或结尾的键绑定。

Here's an example: 这是一个例子:

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()

Custom key binding 自定义键绑定

If you do not want to affect any widget except one, or do not want to affect other aspects of tkinter that depend on the definition of a 'word', you can create your own binding to select whatever you want. 如果您不想影响除一个小部件之外的任何小部件,或者不想影响依赖于“单词”定义的tkinter的其他方面,您可以创建自己的绑定以选择您想要的任何内容。

The important thing to remember is that your binding should return the string "break" in order prevent the default behavior for double-click: 要记住的重要一点是,绑定应该返回字符串"break" ,以防止双击的默认行为:

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

To facilitate this, the text widget has a search method that makes it possible to search backwards and forwards through the text for a given string or regular expression. 为了实现这一点,文本小部件有一个search方法,可以在文本中向后和向前搜索给定的字符串或正则表达式。

Is there a way to do it? 有办法吗?

Of course, and not even one way. 当然,甚至没有一种方式。 But anyway - we need a custom class for our Text widget, so let's start: 但无论如何 - 我们需要为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': ''
                               }

The optional delimiters leads to two options: 可选的delimiters有两个选项:

  1. If delimiters are presented we can rely on RegEx search for delimiters. 如果出现分隔符,我们可以依赖RegEx search分隔符。

  2. If delimiters are ommited we can rely on build-in expressions, especially those two (RegEx-like word boundaries): wordstart and wordend . 如果分隔符中省略我们可以依靠内置的表情,尤其是那两个(正则表达式样字边界): wordstartwordend According to docs : 根据文件

    wordstart and wordend moves the index to the beginning (end) of the current word. wordstartwordend移动当前字的索引的开始(结束)。 Words are sequences of letters, digits, and underline, or single non-space characters. 单词是字母,数字和下划线的序列,或单个非空格字符。

The logic is simple - when double click occures - we trace this event and store indexes in a dictionary. 逻辑很简单 - 当双击发生时 - 我们跟踪此事件并将索引存储在字典中。 After that we handle change of the selection, and act accordingly the choosen option (see above). 之后我们处理选择的更改,并相应地采取选择的选项(见上文)。

Here's a complete snippet: 这是一个完整的片段:

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()

In conclusion, if you doesn't really care about delimiters, but about words - the second option is OK, otherwise - the first one. 总之,如果你真的不关心分隔符,而是关于单词 - 第二种选择是好的,否则 - 第一种选择。

Update: 更新:

Many thanks to @Bryan Oakley for pointing that 'break' -string prevents the default behaviour, so code can be shortened to just one callback, there's no need in <<Selection>> anymore: 非常感谢@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