What is this

It's all about a bar of tabs. Or a tab bar if you like it more.

The tab bars are widely used in various editors, e.g. in alited, TKE, Geany, Kate, Pluma - all very good for Tcl/Tk programming, by the way.

If you need a Tcl/Tk widget similar to the tab bars of those editors, you might try bartabs.

The bartabs is a tab bar widget with a lot of options. It can be used as an independent widget or along with apave package as bts widget.

Specifically, bartabs does its best in alited.


The bartabs package provides Tcl/Tk widget containing tabs, with the following features:

  • scrollable tabs
  • moveable tabs
  • closeable tabs
  • drag-and-drop tabs
  • disabled and enabled tabs
  • static and changeable bars
  • selectable and multi-selectable tabs
  • tooltips on tabs and popup menu's items
  • tabs can be marked with a color or an image
  • tabs can fire user commands at closing, selecting, moving
  • tabs can be sorted, including user commands to compare tabs
  • bars and tabs are configureable, including user options (little database)
  • bars are enhanced with popup menus, including user items and submenus
  • bars and tabs may be changed "on fly" (move, scroll, delete, insert, rename etc.)
  • at need, there may be multiple independent Bar objects controlled by one Bars object

A common appearance of bartabs is below:

Here the details are:

  • the current file is test2_pave.tcl
    • its full name is displayed by a caller of bartabs
  • currently hovered file is test_pavedialog.tcl
    • its name is displayed short, due to the -lablen 16 option
    • so, its real name is displayed by bartabs in the tip
    • its tab has Close button at the right
  • Scroll left and Scroll right buttons are at both edges of bartabs
  • Scroll right button is disabled, as there are no tabs beyond the right edge

A bit customized versions of bartabs may look like the following.

Here the details are:

  • a color scheme is changed
  • the hovered tab has no tip, as its label isn't shortened
  • Scroll right button is enabled, as two tabs were scrolled to the right

Here the details are:

  • tabs have no border due to -bd 0 option
  • bartabs got -static yes option, so the hovered tab has not Close button

Here the details are:

  • bartabs is made -static no -bd 1 again
  • currently edited file is underlined instead of blue background, due to -fgsel {} option
  • some tabs are marked with magenta foreground, meaning "modified" or similar

Here the details are:

  • bartabs got -imagemark option, so the tabs are marked with an image
  • if not marked or hovered, the tabs can show its own images

Here the details are:

  • tabs can be multi-selected with Ctrl+Click
  • thus, the caller of bartabs can handle the batch of tabs
The batch of selected tabs can be moved by means of popup menu item "Tabs behind".

Here the details are:

  • handlers can be called using hot keys or a popup menu bound to bartabs
  • by default, the menu actions include: 1) choosing a tab 2) moving a tab 3) closing tab(s)
  • the list of tabs contains three separated tab lists: the invisible at left, the visible, the invisible at right
  • a tab can be moved with drag-and-drop or with "... behind" menu action
  • static (-static yes) bartabs has no actions except for choosing a tab
  • even for static bartabs, its caller can provide additional menu actions and submenus

Calling bartabs

The commands to call bartabs are as simple as:

after idle [list NS::fillBar $wframe]
after 10 [list NS::fillBar $wframe]
NS::fillBar $wframe
  • NS::fillBar is a caller that should create a bar of tabs and call a method of bartabs to show the bar; there are a few of such methods
  • wframe is a path to a parent widget that should hold bartabs, most likely a frame

However, before calling any NS::fillBar it's necessary to create bartabs::Bars object, one for all possible bars:

bartabs::Bars create NS::bars

The NS::fillBar usually contains a code similar to:

# make a list of main options
set barOptions [list -wbar $wframe -wbase .somepath \
  -csel {NS::selTab %t} -cdel {NS::delTab %t} \
  -menu [list sep "com {New tab...} {NS::addTab %b}"]

# add tabs' labels
foreach lbl $labels {lappend barOptions -tab $lbl}

# create the bar
NS::bars create NS::bar $barOptions

# show the bar with $label1 as a current tab
set TID [NS::bar tabID $label1]
NS::bar $TID show
Before closing the application, a clearance is necessary:
NS::bars destroy

The details about all these Bars, NS::bars, TID etc. are considered below.


For the full list of bartabs interface methods and their sources, please refer to Reference.

The bartabs defines three TclOO classes:

  • Tab deals with tabs
  • Bar deals with a bar of tabs
  • Bars deals with bars of tabs

However, only the Bars class is used to create bars along with tabs. It can be also used to deal with any bars and tabs, providing all necessary interface.

The Bar does not create a real TclOO object, rather it is sort of syntax sugar for a convenient access to the bar methods. An application can create and handle as many bars as required.

The Tab does not create a real TclOO object as well. It serves actually for structuring bartabs code as for tab methods. Thus, its methods are accessible through the bars ("real" TclOO) and bar ("sugar") objects.

A common work flow with bartabs looks like this:

1) Firstly, we create a bartabs::Bars object, e.g.

bartabs::Bars create NS::bars

2) Then we create a bar object passing it a list of bar options, e.g.

NS::bars create NS::bar $barOptions

3) At last, we show the bar and select a tab as the current:

set TID [NS::bar tabID "tab label"]  ;# get a tab's ID by its label
NS::bar $TID show                    ;# show the bar and select the tab

or just draw the bar without mind-breaking about its tabs:

NS::bar draw    ;# show the bar without selecting a tab

4) The rest actions include:

  • responses to a selection of tab (through -csel command option of Bar object)
  • responses to a deletion (closing) of tab (through -cdel / -cdel2 command option of Bar object)
  • responses to a reorganization of bar (through -cmov / -cmov2 / -cmov3 command option of Bar object)
  • inserting and renaming tabs
  • disabling and enabling tabs
  • marking tabs with colors or icons
  • processing the marked tabs
  • processing multiple tabs selected with Ctrl+click
  • scrolling tabs to left/right through key bindings
  • calling other handlers through key bindings and bartabs menu
  • using cget and configure methods to change the bar/tab appearance
  • redrawing bars at some events
  • removing and creating as much bars as required

Few words about BID and TID mentioned throughout the bartabs.

These are identifiers of bars and tabs, of form bar<index> and tab<index> where <index> is an integer increased from 0 in order of bar/tab creation. The bars and the tabs of all bars have unique IDs.

You can use these literals freely, along with BIDs and TIDs gotten from bartabs methods. For example, if you know that "some tab" was created third, you can show it straightforward:

NS::bar tab2 show    ;# show the 3rd tab (TID=tab2)

instead of

NS::bar [NS::bar tabID "some tab"] show    ;# find and show the tab by its name

The "sugariness" of NS::bar consists in its being equivalent to NS::bars $BID, just to save words and avoid typos.

There is also a "sugar" for tabs:

NS::bar create NS::tab "some label"
which creates NS::tab object for the tab labeled "some label", just to run its methods, for example:
NS::bar create NS::tabMain "a boss tab"      ;# create a tab object by the tab's label
NS::tabMain configure -text "me the boss"    ;# a new boss enters
NS::tabMain show                             ;# the tab object didn't change its TID
NS::bar create NS::tabMeBoss "me the boss"   ;# it's the same TID
NS::tabMeBoss close                          ;# closes NS::tabMain too

For examples of using tab objects, refer to test.tcl (a line with ::bar1 create ::tab8 and below it).

Note: The tabs can be of the same name only when they belong to different bars. Inside a bar the tabs should have unique names. If some names are going to be equal you can supply them with prefixes or suffixes, sort of "subdir/", "(2)", "(copy 1)" etc. The tabID method helps to get a tab's ID by its label.

Below is a short description of bartabs classes and methods. For more details, please refer to Reference.


The methods of bartabs::Tab class are called from NS::bars or NS::bar object and are passed: tab ID (TID), method name, arguments.


OBJECT TID method arguments

For example:

NS::bars $TID show
NS::bar $TID close
or using a tab object:
NS::bar create NS::tab "tab label"
NS::tab show
NS::tab close

showShows a tab in a bar and sets it current.
closeCloses (deletes) a tab and updates the bar.
visibleChecks if a tab is visible.


The methods of bartabs::Bar class are called from NS::bar object or (more wordy) from NS::bars object.


BAR_OBJECT method arguments

BARS_OBJECT BID method arguments

For example:

NS::bar popList $X $Y
NS::bars $BID popList $X $Y

cgetGets values of options. Used for bars & tabs.
configureSets values of options. Used for bars & tabs.
insertTabInserts a tab into a bar.
tabIDGets TID by tab's label.
drawDraws the bar tabs. Used at changing the visible contents.
updateUpdates the bar in "hard" way.
scrollLeftScrolls the bar tabs to the left.
scrollRightScrolls the bar tabs to the right.
clearForgets (hides) the currently shown tabs of a bar.
listTabGets a list of bar tabs.
listFlagGets a list of bar tabs with flags "visible", "marked", "selected", "disabled".
popListShows a stand-alone popup menu of tabs.
removeRemoves a bar info from btData dictionary and destroys it.
sortSorts the tabs. Can sort increasing / decreasing and compare
tabs by a user command (default: by names).
moveTabMoves a tab to a new position. After the call(s) of "moveTab",
the bar must be updated with "TAB show" or "BAR draw".
closeAllCloses tabs of bar.
Can close all tabs, all at left / right side of a current tab.
checkDisabledMenuChecks whether the popup menu's "Close" items are disabled.
Used e.g. to display the appropriate items in app's main menu.


The methods of bartabs::Bars class are applied to all of registered bars.


BARS_OBJECT method arguments

For example:

NS::bars drawAll
NS::bars markTab tab11 tab99

createCreates a tab bar.
drawAllRedraws all tab bars.
updateAllUpdates all tab bars in "hard" way.
markTabMarks tab(s) in bars.
Can be used also by NS::bar and NS::tab "sugary" objects.
unmarkTabUnmarks tab(s) in bars.
Can be used also by NS::bar and NS::tab "sugary" objects.
selectTabSelects tab(s) in bars.
Can be used also by NS::bar and NS::tab "sugary" objects.
unselectTabUnselects tab(s) in bars.
Can be used also by NS::bar and NS::tab "sugary" objects.
moveSelTabChanges a tab's or selected tabs' position in bar.
onSelectCmdRuns a command (set by "-csel3") on a list of tabs.
disableTabDisables tab(s) in bars.
Can be used also by NS::bar and NS::tab "sugary" objects.
enableTabEnables tab(s) in bars.
Can be used also by NS::bar and NS::tab "sugary" objects.
isTabChecks if a tab object exists.
Can be used also by NS::bar and NS::tab "sugary" objects.
removeAllRemoves all tab bars.


The following conventions are accepted:
  BID means a bar's ID
  TID means a tab's ID
  NS::bars means a bartabs::Bars object created with TclOO
  NS::bar means a bar object created with NS::bars object

Below are listed the options of NS::bar.

-wbarparent widget of the bar;
it's a widget (most likely a frame) to hold the bar's tabs
Note: this option is obligatory
-wbar .win.framain.frabts
-wbase base widget whose width defines the width of the bar example:
-wbase .win.editor
in most cases, this option suffices to define the width of bartabs widget at the resizing
-wproc Tcl/Tk code to calculate the bar's width;
the option can include %b to be replaced with BID
Note: this option is used along with -wbase option
which sets a base widget to watch the bar's resizing
the option can be as simple as following:
-wproc {winfo width .win.mframe.text1}
or more complex:
proc NS::btsWidth {BID w1 w2} {
  # w1, w2 are widgets restricting the bar's width
  set bl [expr {[winfo width $w1]-[winfo width $w2]-40}]
  # a condition could mess up
  if {$NS::condition} {incr bl [$NS::proc $BID]}
  return $bl
NS::bars create NS::bar [list -wbar $wbar \
  -wproc [list NS::btsWidth %b $wmain $wleft] -tab Noname]
-static disables closing and moving the tabs by bartabsby default:
-static no
-hidearrows hides the scrolling arrows instead of disabling themby default:
-hidearrows no
use this option carefully: hiding arrows is good for static bars with few tabs
-expand expands tabs to fill the barby default:
-expand no
Note: -expand may be a number > 1 (e.g. -expand 8) meaning:
"do expand only when tabs' number > it" which makes "smart expanding"
-tab label of tab;
a sequence of -tab and -imagetab options defines a tab list
Note: the label must be unique throughout the bar it belongs to
-tab {First item} -tab {Second item}
-imagetab label of tab and image;
a sequence of -tab and -imagetab options defines a tab list
Note: the label must be unique throughout the bar it belongs to
-imagetab {{First item} img1} -imagetab {{Second item} img2}
-tabcurrent ID of current tab; this option is read only;
despite that many tabs may be selected with Ctrl+Click,
only one tab is the current tab
by default:
-tabcurrent -1
i.e. no current tab, no related info
show and listFlag methods deal with this option
-listlenlength of tab list; this option is read only
-scrollsel if "yes", a current tab position is scrolled with the arrow buttons
if "no" a left/right tab is scrolled with the arrow buttons
by default:
-scrollsel yes
-tleftindex of left tab to displayby default:
-tleft 0
-tright index of right tab to displayby default:
-tright end
i.e. as many tabs are displayed as the bar can hold
commands called before and after displaying the selected tab;
the second (-csel2) command may be time-consuming:
this separation helps to display the tab without blinking
three placeholders may be set in the option value:
%b - BID, %t - TID, %l - label
for example:
-csel {NS::btsSel %b %t} -csel2 {NS::btsSel2 %b %t}
-csel3 command called at (un)selecting tabusually the command displays a number of selected tabs, e.g.
puts [llength [NS::bar cget -select]]
commands called before and after moving and closing a tab-cmov, -cdel commands should return 1 / yes / true to enable the action, 2 to disable, 0 to cancel;
three placeholders may be set in the option value:
%b - BID, %t - TID, %l - label
for example:
  -cdel {NS::btsDel %b %t {%l}}
  proc NS::btsDel {bid tid lab args} {
where NS::btsDel is declared with additional arguments (args) containing options:
-withicon is "true" when a tab is closed by [x] icon
-first is 1 for the first tab of list, 0 for other tabs of list, -1 for a tab closed alone e.g. by [x] icon
-cmov3 command called after moving tab(s) from the popup menu-cmov3 command has omitted TID (or args) argument that is empty if selection of tabs are moved,
otherwise it's a moved tab's ID, thus one placeholder is set in the option value: %t - TID
for example:
  -cmov3 {NS::onMovingTabs %t}
-menu list of additional popup menu items containing:
1) type goes first: c - command, m - cascade menu, s - separator
2) menu label
3) menu command or {}
4) submenu name or {}
5) disabling command or {}
6) tip text or {}
7) checkbutton's variable name or {}
the optional disabling command takes three arguments: BID, TID, tab label
and returns a list of maximum 4 items:
  • state:
    • 0, no, false if the item should be enabled at the menu popup
    • 1, yes, true if the item should be disabled at the menu popup
    • 2 if the item should be hidden at the menu popup
  • tab image may be omitted or {}
  • tab label may be omitted or {}
  • tab accelerator may be omitted
Instead of the disabling command, the list of the same 4 items can be set.
The tip text would be shown over its menu item at hovering it with a mouse pointer.
The checkbutton's variable name (if set) makes the "checkbutton" menu item.
-menu [list \
  s \
  "c {Add } {NS::addTab %t} {} ::NS::isDisableTab" \
  {c {View selected} {NS::ViewSelTabs %b}} \
  {c {Unselect all} {NS::UnselectAll %b}} \
  s \
  "m {Options} {} mnusw" \
  "c {Switch -bd} {NS::switchBts %b %t -bd} mnusw {0 imgSW {%l} Ctrl+B}"]
proc ::NS::isDisableTab {BID TID label} {
  if {$NS::conditionToDisable} {return 1}
  if {$NS::conditionToHide} {return 2}
  return [list 0 imgOK "$label - OK" Ctrl+Enter]
Here we have two disabling options: by a command ::NS::isDisableTab and by a list {0 imgSW {%l} Ctrl+B}.
Note: the disabling command must be fully qualified.
list of marked, disabled or selected tabs' IDsthese option can be used to save and restore the list of marked, disabled or selected tabs:
set savedmarks [NS::bar cget -mark]
NS::bar configure -mark $savedmarks
NS::bar draw
fore- and background of tabs marked with markTab
which is controlled by the application e.g. to mark the modified tabs
by default:
-fgmark #800080 -bgmark ""
i.e. only foreground will be marked;
this option depends on a color scheme in use (see e.g. test2_pave.tcl of apave package)
-imagemarkimage for tabs marked with markTab
which is controlled by the application e.g. to mark the modified tabs;
this option overcomes -fgmark, -bgmark options (see above)
by default:
-imagemark ""
i.e. -fgmark, -bgmark options are active
attributes for selection:
  • if both set, fgsel and bgsel mean colors for selection
  • if bgsel == {}, fgsel != {} means a widget for selection
  • if fgsel == {}, then 'selection' is underlined
by default:
-fgsel "." -bgsel ""
i.e. the colors are taken from [ttk::style configure .]
examples: 1) -fgsel white -bgsel red 2) -fgsel .main 3) -fgsel ""
this option depends on a color scheme in use (see e.g. test2_pave.tcl of apave package)
-font font attributes of tab labelsby default:
-font [font configure TkDefaultFont]
-relief relief of tabsby default:
-relief groove
padding of tabby default:
-padx 1 -pady 1
-bdborder of tab (0 or 1)by default:
-bd 0
-separatordraws a separator under a bar of tabsby default:
-separator 1
-lablen maximum of tab label's length;
0means no limit
by default:
-lablen 0
this option sets also the tips for shortened labels
-dotip setting -dotip yes forces tips of scrolling arrowsby default:
-dotip no
  • maximum of tips for scrolling arrows
  • maximum of lines in menu "List"
0means "no limit for both"; -1means "no tips" and "no limit for lines"
by default:
-tiplen 0
when you put 30-40 or more tabs into a bar, this option would help;
however, for more than 50 tabs bartabs is not an adequate widget; try treeview / tablelist instead
see also -lowlist option below
-comlist a command to be called at choosing from the tab list of popup menuthe command can include wildcards:
  %i is an index of chosen tab
  %t is a -tip option value of chosen tab
for example: BAR configure -fgmark $fgmark -comlist {::alited::bar::SelFile "%t"}
Note: The tab list can be also shown by a user code, using popList method.
-sortlist -sortlist yes means the tab list shown by bartabs
will be sorted
-sortlist no
Note: The tab list can be also shown by a user code, using popList method.
This method has an argument to have the list sorted.
-lowlist -lowlist no / yes sets normal / low height of the tab lists of popup menu
-lowlist number sets a size of tab list font (if number is greater than 1)
by default:
-lowlist no
the -lowlist yes option makes sense for the themes with large font size
see also -tiplen option above
-lifo if -lifo yes is set and the selected tab is not visible
(i.e. selected from the popup menu) it is placed in 0th position
by default:
-lifo no

The NS::bar configure and NS::bar cget methods are used to set and get the bar options. For example, you can run

lassign [NS::bar cget -tleft -tright] tleft tright
NS::bar configure -tleft $tleft -tright $tright
NS::bar clear
NS::bar draw
to show your tabs within the saved range.

There are also two analogue methods NS::bar TID configure and NS::bar TID cget to set and get the tab options however few they are:

  • -text sets a tab label's text
  • -tip sets a tab label's tip (overcoming -lablen option)

That said, these methods may be useful to store and fetch your own options, i.e. any data related to your bars and tabs. Sort of little data bases.

For some examples of using bartabs options, please refer to test.tcl of or test2_pave.tcl of apave package.

Tips and hints

How to make the bartabs be resizable?

At calling bartabs, the calculation of bar width may present a problem. Though, if a bar is created from scratch it may contain a "noname" tab or no tabs at all which would hardly set a problem.

But when the bartabs is called with a batch of tabs e.g. fetched from a previous session, it should be supplied with a maximum of bar width to place the tabs. In general, only a part of tabs can be placed on a bar widget, as its width is usually restricted by another widget, mostly by a contents editor (text, layout, picture, map etc.).

The after idle (or after 10) allows to call bartabs at the moment when the restricting widget has been highly likely updated, so that bartabs can get its geometry and calculate a room to hold the tabs. Having done this calculation, bartabs will place on the bar as many tabs as its room allows. Also, the bartabs would be able to recalculate its width at resizing the restricting widget.

The described above -wbase and -wproc options allow to calculate the bar width dynamically:
  • -wbase sets a path to a widget whose width should restrict the bar width; in most cases its width is equal to the width of the bar
  • -wproc sets a procedure that calculates the bar width in more peculiar surroundings

There are few methods to show a bar. What's the best?

The typical use cases are as follows:

1) Some tabs were renamed. Then the best method is show applied to any of renamed tabs:

NS::tab1 configure -text "Text of tab1"
NS::tab2 configure -text "Text of tab2"
NS::tab1 show yes  ;# "yes" argument means a forced updating
or the "mild" updating:
NS::bar clear
NS::bar draw

2) A range of visible tabs should be changed. Then the best way is clear and draw:

NS::bar configure -tleft $tleft -tright $tright
NS::bar clear
NS::bar draw

3) An important bar option (like -static, -hidearrows) was changed. Then the best way is the "hard" updating with update method:

NS::bar configure -static yes
NS::bar update

4) Two special methods allow you to scroll tabs to the left/right. The appropriate key bindings may launch them:

NS::bar scrollLeft
NS::bar scrollRight
Probably, this programmable scrolling makes only sense with -scrollsel yes option.

When to use -static, -hidearrows, -expand options?

These options aren't compatible fine in all cases, so use them carefully.

Below is the list of possible use cases.

nononodefault, standard usage
good when the -wbar widget has -fill x option
nonoyesas previous, except for tabs expanded
not good when tabs are few
yesyesnostatic bar like ttk::notebook
good only when all tabs are visible (scrolling arrows hidden)
yesyesyesstatic bar like ttk::notebook, just with expanded tabs
good only when all tabs are visible (scrolling arrows hidden)
yesnoyesstatic bar like ttk::notebook with scrolling arrows
good for a long list of tabs (options, preferences etc.)
noyesyeschangeable bar, the tabs expanded
good only when all tabs are visible (scrolling arrows hidden)
not good at scrolling because it's possible to click on a tab instead of a scrolling arrow
noyesnochangeable bar, the tabs not expanded
good only when all tabs are visible (scrolling arrows hidden)
yes, nononoif -wbar widget hasn't -fill x option, this setting makes no sense probably
see e.g. in test.tcl of how 2nd bar behaves

Note: -static yes does not forbid you to add, remove or mark the tabs programmably. It's the user who cannot change the static bars.


bartabs stuff is available here:

Notice that bartabs is still disposed to be updated.

See also

  • TKE editor: includes tabbar.tcl, Tcl/Tk tab bar widget
    • advantages: Tk canvas with animation, clever code
    • disadvantages: a bit buggy, might have more options