Views User Guides > 2D Graphics > Prototypes > Using Prototypes in C++ Applications > Linking Prototypes to Application Objects
Linking Prototypes to Application Objects
This section describes the three methods that can be used to link prototypes to application objects:
*Setting Values Directly: This is the easiest way if you simply want to feed values from your application to the views.
*Using Group Mediators: This allows the application to both drive the interface and be notified of value changes produced by the user.
*Using Proto Mediators: This enables you to build object factories that will link application classes with prototypes, thereby creating the interface of a dynamic application automatically.
Setting Values Directly
The sample base_feed (contained in the <ILVHOME>/samples/protos directory) shows how to drive your interface from your application. Once you have downloaded a panel containing instances of prototypes, or created your instances in a manager or container, you retrieve the instances that you want to edit:
IlvGroupHolder* groupHolder= IlvGroupHolder::Get(manager);
IlvGroup* myThermometer= groupHolder->getGroup(“thermometer”);
Then, you change its values with the IlvGroup::changeValue method:
if (myThermometer)
myThermometer->changeValue(IlvValue(“temperature”,(IlUInt) 20)));
Using Group Mediators
A group mediator (class IlvGroupMediator) 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]);
}
Using Proto Mediators
A proto mediator (class IlvProtoMediator) is a subclass of IlvGroupMediator and is used to dynamically create prototype instances of a given class and place them in a manager or container. The idea is to design a specific prototype for each main application class. When an object is created by the application, a corresponding prototype is instantiated and placed in the manager. This allows you to create a graphical user interface for a complete application, separating the user interface design from the functional core of the application. The following samples from the <ILVHOME>/samples/protos directory implement this design pattern: interact_synoptic to build an air-traffic control simulator, and synoptic to build a simulator for a manufacturing plant.
For example, assuming the same base application (Machines and Boilers), you want each Boiler instance to be represented and edited at the same time by the user. Create a subclass of IlvProtoMediator:
class BoilerUI: public IlvProtoMediator, public MachineObserver {
public:
BoilerUI(IlvManager*m,Boiler*b)
:IlvProtoMediator(m,"BoilerPrototype",b)
{
observe(b);
IlvSymbol* vals[2] = {
IlvGetSymbol( "x"), IlvGetSymbol("y") };
update(vals); // Sets the position of the current instance.
// The application must have a way of specifying where to place
// the object. Alternatively, you can handle the placement by
// explicitly setting the x and y values of the BGO.
install(m); // Place the prototype in the manager
}
// Other methods are the same as the BoilerUI using the GroupMediator.
};
Now, the application can have a global “user interface factory” responsible for generating prototype instances as soon as it creates its internal objects. The code of this factory may look like the following pseudo-code:
class myApplication {
list<Boiler*> boilers;
void initUI (IlvManager* m) {
for each machine in boilers
new BoilerUI(m, machine);
}
void add_boiler(Boiler* b) {
boilers.append(b);
new BoilerUI(getManager(), b);
}
};
Published date: 05/24/2022
Last modified date: 02/24/2022