[C++-sig] scriptable widget system
István Csanády
istvancsanady at gmail.com
Tue Sep 13 23:26:44 CEST 2011
Hi All!
I am developing a custom widget system. The widgets are written in C++,
since the rendering requires very high performance. The widgets are loaded
from xml files. A widget can contain several different UI elements, for
example a slider, a textbox, a label, buttons, etc... Until now I used a
simple but quite powerful solution to synchronize the UI values and the
python object attributes. The concept is the following: C++ UI elements can
have properties (see the code below). Properties can be bounded to a python
object's attribute. If a property changes, it sets the python attribute, and
after the setter ran, it gets the actual python value, and sets the
property's value to it. (For better understanding: when a property changes
because of some user input it rather suggests a value then set it. This is
useful when for example you want a slider to be able to set only to odd
values, or want to limit the availible values of a numeric text box. So this
way you have full control of the "control UI elements" values.) From the
other side: when you set an attribute of the python object the object checks
if it is bounded to any C++ property, and if it is, then it sets the
property to the value. Here are some code snippets:
This is the C++ property:
template <typename T> class Property : public PythonPropertyBinder {
public:
void bindToAttribute(PythonComponent* component,std::string name) {
//Add a PyCFunction to the bindings of the python object.
}
void unbind() {
//Remove the bindings.
}
T& operator= (const Property<T>& p) {
return (*this = p.value);
}
T & operator = (const T &i) {
if(!updating) {
updating = true;
if(component) {
component->setAttribute(i, pythonAttributeName.c_str());
component->getAttribute(value,pythonAttributeName.c_str
());
PyErr_Print();
}
else {
value = i;
}
updating = false;
notifyObservers();
return value;
}else {
return value;
}
}
operator T const & () const {
return value;
}
void addObserver(PythonPropertyBinderObserver* observer) {
observers.insert(observer);
}
void removeObserver(PythonPropertyBinderObserver* observer) {
observers.erase(observer);
}
Property(const T& v):Property() {
value = v;
}
Property():updating(false),component(nullptr) {
//set up stuff...
}
~Property() {
unbind();
}
private:
T value;
std::string pythonAttributeName;
//...not interesting
};
And for example a slider looks like the following:
class Slider : public View, public PythonPropertyBinderObserver {
public:
//...
Property<double> value;
Property<double> low;
Property<double> high;
//...
};
The python class:
class Component(object):
def bind_to_attribute(self,attribute_name, cpp_property_setter):
if not hasattr(self,attribute_name):
return
if self.binder_functions.has_key(attribute_name):
self.binder_functions[attribute_name].add(cpp_property_setter)
else:
s = set()
s.add(cpp_property_setter)
self.binder_functions[attribute_name] = s
def unbind_from_attribute(self,attribute_name, cpp_property_setter):
if self.binder_functions.has_key(attribute_name):
self.binder_functions[attribute_name].remove(cpp_property_setter
)
def __setattr__(self,attributename,value):
super(Component,self).__setattr__(attributename,value)
self.sync_attribute(attributename)
def sync_attribute(self,attributename):
if(hasattr(self,'binder_functions')
andself.binder_functions.has_key(attributename)):
s = self.binder_functions[attributename]
for cpp_property_setter in s:
cpp_property_setter()
'''
And several other methods...
'''
So this way I can simply bind a UI element's property to a python attribute,
and it is guaranteed that they will be the same (until a python property is
not accessed by its setter...)
For example:
class Slider(Component):
def get_value(self):
return self.__value
def set_value(self,value):
if((value<=self.high and value>=self.low) or (value>=self.high
andvalue<=self.low)):
if self.integers_only:
self.__value = round(value)
else:
self.__value = value
self.run()
def get_high(self):
return self.__high
def set_high(self,high):
self.__high = high
def get_low(self):
return self.__low
def set_low(self,low):
self.low = low
value = property(get_value,set_value)
low = property(get_low,set_low)
high = property(get_high,set_high)
And then all I have to do from the C++ UI:
Slider* slider = new Slider(Point(20, 30.), 195.);
slider->value.bindToAttribute(controller->getModel(),"value");
slider->low.bindToAttribute(controller->getModel(),"low");
slider->high.bindToAttribute(controller->getModel(),"high");
So this way:
- the python objects work like models
- the values are synced
- the script writer is not able access to any other properties of the UI
elements
- however one have total control of the values
- all the widgets can work without binded to any UI elements
- if I want to serialize the objects (and I want to), the PyCFunctions and
other - runtime added - objects can be easily excluded
- properties can be easily bounded and unbouded
- I did not have to create any new types, such as MySyncedFloat,
MySyncedString etc.
- the script writer doesn't even have to know about that a value will be
bounded to a UI element
- the UI elements are not strongly connected to any python code
- more UI elements can be bounded to a single python attribute for example I
can bind an attribute to a slider's max value and to a numeric text box at
the same time
- this approach can be easily used with a graphical interface builder: one
just have to drag and drop some UI elements to a widget, and tell the
interface builder that a property of an element will be bounded to a python
attribute. Then it can be exported to an XML and loaded runtime and bind the
properties runtime. Cool.
This approach worked very well. But... Few days ago our UI/UX designer
created some new UI elements which I call "batched controllers". A batched
controller is an array of same controllers for example sliders. The number
of the sliders can be changed runtime for example when I click on a button a
new slider is added. And this type of view can not be used with the previous
approach, since a value is determined by two values: the ordinal of the
slider and the value itself.
So my question is: does anybody have any idea how to create something that
works like I previously specified for these "batched controllers"
(==properties with indices)
István
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/cplusplus-sig/attachments/20110913/550c57b9/attachment-0001.html>
More information about the Cplusplus-sig
mailing list