[C++]GUI Design - Object-Specific Behavior

Started by
7 comments, last by rasher_b2 14 years, 10 months ago
I'm going about designing a GUI system from scratch in my... eh... excuse for an engine that I've begun as a side-project. ^^() I'm working in C++ and taking advantage of inheritance and polymorphism. The GUI system is composed of an event system and GUIObject classes. Now, my issue is this... these are all classes. Every button has the same behavior. Clicking "ButtonA" won't be any different than clicking "ButtonB" without making them different classes. How does one design the system so that individual objects are bound to do certain actions? I've considered having a function pointer in the GUIButton class that is assigned to a function that will handle the click. This function's first parameter would be a pointer to a GUIButton. Essentially, making a "method pointer" that's outside of the class. In this way, the library of GUI classes aren't modified, but instead, in the code that creates/contains the GUI classes, those "method pointers" can be assigned to accomplish customized functionality. I think this is a similar approach to what C# does with .NET in Windows Forms? Should I follow their model and essentially have button's handle their events and then call "method pointers" to accomplish the customized functionality?
Advertisement
Have you read through the info available here? You might find it useful.
"I thought what I'd do was, I'd pretend I was one of those deaf-mutes." - the Laughing Man
I hadn't seen it before. Could be of use, though I'd rather not approach this problem from Win32/C. Gods know I have enough distaste for the Win32 API. I think its similar enough in concept to how C# .NET works anyways. Guess this system of "method pointers" will work.
I've attacked this very issue with a set of classes that I have dubbed "EVENT_ACTION" classes. In essence, my button class has an abstract method called "Click".

In order not to *have* to inherit and program a new "Click" method for each button, I have created a template class that takes a parent and child class as template parameters:

template <class parent_class, class child_class> class Notify_Event {...};


I then inherit from my GUI_BUTTON class as such:

template <class parent_class> class MyButton : public GUI_BUTTON {...};


Within the MyButton class I define a "Click" method that actually executes the
Notify_Events->Execute() method... Which really points to the parent_class->XXXX method (whatever you assign to MyButton->OnClick event)...

This way, if I have a GUI_WINDOW that owns the button (perhaps a dialog?) I simply use the following code to bind the two together...

MyButton<MyWindow> *btn = new MyButton<MyWindow>(parent_win);btn->OnClick = parent_win->MyButtonClick;...MyWindow::MyButtonClick( GUI_BUTTON sender ) {  // btn->OnClick called...}


It may seem like an awful lot of code and complexity, but I think when dealing with a GUI system as you speak of, it can't be beat. There's something about having a button tell its parent that it was Clicked that just makes so much sense to me (as opposed to using some convoluted switch statements within a message handler).

If you're interested, here is the "Notify_Event" template class source. Other events are handled the same way (MouseMove, MouseOver, KeyDown, KeyUp, Etc) but they have their own *version* of Notify_Event to work with...

///////////////////////////////////////////////////////////////////////////////////// WFNOTIFYEVENT: Calling object (usually a child object) always passes //                itself as a parameter...template <class parent_class, class child_class> class WFKERNEL_API WFNOTIFYEVENT{  public:    typedef WFVOID (parent_class::* parent_method) ( child_class* );  protected:    typedef WFNOTIFYEVENT<parent_class, child_class> EVENT;    // Event-Notifier Target Object...    parent_class* pObject;     // Event-Notifier Method pointer to call...    parent_method pMethod;  public:        WFNOTIFYEVENT( parent_class* parent = NULL ) : pObject(parent), pMethod(NULL) {}    virtual ~WFNOTIFYEVENT( WFVOID ) {};    virtual WFVOID Execute( child_class* sender )     { if (WFTRUE == Assigned()) { (pObject->*pMethod)( sender ); } }    virtual WFBOOL Assigned( WFVOID )     { return ((NULL != pObject) && (NULL != pMethod)) ? WFTRUE : WFFALSE; }    virtual EVENT& SetHandler( const parent_method pfunc )     { pMethod = pfunc; return VALUE_OF(this); }    virtual EVENT& SetObject( parent_class* pobj )     { pObject = pobj; return VALUE_OF(this); }    virtual const parent_class* GetObject( WFVOID ) const     { return pObject; }    virtual const parent_method GetHandler( WFVOID ) const     { return pMethod; }    // NOTE: These can not be virtual...    EVENT& operator= ( const parent_method pfunc )     { return SetHandler( pfunc ); }    EVENT& operator= ( parent_class* pobj )     { return SetObject( pobj ); }};



And here is my MyButton<> class definition file:

  template <class parent_class> class myButtonControl : public WFCONTROL_BUTTON  {    public:      myButtonControl( parent_class* parent ) : WFCONTROL_BUTTON()      {        OnClick.SetObject(parent);        OnMouseOver.SetObject(parent);        OnMouseLeave.SetObject(parent);        OnDestroying.SetObject(parent);        OnChangingFocus.SetObject(parent);        OnShowingHint.SetObject(parent);      }      virtual WFVOID Click( WFVOID )      {        // We're gonna have our parent handle the request for us...        OnClick.Execute(this);      }      virtual WFVOID MouseOver( WFVOID )      {        // We're gonna have our parent handle the request for us...        OnMouseOver.Execute(this);      }      virtual WFVOID MouseLeave( WFVOID )      {        OnMouseLeave.Execute(this);      }      virtual WFVOID Destroying( WFBOOL_PTR value )      {        OnDestroying.Execute(this, value);      }      virtual WFVOID ChangingFocus( WFBOOL value )      {        OnChangingFocus.Execute(this, value);      }      virtual WFVOID ShowingHint( WFBOOL value )      {        OnShowingHint.Execute(this, value);      }      virtual WFVOID StartDrag( WFLONG x, WFLONG y, WFSHIFT_STATE state )      {        OnStartDrag.Execute( this, x, y, state );      }      virtual WFVOID StopDrag( WFLONG x, WFLONG y, WFSHIFT_STATE state )      {        OnStopDrag.Execute( this, x, y, state );      }      virtual WFVOID StillDragging( WFLONG x, WFLONG y, WFSHIFT_STATE state )      {        OnStillDragging.Execute( this, x, y, state );      }      virtual WFVOID DragOver( WFCONTROL_PTR source, WFLONG x, WFLONG y,                               WFCONTROL_DRAG_STATE state, WFBOOL &accept )      {        OnDragOver.Execute( this, source, x, y, state, accept );      }    public:      // Declare an EVENT-ACTION that will allow us to notify our parent...      WFONCLICK_EVENT<parent_class>         OnClick;      WFONMOUSEOVER_EVENT<parent_class>     OnMouseOver;      WFONMOUSELEAVE_EVENT<parent_class>    OnMouseLeave;      WFONDESTROYING_EVENT<parent_class>    OnDestroying;      WFONCHANGINGFOCUS_EVENT<parent_class> OnChangingFocus;      WFONSHOWINGHINT_EVENT<parent_class>   OnShowingHint;      WFONDRAG_EVENT<parent_class>          OnStartDrag;      WFONDRAG_EVENT<parent_class>          OnStopDrag;      WFONDRAG_EVENT<parent_class>          OnStillDragging;      WFONDRAGOVER_EVENT<parent_class>      OnDragOver;  };



Keep in mind, all of the *Events* that I have an EVENT_ACTION attached to are defined as Abstract within the button's base class!



Regards,
Jumpster




I'll go over reading your system to make sense of it, lacking a good deal of sleep due to new neighbors. Just to show the system I managed to get running:

	typedef void (*EventHandler)(void*);	class GUIControl : public GUIObject, public EventClient	{	protected:		bool m_Enabled;	public:		GUIControl();		GUIControl(const GUIControl& Source);		virtual GUIControl& operator=(const GUIControl& Source);		virtual void SetEnabled(bool Enabled);		virtual bool GetEnabled() const;		virtual void ProcessEvent(Event& Input);		EventHandler OnUse;		EventHandler OnSelect;		EventHandler OnDeselect;		virtual ~GUIControl();	};	GUIControl::GUIControl() : GUIObject(), EventClient(), m_Enabled(true), OnUse(0), OnSelect(0), OnDeselect(0)	{		EventSystem::Instantiate()->SubscribeClient(this, IE_CursorMoved);	}	GUIControl::GUIControl(const GUIControl& Source) : GUIObject(Source), EventClient(Source), m_Enabled(Source.m_Enabled), OnUse(Source.OnUse), OnSelect(Source.OnSelect), OnDeselect(Source.OnDeselect)	{		EventSystem::Instantiate()->SubscribeClient(this, IE_CursorMoved);	}	GUIControl& GUIControl::operator=(const GUIControl& Source)	{		GUIObject::operator=(Source);		EventClient::operator=(Source);		m_Enabled = Source.m_Enabled;		OnUse = Source.OnUse;		OnSelect = Source.OnSelect;		OnDeselect = Source.OnDeselect;		return *this;	}	void GUIControl::SetEnabled(bool Enabled)	{		m_Enabled = Enabled;	}	bool GUIControl::GetEnabled() const	{		return m_Enabled;	}	void GUIControl::ProcessEvent(Event& Input)	{		switch(Input.GetEventType())		{		case IE_CursorMoved:			{				D3DXVECTOR2* EventParameters = static_cast<D3DXVECTOR2*>(Input.GetParameters());				if(EventParameters->x >= m_Position.x && EventParameters->y >= m_Position.y && EventParameters->x <= m_Position.x + m_Size.x && EventParameters->y <= m_Position.y + m_Size.y)					SetEnabled(true);				else					SetEnabled(false);			}		default:			return;		}	}	GUIControl::~GUIControl()	{		EventSystem::Instantiate()->UnsubscribeClient(this, IE_CursorMoved);	}


Pretty basic right now. I haven't actually begun to use my EventHandlers in any way. I'd love to find a good book or article on GUI design from scratch. :| I haven't the slightest idea how buttons are made in GUI systems so that they can scale without stretching... have 9 different textures, one for each part of the button? The corners wouldn't stretch, but the top, bottom, left, and right can stretch, and the middle is just a filler?

Further more, making it so that objects that are nested are drawn relative to their parent... IE: A GUIButton contains a GUIText, the button should keep the text positioned with it. The GUIButton calls the GUIText's Draw method, but the Draw method itself draws based on the position data members. Meaning, if the GUIText's position is (0, 0), to denote that it's top-left aligned with the button, it will be drawn at (0, 0) on screen instead. I can think of multiple solutions. While it is "fun" to discover the most appropriate solution my self... I'm extremely bloody tired or refactoring systems over and over and over because of my inexperience. More than happy to see examples of good code to learn from.
I think it's worth considering, who do you need to notify? If you're not trying to make a general UI system (ala WinForms), but a game-specific one, it may be that you only really need to notify the calling code of events. In which case, return values work quite nicely :)

Just something to consider. You don't have to deal with events/signals/slots/function pointers.
Anthony Umfer
A lot of a UI system's design will be based on its requirements. Questions like what you need it for, do you really need a complicated event system etc. Basically, different requirements can often lead to radically different designs.

Quote:Original post by Sion Sheevok
Further more, making it so that objects that are nested are drawn relative to their parent...


A simple GUI might have a widget hierarchy (text are is a child of button). To draw you'd traverse down the hierarchy. Each widget will have its local coordinate space (text is at 10, 10 relative to its parent button). As you traverse down you just transform the coordinate system.

For example - in a widget:

draw();
translate(x, y);
draw each child widget
translate(-x, -y);

Quote:Original post by Sion Sheevok
How does one design the system so that individual objects are bound to do certain actions?

Again, many different approaches can be taken depending on requirements/complexity. Function pointers like you mentioned are relatively simple and would probably work well for most situations (by most situations I mean typical GUIs found in games. Make a large GUI driven program would probably be a different story).

Other options might also be to take an event listener approach like in Java Swing but I think this is overly verbose and complicated, especially for small UI systems.

Yet another approach would be to have event ids and one callback function that gets called for each event. Each widget can be given an event id and calls the handling function when it's activated/something changes. The use a switch statement on the id to decide what to do. Yes, you could have a massive switch statement and it might look ugly but this approach can be relatively simple and for a small UI system it could work quite well.
Quote:Original post by rasher_b2
Other options might also be to take an event listener approach like in Java Swing but I think this is overly verbose and complicated, especially for small UI systems.

public class Button {    private ActionListener l;    private String command;    public void setListener(Actionlistener l) {        this.l = l;    }    public void setCommand(String c) {        this.command = c;    }    public void onMouseUp() {        l.actionPerformed(command);    }}

That's just two private members, two setters, and an overriden method, three method lines of code total. If you only use one listener per button (anonymous inner classes in java can be declared inline when you call the setListener method, dunno bout C++), you can cut out on the command string, too. Is it really overly verbose and complicated?

Another idea would be to have the actual code performed by actionPerformed() sourced from a script when reading in the UI definition.
Quote:Original post by lightbringer
That's just two private members, two setters, and an overriden method, three method lines of code total. If you only use one listener per button (anonymous inner classes in java can be declared inline when you call the setListener method, dunno bout C++), you can cut out on the command string, too. Is it really overly verbose and complicated?


The actual code to set and call the listeners is okay but the part of your code where you're making all the anonymous inner classes to handle the events can get a bit messy. I wrote a GUI with that approach before and found it quite annoying writing anonymous inner classes when making widgets and putting the event handling code in the function that creates the widgets (or having the classes and handling code somewhere else if one decided to set things up that way). It was a simple GUI and I came to the realization that a simpler event system would have been easier to work with even if it was less efficient/abstracted etc. It might not be overly verbose and complicated depending on the situation but for a lightweight GUI I think it might be.

This topic is closed to new replies.

Advertisement