tags:

views:

271

answers:

3

What's the best way to connect a GTK+ signal to a non-static member function?

I have a GUI class in c++ that uses gtk, and i want do something like this:

Gui::Gui ()
{
  gtk_signal_connect(GTK_OBJECT(somObject),
                     "clicked",
                     GTK_SIGNAL_FUNC( &(this->ClickHandler) ),
                     NULL);
}

void Gui::ClickHandler(GtkWidget *w, gpointer data)
{
  // handle the click
}

I know this doesn't work b/c the GTK_SIGNAL_FUNC can't point to a member function unless it's a static function, so what's the best way to do this? Is it possible to use a single proxy handler function and boost::bind somehow? Here is what I tried:

gtk_signal_connect(GTK_OBJECT(somObject), "clicked", 
                   GTK_SIGNAL_FUNC( SigHandler ), 
                   boost::bind( &(Gui::ClickHandler), this) ); 

void SigHandler(GtkWidget *w, gpointer data) 
{
  data(w);
} 

void Gui::ClickHandler(GtkWidget *w) { // handle the click }

here's the error: gui.cc: error: 'data' cannot be used as a function

+2  A: 

I assume your class contains some internal data that the signal handler needs access to. If not, just use a static function for the signal handler. Otherwise, here are a couple of ideas:

  • Create a sigc++ or boost functor out of the member function; set it as the data passed to the signal handler. Use a generic signal handler that just calls the functor.

  • During construction, register each instance of the class somewhere and use a signal handler that calls the member function for each registered instance.

Steve S
i tried what i think is your first suggestion but got an erro, here's the code: gtk_signal_connect(GTK_OBJECT(somObject), "clicked", GTK_SIGNAL_FUNC( SigHandler ), boost::bind( void SigHandler(GtkWidget *w, gpointer data) { data(w) } void Gui::ClickHandler(GtkWidget *w) { // handle the click }here's the error:gui.cc: error: 'data' cannot be used as a function
aaronstacy
Pavel Minaev
A: 

Use a static proxy:

Gui::Gui ()
{
  gtk_signal_connect(GTK_OBJECT(somObject),
                     "clicked",
                     GTK_SIGNAL_FUNC(ClickHandlerProxy),
                     this);
}

void Gui::ClickHandler(GtkWidget *w)
{ /* handle the click */ }

void Gui::ClickHandlerProxy(GtkWidget *w, gpointer p)
{ static_cast<Gui*>(p)->ClickHandler(w); }
John Millikin
this requires me to have a proxy for every handler function, and then the handlers can't be private. is there a way to pass a functor w/ boost::bind such that there could be only one proxy handler that calls the function?
aaronstacy
No, since `boost::bind` still gives you a function object, not a function pointer.
Pavel Minaev
@John: using `dynamic_cast` on `void*` is pointless here, since `void*` is not a polymorphic type. It won't even compile.
Pavel Minaev
@Pavel so there's no way to cast p so that it can be called as a function?
aaronstacy
To downcast from `void*` to some `T*`, you simply use `static_cast` (which will of course result in U.B. if actual type of pointed object isn't `T` or derived).
Pavel Minaev
@pavel: Thanks for the dynamic_cast catch. @aaron: The handlers can be private, just make them virtual.
John Millikin
+1  A: 

You will need the proxies, but you can do some template magic to help you generate them. Something like this:

template <class HandlerClass, class A1, void(HandlerClass::*HandlerFunc)(A1)>
struct GtkHandlerProxy {
  static void HandlerProxy(A1 a1, gpointer p) {
    (static_cast<HandlerClass*>(p)->*HandlerFunc)(a1);
  }
};

and then use it:

Gui::Gui()
{
  gtk_signal_connect(
    GTK_OBJECT(somObject),
    "clicked",
    GTK_SIGNAL_FUNC(&GtkHandlerProxy<Gui, GtkWidget*, &Gui::ClickHandler>
      ::HandlerProxy),
    this);
}

You'd probably want to define a helper macro, too:

#define GTK_SIGNAL_METHOD(Class, Handler, A1) \
   GTK_SIGNAL_FUNC(GtkHandlerProxy<Class, A1, &Class::Handler>::HandlerProxy)

so that you can write:

Gui::Gui()
{
  gtk_signal_connect(
    GTK_OBJECT(somObject),
    "clicked",
    GTK_SIGNAL_METHOD(GtkHandlerProxy, ClickHandler, GtkWidget*),
    this);
}

This only covers the single-argument handler case; to handle something like button_press_event, you'll have to define similar helpers for 2+ arguments. Boost preprocessor library can help avoid repetitive code there.

[EDIT]

Alternatively, you could pass a function object itself as p, which can possibly include captured variables (e.g. boost::bind or boost::lambda). Here's how that would look:

class Gui {
  ...

  // Member to ensure proper lifetime
  boost::function<void(GtkWidget*)> clickHandlerFunc;

  Gui() {
    ...
    clickHandlerFunc = boost::bind(&Gui::ClickHandler, this);
    gtk_signal_connect(
      GTK_OBJECT(somObject),
      "clicked",
      GTK_SIGNAL_FUNC(UniversalHandlerProxy<GtkWidget*>)
      &clickHandlerFunc);       
  }

  template <class A1>
  static void Gui::UniversalHandlerProxy(A1 a1, gpointer p) {
    (*static_cast< boost::function<void(A1)>* >(p))(a1);
  }

  void Gui::ClickHandler(GtkWidget* w, gpointer p) {
    ...     
  }
};
Pavel Minaev
aaronstacy
There was an error in the code - `void(T::*HandlerFunc)(A1)` should've been `void(HandlerClass::*HandlerFunc)(A1)` - I've changed the answer accordingly. See if that helps.
Pavel Minaev
i made the change and the compiler is still giving me errors: on the line where i'm making the static_cast: no matching function for call to Gui::Gui(void*)
aaronstacy
Yup, it has to be `static_cast<HandlerClass*>`, with the asterisk (since we're casting a pointer) - answer fixed again.
Pavel Minaev