GEOS-SC consists of one program, GeosSC.exe. It is made up of pieces of code called modules. Each module is an application, library, or driver. We will show how to create a new application module.
For this exercise, we will create an application that
uses a button to display the phrase "Hello World!" We'll explain the functionality
each application is responsible for. We'll see how to create a simple user
interface.
At each step of this tutorial, you see a set of links like this: (header | source | resource). They link to the complete source code for the application: its header (.h) file, main source code (.cpp) file, and resource (.str) file. (The resource file holds user-visible strings. We'll discuss this later.)
To create your own sample application:
If you are not already in the GEOS-SC Workspace, enter it now: Choose GEOS-SC workspace from the GEOS_SC SDK program group in the Start menu. (You can also open this workspace in Microsoft Visual C++ by choosing Open Workspace from the File menu and selecting GeosSC.dsw.)
To create a new application
C:\geos-sc\appl\>mkdir mystuff C:\geos-sc\appl\>mkdir mystuff\tutsamp |
First, create the C++ source file tutsamp.cpp. Select "New" from the "File" menu.
In the dialog box, select "C++ Source File", enter the File name "tutsamp.cpp", and specify the path to the tutsamp directory in the Location field (in this example, the path is "C:\GEOS-SC\appl\mystuff\tutsamp"). Click "OK".
Repeat these steps to create tutsamp.h, and tutsamp.str. Be sure to specify that these are "C/C++ Header File"s, not "C++ Source File"s. When you are done, these files will appear under Geos-SC files in the file view, as shown in step 4.
C:\geos-sc\appl\mystuff\tutsamp>mkjam Jamfile created. No toolchain specified, using "Microsoft32" No configuration specified, using "ErrorCheck" Using NT.jam Using Microsoft32.jam Using (optional) GeosSc.jam ...found 12 target(s)... ...updating 2 target(s)... MakeDspFile ..\..\..\appl\mystuff\tutsamp\tutsamp.dsp ...updated 2 target(s)... C:\geos-sc\appl\mystuff\tutsamp> |
... Module appl samples shell envshell ; Module appl samples internet netman ; Module appl browser ; Module appl mystuff tutsamp ; Module appl javalaunch : Inactive ; ... |
Fill in the file tutsamp.str with user-visible strings. (resource | header | source)
To open the tutsamp.str file, double-click its entry in the FileView section of the Workspace view (under the GeosSC Files heading).
Replace the contents of this file with the text found
at the (resource) link.
.prefix TUTSAMPAPP FRAME_TITLE "Sample" BUTTON_LABEL "Click Here" LABEL_TEXT "Hello World!" |
This application has three user-visible strings. To allow this, we set up the .str file with a .prefix directive, then define names and values for the three strings.
To use these strings, the application's C++ code must
On Double-Byte Character Set systems, the contents of
the .str file should be Shift-JIS (a superset of ASCII).
Fill in the file tutsamp.h with the code that represents the application to the system. (resource | header | source)
Again using the FileView tab, open the tutsamp.h file. Replace its contents with the text found at the (header) link.
Let's look at the first part of tutsamp.h:
#ifndef _TUTSAMPAPP_H_ #define _TUTSAMPAPP_H_ #include <toolkit/uifact.h> //for UI objects (FlexFrame, etc.) #include <ui/appbase.h> //for AppBase class #include <toolkit/lisact.h> //for ActionListenerInterface class #include <resapp/resapp.h> //for ResidentApplication class class TutorialApp : public ActionListenerInterface, public AppBase { public: TutorialApp(void); // SetAppContext is overridden to build the UI. virtual void SetAppContext(const TCHAR *context); // GetAppContext is overridden to save the application state. virtual TCHAR *GetAppContext(void); // ActionPerformed is overridden to intercept button presses. virtual void ActionPerformed(ActionEvent& event); // Exit is overridden to clean up when the application quits. virtual void Exit(void); private: // Helper function to create our UI. Result BuildUI(void); // Flag to let us know if we have already built our UI. Boolean _builtUI; // This object displays the "Hello World!" string. // We keep its pointer handy so that code can // reference it to make it appear and disappear. FlexLabel *_helloworldLabel; // We keep a pointer to this object // so that we can delete it // when shutting down the application's UI. VerticalFlowLayout *_layout; }; |
The system notifies the Application Base object of important events, including
Because we know that the application base object is in memory when the application is running, it's typical to use this object to keep track of resources associated with that object. We also use the application's class to define variables within the application's "scope" (and avoid cluttering the global name-space). In this example, we'll use the application base object member variables to store some values which will be useful throughout the application's life cycle (_builtUI, _helloworldLabel, and _layout).
The application base object is a handy place to define
miscellaneous application functionality. This application doesn't have
much functionality--it just responds to button presses. We let the application
respond to these button presses by making it inherit from ActionListenerInterface
and overriding ActionPerformed().
Later, we'll see how to make this object "listen" to a
button.
In tutsamp.h: define the resident application. (resource | header | source)
Let's look at the rest of the code in tutsamp.h:
// Strings that are not user-visible const TCHAR *const TUTSAMP_APP_NAME = _TEXT("tutsamp"); // Subclass of ResidentApplication to register the app with the system. class TutorialSampResidentApplication : public ResidentApplication { public: TutorialSampResidentApplication() : ResidentApplication(TUTSAMP_APP_NAME) {}; virtual AppBase *CreateAppBase(void) { return new TutorialApp; } }; static TutorialSampResidentApplication tutsampApp; #endif // _TUTSAMPAPP_H_ |
When the system wants the application to run, it calls CreateAppBase(). Our resident application responds by creating an object of the application's base class.
We use the _TEXT() macro to define a string
constant. Like TCHAR, _TEXT() makes your code adaptable
to different character sets. It returns a TCHAR string.
Fill in the file tutsamp.cpp with the body of the application's code. (resource | header | source)
Again using the FileView tab, open the file tutsamp.cpp. Replace its contents with the text at the (source) link.
Let's look at the first part of this code.
#include <system.h> //for standard GEOS-SC types #include <toolkit/uifact.h> //for UI factory #include <flexui/javaspui.h> //for UI hints #include <tstring.h> //for TCHAR string functions (Tstrcpy) #include "tutsamp.h" //for our class definition #include "resource.h" //for our user-visible strings // Sets the name the system uses for the application static AppNameAttribute tutsampAppName(&tutsampApp, TUTSAMPAPP_FRAME_TITLE); TutorialApp::TutorialApp() : _builtUI( FALSE ), _layout( NULL ) {}; |
We create an AppNameAttribute
object, attaching it (by means of its constructor) to the resident application
object. This attribute object maintains the human-readable string by which
the Program Manager (envshell) can identify the application to
a user. We use the string TUTSAMPAPP_FRAME_TITLE, which we defined
in the .str file. We define a constructor for the class to initialize
its member data.
In tutsamp.cpp: handle start-up and shutdown. (resource | header | source)
Let's look at the next piece of the source file:
// Updates the application's state, building the UI if necessary. /* virtual */ void TutorialApp::SetAppContext(const TCHAR *context) { // Build the application's UI if we haven't already. if ((!_builtUI) && BuildUI() != SUCCESS) { EC_WARN("Could not create UI."); Exit(); } // If there's a context string, parse it and update the UI. if (context) { if (Tstrcmp( context, _TEXT("+")) == 0) { _helloworldLabel->SetVisible(TRUE); return; } if (Tstrcmp( context, _TEXT("-")) == 0) { _helloworldLabel->SetVisible(FALSE); return; } EC_WARN("Unrecognized context string"); } } // End of TutorialApp::SetAppContext() // Encodes the application's state into a string. /* virtual */ TCHAR * TutorialApp::GetAppContext(void) { if (_builtUI != TRUE ) { return NULL; } TCHAR scratch[30]; if (_helloworldLabel->IsVisible() == TRUE) { Tstrcpy( scratch, _TEXT("+")); } else { Tstrcpy( scratch, _TEXT("-")); } TCHAR *context = new TCHAR[Tstrlen( scratch ) + 1 ]; if ( context == NULL ) { EC_WARN("Could not allocate context string; will not save context."); return NULL; } Tstrcpy( context, scratch ); return context; } // End of TutorialApp::GetAppContext // The application is closing; it's time to clean up. /* virtual */ void TutorialApp::Exit(void) { delete _layout; // Call inherited member function to get default behavior. AppBase::Exit(); } // End of TutorialApp::Exit() |
Subclassed AppBase member functions handle these cases:
Since we are using strings of TCHARs, we use the special
string functions Tstrcmp(), Tstrcpy(), and Tstrlen()
defined in tstring.h. Use these functions as you would use the
analogous standard ANSI C string functions, but prepend "T" to the function
name.
In tutsamp.cpp: define BuildUI(), a utility function to set up the user interface objects. (resource | header | source)
Our SetAppContext() function uses a utility
function, BuildUI(), to build the application's user interface.
We define the body of that function here. This function creates the objects
which form the user interface of the application. First, it creates the
frame: a screen or window to which it can add the other objects later.
Let's look at how the function sets up this object.
Result TutorialApp::BuildUI(void) { FlexFrame *frame = theUIFactory->CreateFlexFrame( HINT_FRAME_WITH_NO_CLOSE_BUTTON, TUTSAMPAPP_FRAME_TITLE ); if (frame == NULL) { EC_WARN("Couldn't create frame."); return FAILURE; } if (Add(frame) != SUCCESS) { EC_WARN("Couldn't add frame to the AppBase."); delete frame; return FAILURE; } ... |
You may wonder why we didn't just create the frame with new FlexFrame. Our frame isn't exactly a FlexFrame. It's of a subclass of FlexFrame, a subclass defined by the device's specific UI, a subclass with specialized behavior. (You might end up with a different subclass depending on the lookHint you specify.) We'll interact with this object as if it were a FlexFrame.
The TUTSAMPAPP_FRAME_TITLE string comes from our .str file.
You should always check for failed memory allocations. We check to make sure that the frame was created successfully. If it wasn't, we stop creating UI and return FAILURE. We also use the EC_WARN() macro to report the problem. EC_WARN() is the GEOS-SC equivalent of "printf() debugging"--it prints its message to the DOS shell. You can learn about benefits and details of EC (Error Checking) code in Coding Conventions: Error Checking Code. A list of EC macros is located in the API reference for the sys_info library.
When we've successfully created a frame, we want to add it to the application base object's UI tree. This tree is a hierarchy of UI objects which keeps track of which objects are contained by other objects. When one UI object is contained within another, we say that one is the child and the other is the parent. For example, if a button appears in a dialog box, we say the button is a child of the dialog box. (Only UI objects of classes that inherit from FlexContainer can contain other objects.) To add a UI object as a child of another UI object, use the parent object's Add() member function. You should always check the return value of any OS function that can fail. Here, we check the return value of the application base object's Add() to make sure that the frame was added successfully.
If something went wrong adding the frame to the application
base object, we delete the frame. We need to delete the frame
but not the application base object because when the application exits,
it automatically deletes itself and any objects in its UI tree. We need
to delete the frame because it never became part of the UI tree.
In tutsamp.cpp: Continue writing BuildUI(). (resource | header | source)
Let's look at the rest of the BuildUI() function
definition.
... _layout = new VerticalFlowLayout; if ( _layout == NULL ) { EC_WARN("Couldn't allocate frame's layout"); return FAILURE; } frame->SetLayout(_layout); FlexButton *button = theUIFactory->CreateFlexButton( HINT_BUTTON_JAVA_LOOK_AND_FEEL); if (button == NULL) { EC_WARN("Couldn't create button."); return FAILURE; } if ( frame->Add(button) != SUCCESS ) { EC_WARN("Couldn't add button to the frame."); delete button; return FAILURE; } button->SetLabelString(TUTSAMPAPP_BUTTON_LABEL); if ( button->AddActionListener(*this) != SUCCESS ) { EC_WARN("Couldn't add action listener to the button."); return FAILURE; } _helloworldLabel = theUIFactory->CreateFlexLabel(); if ( _helloworldLabel == NULL ) { EC_WARN("Couldn't create label."); return FAILURE; } if ( frame->Add(_helloworldLabel) != SUCCESS ) { EC_WARN("Couldn't add label to the frame."); delete _helloworldLabel; return FAILURE; } _helloworldLabel->SetText(TUTSAMPAPP_LABEL_TEXT); _helloworldLabel->SetVisible( FALSE ); frame->SetVisible(TRUE); _builtUI = TRUE; return SUCCESS; } // End of TutorialApp::BuildUI() |
We create a VerticalFlowLayout
object and attach it to the frame. This causes the frame to arrange its
children one above the other. If we'd used another kind of layout
object, the frame would arrange its children differently.
After creating a button, we use its SetLabelString()
member function to set the string it will display, "Click Here". We use
its AddActionListener()
member function so that the button will tell the application base object
when the user presses it. Our application can act as a button's listener
because it inherits from ActionListenerInterface.
(Later, we'll see how the application base object handles
button presses.)
(To learn more about the user interface model, see the
Flex UI chapter.)
In tutsamp.cpp: Write an ActionPerformed()
function to handle button presses. (resource
| header | source)
/* virtual */ void TutorialApp::ActionPerformed(ActionEvent& event) { USE_IT( event ); if (_helloworldLabel->IsVisible()) { _helloworldLabel->SetVisible(FALSE); } else { _helloworldLabel->SetVisible(TRUE); } } // End of TutorialApp::ActionPerformed() |
This function takes an argument event, with information
about the button press. We don't need this information, and ignore this
argument. The C++ compiler would generate warnings about unused arguments,
but we avoid this by using the USE_IT() macro. This macro doesn't
do anything, it just avoids the warning.
Build () and execute the executable ().
If the Build tab of the Output window reports "Error executing jam" then there was a compile or link error. Look for notification of errors and warnings.
If some other application appears in the GEOS-SC emulator display window, we need to bring our application to the top. If the display window is showing another application, make the display window active, press the F1 key, and choose Sample in the dialog box that appears, and press the Switch To button.
Click the "Click Here" button to make the "Hello World!" label visible.
To use the state-saving code, do the following:
A dialog box appears.
Behind the dialog box, our application disappears. It has exited.
Behind the dialog box, our application reappears. It has started up, and restored itself.