[Previous] [Contents] [Next]

11. CList Widget

The CList widget has replaced the List widget (which is still available).

The CList widget is a multi-column list widget that is capable of handling literally thousands of rows of information. Each column can optionally have a title, which itself is optionally active, allowing us to bind a function to its selection.

Warning: GtkCList is deprecated. It still works in FPC, but better use GtkTreeView collection of widgets. Unfortunately, GtkTreeView is not documented in the tutorial (yet?).

11.1 Creating a CList Widget

Creating a CList is quite straightforward, once you have learned about widgets in general. It provides the almost standard two ways, that is the hard way, and the easy way. But before we create it, there is one thing we should figure out beforehand: how many columns should it have?

Not all columns have to be visible and can be used to store data that is related to a certain cell in the list.

function gtk_clist_new (columns : gint) : PGtkWidget;

function gtk_clist_new_with_titles (columns : gint;
                                    titles : array of pgchar) : PGtkWidget;

The first form is very straightforward, the second might require some explanation. Each column can have a title associated with it, and this title can be a label or a button that reacts when we click on it. If we use the second form, we must provide pointers to the title texts, and the number of pointers should equal the number of columns specified. Of course we can always use the first form, and manually add titles later.

Note: The CList widget does not have its own scrollbars and should be placed within a ScrolledWindow widget if your require this functionality. This is a change from the GTK 1.0 implementation.


11.2 Modes of Operation

There are several attributes that can be used to alter the behaviour of a CList. First there is:

procedure gtk_clist_set_selection_mode (clist : PGtkCList;
                                        mode : GtkSelectionMode);

which, as the name implies, sets the selection mode of the CList. The first argument is the CList widget, and the second specifies the cell selection mode. At the time of this writing, the following modes are available to us:

Others might be added in later revisions of GTK.

We can also define what the border of the CList widget should look like. It is done through

procedure gtk_clist_set_shadow_type (clist : PGtkCList;
                                     border : GtkShadowType);

The possible values for the second argument are:

 GTK_SHADOW_NONE
 GTK_SHADOW_IN
 GTK_SHADOW_OUT
 GTK_SHADOW_ETCHED_IN
 GTK_SHADOW_ETCHED_OUT

11.3 Working with Titles

When you create a CList widget, you will also get a set of title buttons automatically. They live in the top of the CList window, and can act either as normal buttons that respond to being pressed, or they can be passive, in which case they are nothing more than a title. There are four different calls that aid us in setting the status of the title buttons:

procedure gtk_clist_column_title_active (clist : PGtkCList; column : gint);

procedure gtk_clist_column_title_passive (clist : PGtkCList; column : gint);

procedure gtk_clist_column_titles_active (clist : PGtkCList);

procedure gtk_clist_column_titles_passive (clist : PGtkCList);

An active title is one which acts as a normal button, a passive one is just a label. The first two calls above will activate/deactivate the title button above the specific column, while the last two calls activate/deactivate all title buttons in the supplied clist widget.

But of course there are those cases when we don't want them at all, and so they can be hidden and shown at will using the following two calls.

procedure gtk_clist_column_titles_show (clist : PGtkCList);

procedure gtk_clist_column_titles_hide (clist : PGtkCList);

For titles to be really useful we need a mechanism to set and change them, and this is done using

procedure gtk_clist_set_column_title (clist : PGtkCList;
                                      column : gint;
                                      title : pgchar);

Note that only the title of one column can be set at a time, so if all the titles are known from the beginning, then I really suggest using gtk_clist_new_with_titles() (as described above) to set them. It saves you coding time, and makes your program smaller. There are some cases where getting the job done the manual way is better, and that's when not all titles will be text. CList provides us with title buttons that can in fact incorporate whole widgets, for example a pixmap. It's all done through

procedure gtk_clist_set_column_widget (clist : PGtkCList;
                                       column : gint;
                                       widget : PGtkWidget);

which should require no special explanation.


11.4 Manipulating the List Itself

It is possible to change the justification for a column, and it is done through

procedure gtk_clist_set_column_justification (clist : PGtkCList;
                                              column : gint;
                                              justification : TGtkJustification);

The GtkJustification type can take the following values:

The next function is a very important one, and should be standard in the setup of all CList widgets. When the list is created, the width of the various columns are chosen to match their titles, and since this is seldom the right width we have to set it using

procedure gtk_clist_set_column_width (clist : PGtkCList;
                                      column : gint;
                                      width : gint);

Note that the width is given in pixels and not letters. The same goes for the height of the cells in the columns, but as the default value is the height of the current font this isn't as critical to the application. Still, it is done through

procedure gtk_clist_set_row_height (clist : PGtkCList; height : gint);

Again, note that the height is given in pixels.

We can also move the list around without user interaction, however, it does require that we know what we are looking for. Or in other words, we need the row and column of the item we want to scroll to.

procedure gtk_clist_moveto (clist : PGtkCList;
                            row : gint;
                            column : gint;
                            row_align : gfloat;
                            col_align : gfloat);

The row_align parameter is pretty important to understand. It's a value between 0.0 and 1.0, where 0.0 means that we should scroll the list so the row appears at the top, while if the value of row_align is 1.0, the row will appear at the bottom instead. All other values between 0.0 and 1.0 are also valid and will place the row between the top and the bottom. The last argument, col_align works in the same way, though 0.0 marks left and 1.0 marks right instead.

Depending on the application's needs, we don't have to scroll to an item that is already visible to us. So how do we know if it is visible? As usual, there is a function to find that out as well.

function gtk_clist_row_is_visible (clist : PGtkCList;
                                   row : gint) : GtkVisibility;

The return value is is one of the following:

 GTK_VISIBILITY_NONE
 GTK_VISIBILITY_PARTIAL
 GTK_VISIBILITY_FULL

Note that it will only tell us if a row is visible. Currently there is no way to determine this for a column. We can get partial information though, because if the return is GTK_VISIBILITY_PARTIAL, then some of it is hidden, but we don't know if it is the row that is being cut by the lower edge of the listbox, or if the row has columns that are outside.

We can also change both the foreground and background colors of a particular row. This is useful for marking the row selected by the user, and the two procedures used to do it are:

procedure gtk_clist_set_foreground (clist : PGtkCList;
                                    row : gint;
                                    color : pGdkColor);

procedure gtk_clist_set_background (clist : PGtkCList;
                                    row : gint;
                                    color : pGdkColor);

Please note that the colors must have been previously allocated.


11.5 Adding Rows to the List

We can add rows in three ways. They can be prepended or appended to the list using

function gint gtk_clist_prepend (clist : PGtkCList;
                                 text : array of pchar) : gint;

function gtk_clist_append (clist : PGtkCList;
                           text : array of pchar) : gint;

The return value of these two functions indicate the index of the row that was just added. We can insert a row at a given place using

function gtk_clist_insert (clist : PGtkCList;
                           row : gint;
                           text : array of pchar) : gint;

In these calls we have to provide a collection of pointers that are the texts we want to put in the columns. The number of pointers should equal the number of columns in the list. If the text argument is nil, then there will be no text in the columns of the row. This is useful, for example, if we want to add pixmaps instead (something that has to be done manually).

Also, please note that the numbering of both rows and columns start at 0.

To remove an individual row we use

procedure gtk_clist_remove (clist : PGtkCList; row : gint);

There is also a call that removes all rows in the list. This is a lot faster than calling gtk_clist_remove() once for each row, which is the only alternative.

procedure gtk_clist_clear (clist : PGtkCList);

There are also two convenience functions that should be used when a lot of changes have to be made to the list. This is to prevent the list flickering while being repeatedly updated, which may be highly annoying to the user. So instead it is a good idea to freeze the list, do the updates to it, and finally thaw it which causes the list to be updated on the screen.

procedure gtk_clist_freeze (clist : PGtkCList);

procedure gtk_clist_thaw (clist : PGtkCList);

11.6 Setting Text and Pixmaps in the Cells

A cell can contain a pixmap, text or both. To set them the following procedures are used:

procedure gtk_clist_set_text (clist : PGtkCList;
                              row, column : gint;
                              text : pgchar);

procedure gtk_clist_set_pixmap (clist : PGtkCList;
                                row, columns : gint;
                                pixmap : PGdkPixmap;
                                mask : PGdkBitmap);

procedure gtk_clist_set_pixtext (clist : PGtkCList;
                                 row, column : gint;
                                 text : pgchar;
                                 spacing : guint8;
                                 pixmap : PGdkPixmap;
                                 mask : PGdkBitmap);

It's quite straightforward. All the calls have the CList as the first argument, followed by the row and column of the cell, followed by the data to be set. The spacing argument in gtk_clist_set_pixtext() is the number of pixels between the pixmap and the beginning of the text. In all cases the data is copied into the widget.

To read back the data, we instead use

function gtk_clist_get_text (clist : PGtkCList;
                             row, column : gint;
                             text : ppgchar) : gint;

function gtk_clist_get_pixmap (clist : PGtkCList;
                               row, column : gint;
                               pixmap : PPGdkPixmap;
                               mask : PPGdkBitmap) : gint;

function gtk_clist_get_pixtext (clist : PGtkCList;
                                row, column : gint;
                                text : ppgchar;
                                spacing : pguint8;
                                pixmap : PPGdkPixmap;
                                mask : PPGdkBitmap) : gint;

The returned pointers are all pointers to the data stored within the widget, so the referenced data should not be modified or released. It isn't necessary to read it all back in case you aren't interested. Any of the pointers that are meant for return values (all except the clist) can be nil. So if we want to read back only the text from a cell that is of type pixtext, then we would do the following, assuming that clist, row and column already exist:

var
   mytext : pgchar;
begin
   ...
   gtk_clist_get_pixtext (clist, row, column, @mytext, nil, nil, nil);
   ...
end;

There is one more call that is related to what's inside a cell in the clist, and that's

function gtk_clist_get_cell_type (clist : PGtkCList;
                                  row, column : gint) : TGtkCellType;

which returns the type of data in a cell. The return value is one of

 GTK_CELL_EMPTY
 GTK_CELL_TEXT
 GTK_CELL_PIXMAP
 GTK_CELL_PIXTEXT
 GTK_CELL_WIDGET

There is also a procedure that will let us set the indentation, both vertical and horizontal, of a cell. The indentation value is of type gint, given in pixels, and can be both positive and negative.

procedure gtk_clist_set_shift (clist : PGtkCList;
                               row, column : gint;
                               vertical : gint;
                               horizontal : gint);

11.7 Storing Data Pointers

With a CList it is possible to set a data pointer for a row. This pointer will not be visible for the user, but is merely a convenience for the programmer to associate a row with a pointer to some additional data.

The procedures/functions should be fairly self-explanatory by now.

procedure gtk_clist_set_row_data (clist : PGtkCList;
                                  row : gint;
                                  data : gpointer);

procedure gtk_clist_set_row_data_full (clist : PGtkCList;
                                       row : gint;
                                       data : gpointer;
                                       destroy : TGDestroyNotify);

function gtk_clist_get_row_data (clist : PGtkCList;
                                 row : gint) : gpointer;

function gtk_clist_find_row_from_data (clist : PGtkCList; 
                                       data : gpointer) : gint;

11.8 Working with Selections

There are also procedures available that let us force the (un)selection of a row. These are:

procedure gtk_clist_select_row (clist : PGtkCList;
                                row, column : gint);

procedure gtk_clist_unselect_row (clist : PGtkCList;
                                  row, column : gint);

And also a function that will take x and y coordinates (for example, read from the mousepointer), and map that onto the list, returning the corresponding row and column.

function gtk_clist_get_selection_info (clist : PGtkCList;
                                       x, y : gint;
                                       row, column : pgint) : gint;

When we detect something of interest (it might be movement of the pointer, a click somewhere in the list) we can read the pointer coordinates and find out where in the list the pointer is. Cumbersome? Luckily, there is a simpler way...


11.9 The Signals that Bring it Together

As with all other widgets, there are a few signals that can be used. The CList widget is derived from the Container widget, and so has all the same signals, but also adds the following:

So if we want to connect a callback to "select_row", the callback would be declared like this:

procedure select_row_callback (widget : PGtkWidget;
                               row, column : gint;
                               event : pGdkEventButton;
                               data : gpointer);

The callback is connected as usual with

   gtk_signal_connect(clist, 'select_row',
                      G_CALLBACK(@select_row_callback), nil);

11.10 A CList Example

program ClistExample;

uses glib2, gdk2, gtk2;

{ User clicked the "Add List" button. }

procedure button_add_clicked (data: PGtkCList); cdecl;
const
   { Something silly to add to the list. 4 rows of 2 columns each }
   drink : array [0..3, 0..1] of pgchar = 
   (('Milk', '3 Oz'),		      
    ('Water', '6 l'),
    ('Carrots', '2'),
    ('Snakes', '55'));
var
   indx : integer;
begin
   { Here we do the actual adding of the text. It's done once for each row. }
   for indx := 0 to 3 do
      gtk_clist_append(data, @drink[indx]);
end;

{ User clicked the "Clear List" button. }

procedure button_clear_clicked (data : PGtkCList); cdecl;
begin
   { Clear the list using gtk_clist_clear. This is much faster than calling
     gtk_clist_remove once for each row. }
   gtk_clist_clear(data);
end;

{ The user clicked the "Hide/Show titles" button. }

var
   { Just a flag to remember the status. False = currently visible }
   hide_show_flag : boolean = false;
   
procedure button_hide_show_clicked (data : PGtkCList); cdecl;
begin
   if not hide_show_flag then begin
      { Hide the titles and set the flag to true }
      gtk_clist_column_titles_hide(data);
      hide_show_flag := true;
   end
   else begin
      { Show the titles and reset flag to false }
      gtk_clist_column_titles_show(data);
      hide_show_flag := false;
   end;
end;

{ If we come here, then the user has selected a row in the list. }

procedure selection_made (clist : PGtkCList;
                          row, column: gint;
                          event : PGdkEventButton;
                          data : gpointer); cdecl;
var
   text : pgchar;
begin
   { Get the text that is stored in the selected row and column which was
   clicked in.  We will receive it as a pointer in the argument text. }
   gtk_clist_get_text(clist, row, column, @text);

   { Just prints some information about the selected row }
   writeln('You selected row ', row,
           '. More specifically you clicked in column ', column,
           ', and the text in this cell is ', text);
end;

const
   titles : array [0..1] of pgchar = ('Ingredients', 'Amount');

var
   window, vbox, hbox, scrolled_window, clist : PGtkWidget;
   button_add, button_clear, button_hide_show : PGtkWidget;

begin
   gtk_init(@argc, @argv);

   window := gtk_window_new(gtk_WINDOW_TOPLEVEL);
   gtk_widget_set_size_request(PGtkWIDGET(window), 300, 150);

   gtk_window_set_title(GTK_WINDOW(window), 'GtkCList Example');
   g_signal_connect(window, 'destroy',
                    G_CALLBACK(@gtk_main_quit), nil);

   vbox := gtk_vbox_new(false, 5);
   gtk_container_set_border_width(GTK_CONTAINER(vbox), 5);
   gtk_container_add(GTK_CONTAINER(window), vbox);
   gtk_widget_show(vbox);

   { Create the ScrolledWindow to pack the CList widget into it. }
   scrolled_window := gtk_scrolled_window_new(nil, nil);
   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);

   gtk_box_pack_start(GTK_BOX(vbox), scrolled_window, true, true, 0);
   gtk_widget_show(scrolled_window);

   { Create the CList. For this example we use 2 columns }
   clist := gtk_clist_new_with_titles(2, titles);

   { When a selection is made, we want to know about it. The callback
   used is selection_made, and it's code can be found above }
   g_signal_connect(clist, 'select_row',
                    G_CALLBACK(@selection_made), nil);

   { It isn't necessary to shadow the border, but it looks nice :) }
   gtk_clist_set_shadow_type(GTK_CLIST(clist), GTK_SHADOW_OUT);

   { What however is important, is that we set the column widths as
    they will never be right otherwise. Note that the columns are
    numbered from 0 and up (to 1 in this case). }
   gtk_clist_set_column_width(GTK_CLIST(clist), 0, 150);
   gtk_clist_set_column_width(GTK_CLIST(clist), 1, 100);

   { Add the CList widget to the vertical box and show it. }
   gtk_container_add(GTK_CONTAINER(scrolled_window), clist);
   gtk_widget_show(clist);

   { Create the buttons and add them to the window. See the button
    tutorial for more examples and comments on this. }
   hbox := gtk_hbox_new(false, 0);
   gtk_box_pack_start(GTK_BOX(vbox), hbox, false, true, 0);
   gtk_widget_show(hbox);

   button_add := gtk_button_new_with_label('Add List');
   button_clear := gtk_button_new_with_label('Clear List');
   button_hide_show := gtk_button_new_with_label('Hide/Show titles');

   gtk_box_pack_start(GTK_BOX(hbox), button_add, true, true, 0);
   gtk_box_pack_start(GTK_BOX(hbox), button_clear, true, true, 0);
   gtk_box_pack_start(GTK_BOX(hbox), button_hide_show, true, true, 0);

   { Connect our callbacks to the three buttons }
   g_signal_connect_swapped(button_add, 'clicked',
			    G_CALLBACK(@button_add_clicked), clist);
   g_signal_connect_swapped(button_clear, 'clicked',
			    G_CALLBACK(@button_clear_clicked), clist);
   g_signal_connect_swapped(button_hide_show, 'clicked',
			    G_CALLBACK(@button_hide_show_clicked), clist);

   gtk_widget_show(button_add);
   gtk_widget_show(button_clear);
   gtk_widget_show(button_hide_show);

   { The interface is completely set up so we show all the widgets and
   enter the gtk_main loop }
   gtk_widget_show(window);

   gtk_main();
end.

[Previous] [Contents] [Next]