Multi-Line Entry Widget in Snit

jnc Dec 2, 2009 - Tk currently lacks a multiline entry widget. The text widget is a great widget but a bit complex if you just need a multiline plain text widget that acts like entry.

This is one of many solutions on the wiki. This solution includes support for:

  • -textvariable
  • attaching scroll bars (vertical by default)
  • handling Tab and Shift-Tab correctly
  • optionally allowing tabs inside of the widget with the -allowtab option
  • readonly mode with common keyboard navigation using the -readonly option
 snit::widget multiline_entry {
    delegate option * to text
    delegate method * to text

    # On/Off options
    option -yscroll -default yes
    option -xscroll -default no
    option -allowtab -default no
    option -readonly -default no

    # Miscellaneous options
    option -textvariable -default 0

    constructor { args } {
        install text using text $win.txt
        grid $win.txt -row 0 -column 0 -sticky nswe

        $self configurelist $args

        if { [$win cget -yscroll] } {
            $win.txt configure -yscrollcommand [list $win.vsb set]
            ttk::scrollbar $win.vsb -command [list $win.txt yview]
            grid $win.vsb -row 0 -column 1 -sticky nsw

        if { [$win cget -xscroll] } {
            $win.txt configure -xscrollcommand [list $win.hsb set]
            ttk::scrollbar $win.hsb -orient horizontal -command [list $win.txt xview]
            grid $win.hsb -row 1 -column 0 -sticky we

        grid rowconfigure $win 0 -weight 1
        grid columnconfigure $win 0 -weight 1

        if {[$win cget -textvariable] != 0} {
            set varName [$win cget -textvariable]
            upvar 3 $varName v
            $win.txt insert 1.0 $v
            $win.txt mark set insert 1.0
            trace add variable v write [list $self setContent]
            trace add variable v read [list $self getContent]

        bind $win <FocusIn> [list focus $win.txt]

        if { !$options(-allowtab) } {
            bind $win.txt <Shift-Tab> [list $self focusPrev]
            bind $win.txt <Tab> [list $self focusNext]

        if { $options(-readonly) } {
            bind $win.txt <KeyPress>        break
            bind $win.txt <ButtonRelease-2> break

            bind $win.txt <Down>  continue
            bind $win.txt <Up>    continue
            bind $win.txt <Prior> continue
            bind $win.txt <Next>  continue
            bind $win.txt <Left>  continue
            bind $win.txt <Right> continue

    method getTextWidget {} {
        return $win.txt

    method focusPrev {} {
        focus [tk_focusPrev $win]

        return -code break

    method focusNext {} {
        focus [tk_focusNext $win.txt]

        return -code break

    method setContent { name element op} {
        upvar 1 $name x

        $win.txt delete 1.0 end

        if { [array exists x] } {
            $win.txt insert 1.0 $x($element)
        } else {
            $win.txt insert 1.0 $x

    method getContent { name element op } {
        upvar 1 $name x

        if { [array exists x] } {
            set x($element) [$win.txt get 1.0 "end-1 char"]
        } else {
            set x [$win.txt get 1.0 "end-1 char"]

jnc Dec 21, 2009 - Added a -readonly option, however, it also prevents keyboard navigation. Any better ideas?

WHD Redefine the "insert" and "delete" subcommands so that they do nothing if -readonly is true. Then, the Text bindings that change the content of the widget will have no effect. To be really fancy, define your own -state option with three values, "normal", "disabled", and "readonly"; pass the first two along to the hull and set your readonly flag for the third.

Bezoar I rewrote the above widget using TclOO Multi-line TclOO Text Entry Widget just to see how hard it would be to convert between the two. Turns out it was very easy. I did add some features that don't exist in this version. First all option not recognized by the Widget get sent to the internal text widget so you get the -state option mentioned above by WHD for free. I also corrected the fact that you could still cut and paste into a readonly widget and finally when -xscroll = 1 I also shut off wrapping in the text widget otherwise it is useless. I have some test code at the afore mentioned page to test the widget.

jnc Sep 15, 2010 - Updated code, includes better readonly support (keyboard navigation) and no mouse paste ability. Handling of getting/setting via array.

See also