485. How to highlight specific rows or cells in treeview?

Problem

This is in response to Paul's post titled "Access Treeview cell renderers".

He would like to highlight some rows and cells based on some conditions.

In this example, if there is some error, the respective rows will be highlighted in red. The filename of the respective row will also be bold as shown below. This example also illustrates one commonly used technique: the serial number appearing on the left-most column will always stay in running sequence - even when the user deleted some rows.

How to highlight specific rows or cells in treeview?


Solution


Sample Code

1   
2   
3   
4   
5   
6   
7   
8   
9   
10   
11   
12   
14   
15   
16   
17   
18   
19   
20   
21   
22   
23   
24   
25   
26   
27   
28   
29   
30   
31   
32   
33   
34   
35   
36   
37   
38   
39   
40   
41   
42   
43   
44   
45   
46   
47   
48   
49   
50   
51   
52   
53   
54   
55   
56   
57   
58   
59   
60   
61   
62   
63   
64   
65   
66   
67   
68   
69   
70   
71   
72   
73   
74   
75   
76   
77   
78   
79   
80   
81   
82   
83   
84   
85   
86   
87   
88   
89   
90   
91   
92   
93   
94   
95   
96   
98   
99   
100   
101   
102   
103   
104   
108   
109   
110   
111   
112   
113   
114   
115   
116   
117   
118   
119   
120   
121   
122   
123   
124   
125   
126   
127   
128   
129   
130   
131   
132   
133   
134   
135   
136   
137   
138   
139   
141   
142   
143   
144   
145   
146   
147   
148   
149   
152   
153   
156   
157   
158   
159   
160   
161   
162   
163   
164   
165   
166   
167   
168   
169   
170   
171   
173   
174   
175   
176   
177   
178   
179   
180   
<?php
$window = new GtkWindow();
$window->set_size_request(400, 240);
$window->connect_simple('destroy', array('Gtk','main_quit'));
$window->add($vbox = new GtkVBox());

// display title
$title = new GtkLabel("Highlight specific rows or cells in treeview");
$title->modify_font(new PangoFontDescription("Times New Roman Italic 10"));
$title->modify_fg(Gtk::STATE_NORMAL, GdkColor::parse("#0000ff"));
$title->set_size_request(-1, 40);
$vbox->pack_start($title, 0, 0);

$vbox->pack_start(new GtkLabel(
    'You can also delete the rows by pressing the Delete key'), 0);

$vbox->pack_start($hbox = new GtkHBox(), 0);
$hbox->pack_start($button = new GtkButton('Delete Selected Rows'), 0);
$button->connect('clicked', 'on_process_button');

// the 2D table
$data = array(
array('path1', '', 1), // note 1
array('path2', '', 2),
array('playtv.wmv', '10.6MB', 0),
array('filler.wmv', '528.3KB', 0),
array('newhavernport1250.wmv', '203.1MB', 0));

$status_code = array(
    '0' => 'queued',
    '1' => 'error: file not found',
    '2' => 'error: server not found',
);

$view = display_table($vbox, $data);
$view->connect('key-press-event', 'on_keypress');

$window->show_all();
Gtk::main();

function display_table($vbox, $data) {

    // Set up a scroll window
    $scrolled_win = new GtkScrolledWindow();
    $scrolled_win->set_policy( Gtk::POLICY_AUTOMATIC,
        Gtk::POLICY_AUTOMATIC);
    $vbox->pack_start($scrolled_win);

    // Creates the list store
    if (defined("GObject::TYPE_STRING")) {
        $model = new GtkListStore(GObject::TYPE_LONG, GObject::TYPE_STRING,
            GObject::TYPE_STRING, GObject::TYPE_LONG); // note 1
    } else {
        $model = new GtkListStore(Gtk::TYPE_LONG, Gtk::TYPE_STRING,
            Gtk::TYPE_STRING, Gtk::TYPE_LONG); // note 1
    }
    $field_header = array('pos', 'filename', 'size', 'status');
    $field_justification = array(0.5, 0.0, 1.0, 0.0);

    // Creates the view to display the list store
    $view = new GtkTreeView($model);
    $scrolled_win->add($view);

    // Creates the columns
    for ($col=0; $col<count($field_header); ++$col) {
        $cell_renderer = new GtkCellRendererText();
        $cell_renderer->set_property("xalign", $field_justification[$col]);
        $column = new GtkTreeViewColumn($field_header[$col],
            $cell_renderer, 'text', $col);
        $column->set_alignment($field_justification[$col]);
        if ($col>0) $column->set_sort_column_id($col);

        // set the header font and color
        $label = new GtkLabel($field_header[$col]);
        $label->modify_font(new PangoFontDescription("Arial Bold"));
        $label->modify_fg(Gtk::STATE_NORMAL, GdkColor::parse("#0000FF"));
        $column->set_widget($label);
        $label->show();

        // setup self-defined function to display alternate row color
        $column->set_cell_data_func($cell_renderer, "format_col", $col); // note 2
        $view->append_column($column);
    }

    // pupulates the data
    for ($row=0; $row<count($data); ++$row) {
        $values = array($row+1);
        for ($col=0; $col<count($data[$row]); ++$col) {
            $values[] = $data[$row][$col];
        }
        $model->append($values);
    }

    $selection = $view->get_selection();
    $selection->set_mode(Gtk::SELECTION_MULTIPLE);
    return $view;
}

// self-defined function to format the price column
function format_col($column, $cell, $model, $iter, $col_num) {
    $path = $model->get_path($iter); // get the current path
    $row_num = $path[0]; // get the row number
    $status = $model->get_value($iter, 3);
    if ($col_num==3) {
        global $status_code;
        $cell->set_property('text', $status_code[$status]); // note 3
    }

    $row_color = ($row_num%2==1) ? '#dddddd' : '#ffffff';
    $cell->set_property('cell-background', $row_color); // note 4

    $val = $model->get_value($iter, $col_num);
    if ($col_num==1 && $status>0) {
        $cell->set_property('markup', "<span><b>$val</b></span>"); // note 5
    }

    if ($col_num>0 && $status>0) {
        $cell->set_property('foreground', '#FF1A00'); // note 6
    } else {
        $cell->set_property('foreground', '#000000'); // note 7
    }
}

function delete_rows() { // note 8
    global $view;
    $model = $view->get_model();
    $selection = $view->get_selection();
    list($model, $selected_rows) =
        $selection->get_selected_rows();
    if (count($selected_rows)==0) return;

    $i = 0;
    $rows_to_remove = array();
    foreach($selected_rows as $path) {
        $iter = $model->get_iter($path);
        $desc = $model->get_value($iter, 1);
        $qty = $model->get_value($iter, 2);
        $price = $model->get_value($iter, 3);
        print "Selection $i: $desc: $qty ($price)\n";
        ++$i;
        $rows_to_remove[] = $path[0];
    }

    for ($i=count($rows_to_remove)-1; $i>=0; --$i) {
        print "remove row: $rows_to_remove[$i]\n";
        unset($model[$rows_to_remove[$i]]);
    }

    $n = $model->iter_n_children(NULL);
    for ($i=0; $i<$n; ++$i) {
        $iter = $model->get_iter($i);
        $model->set($iter, 0, $i+1);  // note 9
    }
}

function on_process_button($button) {
    delete_rows();
}

function on_keypress($widget, $event) {
    if ($event->keyval==Gdk::KEY_Delete) {
        delete_rows();
        return true;
    } else {
        return false;
    }
}

?>

Output

As shown above.

 

Explanation

We use the code from How to delete multiple rows in GtkTreeView using GtkListStore - Part 2 - by pressing delete key? as the base.

What's new here:

  1. We create a model of 4 columns - the first to store the serial number, the second is the filename, the third is the file size, and the last to hold the status. Since the serial number is automatically generated, the data array contains only 3 columns.
  2. Set up the custom cell display function.
  3. Display the corresponding description of the status code.
  4. Sets the alternate row color.
  5. Bold the filename if there is an error.
  6. Show the row in red if there is an error.
  7. Otherwise show the row in black.
  8. Delete the row if the user presses the Del key.
  9. Regenerate the serial number.

Related Links

Add comment


Security code
Refresh