Publi

Un método para gobernarlos a todos. Identificando quién me llama. [ gtkmm ]

culpablesUna de las características que primero quise probar con Gtk era la posibilidad de crear varios botones, que en su signal_clicked llamaran todos al mismo método, y sea éste el que identifique de dónde viene la llamada. Por ejemplo, en Delphi, siempre que se genera un evento onClick se envía la información del Sender al evento para este mismo propósito.

En el siguiente ejemplo, crearemos un objeto de la clase Sender (que será nuestra ventana principal)

[ sendermain.cpp ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <gtkmm.h>
#include "sender.h"

int main(int argc, char *argv[])
{
  Main entorno (argc, argv);

  // Creamos la ventana
  Sender swin;

  // Ejecutamos
  entorno.run(swin);

  return 0;
}

Ahora creamos el archivo cabecera sender.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ifndef _HWORLD_H
#define _HWORLD_H

#include <gtkmm.h>

using namespace Gtk;

class Sender : public Window
{
 public:
  Sender();
  ~Sender();

  void click(Widget *sender);

  HButtonBox botonera;
  Button *boton[10];        /* 10 botones */
};

#endif

Vemos cómo hemos creado los elementos, sólo una HButtonBox y 10 botones, y sólo un método click() con el parámetro sender de tipo Widget, lo que hacemos es que, cuando llamamos a click() le debemos pasar un puntero indicando la referencia del objeto que hemos hecho click. Lo veremos más claro en sender.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
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
#include "sender.h"
#include <sstream>      // Para hacer texto<<string<<int

Sender::Sender()
{
  int i;
  std::stringstream texto;

  // Configuro la ventana
  set_title("Un método para gobernarlos a todos");
  set_border_width(5);
  set_default_size(400, 200);

  // Configuro la botonera
 
  boton[0]=new Button(Stock::QUIT);
  botonera.pack_start(*boton[0], PACK_SHRINK);

  boton[0]->signal_clicked().connect(sigc::bind<Widget*>(sigc::mem_fun(*this, &Sender::click),(Widget*)boton[0]));

  for (i=1; i<10; i++)
    {
      texto.str("");
      texto<<"Boton "<<i;
      boton[i]=new Button(texto.str().data());
      boton[i]->signal_clicked().connect(sigc::bind<Widget*>(sigc::mem_fun(*this, &Sender::click), (Widget*)boton[i]));
      botonera.pack_start(*boton[i], PACK_SHRINK);
    }

  // Configuro la división
  add(botonera);

  show_all_children();
}

Sender::~Sender()
{
  int i;
  for (i=0; i<10; i++)
    delete boton[i];
}

void Sender::click(Widget *sender)
{
  int i, res;
  std::stringstream ss;

  // Comprobamos procedencia
  for (i=0; i<10; i++)
    {
      if (sender==boton[i])
    break;
    }
  if (i==0)
    hide();
  else
    {
      ss<<"Has pulsado el boton "<<i;
      MessageDialog dialog(ss.str().data());
      res=dialog.run();
    }
}

En este código vemos cómo cuando llamamos a .connect() añadimos lo siguiente:

  • sigc::bind() enlazará una llamada a un método con un parámetro de tipo «Tipo»
  • sigc::mem_fun() como vimos anteriormente generará la referencia de la llamada al método

El tipo del parámetro utilizado será Widget* para que se pueda crear un puntero (hacemos la comparación fácil y rápida) y además, este método sea portable a más tipos de objeto y no sólo botones (además, el método click() espera como parámetro un Widget*).

Por otra parte, en el método click vemos que con:

1
2
3
4
5
  for (i=0; i<10; i++)
    {
      if (sender==boton[i])
    break;
    }

Averiguamos de qué botón viene el click (comparando el sender con todos los posibles botones de procedencia, cuando lo encontramos, salimos del bucle. Si i vale 11 tal vez hayamos cometido un error, o tenemos un sender que no está monitorizado).

Luego si el sender es 0 (el botón de salir) ejecutamos una cosa, y si no mostramos en pantalla un texto.

¡Ok hasta aquí! Aunque queremos métodos para hacer esto más fácil, porque aprender la línea de sigc::bind(sigc::connect(etc,etc…) es trabajo duro, y además nos podemos equivocar fácilmente. Podemos utilizar la siguiente macro (que podemos colocar en sender.h):

1
#define CLICK_CONNECT(widget, method) widget->signal_clicked().connect(sigc::bind<Widget*>(sigc::mem_fun(*this, &method),(Widget*)widget))

y por tanto, las conexiones con signal_click() quedarían así (el texto antiguo aparece comentado):

1
2
3
4
5
6
7
8
9
10
11
//   boton[0]->signal_clicked().connect(sigc::bind<Widget*>(sigc::mem_fun(*this, &Sender::click),(Widget*)boton[0]));
  CLICK_CONNECT(boton[0], Sender::click);
  for (i=1; i<10; i++)
    {
      texto.str("");
      texto<<"Boton "<<i;
      boton[i]=new Button(texto.str().data());
//       boton[i]->signal_clicked().connect(sigc::bind<Widget*>(sigc::mem_fun(*this, &Sender::click), (Widget*)boton[i]));
      CLICK_CONNECT(boton[i], Sender::click);
      botonera.pack_start(*boton[i], PACK_SHRINK);
    }

Aunque gtkmm es muy grande y tiene muchas posibilidades, siempre podemos completar con nuestro código para facilitarnos la vida.

Para compilar este proyecto, podemos hacer lo siguiente:

$ g++ -o sender sendermain.cpp sender.cpp `pkg-config –cflags gtkmm-2.4` `pkg-config –libs gtkmm-2.4`

También podría interesarte....

There are 2 comments left Ir a comentario

  1. Pingback: Bitacoras.com /

  2. Pingback: BlogESfera.com /

Leave a Reply