programación, Anotaciones, linux, gtk+, gtkmm21 May, 2007 4:48 pm

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::TreeModelColumn m_col_id;
Gtk::TreeModelColumn m_col_name;
Gtk::TreeModelColumn m_col_number;
Gtk::TreeModelColumn m_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::RefPtr m_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
#include

ThreadProgress::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.

barras de progreso

programación, Anotaciones, linux, gtk+, gtkmm27 January, 2007 3:27 pm

Despues de trabajar varios meses sobre Windows con las librerias wtl, me he decidido a comenzar con programación gráfica bajo linux. Trás ver varias alternativas al final he optado por
usar las librerias gtkmm, para realizar entornos visuales. Estás librerias, se basan en las
GTK+. Según la wikipedia las gtkmm son utilizadas para desarrollar aplicaciones basadas en GTK+ con c++.

Antes de empezar a trabajar con estas librerias, debemos instalarlas, y tambien sería onveniente
disponer de algún entorno de desarrollo. Como normalmente trabajo con ubuntu, opté por usar anjuta.

Desde el synaptic, instalé los siguientes programas o librerias:
- anjuta
- g++
- libgtkmm-2.4-1c2a
- libgtkmm-2.4-dev
- libgtkmm-2.4-doc
- autoconf
- libtool
- libglademm-2.4-dev

Despues de instalarlo todo, ya podemos crear un proyecto gtkmm, y compilar nuestro primer
programa de ejemplo:

#include
int
main (int argc, char *argv[])
{
Gtk::Main kit(argc, argv);
Gtk::Window window;
kit.run(window);
return 0;
}

Dicho así suena muy sencillo, pero la verdad es que me costó unas cuantas horas dejarlo todo
preparado.