Tutorials > 2D Graphics > Linking Your Prototype to Application Objects > Step 3: Creating a Group Mediator to Handle User Feedback
Step 3: Creating a Group Mediator to Handle User Feedback
You want the application not only to drive the display, but also to handle some user input that affects internal parameters. To do this, the prototype myproto in Views Studio has been slightly extended to add a slider-like object. The user will use this slider to change the animation rate of the thermometer, making the temperature rise and fall faster or slower depending on its value.
You may want to open the library myLib.ipl in Views Studio to check the attributes and behaviors that have been added in order to achieve this effect.
Linking an application object and a prototype instance is done using the Mediator design pattern, implemented by the class IlvGroupMediator. To use this class, you should include the following file:
#include <ilviews/protos/grouplin.h>
Defining a Token Application Object
In this tutorial, assume that there is one simple object, a thermometer, that you want to display and control through a user interface implemented with BGOs:
#if defined(ILVSTD)
#include <cmath> // for sin() function
ILVSTDUSE
#else
#include <math.h>
#endif
struct Thermometer {
IlFloat temperature;
IlUInt acceleration;
IlUInt curval;
IlvTimer timer;
// The display argument is here to allow the use of a Views timer.
Thermometer(IlvDisplay*);
~Thermometer() {}
void adjust_temp();
static void TimerProc(IlvTimer*, IlAny);
};
 
Thermometer::Thermometer(IlvDisplay* dpy)
:temperature(20), acceleration(4), curval(0),
timer(dpy, 0, 200, TimerProc, this) // see Note that follows
{
timer.run();
}
 
void Thermometer::adjust_temp()
{
temperature = 50.0 + (40.0 *
sin(degreesToRadians((IlDouble)curval)));
curval = (curval + acceleration) % 360;
}
void Thermometer::TimerProc(IlvTimer*, IlAny arg)
{
((Thermometer*)arg)->adjust_temp();
}
Note: Some systems may raise a warning about the use of “this” in the construction above. The construction is safe, however, and the warning may be ignored.
The method adjust_temp is called periodically to implement the dynamics of the application. You will notice that this class does not offer any user-interface specific functionality. It is meant to function as a stand-alone piece of software that can work with other application objects (in a plant simulator, for instance), but nothing has been done to allow the interactive viewing or editing of its values. In this tutorial, you will create a user interface for this object without modifying its definition at all.
In the main program, after creating the display, create an instance of this application object:
// create and initialize an application object.
Thermometer myThermometer(display);
Defining the GroupMediator of an Application’s Class
To create a user interface for an instance of Thermometer, you define a subclass of GroupMediator that handles this Thermometer class:
struct TemperatureWatcher: public IlvGroupMediator
{
TemperatureWatcher(IlvGroup* g, Thermometer* a)
: IlvGroupMediator(g, a) {
if(!tempSymbol) tempSymbol=IlvGetSymbol("temperature");
if(!accSymbol) accSymbol=IlvGetSymbol("acceleration");
}
IlBoolean changeValues(const IlvValue*, IlvUShort);
void queryValues(IlvValue*, IlvUShort) const;
inline Thermometer* getThermometer() const {
return (Thermometer*) getObject(); }
static IlvSymbol* tempSymbol;
static IlvSymbol* accSymbol;
};
IlvSymbol* TemperatureWatcher::tempSymbol;
IlvSymbol* TemperatureWatcher::accSymbol;
You need to define how to update the values of Thermometer when they are edited by the user. The changeValues method establishes a one-to-one correspondence between attribute values of the prototype instances and the Thermometer members:
// Method handling with user input and updating the application.
IlBoolean
TemperatureWatcher::changeValues(const IlvValue* v, IlvUShort n)
{
if(locked()) return IlFalse;
for (IlUInt i=0;i<n;i++) {
if (v[i].getName() == accSymbol)
getThermometer()->acceleration = (IlUInt) v[i];
// You do not want the temperature value to be editable by the user.
// If this was the case, the following code would simply be added:
// else if (v[i].getName() == tempSymbol)
// getThermometer()->temperature = (IlFloat) v[i];
}
return IlTrue;
}
You need to specify through which prototype attributes the values of Thermometer are to be reflected in the user interface. The queryValues method establishes here a one-to-one correspondence between the Thermometer attributes and the values of a prototype instance representing it:
// Method that synchronizes the group to the application values.
void TemperatureWatcher::queryValues(IlvValue* v, IlvUShort n) const
{
for (IlUInt i=0;i<n;i++) {
if (v[i].getName() == tempSymbol)
v[i] = getThermometer()->temperature;
else if (v[i].getName() == accSymbol)
v[i] = getThermometer()->acceleration;
}
}
Linking an Application Object to its Representation
To instantiate the user interface for the Thermometer application, create an instance of myThermometer that watches it. In the main function, add the following code after having retrieved the group myThermometer from the group holder (but before showing the main window):
TemperatureWatcher watch(tempRep, &myThermometer);
watch.update();
IlvTimer timer(display, 0, 200, TimerProc, &watch);
timer.run();
The timer will periodically check the state of the thermometer instance to update the presentation if needed.
Synchronizing the Application Object and its Display
Finally, you need to define the asynchronous update routine before the main function:
static void
TimerProc(IlvTimer*, IlvAny arg)
{
TemperatureWatcher* watcher=(TemperatureWatcher*) arg;
watcher->update();
}
The TemperatureWatcher will periodically update itself according to the current values of the Thermometer objects.
Note: Other mechanisms could be implemented, such as having the Thermometer class behave according to the Observer/Observable design pattern and being able to notify other objects of its changes. This method is slightly more efficient. The samples in $ILVHOME/samples/protos implement this technique. However, the use of a triggered asynchronous observer is a useful technique to implement applications that have real-time constraints: at any time you can adjust the rate at which the user interface is refreshed, if needed to meet severe time constraints.
Published date: 05/24/2022
Last modified date: 02/24/2022