[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