|
Communication Between the Century class and its GUI
|
Tom Hilinski
|
hilinski@lamar.colostate.edu
|
19Sep99
|
Introduction
The Century soil organic matter model is implemented as a C++ class with two
types of controllers: a command-line interface and a GUI using the V library.
The command-line controller writes all messages for the user to stdout (cout).
Interaction with the GUI controller is much more complex.
The model must interact with the GUI at specified time steps to display a
progress bar and to provide the user with status messages.
If not run as a stand-alone process, the Century model, class TCentury, needs
to communicate with its controlling functions, e.g., the GUI. Ideally this
should be implemented using a platform-portable threaded class. In a rather
crude attempt to allow messaging without threads, I have implemented the
following:
-
msgFunction
-
A TCentury member variable which points to an
external function accepting a string argument. This allows Century to pass
messages to an external function for display to the user. e.g., the V library
class, vNotice. Use the member function UseMsgFunction to set this pointer.
-
yearFunction
-
A TCentury member variable which points to an
external function accepting a long argument containing the current simulation
year. This allows an external function to monitor the progress of the
simulation. e.g., a progress bar display. Use the member function
UseYearMsgFunction to set this pointer.
-
cancelRequest
-
A TCentury member variable which is true when a
request to cancel the simulation is made. Each iteration of the simulation loop
checks for this request. If true, the simulation loop is exited. Use the member
function PostCancelRequest to post this message. This request is generated
when the user clicks the Cancel button on the Progress Bar dialog.
Member Variables and Functions
The declarations for these variables and their inline functions are:
//--- data - for asynchronous communication with external functions
protected:
void (*msgFunction)(const char*); // function to display a msg
void (*yearFunction)(const long); // function to get sim. year
bool cancelRequest; // true if request to cancel
// the simulation posted.
bool simCompleted; // true if the simulation ran
// to completion.
//--- functions
public:
// UseMsgFunction
// Sets the external function which will receive messages from Century.
void UseMsgFunction (
void (*useMsgFunction)(const char* msg) )
{
if ( useMsgFunction )
msgFunction = useMsgFunction;
}
// UseYearMsgFunction
// Sets the external function which will receive the current
// simulation year from Century.
void UseYearMsgFunction (
void (*useYearFunction)(const long year) )
{
if ( useYearFunction )
yearFunction = useYearFunction;
}
// PostCancelRequest
// Posts a simulation cancel request.
void PostCancelRequest ()
{
cancelRequest = true;
}
// SimulationCompleted
// Returns true if the simulation has completed, else false.
bool SimulationCompleted () const
{
return simCompleted;
}
Displaying Messages
The Century model is built in two forms, a command-line form that runs from a
shell prompt, and a GUI form, in which a GUI controller initiates the model
simulation. In order to display message to the user the model has to
distinguish, at compile-time, between sending messages to stdout or to some
external function which will display the message. As shown below, a
#define
is used to distiguish two different global functions for this purpose. The
function declarations are:
#ifdef STANDALONE
// assume that Century DOES have direct access to the console for I/O
void DisplayUserString (
const char *str); // string to display to user
#else
// assume that Century DOES NOT have direct access to the console for I/O
void DisplayUserString (
void (*msgFunction)(const char*), // pointer to external function
const char *str ); // string to display to user
#endif
The function definitions are brief
#include
void DisplayUserString (
const char *str)
{
if (str && *str)
cout << str << endl;
}
void DisplayUserString (
void (*msgFunction)(const char*), const char *str )
{
if (str && *str && msgFunction )
(*msgFunction)(str);
}
Optionally, rather than using a
#define
, the model could be using a run-time check to deterimine which function to
call.
In the Century model, the simulation has a set of tasks it has to perform at
the start of each simulation year. This is a convenient place to call the
external function which accepts the current simulation year value. The first
few lines of this simulation loop is shown below. A class TSimTime manages the
simulation time calculations; the instance of this class used in the snippet is
named st. This snippet also shows handling of an asynchronous
Cancel request, and the displaying of simulation time to the user.
char msg[161]; // String for user messages
ostrstream os (msg, sizeof(msg)); // ...and a stream to create them
while (moreToDo) // Do for each time step (month)
{
if ( st.IsStartOfYear() ) // Perform annual tasks
{
if ( yearFunction && st.year % updateInterval == 0 )
(*yearFunction)(st.year);
EachYearsTasks ();
}
// Now do the model's work for this time step...
// Write output variables
if ( st.TimeForOutput() )
{
os << "output time: year " << st.year
<< ", month " << (short)st.month << ends;
#ifdef STANDALONE
::DisplayUserString (msg);
#else
::DisplayUserString (msgFunction, msg);
#endif
os.seekp (ios::beg);
}
// Check for cancelation requested posted asynchronously
// via PostCancelRequest().
if ( cancelRequest )
break;
// Update simulation time, then quit or continue...
}
How the GUI Handles User Messages to the Model
In order to receive messages from the model, the V application class cannot use
member functions, since their pointers cannot be used by the Century class. So,
global functions are use, as defined below.
// DisplayToMsgWindow
// Writes the string contents to the message window.
// This is for Century to have a function pointer that is not
// a class member for its century->UseMsgFunction() method.
static void DisplayToMsgWindow (const char* str)
{
if ( str && *str )
{
MsgText (str);
::myApp.CheckEvents (); // V will process the event
}
}
// UpdateProgressDialog
// Updates the progress bar in the progress dialog.
// This is for Century to have a function pointer that is not
// a class member for its century->UseYearMsgFunction() method.
static void UpdateProgressDialog (const long year)
{
if ( !::progDlg ) // anything to do?
return;
static long numYears = 0, yearStart = 0;
// calc only once
if ( !numYears )
{
yearStart = ::curMgmt->GetSimInfo().yearStart;
numYears = ::curMgmt->GetSimInfo().yearEnd - yearStart + 1;
}
// calc percent completed
int percent = (float)(year - yearStart + 1) / numYears * 100.0 + 0.5;
::progDlg->Update (percent);
::myApp.CheckEvents (); // V will process the event
}
// PostCancelRequest
// Posts a cancel request to the Century process.
// This is used by class TProgressDlg in its UseCancelFunction() method.
static void PostCancelRequest ()
{
if ( ::century ) // anything to do?
::century->PostCancelRequest ();
}
The member function which starts the simulation also sets up the message
functions. In the snippet below, the global pointer to the Century class
instance is called to assign the message function pointers. Also, the progress
dialog class, TProgressDlg, has a Cancel button, which, when pressed, results
in the posting of a Cancel request to the simulation instance, via the global
function PostCancelRequest, shown above.
// RunCentury
// Run the Century simulation using the current configuration.
// Returns false if run w/out error, else true if error or canceled.
bool TApp::RunCentury ()
{
// Initialize the model instance...
::century = new TCentury ( /* ... */ );
// Assign pointers to the message functions
::century->UseMsgFunction ( ::DisplayToMsgWindow );
::century->UseYearMsgFunction ( ::UpdateProgressDialog );
// Set up the progress bar dialog
::progDlg = new TProgressDlg (this, /* ... */);
::progDlg->UseCancelFunction ( ::PostCancelRequest );
// Run simulation (does not show exception handling)
::century->RunSimulation ();
}
Copyright © 1999, Thomas E. Hilinski. All rights reserved
|