Quick answer (synthesizes this page): Make text widget $tx read-only: foreach event { <>} { bind $tx $event break } bind $tx {event generate %W <>} Back to normal: bind $tx {} [Ro]: To whomever edited the above code (adding <>) I understand your reasoning, re: middle click on Unix. But how to set it back to normal? Not sure if it makes it too complex now. I was aiming for an 80% solution with minimal code, people interested in bulletproof implementations can read further down the page for more complex and more complete solutions. [slebetman]: Sorry.. I did it. But if you've read this page fully then you'd realise that the "Quick answer" is really: rename .t .t.internal proc .t {args} { switch [lindex $args 0] { "insert" {} "delete" {} "default" { return [eval .t.internal $args] } } } To make programmatic edits: .t.internal insert end $sometext Back to normal: rename .t.internal .t This is a 100% solution with minimal code. Quick answers shouldn't contain incomplete solutions which may fail in certain conditions when a simple complete solution exists. In fact it's easy enough to wrap this as a proc: proc makeReadOnly {textwidget} { rename $textwidget $textwidget.internal proc $textwidget {args} [string map [list WIDGET $textwidget] { switch [lindex $args 0] { "insert" {} "delete" {} "default" { return [eval WIDGET.internal $args] } } }] } ---- [GJS]: I modified the makeReadOnly proc to add "-state readonly" ====== proc makeReadOnly {textwidget} { global readOnlyText set readOnlyText($textwidget) readonly rename $textwidget $textwidget.internal bind $textwidget [list rename $textwidget {}] proc $textwidget {args} [string map [list WIDGET $textwidget] { global readOnlyText switch [lindex $args 0] { configure { set pass [list] for {set i 0} {$i < [llength $args]} {incr i} { switch -- [lindex $args $i] { -state { set readOnlyText(WIDGET) [lindex $args [incr i]] if {$readOnlyText(WIDGET) eq ""} { lappend pass -state } elseif {$readOnlyText(WIDGET) ne "readonly"} { lappend pass -state $readOnlyText(WIDGET) } else { lappend pass -state normal } } default {lappend pass [lindex $args $i]} } } return [eval WIDGET.internal $pass] } insert - delete { if {$readOnlyText(WIDGET) ne "readonly"} { return [eval WIDGET.internal $args] } } default { return [eval WIDGET.internal $args] } } }] } ====== ---- Usenet posters frequently ask for a read-only text widget. This perhaps means that it would be a good idea to build one in; no one has done this yet for Tk's core or [Tkinter], to the best of my knowledge, though Perl/Tk has enjoyed the benefits of Tk::ROText since the late '90s. [Ro] If you mean 'build one in' as into the core, I disagree. It's so easy to do in just a few lines of pure Tcl. See the next entry for how to do it with the Widget Callback Package ([Wcb], a pure Tcl package). ---- I believe easily the most comprehensive and recommendable solution is the Widget Callback Package ([Wcb], by Csaba Nemethi [http://www.nemethi.de]) which lets you do lots of other things besides readonly. It's pure Tcl and uses the command renaming approach instead of mass re-binding. (Roy Terry 25Dec01) [Ro] Here is how to do it - solution created by Csaba Nemethi. Features: * The package is a general solution to a recurring problem. * The package is in pure Tcl/Tk code. * This example still allows the user to copy the contents of the read-only widget! This is very handy if you're building some kind of log window. Code: # # The hardest thing about this is just setting # your ::auto_path properly so Tcl can find the wcb package ;) # # # Load the required package. # package require wcb # # Create and pack the read-only text widget. # set tx .tx text $tx -wrap word pack $tx -fill both -expand 1 # # Reject user modifications to the text widget. # proc rejectModification {w args} { wcb::cancel return } # # Protect the text widget from insertions and deletions by the user. # # Note: Wcb renames $tx to _$tx and creates a wrapper command. # wcb::callback $tx before insert rejectModification wcb::callback $tx before delete rejectModification # # Create and pack a button that programmatically inserts text into # the read-only text widget. # # Note: The real text widget is "_$tx" not "$tx". This is because # the Wcb package renames the text object in the goal of # intercepting command messages. # set b .b button $b -text "Insert a Greeting" -command [list _$tx insert end "Howdy\n"] pack $b -fill x ---- [Ro] If you're making a read-only text widget, chances are that you want to enable keyboard focus switching when the tab key and the shift-tab key-combo is pressed. Check out [text] for how to do this with 6 lines of code. ---- There's an abundance of pure-Tcl procedural solutions. My favorite is to set the state of the text widget to disabled. Updates can still be effected programmatically either through use of a textvariable, or by toggling the state, updating the text, and then toggling the state again. I believe the only disadvantage of this approach is that disabling the widget disables its ability to serve as a Copy (as in Copy-Paste) source. (This is not true! You can still copy from the text - Bruce) (How? - I can't get it to happen - impossible to select! - [EMJ]) (I think it's a bug in the Windows version; could it be that Bruce runs unix and [EMJ] uses Windows? -- [Bryan Oakley] ; true - [EMJ]) I can do it windows too, the trick is to get focus to the window, the default bindings for the text widget don't assign focus on a button1 click unless the state is normal, so either explicitly call "focus .t" or else add a binding to do it. bind .t <1> {focus %W} - Bruce [LV] Would it be worthwhile to report this different and request the binding in question be added for Windows? ---- Many solutions tinker with bindings. Eventually I expect we'll summarize in this space the information found in http://groups.google.com/groups?th=1c8bca5c914f27ac , http://groups.google.com/groups?th=b3b2de32ea3ba00c , and so on. Aaaargh - Deja references are now useless! (Not really...) ---- ([Kevin Kenny] - 6 August 2001) Doing read-only [text] widgets with bindings is really doing it the hard way. The easy way is to [[rename]] the widget command. I don't have a full worked example of this for a read-only text widget, but I have one for a text entry that supports a ''-textvariable'' option: [Text variable for text widget]. The ideas there should be clear enough for someone to put together a read-only widget. ([bbh] - 10 Jan 2002) The only problem with using rename on the widget command is that affects both user edits AND program edits. May times I have a read only text ''I'' want to be able to modify the text from my script, but i don't want the '''user''' to able to muck with it. That's why I like the bindings solution - it save me from wrapping all my text updates inside config calls to update the state of the widget back and forth. [KBK] 8 April 2002 - I missed this message for a while. But what's the problem? If you rename .my.text.widget to internal.my.text.widget so as to intercept the commands that change the text, you can always call the renamed widget from your script when you ''want'' the text to change. ([bbh] - 10 Nov 2002) Been a while since I've been to this page, all I can say is "DOH!" You are completely right on this one. [slebetman] 21 December 2004 - a really short example of this: pack [text .t] -fill both -expand 1 # make text read-only: rename .t .t.internal proc .t {args} { if {[lindex $args 0] != "insert"} { return [eval .t.internal $args] } } All standard text behaviors work including Control-c, arrow keys and mouse scrollwheel. To insert text simply do: .t.internal insert end "new test" [jhh] 22 November 2005 - I like this solution, but it still allows deleting text from the widget. I would replace the proc in the example above by: proc .t {args} { switch [lindex $args 0] { "insert" {} "delete" {} "default" { return [eval .internal.t $args] } } } ---- Here is a solution using bindings: * go to the tk lib directory * copy the text.tcl to ROtext.tcl * edit the ROtext.tcl file changing all "bind Text" refs to "bind ROText" * delete all procedures defined * delete all bindings that modify the text * either source this file in your code or edit text.tcl and add the following '''source [[file join [[file dirname [[info script]]]] ROtext.tcl]''' * Now in you code after createing a text widget just call '''bindtags $t [[list $t ROText . all]''' The user can then still interact with the widget, keyboard traversal, slections, etc. but no changing is allowed by user. This way the prgram can still add/delete text without having to wrap it with config -state calls - Bruce ---- [BBH] even better - use the methods at [Inheriting Widget Binding Classes] as it will do it on the fly with no need for extra files and such ---- ulis: the easiest way I found to make a text widget read-only: text .a .a insert 1.0 "some text" bind .a { break } [GPS]: The problem with that solution as I see it is that the user might paste text via the mouse. In my read-only solution I allow the user to select text, but not remove it. ulis: How to paste text via the mouse? [GPS]: In Unix/X you press the middle mouse button after selecting text. ulis: Okay, I found it. So, why not: text .a .a insert 1.0 "some text" bind .a { break } bind .a <> { break } [GPS]: That looks like a concise way of doing it. I can't think of any problems with that approach, unless the bindtags order somehow got out of whack (highly unlikely). I however modify a copy of the text widget bindings for my project, so it's easier for me to use the bind:* commands I wrote. [RS]: I like ulis's version, which one could simplify to foreach event { <>} {bind .a $event break} ulis, 2002-07-06: Idea came from 'rand mair fheal' (mair_fheal@www.yahoo.com) in a c.l.t thread: http://groups.google.com/groups?threadm=028ca7a9807678409e2a96daabcdf3a0.25900%40mygate.mailgate.org ---- ulis, 2002-07-27 The previous binding solution does not permit to copy selection with Control-c. This can be easily corrected by: bind .a { event generate .a <> } But even that does not permit key navigation inside the text widget. Here is a proc that set a text widget read-only by forbidding the insert and delete options usage. Inserting and deleting chars are only forbidden during key press. # ========================== # set a text widget to a read-only or normal 'state' # (the -state option is not modified) # w is the path of the text widget # if flag == 1, set $w to read-only # if flag == 0, set $w to normal # ========================== proc ROText {w {flag 1}} \ { if {$flag} \ { # check if already donne if {[info procs ROTextKey$w] != ""} { return $w } # ------------------ # set $w a read-only text widget # ------------------ # key press global flag proc proc ROTextKey$w {} { return 0 } bind $w [list proc ROTextKey$w {} { return 1 } ] bind $w [list proc ROTextKey$w {} { return 0 } ] # read-only $w proc rename $w ROText$w proc $w {cmd args} \ { # get widget path set w [lindex [info level 0] 0] if {[ROTextKey$w]} \ { # a key was pressed and not released: # forbid inserting & deleting chars switch -glob -- $cmd \ { del* { # forbidden } ins* { # forbidden } default { uplevel [linsert $args 0 ROText$w $cmd] } } } \ else \ { # a key was not pressed # permit all uplevel [linsert $args 0 ROText$w $cmd] } } } \ else \ { # ------------------ # set $w a normal text widget # ------------------ rename $w "" rename ROText$w $w rename ROTextKey$w "" } # return path return $w } # ========================== Here is a little demo: pack [ROText [text .t]] .t insert end "this is a little demo\n" ---- The [Snit's Not Incr Tcl] page has a similar solution to the above; but it's shorter. ---- [FW]: The text widget's ''-disabled'' option was created for this very purpose, wasn't it? Other than the flashing cursor that's missing in a disabled text widget, there's no difference. On some Windows Tcl versions you need to bind the widget to gain focus correctly so you can highlight text, etc., are there any other disadvantages to this approach? text .t -state disabled bind .t <1> {focus %W} ;# Not even needed in most cases The only behavioral difference here is that appending text to the end of the widget means you have to set the state normal, add it, and set it disabled again. ---- [LES] on May 08, 2004: What I often wish for is a read-only '''tag'''. I mean, assigning to specific text segments the ability not to be changed or deleted although everything else around it can be edited normally. [FW]: For a hack, I might suggest an embedded entry. [LES]: I beg your pardon? [FW]: Embed an entry widget (without a border, and with the same background color) into the text widget. It's not so great, but it more or less accomplishes the task. For one solution to having read-only text tags see [Read-Only Text Megawidget with TclOO] ---- !!!!!! %| [Category GUI] | [Category Widget] |% !!!!!!