Working with Embedded Wizard: Managing component state
How a GUI component appears and behaves depends not only on its implementation but also is determined by the state information stored actually in the component. For example, a simple push button can assume the states released or pressed depending on whether the user is currently touching the button or not. When the user interacts with the button, the button updates its current state accordingly.
In the simplest case, the current state can be reflected by the properties of views existing within the component. The push button, for example, can appear with white background when it is in the released state and with red background when it is in the pressed state. The current state of the button is in this case simply reflected by the property Color of a Filled Rectangle view.
In practice however, GUI components are more complex and in the component implementation you have to manage many different states. The push button, for example, could also assume the states disabled and enabled, or focused and not focused. If the button is in the disabled state, your implementation should configure the button to appear e.g. with pale colors and the button should ignore all user interactions. In turn, if the button is in the focused state the button should display a kind of visual feedback indicating to the user, that this button is actually able to receive keyboard events.
A slider component, in turn, requires other state information to be managed. Here the slider stores a value (usually a number) reflecting the current position of the slider's thumb. When the user interacts with the slider, the value changes accordingly. The states are thus not necessarily discrete values. They can also be numbers, strings, colors, etc. All the data types implemented in Chora are suitable to represent the state information.
In this chapter you learn about the concept of component states and how you implement the state management in your particular component. Moreover you learn here also about the common states, supported originally by every GUI component.
Store the state in variables and arrays
Variable and array members are ideal to store within the component its current state information. When the state changes, the affected member is simply initialized with a new value. To query the current state it is sufficient to evaluate the member. For example, a toggle button can assume two states which alternate every time the user taps the button. This state information can thus be stored in a simple bool variable. The following Chora code demonstrates the possible implementation for such toggle button how it handles the events generated by the Touch Handler:
// Let's assume, the toggle button contains a variable 'toggleState' // representing its current (latest) state. Then with every touch // interaction the variable is alternated. toggleState = !toggleState; // Depending on the new state update the appearance of the component. // Assuming the toggle button is composed from a background rectangle // and label text, adapt the colors of those both views. if ( toggleState ) { // White text on red background if the button is in the 'switched on' // state LabelText.Color = #FFFFFFFF; BackgroundRectangle.Color = #FF0000FF; } else { // Red text on white background if the button is in the 'switched off' // state LabelText.Color = #FF0000FF; BackgroundRectangle.Color = #FFFFFFFF; } // Finally, notify the owner of the toggle button, that it has alternated // its state. This is achieved by sending signals to slot methods stored // in properties e.g. 'OnSwitchOn' and 'OnSwitchOff' existing in this // button: if ( toggleState ) signal OnSwitchOn; else signal OnSwitchOff;
Similarly, you can manage within your component multiple variables reflecting the different component states. Every time a state changes, the corresponding variable is modified. The usage of variables to manage the component's current state is thus not a complex task. With the following example project you can analyze the above toggle button implementation more in detail:
Please note, the example presents eventually features available as of version 8.10
Store the state in properties
Similarly to the above presented approach with variables, properties can also be used to store the current state of a component. However, compared with a variable, the property has the additional advantage, that it can be configured conveniently in the Inspector window. In this manner, you can predetermine the desired initial state for the component already at the design time.
This can, for example, be the value a slider component should initially assume. In this case the property has two functions, it stores primarily the current state of the slider component and at the same time it implements the interface to control the slider. Moreover, through this interface the owner of the slider component can query its current state conveniently.
The following example demonstrates the implementation of such slider component with a property reflecting its current state. Every time the user pulls on the slider's thumb, the slider notifies its owner about the alternation and the owner can query the slider's new value by simply evaluating the property:
Please note, the example presents eventually features available as of version 8.10
Common component states
Besides the above described technique to maintain within a component its particular state information, every component manages internally also few states, which are common to all GUI components. During the lifetime of the component instance these states are updated automatically by the Mosaic framework so usually you don't need to take care of them. You should however understand which states are available and what is their function. The following table provides an overview of the most important states. The entire set is described in Core::ViewState:
State |
Description |
---|---|
Enabled |
This state is active if the component is potentially able to react to user interactions, be it via keyboard or touchscreen. This state corresponds to the value of the property Enabled of the component itself and can depend on the enabled state of the superior component. See also DeriveEnabledState. |
Selected |
This state is active if the component is the selected one within its superior owner component. The selection is controlled by the property Focus of the owner component. |
Focused |
This state is active if the component is the selected one within its owner and the owner itself does actually belong to the focus path. Accordingly the component is able to receive keyboard events. |
Modal |
This state is active if the component is modal. Modal components grab temporarily all user inputs suppressing other GUI components from being able to react to user interactions. As such, modal components are useful to display e.g. alert panels the user has to confirm first in order to continue with the application. A component becomes modal when you pass it as parameter in an invocation of the method BeginModal. |
Dialog |
This state is active if the component is actually presented as dialog in context of its owner component. A component becomes dialog when you pass it as parameter in an invocation of the method PresentDialog. |
During the lifetime of the component instance, when one of the above described states is activated or deactivated, the component invokes automatically its own method UpdateViewState. You can override this method and implement there code to react to the state alternation. Usually, the implementation will update the appearance of the component so it reflects its new state. For example, when a push button component looses the Enabled state, the button should change its appearance indicating to the user that it is actually not available. Typically, such disabled button will appear pale or grayed out.
The UpdateViewState method is called automatically by the Mosaic framework when it is necessary. You don't need or even you should not call this method directly. It is sufficient, that you override and implement the method. Usually, when you have created a new empty GUI component as described in Creating new components the component contains already the overridden UpdateViewState method.
★If your component doesn't contain the overridden UpdateViewState method, you have to override it first.
★Then you can open the method and ...
★... by using the Code Editor implement your desired functionality to adjust the component appearance to its new state.
When the method UpdateViewState is executed, your implementation can evaluate its parameter aState. The value of this parameter corresponds to a set containing the currently active states. By using the instant method contains you can individually test, whether the particular state is currently active or not. The following code demonstrates a possible implementation of the UpdateViewState method:
// First give the ancestor class a chance to process this event. super( aState ); // Now, is the component actually enabled? if ( aState.contains( Core::ViewState[ Enabled ])) { // Yes, the component is actually 'Enabled'. Then display // the component (e.g. its label, etc.) with black color. LabelText.Color = #000000FF; } // Otherwise the component appears grayed out else LabelText.Color = #AAAAAAFF; // Is the component actually Focused and Modal? Then show some focus // border around it. BorderView.Visible = aState.contains( Core::ViewState[ Focused, Modal ]);
The following example demonstrates a push button implementing the UpdateViewState method. Accordingly, disabled buttons appear grayed out. The focused button, in turn, appears with an additional border. In this manner the user can recognize the state in which the button remains actually:
Please note, the example presents eventually features available as of version 8.20
TIP
Starting with Embedded Wizard version 13, changing the Enabled state of a component can be propagated to all views existing within the component automatically affecting so the visual state of the views. To enable this behaviour set the property DeriveEnabledState in the component which should update its state according to the state of its superior component. That means, you activate the mode in the sub-ordinated component.
UpdateViewState() and component specific states
Your implementation of the UpdateViewState method can also take care of other component specific states and if necessary update the component appearance. Generally, whenever the component alternates one of its particular states, the component can request an invocation of its UpdateViewState method. Thereupon, within the method, the component can process the state alternation and adapt its appearance. Ideally, with this approach the complete update code can be accommodated within the UpdateViewState method only.
The invocation of the UpdateViewState method is requested by explicitly calling the method InvalidateViewState. Technically seen, InvalidateViewState limits to remember internally that the component wants the UpdateViewState invocation. The invocation is performed later shortly before the next screen update begins. In this manner, several InvalidateViewState invocations are accumulated and result in a single UpdateViewState invocation. This is in particular convenient if the code to execute during the update is complex.
For example, let's imagine a sophisticated slider component providing properties for the slider's current value and the lower and the upper end of the possible value range. Based on these three properties the slider calculates the actual position of its thumb. Thus modifying one property, no matter which, requires the component to recalculate the position. With the classical approach, you would implement the update code for every property individually in its corresponding onset method. From this results that when all three properties are changed at once, the position would be recalculated unnecessarily three times.
With the UpdateViewState approach you can simplify and optimize this implementation. Instead of recalculating the thumb position, the property, after being changed, limits to request the component to update its state only. This is implemented in the onset method associated to the affected property. For example, for the property MinValue (which in our slider example determines the lower end of the possible value range) the onset method could be implemented as follows:
// If the property 'MinValue' doesn't change - nothing to do. if ( pure MinValue == value ) return; // Remember the new value of the property. pure MinValue = value; // AND request the component to update its state. InvalidateViewState();
Accordingly, modifying the property MinValue doesn't update immediately the position of the slider's thumb. The implementation remembers the new property value and requests the component to run its UpdateViewState method. The thumb position is updated later when the method UpdateViewState is executed. The following could be the corresponding implementation:
// Always give the ancestor class a chance to process this event. super( aState ); // Calculate the range in pixel inside which the thumb can be moved. // Here we assume, the range is determined by the width of the 'Track' view. var int32 pixelRange = Track.Bounds.w - Thumb.Bounds.w; // Calculate the range in which the slider's current value is valid. var int32 valueRange = MaxValue - MinValue; // From the slider's current value and the both ranges calculate the corresponding // pixel position of the thumb. Note, the thumb can be moved along the 'Track' view // only. var int32 pos = Track.Bounds.x1 + (( Value - MinValue ) * pixelRange ) / valueRange; // Arrange the thumb at the just calculated position. Thumb.Bounds.origin.x = pos;
As far as it is possible with your particular component design, we recommend to use this here presented UpdateViewState approach to perform all component relevant update operations. The following example demonstrates once more the implementation of a slider component using this approach:
Please note, the example presents eventually features available as of version 8.10