I'd like to build a tabbed display like the ttk::notebook widget, something I've done before in JavaScript and now would like to move to Tcl/Tk. I read on the Tk Docs site that the notebook tab is made for a small number of tabs of small size, and that one would need to customize that a bit anyway to get the close button on each tab and similar functionalities.
In JavaScript, the "frame" for each tab had the same geometry and was positioned absolutely at the same point within the parent container; and only one tab frame was visible at any one time. When a tab button was clicked, the visibility of the current tab was set to hidden and the new tab to visible.
To do the same in Tk, should all tabs be "gridded" in the same row and column, and the stacking order changed when a new tab button is clicked; or, since Tk retains the geometry of a removed frame, should only one tab be gridded at a time and replaced each time a tab button is clicked? Or is there a better approach?
Each tab has an editable text widget and I need to capture any changes if the user edits the text, leaves the focus in the text widget, and clicks a new tab button.
Thank you.
package require Tk
ttk::style theme use clam
ttk::style configure TScrollbar -arrowsize 20
wm geometry . "[winfo screenwidth .]x[winfo screenheight .]+0+0"
wm title . "Notebook Test"
ttk::label .main_l -text "Main Label"
grid .main_l -row 0 -column 0 -sticky ew
grid columnconfigure . 0 -weight 1
grid rowconfigure . 1 -weight 1
foreach n { 3 2 1 } {
ttk::frame .f$n -relief solid
tk::label .f$n.l -text "Tab $n"
tk::text .f$n.t -yscrollcommand ".f$n.v set" \
-background white -padx 10 -pady 10 -font { Georgia 12 } \
-wrap word -borderwidth 10 -relief flat
ttk::scrollbar .f$n.v -orient vertical -command ".f$n.t yview"
grid .f$n -row 1 -column 0 -sticky nsew
grid .f$n.l -in .f$n -row 0 -columnspan 2 -sticky ew
grid .f$n.t -in .f$n -row 1 -column 0 -sticky nsew
grid .f$n.v -in .f$n -row 1 -column 1 -sticky ns
grid rowconfigure .f$n 1 -weight 1
grid columnconfigure .f$n 0 -weight 1
set tab_foc($n) .f$n
}
focus .f1
set cur_tab 1
bind . <Alt-KeyPress-1> { navigate 1 }
bind . <Alt-KeyPress-2> { navigate 2 }
bind . <Alt-KeyPress-3> { navigate 3 }
proc navigate { n } {
global cur_tab tab_foc
set tab_foc($cur_tab) [focus]
set cur_tab $n
focus $tab_foc($n)
raise .f$n
}
I attempted a simple example using the ttk::notebook
styled to have no tabs and using the select
method of the notebook to change tabs. There is a bit of flickering when the tabs are changed, that is not present using the previous code above. Perhaps, the notebook uses grid remove
and it behaves like display:block and display:none
versus visibility:visible and visibility:hidden
in JavaScript, in that display
alters the layout and can cause flicker while visibility
doesn't change layout. I don't know for sure, of course.
package require Tk
ttk::style theme use clam
ttk::style configure TScrollbar -arrowsize 20
ttk::style layout Plain.TNotebook.Tab null
wm geometry . "[winfo screenwidth .]x[winfo screenheight .]+0+0"
wm title . "Notebook Test"
set nb [ttk::notebook .pnb -style Plain.TNotebook]
ttk::label .main_l -text "Main Label"
grid .main_l -row 0 -column 0 -sticky ew
grid columnconfigure . 0 -weight 1
grid rowconfigure . 1 -weight 1
grid $nb -row 1 -column 0 -sticky nsew
foreach n { 1 2 3 } {
set f [ ttk::frame $nb.f$n -relief solid ]
tk::label $f.l -text "Tab $n"
tk::text $f.t -yscrollcommand "$f.v set" \
-background white -padx 10 -pady 10 \
-font { Georgia 12 } -wrap word -borderwidth 10 -relief flat
ttk::scrollbar $f.v -orient vertical -command "$f.t yview"
$nb add $f -sticky nsew
grid rowconfigure $f 1 -weight 1
grid columnconfigure $f 0 -weight 1
grid $f.l -in $f -row 0 -columnspan 2 -sticky ew
grid $f.t -in $f -row 1 -column 0 -sticky nsew
grid $f.v -in $f -row 1 -column 1 -sticky ns
}
$nb select $nb.f1
bind . <Alt-KeyPress-1> { navigate 1 }
bind . <Alt-KeyPress-2> { navigate 2 }
bind . <Alt-KeyPress-3> { navigate 3 }
proc navigate { n } {
global nb
$nb select $nb.f$n
}
I also tried using place
and still experience the flickering. Perhaps, I've coded something wrong.
package require Tk
ttk::style theme use clam
ttk::style configure TScrollbar -arrowsize 20
wm geometry . "[winfo screenwidth .]x[winfo screenheight .]+0+0"
wm title . "Notebook Test"
ttk::label .main_l -text "Main Label"
place .main_l -relx 0.0 -rely 0.0 -relwidth 1.0 -height 30
foreach n { 3 2 1 } {
set f [ ttk::frame .f$n -relief solid ]
tk::label $f.l -text "Tab $n"
tk::text $f.t -yscrollcommand "$f.v set" \
-background white -padx 10 -pady 10 \
-font { Georgia 12 } -wrap word -borderwidth 10 -relief flat
ttk::scrollbar $f.v -orient vertical -command "$f.t yview"
place $f -relx 0.0 -y 31 -relwidth 1.0 -relheight 1.0 -height -30
place $f.l -in $f -relx 0.0 -rely 0.0 -relwidth 1.0 -height 30
place $f.t -in $f -relx 0.0 -rely 0.0 -y 30 -relwidth 1.0 -width -23 -relheight 1.0 -height -30
place $f.v -in $f -relx 1.0 -x -22 -rely 0.0 -y 31 -width 22 -relheight 1.0 -height -30
if { $n > 1 } { place forget $f }
}
set curTab 1
bind . <Alt-KeyPress-1> { navigate 1 }
bind . <Alt-KeyPress-2> { navigate 2 }
bind . <Alt-KeyPress-3> { navigate 3 }
proc navigate { n } {
global curTab
place .f$n -relx 0.0 -y 31 -relwidth 1.0 -relheight 1.0 -height -30
raise .f$n
place forget .f$curTab
set curTab $n
}
Using grid
alone also has the flickering, but using lower
before mapping and executing update idletasks
before removing the currently mapped tab, appears to have eliminated or significantly reduced the flickering. That is, attempting to map the tab behind the current tab, such that it is similar to hidden, update idletasks so that it is sized, and then unmap the current tab. Odd to me is that if click in an entry element and then navigate to a different tab and type some text before doing anything else, the text appears in the tab that was removed by grid remove
. Why? It is not in the focus cycle any longer but retains the keyboard focus after being removed.
grid .main_l -row 0 -column 0 -sticky ew
grid columnconfigure . 0 -weight 1
grid rowconfigure . 1 -weight 1
foreach n { 3 2 1 } {
set f [ ttk::frame .f$n -relief solid ]
tk::label $f.l -text "Tab $n"
ttk::entry .f$n.e1
ttk::entry .f$n.e2
tk::text $f.t -yscrollcommand "$f.v set" \
-background white -padx 10 -pady 10 -font { Georgia 12 } -wrap word -borderwidth 10 -relief flat
ttk::scrollbar $f.v -orient vertical -command "$f.t yview"
grid .f$n -row 1 -column 0 -sticky nsew
grid rowconfigure .f$n 1 -weight 1
grid columnconfigure .f$n 0 -weight 1
grid .f$n.l -in .f$n -row 0 -columnspan 2 -sticky ew
grid .f$n.t -in .f$n -row 1 -column 0 -sticky nsew
grid .f$n.v -in .f$n -row 1 -column 1 -sticky ns
grid .f$n.e1 -in .f$n -row 0 -column 2
grid .f$n.e2 -in .f$n -row 1 -column 2
if { $n > 1 } { grid remove $f }
}
set curTab 1
bind . <Alt-KeyPress-1> { if { $curTab != 1 } { navigate 1 } }
bind . <Alt-KeyPress-2> { if { $curTab != 2 } { navigate 2 } }
bind . <Alt-KeyPress-3> { if { $curTab != 3 } { navigate 3 } }
proc navigate { n } {
global curTab
lower .f$n
grid .f$n
update idletasks
grid remove .f$curTab
set curTab $n
}
grid remove
comnand might help. (Put all the widgets in the same grid cell.) Or maybe this is a job forplace
? (If the latter, you'll want to watch for<Configure>
events in the child widgets.) And in extremis you can make thecanvas
for sure. A ttk style on the notebook might also do the trick, and surely someone has solved this already? – Donal Fellowsraise
andlower
won't work. For sure. – Donal Fellowsgrid remove
retain the undo stack? – Gary