Multi-Line Text Entry Widget - With Entry Widget Like Field To Field Tabbing

Peter Newman - 13 March 2005

This is a multi-line text entry widget I wrote. It's no doubt similar to LabelText -A Multi Line Entry Widget and Multiline expanding entry widget - but also supports entry widget like field to field tabbing.


Bryan Oakley - 02 April 2009 - this code is busted. It uses something called ::zproc::args and zprocOptionDefaults but never defines such a thing. A quick search of the wiki shows no other pages that define these commands.


The text widget, by default, uses Tab and Shift+Tab for 8-space text editor/word processor tabbing. But a multi-line text entry widget is probably going to be used, along with other entry, etc. widgets, in a data entry form. Where Tk, by default, uses Tab and Shift+Tab for field to field tabbing.

Dumping the text widget's default bindings (with bind Text, and bind Text <Key-Tab>, etc), we get:-

        <Key-Tab>
               if { [string equal [%W cget -state] "normal"] } {
                       tk::TextInsert %W \t
                       focus %W
                       break
                       }

        <Shift-Key-Tab>
                break

        <Control-Key-Tab>
                focus [tk_focusNext %W]

        <Control-Shift-Key-Tab>
                focus [tk_focusPrev %W]

In other words, Tab and Shift+Tab do text editor/word processor type, 8 space tabbing - and with Ctrl as well they do field to field tabbing. So you'd think you could obtain the desired field to field tabbing behaviour, by simply switching Tab and Shift+Tab to the above <Control-Key-Tab> and <Control-Shift-Key-Tab> bindings.

But whereas:-

 bind .myTextWidget <Shift-Key-Tab> {focus [tk_focusPrev %W]}

works,

 bind .myTextWidget <Key-Tab> {focus [tk_focusNext %W]}

doesn't. It's some months since I did this, and I can't remember why the Tab key binding won't work. But it's easily fixed with Wcb, as in the code below.

PWQ 25 May 05, The above bindings work, but the default binding on Text, refocus to %W after insertion of the tab character (which is lost due to the %W no longer being the focus of the event).

Simply add ;break after the bindings and the tabbing will work as expected.

Apart from the bindings however, the text widget itself, is just a text widget. The text entry widget also supports auto scrollbars - via Scrodget. You could replace Scrodget with BWidget::ScrolledWindow - though Scrodget blows ScrolledWindow away.

The widget uses some other routines from my own personal software library. I'll try and load these up shortly. In the meantime, you'll just have to hack it. However, making this code work isn't that important. You can easily roll your own. I really just put the code up to document the fixing of the field to field tabbing behaviour, with text widgets used as multi-line entry widgets.


Quick Summary - How To Create An Instant Multi-Line Text Entry Widget

A multi-line text entry widget is just a text widget, with it's Tab and Shift+Tab key bindings modified to give the field to field tabbing behaviour normally associated with the entry and other Tk widgets.

To create one just:-

  1. Create and pack/grid/place your text widget in the normal fashion,
  2. Bind the Tab and Shift+Tab keys as follows:-
 bind .myTextWidget <Key-Tab>       {focus [tk_focusNext %W]}
 bind .myTextWidget <Shift-Key-Tab> {focus [tk_focusPrev %W]}
  1. But if you find, as I did, that the above <Key-Tab> binding doesn't work, add (or replace it with):-
 package require wcb
 ::wcb::callback .myTextWidget before insert BeforeInsert_CancelTab

where BeforeInsert_CancelTab is the routine at the bottom of the code below.


rdt But isn't it a simple matter of making the bind command include a '; break' at its end ?? For example:

 bind .myTextWidget <Key-Tab>       {focus [tk_focusNext %W]; break}
 bind .myTextWidget <Shift-Key-Tab> {focus [tk_focusPrev %W]; break}

Peter Newman 17 March 2005: You may well be right rdt. It's such a long time since I did this, I can't remember if I found out why the <Key-Tab> binding didn't work. But it's strange that the <Shift-Key-Tab> binding works, but not the <Key-Tab> one.

But if you or someone else has tested out the break method, and it works, then that would be the way to go.

(P.S. I must admit I haven't got a clue about what's going on with that break thing. Can you explain it?)

NEM: From the bind manpage [L1 ]: "The continue and break commands may be used inside a binding script to control the processing of matching scripts. If continue is invoked, then the current binding script is terminated but Tk will continue processing binding scripts associated with other tag's. If the break command is invoked within a binding script, then that script terminates and no other scripts will be invoked for the event."

You might also want to look into the bindtags command. I put a little example at the bottom of this page on how to use it (means you don't have to bind for each individual widget).


Text Widget Enhancement

A useful (and surely very easily implemented,) enhancement to the text widget, would be to add a:-

  • -fieldToFieldTabbing

option - so that one could instantly create a multi-line text entry widget, simply by using the standard text widget, and setting that option ON.

The option itself would just switch the text widget's existing <Key-Tab>/<Shift-Key-Tab> and <Control-Key-Tab>/<Control-Shift-Key-Tab> bindings - so that Tab and Shift+Tab did the field to field tabbing that's usually desired with widgets in a data entry form - instead of the text editor/word processor type 8 space tabbing that they would otherwise do (and which is usually not required, in a multi-line text entry widget).


 # ==============================================================
 # TEXTNTRY.TCL
 # ------------
 # (C) 2004 Peter Newman
 # -------------------------------------------------------------- 
 # Multi-Line Text Entry Widget...
 #       ...in other words, it's a widget like the standard Tk 
 #       `entry' widget - but one that:-
 #       a) Allows the text entry window to span multiple lines,
 #          and;
 #       b) Allows multiple lines of text to be entered and 
 #          edited (if desired).
 #
 # It also supports Tab and Shift+Tab the same way the `entry'
 # and other Tk widgets do - move you from field to field in a
 # data entry form. This is necessary because the text widget,
 # by default, uses Tab and Shift+Tab for 8-SPace tabbing. Which
 # isn't usually that necessary in a small multi-line entry
 # widget - though having Tab and Shift+Tab move you consistently
 # forward and backwards through the fields in the data entry
 # form is.
 # --------------------------------------------------------------
 # See also: MLINEENT.TCL. (Though TEXTNTRY.TCL is an upgraded 
 # version of MLINEENT.TCL.)
 # ==============================================================

 # --------------------------------------------------------------
 # Begin NAMESPACE
 # --------------------------------------------------------------

 namespace eval ::TextEntryWidget {

 # --------------------------------------------------------------
 # Return Text Entry Widget's - Text Widget Pathname
 # --------------------------------------------------------------

 proc ReturnTextEntryWidgets_TextWidgetPathname { textEntryWidgetPathname } {

        # ------------------------------------------------------
        # ::TextEntryWidget::ReturnTextEntryWidgets_TextWidgetPathname
        #       textEntryWidgetPathname
        # ------------------------------------------------------
        # Returns the pathname of the text widget proper, for 
        # the specified `text entry widget'...
        # ------------------------------------------------------
        # RETURNS:-
        #       textWidgetPathname
        # ------------------------------------------------------

        lassign [ ReturnSubWidgetPathnames      \
                $textEntryWidgetPathname        \
                ]                               \
                junk1                           \
                junk2                           \
                textWidgetPathname              \
                ;

        # ------------------------------------------------------

        return $textWidgetPathname ;

        # ------------------------------------------------------

        }

 # --------------------------------------------------------------
 # Return Sub Widget Pathnames
 # --------------------------------------------------------------

 proc ReturnSubWidgetPathnames { textEntryWidgetPathname } {

        # ------------------------------------------------------
        # ::TextEntryWidget::ReturnSubWidgetPathnames
        #       textEntryWidgetPathname
        # ------------------------------------------------------
        # NOTES!
        # ------
        # 1. `textEntryWidgetPathname' is also the 
        #    `containerFramePathname' of the `container frame'
        #    in which the `text entry widget' is wrapped...
        #
        # 2. Use:-
        #       scrodgetPathname returnSubWidgetPathnames
        #    if you want the pathnames of the (frame and 
        #    scrollbar) widgets in the scrodget.
        # ------------------------------------------------------
        # RETURNS:-
        #       containerFramePathname
        #       scrodgetPathname
        #       textWidgetPathname
        # ------------------------------------------------------

        set containerFramePathname $textEntryWidgetPathname ;

        set scrodgetPathname       ${containerFramePathname}.scrodget ;

        set textWidgetPathname     ${scrodgetPathname}.text ;

        # ------------------------------------------------------

        return [ list                           \
                $containerFramePathname         \
                $scrodgetPathname               \
                $textWidgetPathname             \
                ]                               \
                ;

        # ------------------------------------------------------

        }

 # --------------------------------------------------------------
 # Create
 # --------------------------------------------------------------

 proc Create { args } {

        # ------------------------------------------------------
        # ::TextEntryWidget::Create             DEFAULTS
        #       widgetPathname                  --------
        #
        #       -textWidgetHeight_inLines       10
        #       -textWidgetWidth_inChars        64
        #
        #       -maxTextLength_inLines          0 (= unlimited)
        #       -maxTextWidth_inChars           0 (= unlimited)
        #
        #       -frameBorderwidth               3 (pixels)
        #       -frameRelief                    sunken
        #       -frameBackgroundColour          Gray
        #
        #       -bodyBreathingSpace_inPixels    3mm
        #       -bodyBackgroundColour           White
        #
        #       -startText                      {}
        #
        #       -containerFrameOptions          [list]
        #
        #       -scrodgetOptions                [list]
        #       -scrodgetPackOptions            [list]
        #
        #       -scrodgetFrameOptions           [list]
        #       -scrodgetScrollbarOptions       [list]
        #
        #       -textWidgetOptions              [list]
        #       -textWidgetGridOptions          [list]
        #
        # ------------------------------------------------------
        # OVERVIEW
        # --------
        # A text entry widget is like:-
        #
        #   +----------------- container frame ----------------+
        #   |                                                  |
        #   |     use the container frame's `borderwidth',     |
        #   |     if you want to create a raised/sunken etc    |
        #   |          frame around the entire widget          |
        #   |                                                  |
        #   |       -frameBorderwidth,                         |
        #   |       -frameRelief, and;                         |
        #   |       -frameBackgroundColour,                    |
        #   |       are mapped here.                           |
        #   |                                                  |
        #   |   +---------------  scrodget ----------------+   |
        #   |   |                                          |   |
        #   |   |      use the scrodget outer frame's      |   |
        #   |   |     `borderwidth', if you want some      |   |
        #   |   |   `breathing space' between the outer    |   |
        #   |   |     border, and the scrollbars/text      |   |
        #   |   |                 proper                   |   |
        #   |   |                                          |   |
        #   |   |   +---------- text widget --------+--+   |   |
        #   |   |   |                               |^^|   |   |
        #   |   |   |  You're then free to use the  |  |   |   |
        #   |   |   |  text widget and scrollbar    |  |   |   |
        #   |   |   |  `borderwidth', `background', |  |   |   |
        #   |   |   |  `relief', `padx' and `pady'  |  |   |   |
        #   |   |   |  etc, etc options, to get any |  |   |   |
        #   |   |   |  other layout effects you     |  |   |   |
        #   |   |   |  want.                        |  |   |   |
        #   |   |   |                               |  |   |   |
        #   |   |   |  By default, the              |XX|   |   |
        #   |   |   |  `borderwidth' and `padx'/    |  |   |   |
        #   |   |   |  `pady', etc, are all set to  |  |   |   |
        #   |   |   |  0.                           |  |   |   |
        #   |   |   |                               |vv|   |   |
        #   |   |   +-------------------------------+--+   |   |
        #   |   |   |<      XX                     >|  |   |   |
        #   |   |   +-------------------------------+--+   |   |
        #   |   |                                          |   |
        #   |   +------------------------------------------+   |
        #   |                                                  |
        #   +--------------------------------------------------+
        #
        # It's main features are:-
        #
        # Like A Multi-Line Entry Widget
        # ------------------------------
        # The `Text Entry Widget' is intended to act like a 
        # standard Tk `entry' widget - but one that allows
        # multiple lines of text to be entered and edited.
        #
        # Tabbing
        # -------
        # The `Text Entry Widget' handles Tab and Shift+Tab the 
        # same way the `entry' widget does. In other words, it
        # uses them to move from field to field (widget to
        # widget) - in an assumed multi-widget data entry form.
        #
        # It DOESN'T use them to support text editor/word
        # processor type tabbing - as the normal, unmodified
        # text widget does. In the `Text Entry Widget', this use
        # of the Tab keys ISN'T available.
        #
        # Put another way, the `text entry Widget' is intended 
        # where you want a small but multi-line text entry box -
        # like the HTML <TEXTAREA> box:-
        #
        #       <TEXTAREA NAME="feedback" ROWS=5 COLS=20>
        #           Blah blah blah...
        #       </TEXTAREA>
        #
        # In this usage, the field to field/widget to widget
        # type tabbing supported by the `entry' widget is more
        # important/useful than the text editor/word processor
        # type tabbing supported by the `text' widget.
        #
        # But this means that the `Text Entry Widget' probably
        # isn't suited to editing large text files, for example.
        #
        # (Physical Text) Widget Size vs. (Logical) Text Size
        # ---------------------------------------------------
        # With the `Text Entry Widget':-
        #
        #   -- The width and height of the text entry/editing 
        #      area, in lines and characters of text, and;
        #
        #   -- The MAXIMUM allowable width and height of the 
        #      text to be entered/edited, also specified in
        #      lines and characters of text,
        #
        # are two DIFFERENT things.
        #
        # They're specified with the:-
        #
        #       -textWidgetHeight_inLines   (default = 10)
        #       -textWidgetWidth_inChars    (default = 64)
        #
        # and;
        #
        #       -maxTextLength_inLines      (default = 0 = no limit)
        #       -maxTextWidth_inChars       (default = 0 = no limit)
        #
        # parameters respectively.
        # ------------------------------------------------------
        # NOTES!
        # ------
        # 1. `widgetPathname' is the pathname for the enclosing 
        #    `container frame'. `Create' will create this
        #    container frame (and it's contents). So:-
        #
        # 2. `widgetPathname' shouldn't exist before `Create' is 
        #    called.
        #
        # 3. `Create' DOESN'T `geometry manage' (pack, grid or 
        #    place) the created `container frame'. It's the
        #    CALLER's job to do this.
        #
        # 4. To get rid of the text entry widget, just destroy
        #    `widgetPathname'.
        # ------------------------------------------------------
        # RETURNS:-
        #       textWidgetPathname
        #               --> Created OK
        #       --OR--
        #       {}      --> ERROR (error message issued)
        #
        # ------------------------------------------------------

        set errorCode {} ;

        # ------------------------------------------------------
        # Analyse the input parameters...
        # ------------------------------------------------------

        ::zproc::args                                                                   \
                                                                                        \
                {}                                                                      \
                                                                                        \
                widgetPathname                                                          \
                                                                                        \
                {                                                                       \
                                                                                        \
                -textWidgetHeight_inLines               textWidgetHeight_inLines        \
                -textWidgetWidth_inChars                textWidgetWidth_inChars         \
                                                                                        \
                -maxTextLength_inLines                  maxTextLength_inLines           \
                -maxTextWidth_inChars                   maxTextWidth_inChars            \
                                                                                        \
                -frameBorderwidth                       frameBorderwidth                \
                -frameRelief                            frameRelief                     \
                -frameBackgroundColour                  frameBackgroundColour           \
                                                                                        \
                -bodyBreathingSpace_inPixels            bodyBreathingSpace_inPixels     \
                -bodyBackgroundColour                   bodyBackgroundColour            \
                                                                                        \
                -startText                              startText                       \
                                                                                        \
                -containerFrameOptions                  containerFrameOptions           \
                                                                                        \
                -scrodgetOptions                        scrodgetOptions                 \
                -scrodgetPackOptions                    scrodgetPackOptions             \
                                                                                        \
                -scrodgetFrameOptions                   scrodgetFrameOptions            \
                -scrodgetScrollbarOptions               scrodgetScrollbarOptions        \
                                                                                        \
                -textWidgetOptions                      textWidgetOptions               \
                -textWidgetGridOptions                  textWidgetGridOptions           \
                                                                                        \
                }

        # ------------------------------------------------------
        # Set the defaults...
        # ------------------------------------------------------

        set defaultBodyBreathingSpace_inMM 3 ;

        # ------------------------------------------------------
                                                                                                        \
        set defaultBodyBreathingSpace_inPixels [ expr int( round(                                       \
                (                                                                                       \
                [::MonitorSize::Mm2Pixels_Horizontally_IntegerResult $defaultBodyBreathingSpace_inMM]   \
                +                                                                                       \
                [::MonitorSize::Mm2Pixels_Vertically_IntegerResult   $defaultBodyBreathingSpace_inMM]   \
                )                                                                                       \
                /                                                                                       \
                2                                                                                       \
                ) ) ]                                                                                   \
                ;

        # ------------------------------------------------------

        zprocOptionDefaults                                                             \
                                                                                        \
                textWidgetHeight_inLines        10                                      \
                textWidgetWidth_inChars         64                                      \
                                                                                        \
                maxTextLength_inLines           0                                       \
                maxTextWidth_inChars            0                                       \
                                                                                        \
                frameBorderwidth                3                                       \
                frameRelief                     sunken                                  \
                frameBackgroundColour           Gray                                    \
                                                                                        \
                bodyBreathingSpace_inPixels     $defaultBodyBreathingSpace_inPixels     \
                bodyBackgroundColour            White                                   \
                                                                                        \
                startText                       {}                                      \
                                                                                        \
                containerFrameOptions           [list]                                  \
                                                                                        \
                scrodgetOptions                 [list]                                  \
                scrodgetPackOptions             [list]                                  \
                                                                                        \
                scrodgetFrameOptions            [list]                                  \
                scrodgetScrollbarOptions        [list]                                  \
                                                                                        \
                textWidgetOptions               [list]                                  \
                textWidgetGridOptions           [list]                                  \
                                                                                        \
                ;

        # ------------------------------------------------------
        # Get the (sub) widget pathnames...
        # ------------------------------------------------------

        # ------------------------------------------------------
        # ::TextEntryWidget::ReturnSubWidgetPathnames
        #       textEntryWidgetPathname
        # ------------------------------------------------------
        # NOTES!
        # ------
        # 1. `textEntryWidgetPathname' is also the 
        #    `containerFramePathname' of the `container frame'
        #    in which the `text entry widget' is wrapped...
        #
        # 2. Use:-
        #       scrodgetPathname returnSubWidgetPathnames
        #    if you want the pathnames of the (frame and 
        #    scrollbar) widgets in the scrodget.
        # ------------------------------------------------------
        # RETURNS:-
        #       containerFramePathname
        #       scrodgetPathname
        #       textWidgetPathname
        # ------------------------------------------------------

        lassign [ ReturnSubWidgetPathnames              \
                $widgetPathname                         \
                ]                                       \
                containerFramePathname                  \
                scrodgetPathname                        \
                textWidgetPathname                      \
                ;

        # ------------------------------------------------------
        # `Container frame'...
        # ------------------------------------------------------

        frame $containerFramePathname                   \
                -borderwidth    $frameBorderwidth       \
                -relief         $frameRelief            \
                -background     $frameBackgroundColour  \
                ;

        # ------------------------------------------------------

        foreach {optionName optionValue} $containerFrameOptions {

                $containerFramePathname configure               \
                        $optionName     $optionValue            \
                        ;

                }

        # ------------------------------------------------------
        # Scrodget...
        # ------------------------------------------------------

        package require scrodget ;
                # Scrodget 2.0

        # ------------------------------------------------------

        scrodget $scrodgetPathname              \
                -autohide       1               \
                -scrollsides    se              \
                ;

        # ------------------------------------------------------

        foreach {optionName optionValue} $scrodgetOptions {

                $scrodgetPathname configure                     \
                        $optionName     $optionValue            \
                        ;

                }

        # ------------------------------------------------------

        $scrodgetPathname frame configure                       \
                -borderwidth    $bodyBreathingSpace_inPixels    \
                -relief         flat                            \
                -background     $bodyBackgroundColour           \
                ;

        # ------------------------------------------------------

        foreach {optionName optionValue} $scrodgetFrameOptions {

                $scrodgetPathname frame configure               \
                        $optionName     $optionValue            \
                        ;

                }

        # ------------------------------------------------------
        # NOTE!
        # -----
        # 1. When configuring the scrollbars, we configure all 4 
        #    possible scrollbars, irrespective of whether the
        #    scrollbar concerned is to be displayed or not...
        #
        #    (This just keeps the code simpler.)
        #
        # 2. On Windows, the `-troughcolor' and `-background' 
        #    options DON'T WORK.
        #
        #    This is because the Tk `scrollbar' widget just 
        #    ignores support these options on Windows (because
        #    the Windows API - which provides the actual
        #    scrollbar - doesn't support them either).
        # ------------------------------------------------------

        $scrodgetPathname northScroll configure                 \
                -borderwidth            0                       \
                -elementborderwidth     2                       \
                -background             $bodyBackgroundColour   \
                -troughcolor            $bodyBackgroundColour   \
                -highlightthickness     0                       \
                ;

        # ------------------------------------------------------

        $scrodgetPathname southScroll configure                 \
                -borderwidth            0                       \
                -elementborderwidth     2                       \
                -background             $bodyBackgroundColour   \
                -troughcolor            $bodyBackgroundColour   \
                -highlightthickness     0                       \
                ;

        # ------------------------------------------------------

        $scrodgetPathname westScroll configure                  \
                -borderwidth            0                       \
                -elementborderwidth     2                       \
                -background             $bodyBackgroundColour   \
                -troughcolor            $bodyBackgroundColour   \
                -highlightthickness     0                       \
                ;

        # ------------------------------------------------------

        $scrodgetPathname eastScroll configure                  \
                -borderwidth            0                       \
                -elementborderwidth     2                       \
                -background             $bodyBackgroundColour   \
                -troughcolor            $bodyBackgroundColour   \
                -highlightthickness     0                       \
                ;

        # ------------------------------------------------------

        foreach {optionName optionValue} $scrodgetScrollbarOptions {

                # ----------------------------------------------

                $scrodgetPathname northScroll configure         \
                        $optionName     $optionValue            \
                        ;

                # ----------------------------------------------

                $scrodgetPathname southScroll configure         \
                        $optionName     $optionValue            \
                        ;

                # ----------------------------------------------

                $scrodgetPathname westScroll configure          \
                        $optionName     $optionValue            \
                        ;

                # ----------------------------------------------

                $scrodgetPathname eastScroll configure          \
                        $optionName     $optionValue            \
                        ;

                # ----------------------------------------------

                }

        # ------------------------------------------------------

        pack $scrodgetPathname                  \
                -expand 1                       \
                -fill   x                       \
                ;

        # ------------------------------------------------------

        foreach {optionName optionValue} $scrodgetPackOptions {

                pack configure $scrodgetPathname                \
                        $optionName     $optionValue            \
                        ;

                }

        # ------------------------------------------------------
        # Text widget...
        # ------------------------------------------------------

        text $textWidgetPathname                                        \
                -borderwidth            0                               \
                -background             $bodyBackgroundColour           \
                -highlightthickness     0                               \
                -height                 $textWidgetHeight_inLines       \
                -width                  $textWidgetWidth_inChars        \
                -wrap                   none                            \
                ;

        # ------------------------------------------------------

        if { $startText ne {} } {
                $textWidgetPathname insert end $startText ;
                }

        # ------------------------------------------------------

        foreach {optionName optionValue} $textWidgetOptions {

                $textWidgetPathname configure                   \
                        $optionName     $optionValue            \
                        ;

                }

        # ------------------------------------------------------

        $scrodgetPathname associate $textWidgetPathname ;

        # ------------------------------------------------------

        foreach {optionName optionValue} $textWidgetGridOptions {

                grid configure $textWidgetPathname              \
                        $optionName     $optionValue            \
                        ;

                }

        # ------------------------------------------------------
        # Fix up `Tab' and `Shift+Tab'...
        #
        # QUICK SUMMARY
        # -------------
        # We want `Tab' and `Shift+Tab' to switch between fields
        # (as they do in the `entry' widget, for example) -
        # instead of handling tabbing, as they do in the
        # standard Text widget...
        #
        # For `Shift+Tab' all we need do is restore the normal 
        # <Shift-Key-Tab> binding.
        #
        # But for `Tab' alone, this doesn't work (though we do 
        # it amyway). We've also got to use the `wcb' package
        # to:-
        #
        # a) Stop the `text' widget from processing the `Tab' 
        #    key, and;
        #
        # b) Call the required `focus [tk_focusNext %W]]'.
        #
        # IN MORE DETAIL
        # --------------
        # 1. THE `MULTI LINE TEXT ENTRY WIDGET'
        #       This is a widget that behaves like the standard
        #       Tk `entry' widget - but allows for multiple
        #       lines of text...
        #
        # 2. THE `TAB' KEY
        #       In the standard Tk `text' widget, `Tab' and
        #       `Shift+Tab' are handled in much the same way as
        #       they are in any standard text editor or word
        #       processor.
        #
        #       In other words, Tab/Shift+Tab advance the cursor
        #       to the next/previous Tab stop (which by default,
        #       are usually eight SPaces apart).
        #
        #       But for the `Multi Line Text Entry Widget', we
        #       want `Tab'/`Shift+Tab' to behave as they do for
        #       the `entry' widget.
        #
        #       In other words, to move the cursor to the
        #       next/previous widget/data entry field.
        #
        # 3. THE DEFAULT TEXT WIDGET TAB KEY BINDINGS
        #       These are:-
        #
        #               <Key-Tab>
        #                       if { [string equal [%W cget -state] "normal"] } {
        #                               tk::TextInsert %W \t
        #                               focus %W
        #                               break
        #                               }
        #
        #               <Shift-Key-Tab>
        #                       break
        #
        #               <Control-Key-Tab>
        #                       focus [tk_focusNext %W]
        #
        #               <Control-Shift-Key-Tab>
        #                       focus [tk_focusPrev %W]
        #
        #       In other words `Tab'/`Shift+Tab' does text
        #       editor/word processor type tabbing.
        #
        #       But used with the `Ctrl' key, we get the `entry'
        #       widget type behaviour.
        #
        # 4. `SHIFT+TAB'
        #       `Shift+Tab' is easily restored to the required
        #       behaviour with:-
        #
        #               bind $textWidgetPathname        \
        #                       <Shift-Key-Tab>         \
        #                       {focus [tk_focusPrev %W]} ;
        #
        #       But this doesn't work with the `Tab' key.
        #
        # 5. `TAB'
        #       There might be other ways of fixing the `Tab'
        #       key.
        #
        #       But the only one I've found so far, is to use
        #       the `wcb' package to add a `Before Insert'
        #       routine to the text widget that, when it detects
        #       the `Tab' key (or more precisely; "\t"):-
        #
        #       a) Calls `::wcb::cancel' - to stop it being seen 
        #          by the text widget, and then;
        #
        #       b) Does `focus [tk_focusNext %W]' - to generate 
        #          the behaviour that we want.
        # ------------------------------------------------------

        bind $textWidgetPathname <Key-Tab>       {focus [tk_focusNext %W]} ;
                # Doesn't work (see above). But we restore this 
                # default binding anyway...

        # ------------------------------------------------------

        bind $textWidgetPathname <Shift-Key-Tab> {focus [tk_focusPrev %W]} ;

        # ------------------------------------------------------

        package require wcb ;

        # ------------------------------------------------------

        ::wcb::callback $textWidgetPathname before insert       \
                ::TextEntryWidget::BeforeInsert_CancelTab       \
                ;

        # ------------------------------------------------------
        # That's that!
        # ------------------------------------------------------

        return $textWidgetPathname ;

        # ------------------------------------------------------

        }

 # --------------------------------------------------------------
 # Before Insert - Cancel Tab
 # --------------------------------------------------------------

 proc BeforeInsert_CancelTab { widgetPathname insertIndex stringToInsert } {

        # ------------------------------------------------------
        # Cancel `Tab'...
        # ------------------------------------------------------

        if { $stringToInsert eq "\t" } {

                # ----------------------------------------------
                # Make the text widget ignore the `Tab' key...
                # ----------------------------------------------

                ::wcb::cancel {} ;

                # ----------------------------------------------
                # And make it instead, move the focus to the 
                # next window...
                # ----------------------------------------------

                set cleanedUpWidgetPathname [string range $widgetPathname 3 end] ;
                        # Removes the "::_" that the `wcb' 
                        # package prepends to the pathnames of
                        # the text widgets it's attached to.

                # ----------------------------------------------

                after idle "focus [tk_focusNext $cleanedUpWidgetPathname]" ;
                        # Don't know why the `after idle' is 
                        # required - but it doesn't work without
                        # it...

                # ----------------------------------------------

                }

        # ------------------------------------------------------

        }

 # --------------------------------------------------------------
 # End NAMESPACE (Text Entry Widget)
 # -------------------------------------------------------------- 
 }

NEM offers the following as a quick solution to the <Tab> overriding:

 # Create a new bindtag EText for an entry-text, which overrides the <Tab>
 bind EText <Tab> "[bind all <Tab>]; break"
 bind EText <Shift-Tab> "[bind all <Shift-Tab>]; break"
 # A simple "constructor", which inserts the new bindtag
 proc etext {path args} {
     text $path {expand}$args ;# Warning: 8.5ism!
     set idx [lsearch -exact [bindtags $path] Text]
     bindtags $path [linsert [bindtags $path] $idx EText]
     return $path
 }
 # A test:
 pack [etext .t -highlightthickness 2]
 pack [entry .e]

NEM - updated on advice from kbk to avoid copying excess bindings.


[ Category Widget ]