GUI Logic

Started by
4 comments, last by john_t 18 years, 1 month ago
For some background, I've been rolling my own GUI library just to see how it's done. For the most part, it's been smooth coding but I just recently got tripped up with communication within my GUI. My GUI objects are set up in an n-ary tree. Mouse and keyboard input is introduced as a "message" into the GUI tree at the root and is passed down the tree (through a polymorphic on_event() function) until the appropriate object recieves and handles the message. I've been passing internal GUI messages around using the same system. To avoid messages potentially bouncing around the tree forever and never get handled, I've imposed a limitation on the system requiring that messages always be passed DOWN the tree; if nothing is passed up and the tree is finite the message is guaranteed to stop at some point. This has worked fine until I started trying to introduce more complicated GUI features. Specifically, I recently implemented close, maximize, and minimize buttons for windows. I got these to work by coding under the assumption that these buttons are always the direct children of the window they're in. So, for example, the close button simply removes its parent from the GUI tree on a mouse click message. This works fine, however, I recently implemented a toolbar class. Now, it would appear to me that the logical way to structure a window for the classic Windows look would be to make the close/min/max buttons children of a toolbar at the top of the screen, where the toolbar is a child of the window. However, I can then no longer assume as I did with my implementation of these buttons that their direct parent is a window. I thought about creating GUI messages that these buttons would pass up the tree until they find a window to deliver them to. However, I would then have messages moving up and down my message tree, and as the system grows more complex, I would be extremely suspicious of a lurking infinite loop. How do other GUIs handle this logic?
Advertisement
As far as I know, Windows (the OS) uses a similar system. Only the button (min/max/close) is handled differently. Instead of directly acting on the window they belong to, they just send a new message from the root down the tree. The appropriate window will receive it and handle it by closing, minimizing, maximizing or do whatever is suit. This way messages still always go down and you can get rid of the button-as-child assumption.

Illco
Thank you! I'll try doing it this way. Could you possibly clarify how "the appropriate window will receive it and handle it" though? How would the message be delivered to the correct window; I suppose I could pass a pointer to the target window in the message, at which point the buttons would need to keep track of something like an "action target" pointer?
Since you wrote the appropriate object recieves and handles the message I assumed you already had this up and going. Yes you could pas the pointer around though you should be careful doing so (an evil button might kidnap the pointer and destroy it without the tree knowing for example).

In Windows handles are used to identify the correct window (each window has a unique handle or ID) and buttons are also windows (as is any GUI element in the Windows case) and hence have their own handles.

At any rate the button must still know about the window it belongs to but now it can be any relationship you like.

Illco
You could keep pointers and do an "if (pointer == this)" type of thing, or you could also do an ID system. Each message could have a target_id and every new widget gets my_id = global_ID_counter++ when it's created;

However, as time goes by, i'm starting to be more and more interested in the signal/slot method, which is worth your time if you have never looked into it before. You basically tie a widget's "messages" to specific recipients. And that way you don't have messages flying around willy-nilly. It's a form of the Observer system to some degree. If you want to roll your own Observer system, you could do this:

- each widget keeps a list of observers (which probably includes child widgets)

- you can make a widget "register" with another object as an observer. when the object has a message to broadcast, it send it to everyone in it's list of observers, sort of like when you send out email to a mailing list. Now, only objects that have "subscribed" to another object get messages from that object.

In fact, you can have my little Observer system here. It's free code. Debugged, but not gauranteed to be usefull :-)

UniversalObserver.h
#ifndef UNIVERSAL_OBSERVER_H#define UNIVERSAL_OBSERVER_H#include <list>namespace LGC {/*Observer signals are signals that are broadcast by a Subject and received by an Observer. The signal reflects the status or change in status of the Subject, not the Observer. The Observer may deal with the Subject's broadcast signals however it wishes. Observers respond to this "stimulus" in a generic fashion. When they receive a signal, they perform an action.  The signal is not tied to a specific object but the response to that signal may be.*//*NOTE: if you want a more flexible system, at the expense of beinga bit slower, you can use strings instead of enums.*/enum observer_signal {	// PLAYER SIGNALS	SIGNAL_UP = 0,	SIGNAL_DOWN,	SIGNAL_LEFT,	SIGNAL_RIGHT,	SIGNAL_FIRE,	SIGNAL_CHARGE,	SIGNAL_CHARGE_RELEASE,	SIGNAL_JUMP,	SIGNAL_DEATH,	SIGNAL_DAMAGE,	SIGNAL_CONTACT,	SIGNAL_MOVE, // doubles as a GUI signal	SIGNAL_STOP,		// GUI SIGNALS	SIGNAL_BUTTONCLICK,	SIGNAL_MOUSECLICK,	SIGNAL_FOCUS,		// ANYTHING ELSE 	SIGNAL_CUSTOM_1 = 101,	SIGNAL_CUSTOM_2 = 102,	SIGNAL_CUSTOM_3 = 103,	SIGNAL_CUSTOM_4 = 104,	SIGNAL_CUSTOM_5 = 105,	SIGNAL_CUSTOM_6 = 106,	SIGNAL_CUSTOM_7 = 107,	SIGNAL_CUSTOM_8 = 108,	SIGNAL_CUSTOM_9 = 109		};					// predecclass UniversalSubject;class UniversalObserver {public:	UniversalObserver();		virtual ~UniversalObserver(); // report unregister to subject		// This is where all the real signal-response meat goes:	virtual void ReceiveSignal( observer_signal sig ) = 0;		// Use these functions so that the class will record the Subject. 	// When we die, we automatically unregister ourself.	void RegisterAsObserverWith( UniversalSubject* subject );	void UnregisterAsObserverWith( UniversalSubject* subject );	private:	std::list<UniversalSubject*> subjects;		typedef std::list<UniversalSubject*>::iterator subject_iter;		};				class UniversalSubject {public:	UniversalSubject();		virtual ~UniversalSubject(); 		void RegisterObserver( UniversalObserver* obs );		void UnregisterObserver( UniversalObserver* obs );	protected:	void BroadcastSignal( observer_signal sig );	private:	std::list<UniversalObserver*> observers;		typedef std::list<UniversalObserver*>::iterator observer_iter;		};			} // end namespace LGC		#endif


UniversalObserver.cpp
#include "UniversalObserver.h"namespace LGC {UniversalObserver::UniversalObserver() {	subjects = std::list<UniversalSubject*>();	}UniversalObserver::~UniversalObserver() {	// unregister with all subjects	for (subject_iter i = subjects.begin(); i != subjects.end(); i++) {		(*i)->UnregisterObserver( this );		}	}	void UniversalObserver::RegisterAsObserverWith( UniversalSubject* subject ) {     if (!subject) { return; }	subject->RegisterObserver( this );	subjects.push_back( subject );	}	void UniversalObserver::UnregisterAsObserverWith( UniversalSubject* subject ) {	if (!subject) { return; }	subject->UnregisterObserver( this );	for (subject_iter i = subjects.begin(); i != subjects.end(); ) {		if (*i == subject) { i = subjects.erase(i); } else { i++; }		}		}	UniversalSubject::UniversalSubject() {	observers = std::list<UniversalObserver*>();	}UniversalSubject::~UniversalSubject() {	// notify observers that we are dead	// NOTE: doing this modifies our own list because UnregisterAsObserverWith()	// also calls our own UnregisterObserver() via the other object.	while ( observers.size() != 0 ) {		(*observers.begin())->UnregisterAsObserverWith( this );		}	}void UniversalSubject::RegisterObserver( UniversalObserver* obs ) {	if (!obs) { return; }	observers.push_back( obs );	}void UniversalSubject::UnregisterObserver( UniversalObserver* obs ) {	if (!obs) { return; }	for (observer_iter i = observers.begin(); i != observers.end(); ) {		if (*i == obs) { i = observers.erase(i); } else { i++; }		}	}void UniversalSubject::BroadcastSignal( observer_signal sig ) {	for (observer_iter i = observers.begin(); i != observers.end(); i++) {		(*i)->ReceiveSignal( sig );		}	}	} // end namespace LGC	
Thanks again--this has all been very helpful.

This topic is closed to new replies.

Advertisement