If you have a complex user interface, you'll need to find ways to organize it that don't overwhelm your users. There are several different approaches to doing this. Both general-purpose and platform-specific human interface guidelines are good resources when designing your user interface.
When we talk about complexity in this chapter, we don't mean the underlying technical complexity of how the program is implemented. Instead, we mean how it's presented to users. A user interface can be pulled together from many different modules, built from hundreds of widgets combined in a deeply nested hierarchy, but that doesn't mean users need to perceive it as complex.
One benefit of using multiple windows in an application can be to simplify the user interface.
Done well, it can require users to focus only on the contents of one window at a time to complete a task.
Forcing them to focus on or switch between several windows can also have the opposite effect.
Similarly, showing only the widgets relevant to the current task (i.e., via grid
) can
help simplify the user interface.
If you do need to display a large number of widgets onscreen at the same time, think about
how to organize them visually. We've seen how grid
makes it easy to align
widgets with each other. White space is another useful aid. Place related widgets close
to each other (possibly with an explanatory label immediately above) and separate them from other
widgets by white space. This helps users organize the user interface in their own minds.
The recommended amount of white space around different widgets, between groups of widgets, around borders, etc., is highly platform-specific. While you can do an adequate job without worrying about exact pixel numbers, you'll need to tune this for each platform if you want a highly polished user interface.
Separator widgets.
A second approach to grouping widgets in one display is to place a thin horizontal or vertical rule between groups of widgets; often, this can be more space-efficient than using white space, which may be relevant for a tight display. Tk provides a simple separator widget for this purpose.
Separators are created using the ttk.Separator
class:
s = ttk.Separator(parent, orient=HORIZONTAL)
Separators are created using the ttk::separator
command:
ttk::separator .s -orient horizontal
Separators are created using the Tk::Tile::Separator
class:
s = Tk::Tile::Separator.new(parent) { orient 'horizontal' }
new_ttk__separator
method, a.k.a. Tkx::ttk__separator
:
$sep = $parent->new_ttk__separator(-orient => 'horizontal');
The orient
option may be specified as either horizontal
or vertical
.
Labelframe widgets.
A labelframe widget, also commonly known as a group box, provides another way to group
related components. It acts like a normal ttk::frame
, in that it contains other widgets that you
grid
inside it. However, it is visually set off from the rest of the user interface.
You can optionally provide a text label to be displayed outside the labelframe.
Labelframes are created using the ttk.Labelframe
class:
lf = ttk.Labelframe(parent, text='Label')
Labelframes are created using the ttk::labelframe
command:
ttk::labelframe .lf -text "Label"
Labelframes are created using the Tk::Tile::Labelframe
class:
lf = Tk::Tile::Labelframe.new(parent) { text 'Label' }
new_ttk__labelframe
method, a.k.a. Tkx::ttk__labelframe
:
$lf = $parent->new_ttk__labelframe(-text => "Label");
Panedwindow widgets (shown here managing several labelframes).
A panedwindow widget lets you stack two or more resizable widgets above and below each other (or to the left and right). Users can adjust their relative heights (or widths) by dragging a sash located between the panes. Typically the widgets you're adding to a panedwindow will be frames containing many other widgets
ttk.Panedwindow
class:
p = ttk.Panedwindow(parent, orient=VERTICAL)
# two panes, each of which would get widgets gridded into it:
f1 = ttk.Labelframe(p, text='Pane1', width=100, height=100)
f2 = ttk.Labelframe(p, text='Pane2', width=100, height=100)
p.add(f1)
p.add(f2)
Panedwindows are created using the ttk::panedwindow
command:
ttk::panedwindow .p -orient vertical
# two panes, each of which would get widgets gridded into it:
ttk::labelframe .p.f1 -text Pane1 -width 100 -height 100
ttk::labelframe .p.f2 -text Pane2 -width 100 -height 100
.p add .p.f1
.p add .p.f2
Panedwindows are created using the Tk::Tile::Paned
class:
p = Tk::Tile::Paned.new(parent) { orient 'vertical' }
# two panes, each of which would get widgets gridded into it:
f1 = Tk::Tile::Labelframe.new(p) {text 'Pane1'; width 100; height 100;}
f2 = Tk::Tile::Labelframe.new(p) {text 'Pane2'; width 100; height 100;}
p.add f1, nil
p.add f2, nil
The extra nil
parameter to add
can be replaced with a hash of
pane-specific options, which usually aren't needed.
new_ttk__panedwindow
method, a.k.a. Tkx::ttk__panedwindow
:
$p = $mw->new_ttk__panedwindow(-orient => 'vertical');
# two panes, each of which would get widgets gridded into it:
$f1 = $p->new_ttk__labelframe(-text => "Panel", -width => 100, -height => 100);
$f2 = $p->new_ttk__labelframe(-text => "Pane2", -width => 100, -height => 100);
$p->add($f1);
$p->add($f2);
A panedwindow is either vertical
(its panes are stacked vertically on top of each other)
or horizontal
. Importantly, each pane you add to the panedwindow must be a
direct child of the panedwindow itself.
Calling the add
method adds a new pane at the end of the list of panes.
The insert
method allows you to add a new pane before the last pane; pass the position (0..n-1) as the first parameter.
If the pane is already managed by the panedwindow, it will be moved
to the new position. You can use the forget
method to remove a pane from the panedwindow, passing it either the window to remove or its position as a parameter.
There are a number of other options available to fine-tune the display. You can assign relative weights to each pane so that if the overall panedwindow resizes, certain panes will be allocated more space than others. As well, you can adjust the position of each sash between items in the panedwindow. See the command reference for details.
Notebook widgets.
A notebook widget uses the metaphor of a tabbed notebook to let users switch between one of several pages using an index tab. Unlike with paned windows, users only see a single page (akin to a pane) at a time.
Notebooks are created using the ttk.Notebook
class:
n = ttk.Notebook(parent)
f1 = ttk.Frame(n) # first page, which would get widgets gridded into it
f2 = ttk.Frame(n) # second page
n.add(f1, text='One')
n.add(f2, text='Two')
Notebooks are created using the ttk::notebook
command:
ttk::notebook .n
ttk::frame .n.f1; # first page, which would get widgets gridded into it
ttk::frame .n.f2; # second page
.n add .n.f1 -text "One"
.n add .n.f2 -text "Two"
Notebooks are created using the Tk::Tile::Notebook
class:
n = Tk::Tile::Notebook.new(parent)
f1 = Tk::Tile::Frame.new(n); # first page, which would get widgets gridded into it
f2 = Tk::Tile::Frame.new(n); # second page
n.add f1, :text => 'One'
n.add f2, :text => 'Two'
new_ttk__notebook
method, a.k.a. Tkx::ttk__notebook
:
$n = $mw->new_ttk__notebook;
$f1 = $n->new_ttk__frame; # first page, which would get widgets gridded into it
$f2 = $n->new_ttk__frame; # second page
$n->add($f1, -text => "One");
$n->add($f2, -text => "Two");
The operations on tabbed notebooks are similar to those on panedwindows. Each page is typically
a frame and again must be a direct child (subwindow) of the notebook itself. Add a new page and its associated tab
after the last tab with the add
method.
The text
tab option sets the label on the tab; also useful is the state
tab option, which can have the value normal
, disabled
(not selectable), or
hidden
.
To insert a tab at somewhere other than the end of the list, use the
insert
method, passing a position (0..n-1) as the first parameter. To remove a given tab, use the forget
method,
passing it either the position (0..n-1) or the tab's subwindow. You can retrieve the list of all
subwindows contained in the notebook via the tabs
method.
To retrieve the currently selected subwindow, call the select
method. Change the selected tab by
passing either the tab's position or the subwindow itself as a parameter to select
.
print(n.select())
n.select(f2)
puts [.n select]
.n select .n.f2
print n.selected()
n.select f2
Ruby uses selected
rather than select
to return the selected tab.
print($n->select());
$n->select($f2);
The notebook's tab
method allows you to examine or change options for individual tabs. Again, pass it either the
tab's subwindow or its position as the first parameter.
print(n.tab(f1)) # a dictionary of all the tab's options
print(n.tab(f1)['text']) # value of the tab's 'text' option
print(n.tab(f1, 'text')) # another way to get tab's 'text' option
n.tab(f1, text="New Title") # change the tab's 'text' option
puts [.n tab .n.f1] # all the tab's options
puts [.n tab .n.f1 -text] # get the value of the tab's 'text' option
.n tab .n.f1 -text "New Title" # change the tab's 'text' option
print n.itemcget(f1, :text) # get the value of the tab's text option
n.itemconfigure(f1, :text => 'New Title') # change the tab's 'text' option
Ruby uses itemcget
and itemconfigure
instead of tab
.
print($n->tab($f1, -text)); # get the value of the tab's text option
$n->tab($f2, -text => "New Title"); # change the tab's 'text' option
Notebook widgets generate a <<NotebookTabChanged>>
virtual event whenever a new tab is selected.
Again, there are a variety of less frequently used options and commands detailed in the command reference.