Como refrescar las filas de un TreeView en gtkmm
Cuando estamos trabajando con el TreeView de las gtkmm y necesitamos actualizar varias filas de manera simultanea desde diferentes hilos nos encontramos con que las dichosas filas no se actualizan de la manera correcta. Es más se comportan de forma totalmente incoherente.
A los programadores que vengan de Windows, como yo, es posible que en un principio les resulte extraño el comportamiento del TreeView. En realidad es bastante extraño, ya que en Windows, no hace falta hacer ningún tipo de gestión especial, como mucho llamar a Invalidate o a Redraw, pero por desgracia el Gtk TreeView, no tiene implementados este tipo de métodos.
Cuando queremos actualizar la vista del TreeView desde un hilo, difrente al hilo principal, debemos indicarselo al sistema explicitamente. Ya que si no lo hacemos, el sistema no se enterará de los cambios que ha realizado nuestro hilo, y por lo tanto no los reflejerá en pantalla.
Para mostrar una posible forma de actualizar el TreeView, desde diferentes threads, he hecho un pequeño programa basado en uno de los ejemplos que hay en la página oficial de gtkmm. El cuerpo del programa sería el siguiente:
file: main.cpp
#include <gtkmm /main.h>
#include <glibmm .h>
#include “listprogress.h”
#include
int main(int argc, char **argv)
{
Glib::thread_init();
gdk_threads_init();Gtk::Main kit(argc, argv);
ListProgress window;gdk_threads_enter();
Gtk::Main::run(window);
gdk_threads_leave();
return 0;
}
En el main, debemos llamar a las funciones Glib::thread_init() y gdk_threads_init(), la primera, nos habilita el funcionamiento de los threads dentro de nuestra aplicación, y la segunda está relacionada con el manejo de pantalla por parte de los diferentes hilos.
Después llamamos a Gtk::Main kit(argc, argv) que inicializa las gtk y construimos un nuevo objeto ListProgress window, el cuál está definido en los archivos posteriores.
Por último sólo nos queda pasarle el control a nuestro nuevo objeto Gtk::Main::run(window), pero como sabemos que el contenido del TreeView va a ser modificado desde diferentes threads, tendremos que incluir la llamada entre estás funciones que nos indican que vamos a manejar el uso de la pantalla; gdk_threads_enter() y gdk_threads_leave().
A continuación mostraré, la definición y declaración del objeto ListProgress.
file: listprogress.h
#ifndef LISTPROGRESS_H
#define LISTPROGRESS_H
#include
#include “threadprogress.h”
class ModelColumns : public Gtk::TreeModel::ColumnRecord
{
public:
ModelColumns()
{
add(m_col_id); add(m_col_name); add(m_col_number); add(m_col_percentage);
}
Gtk::TreeModelColumnm_col_id;
Gtk::TreeModelColumnm_col_name;
Gtk::TreeModelColumnm_col_number;
Gtk::TreeModelColumnm_col_percentage;
};
class ThreadProgress;
class ListProgress: public Gtk::Window
{
public:
ListProgress();
~ListProgress();protected:
virtual void on_button_quit();
protected:
ThreadProgress* mTrhead1;
ThreadProgress* mTrhead2;
ThreadProgress* mTrhead3;
Gtk::VBox m_VBox;
Gtk::ScrolledWindow m_ScrolledWindow;
Gtk::TreeView m_TreeView;
Glib::RefPtrm_refTreeModel;
Gtk::HButtonBox m_ButtonBox;
Gtk::Button m_Button_Quit;
public:
ModelColumns m_Columns;
};
#endif
file: listprogress.cpp
#include “listprogress.h”
ListProgress::ListProgress() : m_Button_Quit(”Salir”)
{
set_title(”Gtk::TreeView (ListStore) Ejemplo”);
set_border_width(5);
set_default_size(400, 200);add(m_VBox);
m_ScrolledWindow.add(m_TreeView);
m_ScrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);m_VBox.pack_start(m_ScrolledWindow);
m_VBox.pack_start(m_ButtonBox, Gtk::PACK_SHRINK);m_ButtonBox.pack_start(m_Button_Quit, Gtk::PACK_SHRINK);
m_ButtonBox.set_border_width(5);
m_ButtonBox.set_layout(Gtk::BUTTONBOX_END);
m_Button_Quit.signal_clicked().connect( sigc::mem_fun(*this, &ListProgress::on_button_quit) );m_refTreeModel = Gtk::ListStore::create(m_Columns);
m_TreeView.set_model(m_refTreeModel);Gtk::TreeModel::Row row = *(m_refTreeModel->append());
row[m_Columns.m_col_id] = 1;
row[m_Columns.m_col_name] = “Billy Bob”;
row[m_Columns.m_col_number] = 10;
row[m_Columns.m_col_percentage] = 0;
mTrhead1 = new ThreadProgress(&row, &m_Columns, 20000);
mTrhead1->Start();row = *(m_refTreeModel->append());
row[m_Columns.m_col_id] = 2;
row[m_Columns.m_col_name] = “Joey Jojo”;
row[m_Columns.m_col_number] = 20;
row[m_Columns.m_col_number] = 0;
mTrhead2 = new ThreadProgress(&row, &m_Columns, 40000);
mTrhead2->Start();row = *(m_refTreeModel->append());
row[m_Columns.m_col_id] = 3;
row[m_Columns.m_col_name] = “Rob McRoberts”;
row[m_Columns.m_col_number] = 30;
row[m_Columns.m_col_percentage] = 0;
mTrhead3 = new ThreadProgress(&row, &m_Columns, 60000);
mTrhead3->Start();m_TreeView.append_column(”ID”, m_Columns.m_col_id);
m_TreeView.append_column(”Nombre”, m_Columns.m_col_name);
m_TreeView.append_column_numeric(”Número formateado”, m_Columns.m_col_number, “%010d”);Gtk::CellRendererProgress* cell = new Gtk::CellRendererProgress;
int cols_count = m_TreeView.append_column(”Porcentage”, *cell);
Gtk::TreeViewColumn* pColumn = m_TreeView.get_column(cols_count - 1);
if(pColumn)
pColumn->add_attribute(cell->property_value(), m_Columns.m_col_percentage);for(guint i = 0; i < 2; i++)
{
Gtk::TreeView::Column* pColumn = m_TreeView.get_column(i);
pColumn->set_reorderable();
}show_all_children() ;
}
ListProgress::~ListProgress()
{
delete mTrhead1;
delete mTrhead2;
delete mTrhead3;
}
void ListProgress::on_button_quit()
{
hide();
}
Como podeis ver, el código es casi identico al del ejemplo de las gtkmm, sólo tiene una pequeña modificación que consiste en la incursión del objeto ThreadProgress, que describiré a continuación. Este objeto es el encargado de crear los diferentes threads, y de actualizar los datos del TreeView desde los nuevos hilos creados. Su código es el siguiente:
file: threadprogress.h
#ifndef THREADPROGRESS_H
#define THREADPROGRESS_H#include
#include
#include “listprogress.h”class ModelColumns;
class ThreadProgress{
public:
ThreadProgress(Gtk::TreeModel::Row*, ModelColumns*, int);
~ThreadProgress();
protected:
Glib::Thread* thread;
Gtk::TreeModel::Row row;
ModelColumns* column;
int delay;
void main();
public:
void Start();
};
file: threadprogress.cpp
#include “threadprogress.h”
#include
#includeThreadProgress::ThreadProgress(Gtk::TreeModel::Row* row, ModelColumns* colum, int delay)
{
this->row = *row;
this->column = colum;
this->delay = delay;
}
ThreadProgress::~ThreadProgress()
{
}
void ThreadProgress::Start()
{
thread = Glib::Thread::create(sigc::mem_fun(*this, &ThreadProgress::main), true);
if (!thread)
{
std::cout < < "Error al crear el thread" << std::endl;
exit(1);
}
}
void ThreadProgress::main()
{
for(int i = 0; i<=100; i++)
{
gdk_threads_enter();
row[column->m_col_percentage] = i;
gdk_flush();
gdk_threads_leave();
usleep(delay);
}
return;
}
En el constructor inicializamos los campos correspondientes al retardo que le ponemos a cada hilo, la fila a la que representa la barra de progreso del hilo, y la estructura ModelColumns, que nos permitirá acceder a cualquiera de las columnas del TreeView.
El método Start, simplemente se encarga de lanzar el nuevo hilo;
thread = Glib::Thread::create(sigc::mem_fun(*this, &ThreadProgress::main), true);
Y en el método main, que ya estará en un nuevo contexto de ejecución, es donde realizamos la actualización de las barras de progreso con el retardo indicado en el constructor.
void ThreadProgress::main()
{
for(int i = 0; i< =100; i++)
{
gdk_threads_enter();
row[column->m_col_percentage] = i;
gdk_flush();
gdk_threads_leave();
usleep(delay);
}
return;
}
Seguro que hay otras maneras de actualizar las columnas del ListStore desde diferentes hilo, ésta es solo una de las posibles implementaciones, y no creo que sea la mejor. Si alguno conoceis una manera alternativa, ruego que la comenteis.
Os dejo una bonita captura de pantalla del pequeño programita que he hecho.



