ttk::notebook

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.

https://www.tcl-lang.org/man/tcl/TkCmd/ttk_notebook.htm

Tips and Tricks

  Use notebook as a pages manager

A notebook may be used as a pages manager[1 ] 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

  Current selected tab info

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.


  Change tab position

Flame A notebook can change its tab position like this:

    ttk::style configure ENNotebook.TNotebook -tabposition en
    ttk::notebook .nb -style ENNotebook.TNotebook

  Tab list scrolling code

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"
    }

  dragging tab code

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
}

  Tab background color

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.

  Specifically: Tab background color on the Mac

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 [2 ] is an example with a possible workaround.

  Adding an icon to notebook tabs (i.e., close icon)

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.

 Set font of tab

HaO 2016-07-15

font create TitleFont -size 20
ttk::style configure TNotebook.Tab -font TitleFont