Skip to main content
GameDev.net gamedev.net
🔒 Locked

Callbacks in GUI

Started by modena Apr 12, 2005 at 3:54 AM 8 replies 1.9k views
Original Post
modena
modena
I've been wondering how I would go about implementing button callbacks within my GUI system. The way I'd like to do it would to have my button class with the appropriate SetCallback() function that would set the function to call when the button is pressed. button.h
class Button
{
public:
    // Constructor yada yada

    SetCallback( <function pointer syntax> );    // Sets callback of this Button
};



Now the whole GUI system will reside within another Application class, and this is where the Button and its callback function will be stored. application.h

class Application
{
public:
    Application();
    // Other functions

    void PopUpHello();    // The function to use as callback

private:
    Button myButton;    // Our button
}



application.cpp

#include "application.h"

Application::Application()
{
    // Attach the callback function to the button
    myButton.SetCallback(&PopUpHello);
}

void PopUpHello()
{
    MessageBox("Hello!");
}



Ideally this is what the source would look like. When the Button recieves a click message it calls its callback, in this case PopUpHello. Now I know function pointers will be involved in here somehow, but I'm not too sure how to get the functionality I want. Any examples of function pointers I've seen have had the function to point to either be a (1)non-member function or (2)static member function or (3)specifed the name of the class in the function pointer prototype. Option 1 and 2 do not work because the callback will need access to the member data of Application, and option 3 will not work because the name of the class holding the callbacks is not known (won't always be Application.) Has anyone found a solution to this either using function pointers, or another method?
--------------------------<modena> - Comfortably Nub
werekarg
werekarg
i'm using something like this:

class Control : public Control{  public:    virtual void OnLeftMouseDown (int X, int Y);  public:    virtual void SetHandleOnLeftMouseDown(PP_voidret_callback_t callback);  private:    PP_voidret_callback_t mHandleOnLeftMouseDown;     };void Control::SetHandleOnLeftMouseDown(PP_voidret_callback_t callback){   mHandleOnLeftMouseDown      = callback;}void Control::OnLeftMouseDown (int X, int Y){   // specific bla-bla related to how your gui is designed (capture the mouse and so on)   if (mHandleOnLeftMouseDown.valid())      mHandleOnLeftMouseDown.callback(this, X, Y);}


and the callback stuff:

class PP_voidret_callback_t{public:	PP_voidret_callback_t() :	m_p(0), m_pmf(0) { }	template <class T, typename A1>		PP_voidret_callback_t(T* pObj, void (T::*pmf)(A1))		:  m_p(pObj), m_pmf(*reinterpret_cast<void**>(&pmf)) {}	template <class T, typename A1, typename A2>		PP_voidret_callback_t(T* pObj, void (T::*pmf)(A1, A2))		:  m_p(pObj), m_pmf(*reinterpret_cast<void**>(&pmf)) {}	template <class T, typename A1, typename A2, typename A3>		PP_voidret_callback_t(T* pObj, void (T::*pmf)(A1, A2, A3))		:  m_p(pObj), m_pmf(*reinterpret_cast<void**>(&pmf)) {}	template <class T, typename A1>		void set(T* pObj, void (T::*pmf)(A1))			{ m_p = pObj; m_pmf = *reinterpret_cast<void**>(&pmf); }	template <class T, typename A1, typename A2>		void set(T* pObj, void (T::*pmf)(A1, A2))			{ m_p = pObj; m_pmf = *reinterpret_cast<void**>(&pmf); }	template <class T, typename A1, typename A2, typename A3>		void set(T* pObj, void (T::*pmf)(A1, A2, A3))			{ m_p = pObj; m_pmf = *reinterpret_cast<void**>(&pmf); }	template <typename A1>	void callback(const A1& a1)	{		struct foo_t { typedef void (foo_t::*pmf)(A1); };		foo_t* pFake = static_cast<foo_t*>(m_p);		foo_t::pmf pmfFake = *reinterpret_cast<foo_t::pmf*>(&m_pmf);		(pFake->*pmfFake)(a1);	}	template <typename A1, typename A2>	void callback(const A1& a1, const A2& a2)	{		struct foo_t { typedef void (foo_t::*pmf)(A1,A2); };		foo_t* pFake = static_cast<foo_t*>(m_p);		foo_t::pmf pmfFake = *reinterpret_cast<foo_t::pmf*>(&m_pmf);		(pFake->*pmfFake)(a1, a2);	}	template <typename A1, typename A2, typename A3>	void callback(const A1& a1, const A2& a2, const A3& a3)	{	        //typedef void (*pmf)(A1,A2,A3);			        //struct foo_t { pmf mf; };		//foo_t* pFake = static_cast<foo_t*>(m_p);		//pmf pmfFake = *reinterpret_cast<pmf*>(&m_pmf);		//(pFake->*pmfFake)(a1, a2, a3);			        struct foo_t { typedef void (foo_t::*pmf)(A1,A2,A3); };		foo_t* pFake = static_cast<foo_t*>(m_p);		foo_t::pmf pmfFake = *reinterpret_cast<foo_t::pmf*>(&m_pmf);		(pFake->*pmfFake)(a1, a2, a3);	}	bool valid() const { return m_p != 0 && m_pmf != 0; }private:	void* m_p;	void* m_pmf;};


and you may use the whole system like this:

class SuperControl{  public:    void ButtonOnLeftMouseDown(Control *Sender, int X, int Y);   private:    Button* mButton;};SuperControl::SuperControl(){   PP_voidret_callback_t handlerOnLeftMouseDown(this, &SuperControl::ButtonOnLeftMouseDown);   mButton = new Button();   mButton->SetHandleOnLeftMouseDown(handlerOnLeftMouseDown);}void SuperControl::ButtonOnLeftMouseDown(Control *Sender, int X, int Y){  // do something }


i've written it off my head (except the PP_voidret_callback_t which i copy/paste it from my project) so it's unlikely it will compile, but hope will give you an idea of how it's done.

if you want more details, please download my engine (http://aspc.cs.utt.ro/~karg/ppt/) and go to src/Common/Gui for the gui design and check Src/Common/GenericCallback.h for the callback.
grekster
grekster
I have a functor class (that I mostly stole from superpigs enginutity(sp?) articles) that allows me to wrap up all my function pointers.

template<class S>class cFunctorBase{public:	virtual S operator()() = 0;};template<class S, class T>class cFunctor : public cFunctorBase<S>{private:	T* Object;	typedef S (T::*funcType)();	funcType func;public:	cFunctor(T* Obj, funcType f) {Object = Obj; func = f;}	S operator()(){ return((Object->*func)());}};


The sytax for use is ...
cFunctor<Return_Type,Class_Function_Is_Member_Of> MyFunctor(Class_Instance, Function_Pointer);

It seems to work fine so far.
Quote: Original post by BosskIn Soviet Russia, you STFU WITH THOSE LAME JOKES!
darookie
darookie
Take a look at sigslot.
This would simplify your code the way you envision it:
class Button{public:    // Constructor yada yada    sigslot::signal0<> Clicked;    // Signal of this Button};

class Application : sigslot::has_slots<>{public:    Application();    // Other functions    void PopUpHello();    // The function to use as signal slotprivate:    Button myButton;    // Our button}

#include "application.h"Application::Application(){    // Attach the slot function to the button's signal    myButton.Clicked.connect( PopUpHello );}void PopUpHello(){    MessageBox("Hello!");}


HTH,
Pat.
Koshmaar
Koshmaar
Aaaaaahh, keep away from function callbacks in your GUI code if you want to stay sane :-)

I'm currently doing rather big (300+ kb of code) GUI engine and the beginning I've decided to use "class callbacks" (you don't want to know what it means, really).
Bofore this engine, I've done little game (Pongowar) where I used normal function callbacks, like you are planning. In both cases, callbacks proved to be real pain in the ass... writing them, maintaning, debugging, setting, calling etc. was real nigthtmare.

Of course, you will do as you want, but IMHO signals and slots (as Darookie said) are way to go.

clicky
Structural
Structural
In the past I've used two systems for GUI's.
Firstly, was the callback system. In the beginning I used a global function (I didn't know about Functors back then), later I used Functors.
I later switched to a message system that allowed me to push a message on a stack, and every game-loop I'd process all messages.

Personally I favour the callback system. It allows for a greater flexibility of your code. I tend to recycle a lot of code, and the GUI system with the message-system required the message system to be present in the project, which required the presence of my codebase which has Singletons, Managers, Timers, and much more crap I didn't want to use. Dependency hell.

If I were to build another GUI system, I'd add an extra layer between the message system and GUI system. The GUI system would work with callbacks, and the extra layer would translate the callbacks to a message that can be pushed into the message-system.

The callback system is DEFINATELY the easiest to maintain. Just throw an event to any object and function that contains specialised code. The message system is more generic, but comes with some (programming) overhead. IE: You still need to decide what the hell you are supposed to do after you pulled the message out. So, you push it in, you get it out, but then you still need to direct it to some specialised code to deal with it. Not nice if you want to create a quick project.
STOP THE PLANET!! I WANT TO GET OFF!!
Trienco
Trienco
the real fun is when you want to be so completely flexible that you can create your gui from outside sources. something like




you'll probably end up with a map of function names and pointers (or functors). the only thing i could think about as advice if you dont want to build a really complex system: always have a pointer/reference to the "sender" gui element as parameter and assume the target function will take care of extracting the extra info it needs (like the current value of a spinner or text field).

so, pretty much like everyone else (except all functions were part of dialogue classes, hence UIBaseObject):
#define METHOD(x) (void(UIBaseObject::*)(void*))&xclass Callback {private:  UIBaseObject* obj;  void(UIBaseObject::*func)(void*);public:  Callback() : obj(0), func(0) {}  void Set(UIBaseObject* o, void(UIBaseObject::*f)(void*)) {obj=o; func=f;}  operator()(void* sender) {if (obj && func) (obj->*func)(sender);}};class Button {  Callback OnClick;}class Bla : UIDialogue {  void func(void*) {}  Bla() {    Button btn;    btn.OnClick.Set(this, METHOD(func));  }}



you can play around with an old gui using this and look at the source under opengl minigui (though it's far from finished and never will be.. and because of a bug that has to be completely retarded entering some letters like 'i' in the field for name will crash it.. dont ask, but "bla" is safe *cough*).. there's also a highly complex drag and drop inventory system that doesnt seperate gui from game as much as i would have liked.

decent browser version (firefox):
http://festini.device-zero.de/Programming/Downloads/index.xml

not so great browser version (opera and others):
http://festini.device-zero.de/downloads/index.shtml

crappy browser version (ie): non existant (tired of css hacks)

final advice: never try to make something "uberflexible" when it's highly unlikely that it will ever be needed. you end up with lots of lost hair and sleep and a gui so ugly and complicated that you wont want to actually use it.
f@dzhttp://festini.device-zero.de
me22
me22
Another alternative: boost::function ( http://boost.org/doc/html/function.html )

You can declare your callback holders, say for a callback that passes along the mouse position as 2 ints, as boost::function. That way you can use boost::bind, std::mem_fun, and etc to compose functors for the callbacks that can be much nicer, since you can use member functions, pointers to functions, and more, even boost::lamda expressions.

Even a functor that pushes a message into a message queue :P
MaulingMonkey
MaulingMonkey
Quote:
Original post by me22
Another alternative: boost::function ( http://boost.org/doc/html/function.html )

You can declare your callback holders, say for a callback that passes along the mouse position as 2 ints, as boost::function. That way you can use boost::bind, std::mem_fun, and etc to compose functors for the callbacks that can be much nicer, since you can use member functions, pointers to functions, and more, even boost::lamda expressions.

Even a functor that pushes a message into a message queue :P


For callbacks, I agree: http://boost.org/doc/html/function.html.
Another boost alternative: http://boost.org/doc/html/signals.html.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.