A Tk (or rather Ttk) widget that stacks a bunch of widgets on top of each other in a window, with some tabs at the top to allow users to select between the widgets. A common idiom in Windows's Properties dialogs.
A notebook may be used as a pages manager[L1 ] by using a style to turn off the tabs, like this (by Joe English):
ttk::style theme settings default { ttk::style layout Plain.TNotebook.Tab null } ttk::notebook $nb -style Plain.TNotebook
Alternatively, you can set it on the current style (useful on some platforms) with:
ttk::style layout Plain.TNotebook.Tab null ttk::notebook $nb -style Plain.TNotebook
pw: Neither of these methods work on Mac OS X (as of 10.6.8 and Tk 8.5.9). Both methods have some effect but the result looks mangled. I'm not sure why, but this seems to work instead:
ttk::style layout Plain.TNotebook.Tab {Notebook.tab -side left} ttk::notebook $nb -style Plain.TNotebook
tonytraductor I have a question. Using a ttk::notebook, how do I pass the currently selected tab's info to a process?
Answer: [$nb select] returns the widget name of the currently selected pane. In addition, the string "current" may be used as a tab identifier.
Flame A notebook can change its tab position like this:
ttk::style configure ENNotebook.TNotebook -tabposition en ttk::notebook .nb -style ENNotebook.TNotebook
Flame Here is some code to add tab list scrolling to a notebook. The code is not optimized, supports only ne-oriented tabs, and assumes user does not change tab states.
namespace eval SNotebook { # create scroll buttons foreach anchor {n s e w} arrow {uparrow downarrow leftarrow rightarrow} { set uanchor [string toupper $anchor] ttk::style layout ${uanchor}Button.TButton [list Button.focus -sticky nswe -children [list ${uanchor}Button.$arrow -sticky nswe]] } variable scrollpos variable hsizes variable tabheight proc _updatehsizes {nb} { if {![winfo exists $nb.escroll]} { ttk::button $nb.escroll -style EButton.TButton -command [string map [list \$nb $nb] { if {$::SNotebook::scrollpos($nb)>0} { incr ::SNotebook::scrollpos($nb) -1 ::SNotebook::_scroll_notebook $nb } }] } if {![winfo exists $nb.wscroll]} { ttk::button $nb.wscroll -style WButton.TButton -command [string map [list \$nb $nb] { if {$::SNotebook::scrollpos($nb)+1<[llength [$nb tabs]]} { incr ::SNotebook::scrollpos($nb) ::SNotebook::_scroll_notebook $nb } }] } variable hsizes variable tabheight set ntabs [$nb index end] set tabs [$nb tabs] ttk::notebook .tmp -style [$nb cget -style] set p0 [winfo reqwidth .tmp] set hsizes($nb) $p0 for {set i 0} {$i<$ntabs} {incr i} { set tab [lindex $tabs $i] if {1} { ;# theme-independent, but could have side effects eval .tmp add [ttk::frame .tmp.f$i] [::SNotebook::$nb tab $tab] -state normal update idletasks set hsizes($tab) [expr [winfo reqwidth .tmp]-$p0] set p0 [winfo reqwidth .tmp] } else { ;# there is a theme-dependable constant, which includes padding and tab margins eval ttk::label .tmp2 -style TNotebook.Tab [dict remove [::SNotebook::$nb tab $tab] -sticky] set hsizes($tab) [expr [winfo reqwidth .tmp2]+10] destroy .tmp2 } } destroy .tmp ttk::label .tmp -style TNotebook.Tab set tabheight [winfo reqheight .tmp] destroy .tmp } proc _scroll_notebook {nb} { ;# assumes tabposition is nw or ne variable scrollpos variable hsizes variable tabheight if {![info exists scrollpos($nb)]} { set scrollpos($nb) 0 } set startindex $scrollpos($nb) for {set i 0} {$i<$startindex} {incr i} { ::SNotebook::$nb hide $i } set ntabs [$nb index end] set tabs [$nb tabs] set availw [winfo width $nb] set reqw $hsizes($nb) set overflow 0 for {} {$i<$ntabs} {incr i} { set tab [lindex $tabs $i] incr reqw $hsizes($tab) ::SNotebook::$nb add $tab if {$reqw>$availw} { incr i set overflow 1 break } } for {set j $i} {$j<$ntabs} {incr j} { ::SNotebook::$nb hide [lindex $tabs $j] } set h $tabheight set eh [expr 4*$h/5] if {$startindex>0} { set ew [expr $eh*[winfo reqwidth $nb.escroll]/[winfo reqheight $nb.escroll]] place $nb.escroll -x 0 -y 0 -width $ew -height $eh } else { place forget $nb.escroll } if {$overflow} { set ew [expr $eh*[winfo reqwidth $nb.wscroll]/[winfo reqheight $nb.wscroll]] place $nb.wscroll -relx 1.0 -x -$ew -y 0 -width $ew -height $eh } else { place forget $nb.wscroll } } proc snotebook {path args} { ttk::notebook $path {*}$args _updatehsizes $path bindtags $path [linsert [bindtags $path] 1 SNotebook] bind SNotebook <Configure> {::SNotebook::_scroll_notebook %W} rename ::$path ::SNotebook::$path proc ::$path {cmd args} [string map [list \$path $path] { switch $cmd { index - configure - cget - identify - instate - select - state - tabs { ::SNotebook::$path $cmd {*}$args } default { bind SNotebook <Configure> {} set res [::SNotebook::$path $cmd {*}$args] ::SNotebook::_updatehsizes $path bind SNotebook <Configure> {::SNotebook::_scroll_notebook %W} ::SNotebook::_scroll_notebook $path return $res } } }] return $path } } ::SNotebook::snotebook .nb pack .nb -fill both -expand true set i 0 foreach theme [ttk::themes] { frame .f$i pack [button .f$i.b -text "Theme: $theme" -command "ttk::setTheme $theme"] -anchor nw .nb add .f$i -text "Tab $i" incr i } for {} {$i<20} {incr i} { .nb add [text .t$i] -text "Tab $i" }
HaO 2023-03-14: scrolutil also features an extension of ttk::notebook with scrollable tabs.
MG just started playing with the ttk::notebook (Aug 2011), and wrote a little package to allow dragging the tabs with the right mousebutton to reorder them. Only tested on Windows, and only with one theme, but it seems to work OK (though there's a little jumping when you move a switch a small tab with a larger one, which I haven't fixed yet).
Requires a version of ttk::notebook that supports $notebook identify tab $x $y - 8.5.7 doesn't, but 8.6.1 does. (It's in the docs online for 8.5, though, so was presumably added into 8.5.8 or 8.5.9.)
(Using the left mousebutton might be better, but that means making it play nice with the notebook's existing bindings, and it's long since time I went to bed, so that's a task for another day, too.)
MG I just found this again (July 2012), and thought I'd spruce it up a bit. It no longer stutters, and it now uses the left mouse button for dragging instead of the right, which is both more user-friendly and looks better, because the tab always comes to the front before you start dragging it.
namespace eval tabdrag {} bind TNotebook <Destroy> {+tabdrag::destroy %W} bind TNotebook <Button-1> {+tabdrag::click %W %x %y} bind TNotebook <ButtonRelease-1> {+tabdrag::release %W %x %y} bind TNotebook <B1-Motion> {+tabdrag::move %W %x %y} proc ::tabdrag::destroy {win} { variable winstate; array unset winstate ?,$win } proc ::tabdrag::click {win x y} { variable winstate; set what [$win identify tab $x $y] if { $what eq "" || [$win index end] <= 1} { return; } set winstate(x,$win) $x set winstate(t,$win) [lindex [$win tabs] $what] set winstate(e,$win) 0 } proc ::tabdrag::release {win x y} { variable winstate; array unset winstate ?,$win } proc ::tabdrag::move {win x y} { variable winstate; if { ![info exists winstate(x,$win)] || ![info exists winstate(t,$win)] || $winstate(t,$win) eq "" } { return; } set where [$win identify tab $x $y] if { [info exists winstate(a,$win)] } { if { $x < $winstate(a,$win) && $where < $winstate(i,$win) } { unset -nocomplain winstate(a,$win) winstate(i,$win) winstate(j,$win) } elseif { $x > $winstate(a,$win) && $where > $winstate(i,$win) } { unset -nocomplain winstate(a,$win) winstate(i,$win) winstate(j,$win) } } if { $where ne "" } { set what [lindex [$win tabs] $where] } else { set what "" } if { $what eq $winstate(t,$win) } { return; } if { $what eq "" } { # Not over a tab - check to see if we're before or after where we started if { $winstate(e,$win) } { return; } set winstate(e,$win) 1 if { $x < $winstate(x,$win) } { $win insert 0 $winstate(t,$win) } else { $win insert end $winstate(t,$win) } #unset -nocomplain winstate(j,$win) winstate(a,$win) winstate(i,$win) set winstate(x,$win) $x } else { set winstate(e,$win) 0 if { [info exists winstate(j,$win)] && $what eq $winstate(j,$win) } { if { (($x > $winstate(x,$win) && $x > $winstate(a,$win)) || ($x < $winstate(x,$win) && $x < $winstate(a,$win))) } { return;# avoid stuttering when jumping a bigger tab } } $win insert $what $winstate(t,$win) set winstate(j,$win) $what set winstate(a,$win) $x set winstate(i,$win) $where } } pack [ttk::notebook .test] foreach x {Foo Bar Baz Boing Sprocket} { .test add [frame .f$x -height 200 -width 200] -text $x }
Don on clt : I am trying to specify the background tab colors in a TNotebook, in particular the background color of the tab for the selected page
toplevel .top wm geometry .top 500x400+40+50; update .top configure -background wheat ttk::style configure TNotebook -background wheat ttk::style configure TNotebook.Tab -background plum ttk::style map TNotebook.Tab -background [list disabled plum selected green] ttk::notebook .top.nb -width 300 -height 200 .top.nb add [frame .top.nb.f1 -bg wheat] -text "First tab" .top.nb add [frame .top.nb.f2 -bg wheat] -text "Second tab" .top.nb add [frame .top.nb.f3 -bg wheat] -text "Third tab" place .top.nb -in .top -x 100 -y 100
Zipguy 2013-02-07 - You can find out my email address by clicking on Zipguy.
It seems that the colors are determined by the '-bg wheat' in the '.top.nb add' command. I tried changing the second tab to
.top.nb add [frame .top.nb.f2 -bg plum] -text "Second tab"
which did work fine. Then I changed it to
.top.nb add [frame .top.nb.f2] -text "Second tab"
which did make it grey. So then I tried
.top.nb add [frame .top.nb.f2 -class TNotebook.Tab] -text "Second tab"
Which left it grey. Dang! I thought the answer is in getting the '-class TNotebook.Tab' working correctly with the defined TNotebook.Tab which does not seem right. I tried commenting out both the 'ttk::style map', and the 'ttk::style configure TNotebook -background wheat' lines, and it still remained grey!
That means that, it's ignoring the 'ttk::style configure TNotebook.Tab -background plum' line.
TB 2016-07-14 On the Mac, the background of a notebook is darker than the one of the surrounding window/frame. However, ttk does not automatically style the widgets inside a tab with this darker background, but uses the background color of the normal ttk::frame. This will look ugly since it involves changing background colors in the widget hierarchy from light gray, to dark gray, to light gray. There seems to be no easy solution, but [L2 ] is an example with a possible workaround.
Georgios Petasis in a comp.lang.tcl posting dated Fri, 17 Jan 2014 04:11:27 gave the following as an example of how to add an icon to the tab of a ttk::notebook widget:
You can always change the layout, and use a text & an image instead of the label. Then the subcommand identify will report which element was clicked:
bind TNotebook <ButtonPress-1> {+puts [%W identify %x %y]} catch {console show} ttk::style layout TNotebook.Tab { Notebook.tab -children { Notebook.padding -side top -children { Notebook.focus -side top -children { Notebook.text -side left Notebook.image -side right } } } } pack [ttk::notebook .nb] -fill both -expand true ttk::frame .nb.f1 .nb add .nb.f1 -text {tab 1} \ -image [image create photo -width 36 -height 36]
George
MG 2014-01-17 Not sure when it changed, but support for this seems to be built-in, now:
set i [image create photo -width 36 -height 36] $i put red -to 0 0 36 36 pack [ttk::notebook .nb] -expand 1 -fill both set f [ttk::frame .nb.f1] .nb add $f -text "Foo" -image $i -compound right
bll 2017-1-8 The built-in -image cannot be used for a close button, as the subcommand identify will always report back the label name. A layout change is still necessary.
Csaba Nemethi 2021-05-27 Version 1.10 of the Scrollutil package adds the closetab style element, along with commands related to its use in the tabs of ttk::notebook and scrollutil::scrollednotebook widgets.
oldlaptop 2022-07-18 It's possible to apply this layout change only to selected notebooks by cloning the TNotebook and TNotebook.Tab styles; for example:
ttk::style layout CNotebook [ttk::style layout TNotebook] ttk::style map CNotebook {*}[ttk::style map TNotebook] ttk::style map CNotebook.Tab {*}[ttk::style map TNotebook.Tab] ttk::style layout CNotebook.Tab { Notebook.tab -children { Notebook.padding -side top -children { Notebook.focus -side top -children { Notebook.text -side left Notebook.image -side right } } } }
A worked example as a snit megawidget is available in my libcargocult on Github.
HaO 2016-07-15
font create TitleFont -size 20 ttk::style configure TNotebook.Tab -font TitleFont
HaO 2023-03-14: BWidget NoteBook may now fully replaced by ttk::notebook + scrollutil: