142 views
in GUI Development by

Hi everyone,

I have a problem with a small application, composed of:

  • 4 languages bricks (lets says La; Lb; Lc and Ld)
  • 1 style brick (Sa)
  • 1 root component (R)
  • 1 component (Cbase) used as the base for the entire screen + its variant (Cbase_V)
    The variant component having the VariantCond property activated only in case the style is on.
  • 2  component based on Cbase (Ca and Cb)
In the R component, I alternatively and dynamically load Ca and Cb.
In the Cb component, I have a slot that activate or desactivate the style Sa with the following code:
if(this.DSync_NightMode.ptrZEDVariable1^)
  styles = styles + [ NightMode ];  // We turn on the night mode style
else
  styles = styles - [NightMode];  // We turn off the night mode style

 

Now, I know that the styles are changing, but when Cb is loaded, the new styles do not change. I have to unload Cb, then load it back (or load Ca) to see the change take effect.

I even try to force the refresh of the component by doing from the R component either:

if(this.Cb != null)
  this.Cb.InvalidateViewState();

or

this.InvalidateViewState();

Do you have any idea why?

P.S.: I tagged also the languages, because I have the same problem with the language change from subcomponent. (and of course, I set all the property MultiLingual for everyone (Cbase; Cbase_V, Cb and the subcomponent that changes the language in Cb))

Thank you for the help,
Krzysztof

1 Answer

+1 vote
by

Hello Krzysztof,

concerning the language change, as long as the MultiLingual attribute is set correctly for all classes containing multi-lingual initialization expressions, the language change should work automatically. Please check it again. If you can't find the source of the issue, you can extract the problem in a small example project and upload it to this thread for further analysis.

Now let me address your first question. You implement a GUI with multiple color themes (e.g. light and dark theme). In your implementation you tried to use variants of the entire component for this purpose. The problem here, switching on/off a style affects only components instantiated afterwards. Components which are already 'alive' are not affected by this alternation. This is because switching variants also may change the implementation of a component and add new data members/objects to it.

I would use multi-variant constants for this purpose. The idea here:

1. You store all light mode colors in constants. For example, a constant for the background and another constant for the foreground color. If you plan to use 100 colors, you create 100 constants.

2. Now, wherever a GUI component needs the color value, you use the respective constant.

3. To support dark mode, you override the constants by so-called 'variants'. In the variant you can specify another color. Additionally, you specify in the variant a condition when the variant is active.

4. Since you want the variants to be activated dynamically at the runtime, the above mentioned variant condition should depend on a Style member. At the runtime the style can be switched on and off. Accordingly, the variant become active and inactive.

5. When the variant is actually active, all accesses to the original constant result in the value found in the variant. When the variant is inactive, the color value from the original constant is used.

The chapter Managing variants explains the technical aspects more in detail.

This approach has however a peculiarity: the expression containing the multi-variant constant needs to be re-evaluated in order to become a new value (dark or light). The simplest to force this re-evaluation is a new instantiation of all actually visible GUI components. In other words, after you switch on/off the style controlling the color constant variants, you dismiss the existing screen (or screens) and present them again. This should cause the components to be created and initialized again now with new colors.

If you don't want (or you can't) recreate the existing components, I would recommend following trick:

6. Wherever you assign a multi-variant color constant to an initialization expression in Inspector window, prefix this initialization by the %+ operator (see also React automatically to language selection. This operator forces Embedded Wizard to generate additional re-initialization code for the affected initialization. The following screenshot demonstrates the usage of this %+ operator, here in the initialization of the property Color of a Rectangle view with a constant named Application::BkColor:

7. The above mentioned code generation depends on an additional Multilingual attribute, you have to set it true for each GUI component class, you want the automatic color alternation:

As you may have noticed, the trick explained here uses Embedded Wizard automatic language actualization. To force the execution of the code containing the initialization expressions, you have thus to switch the language temporarily.

8. Add a new language member to your project and name it e.g. DummyLanguage.

9. Now, when the user switches the dark/light modes, switch on/off the the style controlling the variants and switch temporarily the language:

For demonstration purpose I have prepared a small example. You will find it in the attachment below named theme-v12.zip. This example is created explicitly for Embedded Wizard Studio version 12. For the next version 13, we have improved this approach so the above mentioned trick with language switch will not be necessary anymore.

https://ask.embedded-wizard.de/?qa=blob&qa_blobid=2374672608207188415

In the example, please note the constants BkColor and FgColor as well as their variants for the dark theme. Also note the Style DarkTheme and the dummy language:

In practice you can add further constants and override them by variants. Then you can use the constants in your project. When the user switches the color theme, the above mentioned trick with language selection triggers all initialization expressions and the GUI should appear updated.

Please note, if colors are determined within the logic of a component (e.g. in some method executed in response to an event), this code is not automatically executed when the the trick with language selection is performed. Only initialization expressions specified in Inspector are using this approach.

Therefore, if you have some color selected within a method, you will need to re-evaluate the corresponding expressions explicitly. For this purpose you copy/paste the affected expression inside the ReInit() method. The ReInit() method is executed when the language is switched. It is thus well suitable to perform the necessary color updates. For demonstration purpose, the example uses such a ReInit() method to update the color of a small rectangle in the upper-left screen corner.

I hope it helps you further.

Best regards

Paul Banach

by

Dear Paul,

Thank you for your answer.
As for the styles, I will take it into consideration in that case during my designs.
As for the language, I checked again, but it is still not working. I have uploaded the project in here: Language update problem project

Thank you again for your help,

by
Hello Krzysztof,

the project is really big. To help me please reduce the project to a simple example demonstrating the issue or provide details where should I look for the problem.

Best regards

Paul
by

Dear Paul,

I tried to reduce it as much as possible. I hope now it will be better.
The component where I have the problem is Screen_Default::Screen_Default, and all the subcomponent that are used in it are in the same unit.
Language update problem project (v2)

Best regards,
Krzysztof

 

by

Hello Krzysztof,

in your implementation the affected Text views are initialized within methods. For example:

This assignment is performed only when the method is executed. Once assigned the value to txt_Caption.String the value does not change until this method is called again.. To benefit from the automatic update after the language selection, the initialization has to be specified in Inspector window (see also React automatically to language selection).

If an assignment is performed within a method, the method will not be executed when language is changed. In such case, you can implement the method ReInit() and put there a copy of the affected initialization expression. ReInit() will be invoked when language is changed and allows you to re-evaluate all affected expressions. See also the section React programmatically to language selection.

In your case you could put the row this.txt_Caption.String = Lib_Strings::GaugeTitle_AirPress; within ReInit() method. The problem here, you have several methods initializing the text view with different strings. The ReInit() method would need to know which string (Air Press, Fuel Level, ...  etc.) should be re-assigned to the text view.

Fortunately, the component tracks its configuration type in its property uiType. Duplicating the switch-case block from the OnSetuiType method within the ReInit() method seems to work:

I hope it helps you further.

Best regards

Paul

by
Dear Paul,

Thank you very much, it works great now.

I have another question if possible, I cannot figure out how to initialize the menu list, so the selected item uppon loading is the one of the current language.
Could you give me a hint please?

Once again, thank you very much,
Krzysztof
by

Hello Krzysztof,

in your language menu (implemented in component Screen_Default::Menu), the Selected state of an item corresponds to the value of the menu's property Focus. See also Common component states. Consequently, to have the second item selected, the Focus property of the menu has to refer this item. Following could be the code to traverse all items and 'focus' the one which corresponds to the currently selected language:

var Core::View view = Outline.FindNextView( null, Core::ViewState[ Enabled, Visible ]);

for ( ; view; view = Outline.FindNextView( view, Core::ViewState[ Enabled, Visible ]))
{
  var Screen_Default::MenuItem item = (Screen_Default::MenuItem)view;

  if ( item && ( item.Language == language ))
    Focus = item;
}

This code could be executed just in the moment when the language menu is initialized, for example in the Init() method of Screen_Default::Menu.

I hope it helps you further.

Best regards

Paul

by
Dear Paul,

Thank you very much.

Krzysztof,

Ask Embedded Wizard

Welcome to the question and answer site for Embedded Wizard users and UI developers.

Ask your question and receive answers from the Embedded Wizard support team or from other members of the community!

Embedded Wizard Website | Privacy Policy | Imprint

...