[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. 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 '', etc), we get:- if { [string equal [%W cget -state] "normal"] } { tk::TextInsert %W \t focus %W break } break focus [tk_focusNext %W] 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 and bindings. But whereas:- bind .myTextWidget {focus [tk_focusPrev %W]} works, bind .myTextWidget {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. 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, 1. '''Bind''' the ''Tab'' and ''Shift+Tab'' keys as follows:- bind .myTextWidget {focus [tk_focusNext %W]} bind .myTextWidget {focus [tk_focusPrev %W]} 1. But if you find, as I did, that the above '''''' key 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 {focus [tk_focusNext %W]; break} bind .myTextWidget {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 '''' binding didn't work. But it's strange that the '''' binding works, but not the '''' one. But if you or someone else has tested out the '''break''' method, and it works, then that would be the way to go. ---- '''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 ''/'' and ''/'' 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 really 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 # # 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 # 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:- # # # if { [string equal [%W cget -state] "normal"] } { # tk::TextInsert %W \t # focus %W # break # } # # # break # # # focus [tk_focusNext %W] # # # 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 \ # \ # {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 {focus [tk_focusNext %W]} ; # Doesn't work (see above). But we restore this # default binding anyway... # ------------------------------------------------------ bind $textWidgetPathname {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 overriding: # Create a new bindtag EText for an entry-text, which overrides the bind EText "[bind all ]; break" bind EText "[bind all ]; 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] ]]