[C++] Event system suggestion (for a GUI library)?

Started by
12 comments, last by _goat 15 years, 9 months ago
Hi, I'm making a GUI library and I'm trying to figure what kind of system for handling events would be best / user-friendliest. I current made a bunch of structures with enum's for instant and state variables: instant is things like mouse-move, mouse-leftbutton-down, mouse-rightbutton-up. These variables are either false or true, and last for one frame and then fall back in their original mode: None. State variables defines what state an object has: the mouse button is down OR up. Internally I can basically do what I want and make it myself easy, but the point is that any user should be able to adapt his program easily to the library. Once a mouse button is pressed on a certain object, one would like to open a new window, for example. Now the question arises: what would be the most user-friendliest and flexible-est method for the event system to be? I could do it like SDL, where you pop an event from a list whenever you want it. - Pro: you decide yourself when to handle events - Con: checks need to be done to know what object has what event - Con: if something is busy and the programmer forgot to put the event-system function in that busy area, users could experience late response I could do it CEGUI's way, where one assigns a function to an object and event combination. - Pro: events are handled immediatly by the function - Pro: no checks need to be done to be sure its a certain object and a certain event, you appoint them both at initialisation - Con: lots of functions I could make users derive from a base-event class. It would somehow a combination of the above methods (but mainly CEGUI's method), you can group things more easily (the C++ way?) rather then functions for ever object/event pair, users can would still need to do checks inside the class to do this or that according to how the class is set up. Users would then assign a class to an object and handle per event a different function or whatever they prefer, inside the class itself. - Pro: events are handled immediatly by the class (-functions) - Pro: user has the freedom of doing what he wants - Con: checks have to be done internally in the class - Con: class overhead? FYI: each component is derived from the same base-class. Each component has a unique ID and name on it's owner. Each component can own other components. Components can have events that can only be acquired in the component itself, by using global events like keyboard or mouse input, these events are things like: mouse goes in/out the field of this component (instant), mouse is on/out of this field (state). Components themselves handle events aswell, a button component would change texture (if an mouse-over-texture is set) when a mouse goes over the button, for example. What would be your suggestion? Do you know other methods? Thanks!
[size="2"]SignatureShuffle: [size="2"]Random signature images on fora
Advertisement
I generally prefer actual events (option 2) to message systems (option 1) for UI. Forcing the user to derive from something is always a crappy option. It makes moving to/from your library more difficult, and the game less flexible.
When you look at them from a sufficiently high level, both your proposed solutions are equivalent: messages are propagated from the operating system to the individual GUI objects, which then react to them by altering their properties. The main question here is that of interfacing: the library user basically generates a set of GUI objects that map to its underlying logic model, and must then connect the model to the GUI structure using code that should be independent from the internal implementation of both the GUI structure and the model. This is a typical Model-View-Controller situation where the View is built using an external GUI library (that you are developing), the Model is opaque, and a Controller must be developed to have the two interact.

As such, a Controller is first responsible for translating user input into model modifications. This can be as simple as copying over a value from a text input field in the View to an object property in the Model, or as complex as parsing a gridview for differences with the model and reordering a collection in the model with support for undoing the operation. It is then responsible for retrieving the data from the model and setting up the view so that it displays the data. As such, the Controller appears as a two-way communications channel, and you have to decide an important factor: will it be triggered by the view, by the model, or by both? Synchronously or asynchronously?

Given the nature of human input (instantaneous modifications followed by long pauses), allowing the Controller to be triggered by the View is an efficient choice. As such, the Controller will provide the View with the code to execute when certain events occur, instead of wastefully and regularly polling the View for new events. Besides, you may wish to allow triggering by the Model, but that is irrelevant to GUI design, as long as both trigger paths are synchronous.

Next comes the question of whether you will need to respond to most events received by a component. If the number of handled events per component is small, then setting their handlers individually is optimal. If the number of handled events per components is big, then setting their handlers all together is optimal. The first situation is done using individual functions, while the second is done using inheritance or interface implementation.

Since C++ has issues with garbage collection and delegates (and the lack thereof), setting individual functions is only rarely an acceptable solution. Instead, I would suggest implementing a behaviour interface. For instance:

class IBehaviour{public:  virtual void OnMouseIn(const Component::MouseInEvent&) {}  virtual void OnMouseOut(const Component::MouseOutEvent&) {}  // .. Other virtual functions};struct Component{  property< boost::shared_ptr<IBehaviour> > behaviour;  property< rectangle > layout;  property< color > color;  component_list subcomponents;};class MyBehaviour { /* Implement functions */ };Component c;c.behaviour = new MyBehaviour();c.layout    = rectangle(0, 0, 100, 100);c.color     = 0x999999;


The property and component_list classes are designed to react to changes by forwarding the change event to the appropriate handler (for instance, to invalidate a tree of components), thus allowing Controller-triggered modifications of the View to happen without regular polling of the View structure.
I've attempted to implement the propery<class T> before in C++ with limited success in terms of acceptable syntax.

For instance, suppose you had declared:
property<int> age;

If I tried to do:
printf("%i\n", age);

Tt would print the address of the variable, not the actual value. This is because the property class only overloads the = and () operators. printf must retrieve the value a different way.
Author Freeworld3Dhttp://www.freeworld3d.org
Quote:Original post by soconne
Tt would print the address of the variable, not the actual value. This is because the property class only overloads the = and () operators. printf must retrieve the value a different way.


This is not an issue with the property implementation. It's an issue with printf, which happens to have known issues with the C++ type system, and with class types in general. If we judged our C++ code in terms of compatibility with printf, we would even have to drop std::string! I wouldn't drop the idea (or even consider it problematic) if the property class didn't support a broken legacy feature such as printf—C++ programmers using printf already know that they must pay attention to the types of their arguments, because of ubiquitous C++ type conversion operators, so this is no issue at all:

printf("%d", static_cast<int>(the_property));
Quote:Original post by ToohrVyk
printf("%d", static_cast<int>(the_property));


Ahh ok. Your way seems better. Thanks! I'm currently developing my own GUI architecture as well, and I opted out of using properties simply b/c of the extra space needed. For N # of integer values in your class, you'd have N # class instantiations for the property class. Seems like a waste?

Author Freeworld3Dhttp://www.freeworld3d.org
Quote:Original post by soconne
I'm currently developing my own GUI architecture as well, and I opted out of using properties simply b/c of the extra space needed. For N # of integer values in your class, you'd have N # class instantiations for the property class. Seems like a waste?


Class instantiations are cheap in a GUI architecture: you don't create that many components, and they don't have that many properties.
So, ToohrVyk, you suggest an internal base class from which the user then would derive from, and make their own function above the virtual definitions in the base-class?

Each component would then have his own behaviour class object, or could multiple components have the same behaviour class object associated (it's a pointer, so I suspect that the idea behind your design? How, then, would you keep track of which component has a new event to be processed?
[size="2"]SignatureShuffle: [size="2"]Random signature images on fora
I liked your syntax for declaring a property, but after searching the net, I couldn't find any that simply took the ValueType in the template parameter list. So I created my own. The strength I see in my implementation is that the get/set methods can be global functions, and not just class methods. The possible drawback is there exists a middle class, namely IAccessor, that sits between the property class and actually calling your get/set methods. Is there an efficiency problem with this?

template <typename ValueType>class property {private:	class iAccessor {	public:		virtual void set(const ValueType& value) = 0;		virtual ValueType get() = 0;	};	template<class T>	class CAccessor : public iAccessor {	public:		CAccessor(T* classObject, void (T::*set)(const ValueType&), ValueType (T::*get)()) {			m_classObject = classObject;			m_set = set;			m_get = get;		}		void set(const ValueType& value) {			(m_classObject->*m_set)(value);		}		ValueType get() {			return (m_classObject->*m_get)();		}	private:		T* m_classObject;		void (T::*m_set)(const ValueType&);		ValueType (T::*m_get)();	};	class FAccessor : public iAccessor {	public:		FAccessor(void (*set)(const ValueType&), ValueType (*get)()) {						m_set = set;			m_get = get;		}		void set(const ValueType& value) {			(*m_set)(value);		}		ValueType get() {			return (*m_get)();		}	private:				void (*m_set)(const ValueType&);		ValueType (*m_get)();	};public:	property() {		m_accessor = 0;	}	template <class T>	void init(T* classObject, void (T::*set)(const ValueType&), ValueType (T::*get)()) {		m_accessor = new CAccessor<T>(classObject, set, get);	}	void init(void (*set)(const ValueType&), ValueType (*get)()) {		m_accessor = new FAccessor(set, get);	}	ValueType operator = (const ValueType& value) {		this->m_accessor->set(value);		return value;	}	operator ValueType() {		return this->m_accessor->get();	}private:	iAccessor* m_accessor;};


std::string name;void setName(const std::string& value) {	printf("set name: %s\n", value.c_str());	name = value;}std::string getName() {	printf("get name: %s\n", name.c_str());	return name;}int age;void setAge(const int& value) {	printf("set age: %i\n", value);	age = value;}int getAge() {	printf("get age: %i\n", age);	return age;}class Test {public:	Test();	property<std::string> name;	property<int> age;};Test::Test() {	name.init(&setName, &getName);	age.init(&setAge, &getAge);}void main() {		Test t;	t.name = "Sean";	t.age = 25;	cout << ((std::string)t.name).c_str() << t.age << endl;}
Author Freeworld3Dhttp://www.freeworld3d.org
Well I ran a stress test for my version and the typical version as found in the following tutorial:
http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4031/

I simply had both call the set method for an std::string property 33 million times. My version took 8 seconds to complete. The above article's version took 6 seconds to complete. Suffice it say, I don't think I should worry about performance, even in a real-time graphics or gui app.
Author Freeworld3Dhttp://www.freeworld3d.org

This topic is closed to new replies.

Advertisement