Working with Embedded Wizard: Managing variants
Variants is a powerful and unique feature introduced into Embedded Wizard in order to simplify the customization of GUI applications. By using variants you can adapt and even extend the functionality of an existing class without the need to modify its original implementation. Similarly, autoobjects, constants, bitmap and font resources existing already in your project can be adapted retrospectively.
Technically seen, the feature variants is based on the object-oriented concepts to derive and override project members. With this concept you create a new variant by simply deriving it from an existing project member. The variant inherits thus implicitly the entire functionality of its original member. Now you can edit the variant and adapt (override) the inherited functionality.
For example, if the variant is derived from a GUI component containing a Filled Rectangle view, you can modify in the variant the properties of this view, e.g. change its color. Or you override a method defined in the original component and implement another behavior for the variant. You can also add new views and methods to the variant. Similarly, when you derive a variant from a bitmap resource, you can change its attributes e.g. FileName or Format, which results in a bitmap variant using another image files or being converted differently.
If your application case it requires you can derive from the original project member even several independent variants. The right variant is determined either dynamically at the runtime of the application just in the moment when the original project member is evaluated in an expression or statically at the code generation time.
The particular thing about this feature is that unlike the classical one-way inheritance, the derived variant may affect the original member. When accessing a project member (e.g. a bitmap resource), Embedded Wizard verifies whether there are any variants derived from it and, if available, uses the right one automatically. This selection is controlled by the variant condition. With the condition you specify individually when the particular variant should be taken in account.
In more sophisticated application cases, you can derive even sub-variants from already existing variants. From this results a kind of overriding hierarchy, which unlike classical inheritance, extends horizontally. The following figure demonstrates this aspect. On the left you see the classical inheritance hierarchy. The class Example::Panel is derived from Core::Group and from it is derived the class Example::AlertPanel. On the right you see the three variants overriding the Example::Panel class. Which variant is used depends on the conditions A, B and C:
Now, when creating an instance of the Example::AlertPanel class, the variants are evaluated and depending on the specified conditions selected. Let's assume, the condition B results in true. In this case the class Example::Panel is overridden by the variant Example::iOSLook. Is the condition C also true, then the variant is additionally overridden by sub-variant Example::iOSDarkLook. Accordingly, the instance of the Example::AlertPanel involves the both variants. It is as if Example::AlertPanel had been derived from Example::iOSDarkLook:
In practice, every class, autoobject, constant, bitmap and font resource can be overridden by one or more variants. During code generation Embedded Wizard evaluates all dependencies between the variants and the original members and optimizes the resulting code as far as possible. For example, if you have a bitmap resource overridden statically by a variant, no code for the original bitmap is generated since the variant is active the whole time.
Use case: dynamic variants
Let's assume, you have just finalized the implementation of a GUI application and all features are so far working well. Now your customer asks you to add to the application the new feature dark look. With this mode the user should be able to switch the appearance of the application between a bright or dark variant. In practice, such adaptation will affect all GUI components and bitmap resources in your project.
With the classic approach you would need to modify every GUI component individually and implement there code to switch between the bright and dark colors. You would also need to manage two sets of bitmap resources and select between them manually.
With the variants feature, however, you leave the already implemented components and bitmap resources untouched. Instead, you derive from every GUI component and bitmap resource a corresponding variant. Then you edit the variant similarly as you did with the original member. For example, you open the variant of a GUI component in the Composer and change there the colors as desired.
In this example the original implementation and the adapted variants co-exist in the resulting application. The user can decide at the runtime which appearance should be used. Accordingly Embedded Wizard selects the right version of the GUI component or bitmap resource dynamically. The following figure shows an AlertPanel component with its bright appearance and the related variant with modified colors. Thus depending on the user's actual preference accessing the AlertPanel class will result either in the original class or the class with additional modifications from the variant:
Use case: static variants
Let's assume your GUI application exchanges a lot of information with the underlying hardware, middleware software or even with the operating system. To do this, you have implemented a Device Interface class as the interface between GUI and the device. The implementation of the Device Interface is thus optimized to communicate with your actual device version. Now, you have decided to support a further version of the device. In other words, you want your GUI application to be able to run on two different hardware, software or operating systems.
Again, with the classic approach you would need to modify the implementation of the Device Interface class. In principle, for every interaction with the underlying device you would need to distinguish whether the application is running on device version A or B, and depending on it, execute the adequate code. With the variants feature, however, you leave the already implemented Device Interface class untouched. Instead, you derive from the Device Interface class a new variant and implement there the code to communicate with the device version B.
Now when you generate code for device version A, the original implementation of the Device Interface class is selected. In turn, if you generate code for the device version B, the variant for version B is used. The selection of the right variant occurs thus statically already at the code generation time. The following figure shows the situation with code generated for the device B. As you see in this case the variant supersedes permanently the original implementation of the DeviceClass:
Derive a new variant
To find out how a new variant is created please see the corresponding chapters referenced in the SEE ALSO section below. Please note, the variant and the original member do not necessarily have to be defined within the same unit. On the contrary, you can create the variant in a separate unit, which permits you to adapt and extend the application without modifying the unit with the original member.
Specify the variant condition
The variant condition determines when the respective variant should be used in order to substitute its origin definition. Only if the condition is fulfilled, the affected variant is applied. In this manner, multiple variants can be implemented and the user can switch between them.
The variant condition is controlled by the attribute VariantCond. Per default, new created variants have the attribute configured with the value true, which means that the variant overrides the original member statically and permanently. You should thus review the attribute VariantCond always just after deriving a new variant.
The variant condition is evaluated during the code generation and at the runtime. If the condition is fulfilled already during the code generation, the affected variant does substitute the origin member permanently – it can’t be switched at the runtime anymore. The condition for such static variants depends usually on a profile. The variant is thus selected, when you generate code for exact this profile. When generating code for another profile, the variant will be simply ignored. Having several profiles in your project you can manage multiple variants of your product or device.
Dynamic variants, in turn, depend on styles, a kind of switches you can activate and deactivate individually at the runtime. The available styles are represented by style members. In principle, for every mode you intend to activate/deactivate in your application at the runtime you should manage in the project a corresponding style member. The name of the member identifies then uniquely the mode. Thus, if you plan to implement in your application the look&feel Android and iOS it is obvious to add two style members and name them Android and iOS.
When you edit the attribute VariantCond, the Inspector provides an Assistant where you can conveniently select the desired profile or style for the condition:
If you have a member overridden by two variants configured with the same condition, Embedded Wizard will report an error since it is not able to determine unambiguously the right variant to use.
Inspect the variants hierarchy
A project member, which has been overridden by a variant is considered as being multi-variant. You can derive as many variants of an original member as you want. Moreover, one variant can override another variant resulting in a hierarchy of variants. With the Browser window you can inspect this hierarchy. Please note, a member overridden by a variant is signed in the Browser with an additional small icon. You can thus recognize multi-variant members easily. Additionally, the corresponding variant condition is shown between a pair of [...] (squared brackets):
Control the selection of static variants
As described above, static variants depend on profiles. For example, if you have a variant configured with its attribute VariantCond to depend on the profile Device_RevB, the variant is used only when you generate code for this profile Device_RevB. Similarly, when you prototype the application, the currently selected profile decides implicitly about the selection of variants.
The following example demonstrates it. In this project you will find a Device Interface class serving as an interface to mediate between the GUI application and the real device. Since this example application should run on two different devices, the Device Interface class is overridden by two variants. The first variant implements the interface to interact with device A and the second variant implements the interface to interact with device B. In turn, the original Device Interface class doesn't contain any particular device specific implementation. It defines just the interface.
Now, the project contains three profiles. One profile is used when you generate code for the device A. The other profile is used when you generate code for device B. Accordingly, the both profile select the above mentioned variants. The third profile, in turn, is used when you test the application in Prototyper. With this profile selected, no variant is used and the application works with the original Device Interface class, which doesn't implement any device specific code. Selecting a profile affects thus the functionality of the application:
Please note, the example presents eventually features available as of version 8.10
Control the selection of dynamic variants
At the runtime the selection of dynamic variants is controlled by the global built-in variable styles. With this variable you can activate and deactivate the styles individually. In the same manner the implementation can evaluate the value actually stored in the variable. For example, you can implement your GUI application to toggle between bright and dark look:
// If the style 'DarkLook' is currently active, deactivate it.
if ( styles.contains([ DarkLook ]))
styles = styles - [ DarkLook ];
// ... otherwise activate it
styles = styles + [ DarkLook ];
The alternation of this variable has an effect on the next access to a multi-variant class, autoobject, constant, bitmap or font resource. Expressions, which have been already evaluated in the past are not updated retrospectively. For example, the following code creates two instances of a multi-variant AlertPanel component and displays them on the screen. The first instance appears with dark look while the second with bright. Both co-exist simultaneously:
// Activate the style 'DarkLook' and create a new instance of the
// AlertPanel. The resulting GUI component will appear dark.
styles = styles + [ DarkLook ];
var Examples::AlertPanel panel1 = new Examples::AlertPanel;
// Deactivate the style 'DarkLook' and create another instance of
// the AlertPanel. Now, the resulting GUI component will appear bright.
styles = styles - [ DarkLook ];
var Examples::AlertPanel panel2 = new Examples::AlertPanel;
// Display both instances on the screen. 'panel1' appears dark.
// 'panel2' in turn bright.
GetRoot().Add( panel1, 0 );
GetRoot().Add( panel2, 0 );
After the creation, every instance retains its original derivation hierarchy until it is disposed again. Es is important to understand, that the both objects are instantiated from different class variants, which means that they may contain different members and implementation. Embedded Wizard ensures, that such objects coexist simultaneously.
If there are two dynamic variants overriding one and the same original member and both are selected since the corresponding styles are active, Embedded Wizard will promote the style with lower order number.
The following example demonstrates the practical application case of dynamic variants. This example implements a simple alert panel. This panel is additionally overridden by a variant and adapted to look dark. The selection of the variant depends on a style, which in this example can be activated and deactivated individually. Accordingly, you will get either the original alert panel or its variant:
Please note, the example presents eventually features available as of version 8.10
Test the variants
In the upper area of the Embedded Wizard window you see the Build toolbar. This toolbar contains various combo boxes to conveniently switch between profiles, languages and styles available in your project:
In particular the first combo box is convenient to test how your GUI component or application will appear and behave with code generated for different profiles. This is the case when your application contains project members overridden by static variants. When you change the selection in the combo box the content shown actually in the Canvas area is updated immediately. Similarly, the actual combo box selection determines the condition when you start a new prototyping session. This means, the Prototyper window will appear with the currently selected profile.