tsc_Form

The tsc_Form class is used to create windows and display textual or graphical data.  It is similar in concept to the .NET Form class.  An example (Key in projection) is shown to the right.

A user form is created by subclassing tsc_Form and implementing the desired behaviour.

A form is populated with one or more controls, of which there are many types, all subclassed from tsc_Control.  Many control subclasses are called Fields - the differences between a control and a field are explained under tsc_Control.

Screensnap of key in projection form

Trimble recommends you use the standard positioning (Layout_Auto) wherever possible.  Survey Core runs on many different platforms with different screen sizes, aspect ratios and resolutions, and more are being added constantly.  Positioning at the pixel level is strongly discouraged; most UI classes provide positioning and sizing in percentages of the window size or grid size, which generally allows forms to work across most platforms.

The standard Survey Core positioning lays out controls on a form in the order they are added to the form, either to the right (if there is room) or below the previous control.  There is no absolute limit to the length of a form - it becomes scrollable when necessary.

Custom layout is described in detail at tsc Form - Custom layout box model

Public Methods

Note: Some methods of tsc_Form return a pointer to the form.  This serves no purpose other than to allow calls to be chained.

tsc_Form ();
The constructor takes no parameters.  The form's subclass may of course be constructed with any parameters desired, and the constructor should configure the form, the controls, and add the controls to the form.  This ensures the initial drawing of the form is accomplished in one operation.  Any setting-up that can only be done to fully created a form or control could be performed in the OnLoad event handler.

void Text (const char* title);
tsc_String Text ();
Sets or gets the title text of the form, displayed in the Survey Core title bar. 

void   ContextHelp(x_Code helpTopic);
x_Code ContextHelp();
Sets or gets the x_Code that represents the help topic that this form should be associated with.  If no help is available, use X_NULL.

tsc_DialogResult  ShowDialog ();
Displays the form and waits until the form has been accepted, closed, escaped, or a thread shutdown has been requested.  This is the typical way to show a form.  Note that the thread that calls ShowDialog must be hosting an active UITask.

bool IsShowing();
Returns whether the dialog is currently running (ie, the window exists).  Does not imply topmost, focus or visibility.

void Close ();
Closes the form with a dialog result of tsc_DialogResult_Ok

tsc_DialogResult DialogResult ();
Returns the last form result, or tsc_DialogResult_None if the form is still running.  Some form event handlers must return a tsc_DialogResult. If this return value is tsc_DialogResult_None then the form continues to run; if any other value is returned then that value becomes the value returned by the DialogResult function.  If the form was executing within a ShowDialog (the usual situation), then the return value from an event handler (other than tsc_DialogResult_None) causes the ShowDialog function to close the form and return to the caller.

The tsc_DialogResult enum contains a member called tsc_DialogResult_Custom.  This or any higher value may be used for other purposes by the plugin.

void SetDialogResult (tsc_DialogResult result);
Sets the dialog result and closes the form.  The call to ShowDialog() will return, and pass the supplied tsc_DialogResult value to the caller.

tsc_FormControlList Controls;
The list of controls/fields on this form.  The list may be altered at any time, but preferably before the form is Shown.  If the Controls list is changed while form is displayed the form will be rebuilt - which may cause a slight screen flicker.  Each control in this list must have a unique identifier; see tsc_FormControlList for ways to use the same prompt for more than one control.

tsc_Control* ActiveControl();
Returns the control with the current focus - note that this can return NULL.

bool ActiveControl (tsc_Control* control);
Selects the given control.  That is, the control is given focus.  This method does not move the form window to the top. If called before the form has been built (eg, in the constructor), the requested control will be selected after it has been added to the form. Returns true if the operation was completed, or false if the field is non-editable or is not present on the form.

static void SetAppCursor(tsc_AppCursors newValue);
enum tsc_AppCursors { Cursors_Default = 0, Cursors_WaitCursor };
Sets or clears an hourglass wait cursor.  This disables all user input and is global in scope, and not just when the form has focus.

Note that on hardware with a touch screen rather than a mouse, there is no mouse cursor as such, and SetAppCursor displays a fixed image instead of changing the mouse's cursor.

 void SetIsTileable(bool isTileable); This method has been replaced by SetSizing.
Sets whether or not this form is able to be tiled along with the map/video form.  This method should be called in the form's subclass constructor and passing true will allow the form to be tiled.  By default a form is not tiled, so there is no need to use this method if the form is not to be tiled. Note: If a form is set tileable using this method, and is also set to a full height from using SetSizing (tsc_WindowSizeMode parameter set to Size_High), then the SetSizing is ignored and a Size_Normal form is created. 

Event handling

Form event handlers (described in more detail below) are always called on the same thread as the form's thread, so cross-thread locking or method invocation is never required.

Many form event handlers return a bool.  If the event was handled completely, the handler should return true, which ends all handling for the event, which will include the default handling. Note that returning true for an event such as OnClick() will prevent the click from reaching the control.  If false is returned, other handlers are searched for and called, until a handler returns true or no more handlers are found.

Note that UI events rarely occur when the form is not the topmost active window, however all other event handlers handlers such as timers will continue to be called up until the form is closed, regardless of its display state.

Handlers are called for pending events at the first opportunity, which is when the form thread is in (or about to enter) a wait state. This state occurs naturally after an event has been handled.

Form wait states occur after exiting an event handling function, and also during calls to most long-running methods such as tsc_TsModes::Measure, or tsc_SurveyCore::Sleep.  Event handling can be explicitly allowed by calling tsc_SurveyCore::DoEvents()

Event handlers may need to protect against events occurring recursively, such as using a simple boolean to track the current state. The following code sample shows how to protect against recursive UI events.

class myForm : public tsc_Form

{

    bool running = false;

    //...     


    virtual bool OnSoftkeyClick (x_Code softkey) 

    {

        if (softkey == X_Start) 

        { 

            if (running) return false;

            running = true;

            this->DoLongRunningOperation ();

            running = false; 

        } 

        if (running && softkey == X_Stop)

        {

            this->KillLongRunningOperation ();

            // We don't set running to false here, that will happen when DoLongRunningOperation returns.

        }

        return true;

    }

};

Threading caveats

Methods on a form and its controls must only be called from the thread on which the form was originally created.  At most times this is not a problem, since the SC API ensures that event handlers will always be called on the same thread as the form. 

If you wish to control a form from a different thread (eg, from a different UITask), we would first suggest a redesign since multiple threads are rarely necessary with SCAPI. However, if such a thing is really required then these classes may be of use:

tsc_Event and ​tsc_EventDelegate are useful for sending data between threads, since the event handler will always be called on the correct thread for the form.

Timer functions

int StartTimer (double repeatSecs);
This method starts a timer that will fire at the given repeat rate.  The first event will occur repeatSecs after StartTimer is called.  In all other respects it is the same as the following overload:

int StartTimer (double initialDelaySecs, double repeatSecs);
The initialDelaySecs parameter allows the time from the StartTimer call to the first timer event to be controlled.  If repeatSecs is zero, negative, or double_Null, the timer is a 'one-shot' and fires only once; otherwise the timer continues to fire at the specified repeat rate.  If the initialDelaySecs is zero, negative, or double_Null then there is no custom inital delay and the periodic timer first fires after repeatSecs.  If both are invalid, a one shot timer is fired immediately. Time values are in seconds and fractions (eg, 0.1 for 100 milliseconds), but don't expect high accuracy or useful periods less than about 0.005 seconds (5 mS).

The timer ID is returned, and may be used when calling StopTimer().  The timer ID is also passed to the form's OnTimer() event handler, which is called each time the timer fires.  These timer events are sent via a message queue and are therefore not accurate beyond say 0.05 sec; to compute times more accurately, use tsc_SurveyCore::LinearTime().  Timers only operate while the window actually exists, so any early StartTimer calls will be remembered and not started until ShowDialog() is called.

Periodic timers that are no longer required should be stopped by calling StopTimer()

void StopTimer (int timerId);
Stops a timer.  The timerId parameter should be the ID that was obtained from StartTimer.  It is not necessary to stop a one-shot timer, except to prevent it firing.  All timers will stop when the form is closed.  Although the timer ID is freed by StopTimer, it is never reused; timer IDs increment perpetually.

void OnTimer (int timerId) override;
Override this function to process timer events.  Take care about calling API functions that wait (eg, MessageBox, ShowDialog, Sleep) in a periodic timer handler, since the handler may continue to be called recursively while in the wait. 

To prevent recursive entry to a timer event handler, a simple boolean gate could be employed:

void MyForm::OnTimer(int timerId)

{     

    static bool inUse = false;  // No special locking required, always happens on the same thread.

    if (!inUse)

    { 

        inUse = true;

        // ... slow processing here ...

        inUse = false;

    } 

}

Windows events

Form events come in two distinct types, windows and monitor.  Normal windows events that pertain to the form will call an overridable virtual function beginning with On.  For instance, after the form is created but before it is displayed, the OnLoad() function will be called.  The form base class contains an empty function for all events, so any that are not overridden will simply do nothing beyond their standard behaviour.  Events are always called on the form's thread, so cross-thread problems are avoided and the need to "Invoke" is mostly unnecessary.

Because Survey Core is primarily designed to run on platforms with touch-sensitive screens rather than a mouse, "mouse" events are actually generated by stylus or touch actions.  On a device which does support a mouse, clicks on buttons other than the left button are mostly ignored, as is mouse "hover".

void OnLoad() override;
The OnLoad event is called after the form has been created but before it is displayed.  It is a good place to perform initializations that can't or should not be done in the constructor.  Configure and add the controls in the constructor rather than in OnLoad, to improve drawing time and prevent flicker. Size and layout mode setting must be done before OnLoad - in the constructor is best.

void OnFocus(bool gained) override;
Called when the form is gains or loses focus.  This may occur more often than expected, not just on the first change of focus.  Take care what operations are performed in the OnFocus method - some functions, such as adding a control to a form, may cause another OnFocus event, which in turn could add another control, and so on until the application runs out of resources and crashes.

void OnSizeChanged() override;
When the size of the area available to the form changes, this event fires. On a fixed-layout device this would be unusual, however on an Android device it would typically happen when the screen was rotated between portrait and landscape. It will also occur on a windows desktop (emulator) when the window is resized, or on some devices where a draggable sizing bar is provided. In all cases the form will be redrawn after OnSizeChanged is called so any custom layout changes should be made in this function using the new values available in tsc_SurveyCore::WindowSize() etc.

bool OnClick (tsc_Control& control) override;
The OnClick event is called for any mouse or keyboard event that 'clicks' a control.  A reference to the affected control is passed in.  Use the GetIdentifier() method of the control to determine which control was clicked.  Return true if the event has been handled.  Note that only a small subset of controls generate this event.  Fields do not generate mouse events; use the OnFieldCompleted event for these.

bool OnSelectionChange (tsc_Control& control) override;
Some controls allow multiple selections, such as List controls.  When the currently selected item is changed in the control, by means of an arrow key or a tap-and-hold, this event handler is called. Use the control parameter to determine which control was affected and query the control for which item has been selected.  Return true if the event has been handled. If false is returned, the event may be passed to other handlers if any exist.

bool OnFieldParse(tsc_Control& control, const char* fieldText, tsc_String& error) override;
Optionally override this to perform the parsing of text in edit control-based fields at the form level.
Return true to prevent the standard SurveyCore parsing, or false to  have the field parsed in the usual way.
To indicate an validation failure, populate the error string with a (translated) error message. This will place focus back on the field, allowing the user to correct the input or abandon the edit.

If the parse was successful, the OnFieldParse method may reformat and update the field's text using control->SetText(...)

This event will not fire if the tsc_Control has decided to handle the parse itself. We wonder what this means; the original author has moved on.

enum tsc_ContextFieldMenuButtonBehaviour OnFieldIsContextMenuButtonPresent(tsc_Control& control) override;
Optionally override to control whether the context field menu button appears for a control (only effective for fields that are based on edit fields).  This can override the menu items that a field would normally include for itself.

void OnFieldContextMenuRequested(tsc_Control& control, tsc_FieldContextMenu& menuItems) override;
Override to append or edit the context field menu options when a menu is requested.  Generally the field will supply and handle its own menu but occassionally you may want to append form state-sensitive options and will be able to do so here.

bool OnFieldContextMenuSelected (tsc_Control& control, x_Code& selected, tsc_String& fieldText, int& cursorPos) override;
Override this method to handle the field context menu selection.  Generally the field will supply and handle its own menu but if additional options have been added they can be processed here.  This will only be called if the tsc_Control has not signaled field completion from within its own handler first (that is, if the tsc_Control handler returns true then this handler will not be called).  See tsc_Control documentation for more details on how to use this event

Return true to indicate that the field edit should be completed with no further menu handling from the SurveyCore system. If false is returned then the selected x_code will be handled if the field knows what to do with it (ie, if it could appear on the field's standard menu).

You can change the selected item to allow sequential handling and change the fieldText to change the field contents.

If you change the field text, then consider updating the cursor position.

bool OnFieldCompleted (tsc_Control& control) override;
This handler is called when the user has completed editing a field, which occurs when s/he moves off the field by pressing Tab, Enter, touching a different field or control, and so forth.   Return true if the event has been handled. Some fields (a CheckBox for instance) emit this event as soon as a change in state occurs.

tsc_DialogResult OnSoftkeyClick(x_Code softkey) override;
The softkey buttons at the bottom of a form each have an x_Code to identify them.  There are a varying number of visible softkeys depending on the platform but they are always accessible by scrolling. The big Enter and Escape buttons are also considered to be softkeys only if they have been added to the form's control list; otherwise they behave in a default fashion and generate no events other than OnClose, etc.  When one of the softkeys is clicked, the OnSoftkeyClick event handler is called.  The return value tells the form whether to continue (if tsc_DialogResult_None is returned) or to close and exit (if any of the other values is returned. See tsc_DialogResult).

Note: Because x_Codes are used throughout Survey Core forms to identify events regardless of their origin, each x_Code must be unique on the form.  For instance, a button control and a softkey cannot use the same x_Code to identify the event.

bool OnFormClosing (tsc_DialogResult result, bool formIsModified, tsc_FormClosingPermission& permission) override;
Override this to control how the form is closed.  The handler is called before any close action has been taken, and whether the form is actually closed or not may be controlled by setting the permission parameter. The default is to close the form.   Return true if the event has been handled and shouldn't be passed to other handlers (this does not affect how the form behaves).

The following example uses OnFormClosing to check that an entered point name starts with a 'P' and does not allow the form to be submitted until it does.

class PointForm : public tsc_Form

{

    tsc_PointField* pf;

    PointForm ()

    {

        pf = this->Controls.Add(new tsc_PointField (X_PointName, "P100"));

    }

    virtual bool OnFormClosing (tsc_DialogResult result, bool modified, tsc_FormClosingPermission& permission)

    {

        if (result == tsc_DialogResult_Ok && !pf->Value().StartsWith ("p", Case_Insensitive))

        {

            pf->DisplayWarningTip ("Point name doesn't start with a 'P'!");

            permission = FormClosing_Deny;

        }

        return true;

     } 

};

 

bool OnMouseDown (tsc_Control& control, int mouseX, int mouseY) override;
Generated by a touch or left mouse button down event only on specific controls such as tsc_ImageControl. Avoid peforming actions on MouseDown, it's generally preferable to use OnMouseUp. The handler must return true if the event has been handled, or false if not.  If false is returned, the form may take other actions and/or call other event handlers.

bool OnMouseUp (tsc_Control& control, int mouseX, int mouseY) override;
Generated by the end of a touch or a left mouse button up event only on specific controls such as tsc_ImageControl. The handler must return true if the event has been handled, or false if not.  If false is returned, the form may take other actions and/or call other event handlers.

bool OnMouseDrag (tsc_Control& control, int mouseX, int mouseY) override;
Generated by a touch and drag or mouse drag event on specific controls such as tsc_ImageControl. The handler must return true if the event has been handled, or false if not.  If false is returned, the form may take other actions and/or call other event handlers.

bool OnMouseDoubleClick (tsc_Control& control, int mouseX, int mouseY) override;
Generated by a double touch or mouse double-click event on specific controls such as tsc_ImageControl. The use of the double-click is discouraged because of the difficulty in tapping the same spot twice (sometimes just one touch is difficult!). The handler must return true if the event has been handled, or false if not.  If false is returned, the form may take other actions and/or call other event handlers.

Monitored events

Monitored events are distinct from windows events, in that a form does not receive them by default.  They are not directly related to the form or to windows.  For example, there is a group of events that are generated by the interface to a total station.  This group of events are abstracted into an interface called tsc_ITsMonitor.  By inheriting a form (or any class) from such an interface, these events may be captured and handled by functions on the form or class.  Any class derived from tsc_Object may process monitor events; forms work this way simply because they are also tsc_Objects.

There are two types of monitor events - global and local.  Global events are sent to any class that wishes to receive them, whereas local events are generated by some particular operation started by the plugin and relate only to that operation.  Local events aren't described here; they are documented in the appropriate tsc_Monitor subclasses.

To handle global events, these three things must be done:


Event delegates

It is possible to generate events or consume events by using the tsc_Event and tsc_EventDelegate classes.  These classes provide an eventing system that allows events to be passed between classes.

Form layout methods

Control layout on a form is automatic by default, using the Survey Core form layout system.  Most Survey Core forms use this system, as can be seen from the Key-in Projection form shown on the right.  Use of automatic layout is encouraged because it will respond correctly to size changes and rotations, whereas a custom layout will have difficulty in being as responsive.

If the size available to the form changes, the OnSizeChanged event is raised, which can react to the event if necessary. Rotating a device's screen will also cause this event.

You can specify a grid layout system by calling SetLayoutMode.  The individual controls can then be assigned row and column placements (or -1 as a default which represents the next available space).

A detailed explanation of custom layouts can be found on the page tsc Form - Custom layout box model.

void SetSizing (tsc_WindowSizeMode sizeMode);
tsc_WindowSizeMode GetSizing ();
enum tsc_WindowSizeMode {Size_Normal, Size_High, Size_Full, Size_Tiled};
These functions allow the size of the form window to be obtained or altered.  Setting should be done in the form constructor.  The default value if SetSizing is not called is Size_Normal.  If the form sizing obscures the softkey buttons then they will not be enabled, although the tsc_EnterSoftkeyControl and tsc_EscSoftkeyControl will still perform their functions.  The size of the form may be specified when using either the automatic or the custom layout mode. The form pictured to the right is using Size_High. Also see tsc_WindowSizeMode.

void SetLayoutMode(tsc_FormLayoutMode mode, tsc_LayoutFlags layoutFlags = tsc_Layout_None);
tsc_FormLayoutMode GetLayoutMode();
To specify the layout mode use SetLayoutMode.  Automatic layout will be used unless specified otherwise by this method.  This layout mode applies to all controls on the form; it must be set before the form is created (eg, in the constructor), and not subsequently changed.

The optional tsc_LayoutFlags parameter allows various aspects of the form layout to be controlled. Layout flags apply to all fields on a form except for any inside group boxes.  The fields within a group box obey the GroupBox's layout flags, not the form's. If they are given no position then they are laid-out using Layout_Auto within the box and don't follow the custom layout.  If a position is given for a field inside a group box it behaves the same as fields outside the group box.

enum tsc_FormLayoutMode { Layout_Auto = 0, Layout_Custom, Layout_Custom_Stretch };

When using Layout_Auto, fields are added to the form in a flow layout fashion, and they move around according to the screen size. Fields overlapping the right margin will be moved to the next line, and a scroll bar is added for fields extending beyond the bottom. Layout_Custom uses a fixed grid which does not change in size with changes of window size, but the form becomes scrollable when the window size is smaller than the grid. The grid size is based on the emulator or TSC7 size, so that designs done on these platforms will look the same on other platforms.  Layout_CustomStretch is basically the same as Layout_Custom, but instead of using scroll bars, the entire grid is stretched or compressed to fit on the screen. The width an height are computed separately, so aspect ratio is not preserved. The controls themselves do not change in size except that edit fields beyond the right margin will be reduced in width to fit.

void SetCustomGridDimensions (int columns, int rows);
void GetCustomGridDimensions (int& columns, int& rows);
The number of grid cells can be specified by the SetCustomGridDimensions method.  The cells are divided equally across the space remaining in the specified window size after margins are subtracted.  The window height used for this calculation is the standard 800-pixel-high window displayed by the emulator.  A smaller window will only reduce the vertical spacing if the layout mode is Layout_CustomStretch. A large number of cells may be specified to permit more precise placement of controls, or a larger one cell per control if they are to be organised in a simple grid layout.  Values smaller than 1 in either dimension are increased to be 1, and the default setting is 2x5.  SetCustomGridDimensions has no effect if SetLayoutMode(Layout_Auto) has been specified.

void SetCustomMarginsPercent (int  top, int  right, int  bottom, int  left);
void GetCustomMarginsPercent (int& top, int& right, int& bottom, int& left);
Use this function to set the four margins around the grid area; the margins constrain the area available to the grid.  They are specified as percentages of the inside dimension of the window.  Note that the order of parameters is the same as the order of margins in a CSS statement as used with HTML page layout.  The margins are ignored when the survey core automatic layout is being used; it is necesary to call SetLayoutMode(Layout_Custom) or SetLayoutMode(Layout_CustomStretch).

void SetGeometricMovement(bool useGeometricMovement);
bool GetGeometricMovement();
The default movement when the user moves with arrow keys around the fields is to follow the flow layout in the order control were added.
Specifying geometric movement means the directional keys follow the actual position of the controls in the window.
Deprecated at version 4.00

Data validation

bool IsControlValid (tsc_Control& control);
Returns the data validity of a specified field, which must be present on the form. No UI is performed.

bool ValidateControl(tsc_Control& control);
Requests validation of a specified field, which must be present on the form.
If the field fails validation, this call will tell the user what is wrong with the field.

x_Code IsFormValid ();
Checks the data validity of all visible fields on the form. Returns the identifier of the first invalid field, or X_NULL if no field is invalid.

x_Code ValidateForm();
Checks the data validity of all visible controls on the form.
This call will invoke UI to tell the user what is wrong with invalid fields.
Returns the identifier of the first invalid field, or X_NULL if no field is invalid.