Chapter 19. Handy Containers

Objective

Designing a good user interface requires logical grouping of items. It also requires that parts not often used is hidden away, but still easy to access. By making use of Gtk2::NoteBook and Gtk2::Expander, you can accomplish this goal.

This lesson will expose the reader to the capabilities of the Gtk2::NoteBook and Gtk2::Expander classes, which are both Gtk2::Container subclasses.

19.1. Descriptions

Terse descriptions of the classes this lesson is about.

19.1.1. Practical sample

This sample will add a Gtk2::NoteBook as the child of a Gtk2::Expander. We will manipulate various properties of the Gtk2::NoteBook to show their influence.

Table 19-1. Gtk2 object classes used

Gtk2 Class Name
Gtk2::Window
Gtk2::VBox
Gtk2::Expander
Gtk2::NoteBook
Gtk2::HSeparator
Gtk2::Label
Gtk2::Table
Gtk2::ComboBox
Gtk2::CheckButton

19.1.1.1. The Screenshot.

Figure 19-1. Handy containers

19.1.1.2. The Code.

The program can be found here: 'gtk2_expander.pl'

  1 #! /usr/bin/perl -w
  2 
  3 use strict;
  4 use Gtk2 '-init';
  5 use Glib qw/TRUE FALSE/; 
  6 
  7 #standard window creation, placement, and signal connecting
  8 my $window = Gtk2::Window->new('toplevel');
  9 $window->signal_connect('delete_event' => sub { Gtk2->main_quit; });
 10 $window->set_border_width(5);
 11 $window->set_position('center_always');
 12 
 13 #add and show the vbox
 14 $window->add(&ret_vbox);
 15 $window->show();
 16 
 17 #our main event-loop
 18 Gtk2->main();
 19 
 20 sub ret_vbox {
 21 
 22 my $vbox = Gtk2::VBox->new(FALSE,5);
 23 
 24 	#create a instance of the Gtk2::Expander class
 25 	my $expander = Gtk2::Expander->new_with_mnemonic('Expand Me');
 26 	
 27 	#create the child that we want to add to it.
 28 	#----------------------------------------------
 29 	#NOTE: If we want to resize
 30 	#the widget containing the Gtk2::Expander Widget 
 31 	#to the size that it was BEFORE the expansion
 32 	#we have to "add / remove" the child on each "expanded / closed" cycle
 33 	#If we just add it, it will be shown and hidden, but we will not be able
 34 	#to shrink the parent containing the Gtk2::Expander instance when hidden
 35 	#-------------------------------------------------
 36 	my $nb = &ret_notebook;
 37 	$nb->show_all;
 38 	
 39 	$expander->signal_connect_after('activate' => sub {
 40 			
 41 		if($expander->get_expanded){
 42 			$expander->set_label('Close Me');
 43 			$expander->add($nb);
 44 				
 45 		}else{ 
 46 			$expander->set_label('Expand Me');
 47 			$expander->remove($nb);
 48 			$window->resize(4,4);	
 49 		}
 50 		return FALSE;
 51 	}); 
 52 	
 53 	
 54 $vbox->pack_start($expander,FALSE,FALSE,0);	
 55 
 56 $vbox->show_all();
 57 return $vbox;
 58 }
 59 
 60 sub ret_notebook {
 61 
 62 #this will create a vbox containing a Notebook 
 63 #and some widgets to manipulate the Notebook's
 64 #properties.
 65 
 66 my $vbox_nb = Gtk2::VBox->new(FALSE,5);
 67 $vbox_nb->set_size_request (500, 300);
 68 
 69 	my $nb = Gtk2::Notebook->new;
 70 	
 71 	#pre-set some properties
 72 	$nb->set_scrollable (TRUE); 
 73 	$nb->popup_enable;
 74 		for (0..10) { 
 75 	
 76 		my $child = Gtk2::Frame->new("Frame of Tab $_");
 77 	
 78 	#________
 79 		#The tab's label can be a widget, and does not
 80 		#need to be a label, here we create a hbox containing
 81 		#a label and close button
 82 		my $hbox = Gtk2::HBox->new(FALSE,0);
 83 		$hbox->pack_start(Gtk2::Label->new("Tab $_"),FALSE,FALSE,0);
 84 	
 85 			my $btn = Gtk2::Button->new('');
 86 			$btn->set_image(Gtk2::Image->new_from_stock('gtk-close','menu'));
 87 			$btn->signal_connect('clicked' => sub {
 88 			
 89 				$nb->remove_page ($nb->page_num($child)); 
 90 			});
 91 			
 92 		$hbox->pack_end($btn,FALSE,FALSE,0);
 93 		$hbox->show_all;
 94 	#________
 95 		
 96 		$nb->append_page ($child,$hbox); 
 97  
 98 
 99 		} 
100 	
101 $vbox_nb->pack_start($nb,TRUE,TRUE,0);
102 $vbox_nb->pack_start(Gtk2::HSeparator->new(),FALSE,FALSE,5);
103 
104 #call the sub that will create the table containing 
105 #controls to manipulate the notebook		
106 $vbox_nb->pack_end(&nb_controls($nb),FALSE,FALSE,0);	
107 	
108 	return $vbox_nb;
109 }
110 
111 
112 sub nb_controls {
113 
114 	my ($nb) = @_;
115 	
116 	my $table = Gtk2::Table->new(3,2,TRUE);
117 	
118 	$table->attach_defaults(Gtk2::Label->new('Tab position:'),0,1,0,1);
119 	
120 		my $cb_position = Gtk2::ComboBox->new_text;
121 		
122 		foreach my $val (qw/left right top bottom/){
123 		
124 			$cb_position->append_text($val);
125 		}
126 		
127 		$cb_position->signal_connect("changed" => sub {
128 		
129 			$nb->set_tab_pos($cb_position->get_active_text);
130 		
131 		});
132 		
133 		$cb_position->set_active(2);
134 
135 	$table->attach_defaults($cb_position,1,2,0,1);
136 	
137 		my $show_tabs = Gtk2::CheckButton->new("Show Tabs");
138 		$show_tabs->set_active(TRUE);
139 		$show_tabs->signal_connect('toggled' =>sub {
140 		
141 			$nb->set_show_tabs($show_tabs->get_active);
142 		
143 		});
144 	$table->attach_defaults($show_tabs,0,1,1,2);
145 	
146 		my $scrollable = Gtk2::CheckButton->new("Scrollable");
147 		$scrollable->set_active(TRUE);
148 		$scrollable->signal_connect('toggled' =>sub {
149 		
150 			$nb->set_scrollable($scrollable->get_active);
151 		
152 		});
153 	$table->attach_defaults($scrollable,1,2,1,2);
154 	
155 		my $popup = Gtk2::CheckButton->new("Popup Menu (right click on tab)");
156 		$popup->set_active(TRUE);
157 		$popup->signal_connect('toggled' =>sub {
158 		
159 			($popup->get_active)&&($nb->popup_enable);
160 			($popup->get_active)||($nb->popup_disable);
161 		});
162 	$table->attach_defaults($popup,0,1,2,3);
163 	
164 	return $table;
165 
166 }

19.1.1.3. Points of interest.

19.1.1.3.1. Gtk2::Expander

  • When you connect to the 'activate' signal, using 'signal_connect', and you query the expanded state of Gtk2::Expander, the state reported will differ from the actual state of the widget.

    This is because user-connected signal handlers run BEFORE the default handlers, so your callback is running BEFORE the Gtk2::Expander's state has been changed.

    To overcome this problem, use the 'signal_connect_after' method. This will run AFTER the Gtk2::Expander's state changed, reporting the right state.

  • A Gtk2::Expander shows and hides its child, but you may want to shrink the parent container containing Gtk2::Expander when the child is hidden. To do this, you actually have to 'remove' the child from the Gtk2::Expander instance each time it gets hidden, and 'add' it again when it is expanded.

    Tip

    There is a much easier way, but using it takes away the ability for the user to resize the window.

    $window->set_resizable(FALSE);
    will cause the window to always "auto shrink" around the packed widgets.

19.1.1.3.2. Gtk2::NoteBook

  • Remember that the label you specify for a Gtk2::NoteBook is just a convenience method. The Gtk2::NoteBook tab can actually contain any Gtk2::Widget. We make use of this feature, by adding a Gtk2::HBox with some contents in our sample.

  • The rest of the program is just used to set various properties of Gtk2::NoteBook. Available Gtk2::NoteBook properties, and methods to get and set them are listed on Gtk2::NoteBook's man page.