Contribution: Save/Load Components, and more! (source code)


#1

This is the game changer right here, a simple functor that iterates every Component in a hierarchy recursively and calls a member function of a specific class, if the Component also derives from that interface. Implemented using dynamic_cast and boost::bind (or std::bind / std::tr1::bind).

Simple implementation, powerful results. You could use this to save and load settings, or to broadcast “events” to components that expose the interface, without having to manage a list of “event Ids” or manually registering a Listener. You don’t need to know who is listening for these member calls, which means you can re-parent components, add and remove them, and in general not care where they are located or if they are there at all.

// Calling this functor will recursively traverse a Component and all of its
// children, and call a member function for each Component that exposes the
// interface (determined via dynamic_cast).
//
// This requires boost::bind, std::tr1::bind, or std::bind to work.
//
/* Usage example

  struct PersistentObject
  {
    virtual void onSaveSettings (XmlElement* xml) = 0;
    virtual void onLoadSettings (XmlElement* xml) = 0;
  };

  void saveAllWindowSettings (XmlElement* xml)
  {
    for (int i = 0; i < Desktop::getInstance().getNumComponents(); ++i)
    {
      Component* c = Desktop::getInstance().getComponent (i);
    
      // Any Component of any open window that derives from PersistentObject
      // will have its onSaveSettings() member called by this broadcast.
      //
      componentBroadcast (c, &PersistentObject::onSaveSettings, xml);
    }
  }
*/

class componentBroadcast
{
public:
  template <class C>
  componentBroadcast (Component* c, void (C::*f)())
  { call <C> (c, boost::bind (f, _1)); }

  template <class C, class T1>
  componentBroadcast (Component* c, void (C::*f)(T1), T1 t1)
  { call <C> (c, boost::bind (f, _1, t1)); }

  template <class C, class T1, class T2>
  componentBroadcast (Component* c, void (C::*f)(T1, T2), T1 t1, T2 t2)
  { call <C> (c, boost::bind (f, _1, t1, t2)); }

  template <class C, class T1, class T2, class T3>
  componentBroadcast (Component* c, void (C::*f)(T1, T2, T3), T1 t1, T2 t2, T3 t3)
  { call <C> (c, boost::bind (f, _1, t1, t2, t3)); }

  template <class C, class T1, class T2, class T3, class T4>
  componentBroadcast (Component* c, void (C::*f)(T1, T2, T3, T4), T1 t1, T2 t2, T3 t3, T4 t4)
  { call <C> (c, boost::bind (f, _1, t1, t2, t3, t4)); }

  template <class C, class T1, class T2, class T3, class T4, class T5>
  componentBroadcast (Component* c, void (C::*f)(T1, T2, T3, T4, T5), T1 t1, T2 t2, T3 t3, T4 t4, T5 t5)
  { call <C> (c, boost::bind (f, _1, t1, t2, t3, t4, t5)); }

  template <class C, class T1, class T2, class T3, class T4, class T5, class T6>
  componentBroadcast (Component* c, void (C::*f)(T1, T2, T3, T4, T5, T6),
             T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6)
  { call <C> (c, boost::bind (f, _1, t1, t2, t3, t4, t5, t6)); }

  template <class C, class T1, class T2, class T3, class T4, class T5, class T6, class T7>
  componentBroadcast (Component* c, void (C::*f)(T1, T2, T3, T4, T5, T6, T7),
             T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7)
  { call <C> (c, boost::bind (f, _1, t1, t2, t3, t4, t5, t6, t7)); }

  template <class C, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8>
  componentBroadcast (Component* c, void (C::*f)(T1, T2, T3, T4, T5, T6, T7, T8),
             T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8)
  { call <C> (c, boost::bind (f, _1, t1, t2, t3, t4, t5, t6, t7, t8)); }

private:
  template <class Interface, class Functor>
  void call (Component* component, Functor const& f)
  {
    Interface* const object = dynamic_cast <Interface*> (component);

    if (object != nullptr)
      f (object);

    for (int i = 0; i < component->getNumChildComponents (); ++i)
      call <Interface> (component->getChildComponent (i), f);
  }
};

For the usage example, all you need to do in order to receive the “save” and “load” callbacks, is change your existing Component-derived class to also derive from PersistentObject, and implement the two functions:

class MyComponent : public Component, public PersistentObject
{
public:
    void onSaveSettings (XmlElement* xml);
    void onLoadSettings (XmlElement* xml);
};

Components in the hierarchy that don’t expose the PersistentObject interface are simply skipped.

Syntax:

// supports up to 8 parameters.
componentBroadcast (component, memberFunction, param1, param2, ..., param8);

#2

Anyone ?!


#3

It’s a nicely packaged class, well done!

In my projects, I don’t really ever let my UI classes store very much state, so whenever I’m saving the app’s state I’d do so via my data model, rather than the components themselves - so this wouldn’t really be something I think I’d use. But as a way of recursing and calling a functor on a tree components, it’s a nice bit of coding!


#4

Thanks! I hear you about the data, and for domain-specific data doing it in the model is certainly the way to go. But this was designed for something more along the lines of saving UI settings. For example, panels that can be expanded and collapsed, resizable panes, column header and sort settings.

This modular approach also makes it easier to write re-usable component widgets that can be placed anywhere in the component hierarchy and still function correctly without having to know about other components and where they are in the hierarchy.