Making multiple selections in tkinter

Viewed 452

Is there any way to make multiple selections in tkinter?

Here's the code:

from tkinter import *

root = Tk()

text = Text(root , width = 65 , height = 20 , font = "consolas 14")
text.pack()

text.insert('1.0' , "This is the first line.\nThis is the second line.\nThis is the third line.")

mainloop()

Here, I want be able to select multiple text from where ever I want.

Here is an Image(GIF) that explains what I mean:

enter image description here

Is there any way to achieve this in tkinter?

It would be great if anyone could help me out.

3 Answers

I made a short demo, with Control key hold you could select multiple text. Check this:

import tkinter as tk


class SelectableText(tk.Text):

    def __init__(self, master, **kwarg):
        super().__init__(master, **kwarg)
        self.down_ind = ''
        self.up_ind = ''
        self.bind("<Control-Button-1>", self.mouse_down)
        self.bind("<B1-Motion>", self.mouse_drag)
        self.bind("<ButtonRelease-1>", self.mouse_up)
        self.bind("<BackSpace>", self.delete_)

    def mouse_down(self, event):
        self.down_ind = self.index(f"@{event.x},{event.y}")

    def mouse_drag(self, event):
        self.up_ind = self.index(f"@{event.x},{event.y}")
        if self.down_ind and self.down_ind != self.up_ind:
            self.tag_add(tk.SEL, self.down_ind, self.up_ind)
            self.tag_add(tk.SEL, self.up_ind, self.down_ind)

    def mouse_up(self, event):
        self.down_ind = ''
        self.up_ind = ''

    def delete_(self, event):
        selected = self.tag_ranges(tk.SEL)
        if len(selected) > 2:
            not_deleting = ''
            for i in range(1, len(selected) - 1):
                if i % 2 == 0:
                    not_deleting += self.get(selected[i-1].string, selected[i].string)
            self.delete(selected[0].string, selected[-1].string)
            self.insert(selected[0].string, not_deleting)
            return "break"


root = tk.Tk()

text = SelectableText(root, width=50, height=10)
text.grid()
text.insert('end', "This is the first line.\nThis is the second line.\nThis is the third line.")

root.mainloop()

So I was trying to delete each selection with the Text.delete(index1, index2) but when the first selection in one line is deleted, the indices changes, making the subsequent delete deleting indices not selected (or out of range in the particular line.

I had to work around another way - first deleting from the first selected to the last selected, just like what BackSpace would do by default, then put back every unselected part in the middle. The Text.tag_ranges gives you a list of ranges selected in this way:

[start1, end1, start2, end2, ...]

where each entry is a <textindex object> with a string property (the index). So you can extract the text between end1 and start2, between end2 and start3, etc. to the end, and store these into a variable (not_deleting) so you can insert them back into the text.

There should be better and neater solutions but for now this is what it is... Hope it helps.

For the delete_ method proposed by Qiaoxuan Zhang, I advise to use the Text.tag_nextrange method in a loop like this :

def delete_sel(self):        
while 1:
    result = self.tag_nextrange(tk.SEL, 1.0)           
    if result :
        self.delete(result[0] , result[1])
    else :
        break

For those who would like to add the missing features:

  • correction of selection when changing direction of sliding

  • take into account the double-click

Take a look here : French site

Short answer: set the exportselection attribute of each Text widget to False

Tkinter gives you control over this behaviour with the exportselection configuration option for the Text widget as well as the Entry and Listbox widgets. Setting it to False prevents the export of the selection to the X selection, allowing the widget to retain its selection when a different widget gets focus.

For example:

import tkinter as tk
...
text1 = tk.Text(..., exportselection=False)
text2 = tk.Text(..., exportselection=False)

you can find more info here: http://tcl.tk/man/tcl8.5/TkCmd/options.htm#M-exportselection

Related