Ian Rehwinkel
Ian Rehwinkel

Reputation: 2615

How can I make a tkinter text widget unselectable?

I want to make my tkinter Text to be only an output and not an input. Through some research I've found that text.config(state="disabled") disables user input, but it still allows for selecting text, which I do not want.

How can I get my Text widget to be unselectable and unwritable?

Upvotes: 2

Views: 2743

Answers (4)

Evan Messinger
Evan Messinger

Reputation: 198

A read-only, unselectable text widget.

class Textarea(tkinter.Text):
  def __init__(self, master, **kw):
    super().__init__(master, **kw)
    # disable text alteration
    self.configure(state="disabled")
    # untag any selection from beginning to end
    def unselect(event):
      self.tag_remove("sel", "1.0", "end")
    # catch different ways selections could be made and unselect before copying or cutting
    good = ["<ButtonRelease-1>", "<Leave>", "<Control-c>", "<Control-C>", "<Control-x>", "<Control-X>"]
    better = good + ["<Shift-Left>", "<Shift-Up>", "<Shift-Right>", "<Shift-Down>", "<Shift-Home>", "<Shift-End>", "<Shift-Next>", "<Shift-Prior>"]
    excessive = better + ["<Shift-KP_1>", "<Shift-KP_2>", "<Shift-KP_3>", "<Shift-KP_4>", "<Shift-KP_6>", "<Shift-KP_7>", "<Shift-KP_8>", "<Shift-KP_9>"]
    for sequence in better:
      self.bind(sequence, unselect, add="+")
    # remove the appearance of selection
    self.configure(selectforeground=self.cget("foreground"), selectbackground=self.cget("background"))
    # disallow export of selection in case anything gets through
    self.configure(exportselection=False)

Tested on python 3.8.2

Upvotes: 2

Blanco
Blanco

Reputation: 96

Here is the simplest method to prevent text from being selected/highlighted when you just want the Text widget to be an ordinary log that is disabled and unselectable.

When I had the issue I figured I just needed to set some Text configuration property (highlightbackground, highlightcolor or selectbackground) to "Black". Nothing worked. The Text widget employs tags that can be used to mark up the Text within the control. Configuration for user defined tags as well as the special tag "sel" have a number of settings including foreground (color) and background (color).

tag_config("sel", background="black")

Too easy right? That doesn't work either.

Turns out that the highlight is actually a bitmap overlaid on the text. This is controlled by the bgstipple (bitmap) configuration for the tag. The documentation indicates that there are a number of system bitmaps (shades of grey) that can be used however it is also possible to specify your own. The bitmap needs to be an xbm and it's easy to create your own as it's a text file.

Put the following in a file named transparent.xbm.

#define trans_width 2
#define trans_height 2
static unsigned char trans_bits[] = {
   0x00, 0x00
};

Here it is...

class TextLog(tk.Text):         
    def __init__(self, *args, **kwargs):  
        super().__init__(*args, **kwargs)
        self.tag_config("sel", bgstipple="@transparent.xbm", 
                                                       foreground="white")
        self.config(state="disabled")

    def write_log(self, text="", clear=False)
        self.configure(state='normal')

        if clear is True:
            self.delete("1.0","end")

        self.insert(tk.END, text)
        self.see(tk.END)
        self.config(state="disabled")

Depending on where the .xbm is relative to the module using the TextLog you may need to prefix it with a path "@path/to/transparent.xbm"

Upvotes: 0

Bryan Oakley
Bryan Oakley

Reputation: 386362

The simplest way is to replace the default text bindings that support selection so that they do nothing. There are a couple ways to do this: using binding tags you can remove all default bindings, or you can remove the bindings to only a subset of default bindings.

Removing all default bindings

All bindings on widgets -- including the default bindings -- are associated with binding tags (also called "bindtags"). The binding tag for the the text widget is "Text", and all default bindings for the text widget are associated with this tag. If you remove that binding tag, you remove all Text-specific bindings.

The default binding tags for any widget is a tuple of the string representation of the widget, the internal widget class (in this case, "Text"), the internal name of the toplevel window (in this case, root), and the special tag "all".

In the following example we change the binding tags so that "Text" is not included, effectively removing all default bindings on the text widget:

import tkinter as tk

root = tk.Tk()
text = tk.Text(root)
text.bindtags((str(text), str(root), "all"))

Removing specific bindings

If you prefer to keep some of the default bindings, you can replace just the ones that you don't want. You do that by creating your own bindings, and having those bindings return the string "break". This special return value tells tkinter to stop processing the event any further.

For example, to prevent a double-click from selecting the word under the cursor you could do this:

text.bind("<Double-1>", lambda event: "break")

The downside to this approach is that you have to figure out what all of the bindings are that are related to the selection mechanism. On the other hand, it gives you complete control over what each key or button press does.

Upvotes: 6

Reedinationer
Reedinationer

Reputation: 5774

I believe you will have to replace it with another widget that such as a Label or LabelFrame to accomplish this. As well you could use a from tkinter import messagebox and have the text you want pop up in another window (like an info window or error message window). I think that as far as the Text widget goes, setting the state to disabled is the best you can do for your purposes unfortunately and users will be able to copy that text despite being unable to edit it.

Upvotes: 0

Related Questions