Using Group Mediators
A group mediator (class ) is used to connect an object of the application to a prototype and serves as an interactive graphic editor for the object (also called an object inspector). The samples inspector and synoptic (contained in the <ILVHOME>/samples/protos directory) implement a group mediator and can be used as a baseline.
The following code sample shows how to develop an application that cleanly separates the user interface from the application code. Assume that you have an application that includes a Machine base class and a Boiler specialization class:
class Machine { // The base class of most application objects.
protected:
list<MachineObserver* > observers;
};
class MachineObserver { // A notification mechanism serving as a
// generic communication means between objects.
public:
void observe(Machine* m) { m->observers.append(this); }
virtual void notify (Machine*);
};
class Boiler : public Machine { // The class for which you want
// to create an object inspector.
public:
// Temperature is an attribute you want the user to have control of.
void set_temperature(float) { ...
for each observer in observers
observer->notify(this);
}
float get_temperature();
};
These classes perform a simulation, a process control, or any computational activity independent of any kind of interactive or graphic behavior. A group mediator allows you to implement a graphical user interface for the Boiler without introducing any dependencies in the application classes, which are assumed to be much more complex.
For this, you want to create a subclass of IlvGroupMediator that will handle the graphic representation and the user interaction of a machine of class Boiler:
class BoilerUI : public IlvGroupMediator, public MachineObserver {
public:
BoilerUI(IlvGroup* ui, Boiler* b) : IlvGroupMediator(ui, b) {
MachineObserver::observe(b);
if (!temperatureSymbol)
temperatureSymbol=IlvGetSymbol(“temperature”);
}
Boiler* boiler() { return (Boiler*) getObject(); }
void queryValues(IlvValue* vals, IlUInt) const {
if (vals[0].getName() == temperatureSymbol))
vals[0] = boiler()->get_temperature();
}
void changeValues(const IlvValue* vals, IlUInt) {
if (vals[0].getName() == temperatureSymbol))
boiler->set_temperature(vals[0]);
}
void notify(Machine*) { update(); }
static IlvSymbol* temperatureSymbol;
};
This class serves as a bridge between a prototype instance and an application object. It defines four methods:
-
The constructor establishes a link and the observe(b) statement declares to the application that it wants to be notified of internal changes occurring to the boiler.
-
The changeValue() method, which is called whenever the user changes an attribute of the object. It notifies the object that it should update its temperature value. It can handle other attributes as well.
-
The queryValue() method, which is called whenever the prototype needs to update its values. It queries the internal values of the object and transfers them to the user interface.
-
The notify() method, which must be called explicitly from within the application whenever an internal attribute of the object changes in order for these changes to be reflected in the user interface. Any call to Boiler::set_temperature() automatically notifies all observers, which means that the notify() method does not need to be called explicitly. Other applications that do not implement an observable/observer design pattern such as this may want to call notify() from other parts of the internal code.
Once the mediator class has been defined, you can dynamically link an object of the application to a prototype instance that is used as a boiler inspector:
IlvGroup* myBoilerInspector = groupHolder->getGroup("BoilerInspector");
BoilerUI* myBoilerUI = new BoilerUI(myBoilerInspector, myBoiler);
You can change the application object being inspected by the prototype at any time:
myBoilerUI->setObject(myOtherBoiler);
Even though this mechanism requires some application-specific coding, it is very generic—any application data structure can be adapted to use it. Once the mediator class has been designed, the user interface and the application become completely independent entities. Each can be developed and maintained separately. The user interface is developed using Views Studio and the application using any application development environment.
The group mediator also has a lock mechanism that can be used to prevent unnecessary refreshes of the user interface. In the above example, the boiler set_temperature method calls the notify() method of the BoilerUI to refresh the user interface. Since the change of values comes from the UI, it may be unnecessary to perform this last refresh. Testing the locked flag prevents such refreshes:
void BoilerUI::changeValues(const IlvValue* vals, IlUInt) {
if (locked()) return;
if (vals[0].getName() == temperatureSymbol))
boiler->set_temperature(vals[0]);
}