Reputation: 2859
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
}
Upvotes: 0
Views: 281
Reputation: 137627
After a bit of research, I found that there's a way to do what you want with the ttk::notebook
(which is good; that handles a whole load of trickiness with size computations, etc.) and this is described on the ttk::notebook wiki page:
# Need this style
ttk::style layout Plain.TNotebook.Tab null
# Make the notebook and pages
ttk::notebook .foo -style Plain.TNotebook
.foo add [button .foo.bar -text bar]
.foo add [button .foo.baz -text baz]
# Select a page; you'll need to provide a mechanism for users to switch
.foo select .foo.bar
There are some additional complexities with borders that you might or might not want to do anything about. (Style coding is complicated, alas.)
Otherwise, grid remove
is a useful tool as it stops managing a widget (causing it to become unmapped and not focusable) without clearing the settings for how you want to manage it; when you put the widget back in, it will use what it had before. However, you'll still have problems with content widgets being different sizes so every time you change page things run the risk of resizing.
Since you're already getting into doing lots of work when you're doing that, you can probably contemplate using place
instead. The main trick there will be to bind <Configure>
on each of the pages so that you can find out what size they are (if/when that changes).
Just changing the stacking order with raise
and lower
does not work. The crucial problem is that that doesn't affect the keyboard focus cycle properly; users will be able to Tab into widgets they can't see and that's very confusing!
In answer to your question in the comments, selections are global within the Tk application by default (or global across all applications on X11; that's the way that PRIMARY
selections work). You can give a widget an independent selection by configuring it to not export the selection.
Upvotes: 1