42 views
in GUI Development by
Hi,

We are designing an application with EmWi 11, consisting of multiple full-size screens. We are making use of PresentDialog(), DismissDialog(), and SwitchToDialog() to overlay these screens. Simple Slide or Fade-in/out animations upon Present and Dismiss are used.

From my understanding, overlaying a dialog with another dialog should capture the focus, making the front dialog modal and disabling any input components in the underlying background dialog. To my surprise, this is not the case in my application: Touch handlers in the hidden dialog continue to accept input, and drop-down lists still open although not visible.

Could you kindly point me to what's wrong please?

Thanks, Steffen

1 Answer

0 votes
by

Hello Steffen,

the observed behavior is correct. Generally, when you present a dialog D inside a GUI component C, all widgets (or touch handlers) existing within the component C will still be able to handle user inputs. Unless these widgets and touch handler are themselves embedded within a separate component E which also have been presented as dialog. This behavior is necessary to allow sophisticated dialog situations. The following chapter explains it more in detail: Take a closer look at the Dialog functionality.

What can you do?

1. Possibly you present the dialogs one inside another? Depending on the desired behavior the simplest could be to ensure that all dialogs are presented within one and the same owner (usually Application component).

2. If the component acting as owner for the presented dialogs contains any other regular widgets or touch handlers, you should embed these within a separate component and present it as dialog too.

I hope it helps you further.

Best regards

Paul Banach

by
Hi Paul,

Thanks for clarification.

We are using this "dialog-on-dialog" approach in order to stack a multitude of screens on top of each other as the user walks through the application logic.
We unstack them as the user unrolls this logic and successively returns to the main screen.

So yes, we present one dialog inside another indeed. This cannot be changed without redesigning the whole application.

So far, we have addressed this behaviour by individually disabling widgets and touch handlers when we progress from one dialog to the next. This actually works, but as our application grows larger and larger, maintaining these Enabled states becomes tedious. Particularly re-enabling the widgets when returning to the previous dialog is difficult to maintain. By using DismissDialog(), we rely on the application to remember which dialog to return to. If we now had to individually remember which widget to re-enable, code would lose its maintainability.

Do you have any recommendation about how a dialog could intercept the moment in which it becomes visible again, so it could properly re-enable its widgets by itself?

Thanks, Steffen
by

Hello Steffen,

We are using this "dialog-on-dialog" approach in order to stack a multitude of screens on top of each other as the user walks through the application logic.

...

So yes, we present one dialog inside another indeed. 

...

By using DismissDialog(), we rely on the application to remember which dialog to return to. If we now had to individually remember which widget to re-enable, code would lose its maintainability.

hmmm ... I don't know your implementation, but it should not be necessary to present one dialog inside another just to achieve the effect of stacking dialogs. In most cases it is sufficient for the application to present all dialogs within a common owner component (usually the Application component).

The order of how the dialogs are stacked corresponds then to the order in which the user walks through the application logic. Invoking DismissDialog() will automatically return to the preceding dialog - what you expect, so far I have understood your application case. Is there possibly some misunderstanding?

So far, we have addressed this behaviour by individually disabling widgets and touch handlers ...

This would be a working approach - but also labor and error intensive. Another possibility would be to make the actually active dialog modal. This prevents all other GUI components and touch handlers from being able to react to user inputs. For this purpose:

1. Just in the moment when you present a new dialog you make it modal:

// Evtl. Avoid race conditions
if ( !IsCurrentDialog())
  return;

// Create an instance of the new dialog
var Core::Group dialog = new Application::Dialog;

// Present it and ...
PresentDialog( dialog, null, null, null, null, null, null, null, null, false );

// ... make it modal
GetRoot().BeginModal( dialog );

2. Just in the moment when you dismiss the actual dialog, also end its modal state. For example, when the dialog dismisses itself:

// Evtl. avoid race conditions
if ( !IsCurrentDialog())
  return;

// End the modal state of the dialog and ...
GetRoot().EndModal( this );

// ... dismiss it
Owner.DismissDialog( this, null, null, null, null, null, false );

Do you have any recommendation about how a dialog could intercept the moment in which it becomes visible again, so it could properly re-enable its widgets by itself?

So far I have understood your application case, I don't think this would be possible. Since the dialogs are nested one inside another, all dialogs are visible and all dialogs are active at the same time. There is no state alternation which can be monitored. Usually, a state alternation of a GUI component is reported to it via its method UpdateViewState(). There you can also implement code to react to the alternation. See also: Common component states.

Does it help you further?

Best regards

Paul Banach

by
Hi Paul,

Thanks for further clarification and the Modal approach.
I will try this at the earliest convenience.

Just in order to make sure that we don't have any fundamental misunderstanding of the Dialog functionality, I'd like to come back to how we are using the "dialog inside dialog" approach.
I might help to have a (simplified) outline of the application logic to verify whether my thoughts are correct:

Assume we have a Main Screen A, a set of Parameter screens B1 to B5, and a set of Configuration screens C1 to C3.
The user can progress through the screens in sequence:
A -> B1 -> A or
A-> B1 -> B2 -> B3 -> B4 -> A
A -> C1 -> A or
A -> C1 -> C2 -> C3 -> C1 -> C2 -> A and so on.
There is no direct transition from Bx to Cx though. The user must return to A first.

So when the user selects the B1 screen from the A screen, we issue PresentDialog(B1). When the user wishes to progress through B2 to B4, we issue SwitchToDialog(B2) etc. When the user wishes to return to A, we issue DismissDialog(Bx).
Same story with the Cx screens.
Whenever I dismiss a dialog, the previous one naturally reappears without any business logic needed to manage which screen comes after which.
This approach seemed natural to me.

Now being aware that this keeps Screen A active (although invisible) in the background all the time, I understand that any widgets inside A need to be disabled prior to switching to B, or B must me made modal (preferably).

One could think of an alternative approach of a complex finite state machine (or business logic) that flattens the hierarchy of these screens and presents a single one, at any time, in the context of the Application component. But wouldn't this be even more difficult to maintain, and wouldn't it waste the intelligence built into the Dialog component?

Thanks, Steffen
by

Hello Steffen,

thank you for the detailed explanation. I would try following approach:

* Let assume there is a further component R representing the root object (the Application component). Such component is found in every EW GUI application.

* The R component itself does not contain any other widgets nor touch handlers. It is empty per default. However, ...

* ... just at the initialization time of the R component, present the A dialog inside it. For example, you can execute PresentDialog( A, ... ); in the Init() method of the Application component. Thereupon A will become the current dialog.

* When the user selects a command in A dialog to enter B1 dialog, present B1 in context of R. For example, GetRoot().PresentDialog( B1, ... ); B1 is the current dialog now. A is inactive and not able to handle any user inputs. GetRoot() is a method to find the root object (the Application component). Please note, starting with EW 12 you can access the root object also by using the global variable rootthis.

* When the user switches from B1 to B2 dialog, use SwitchToDialog(), again in context of the R component. For example, GetRoot().SwitchToDialog( B2, ... ); Thereupon B1 is dismissed and B2 dialog is presented.

* If necessary, the user can navigate forth and back between the B1..B5 dialogs. Just invoke GetRoot().SwitchToDialog( ... ) each time the user wants to switch the dialogs.

* When the user wants to close the Bx dialog, use DismissDialog(). For example, if the dialog wants to dismiss itself, perform the code Owner.DismissDialog( this, ... ); in context of this dialog. Thereupon the Bx dialog disappears and A dialog is restored and re-activated.

* Handling C dialogs should work similar to B dialogs.

With this approach you don't need to disable/enable the widgets and touch handlers.

Does it help you further?

Best regards

Paul Banach

by

Hello again Paul,

Thanks for pointing me to the GetRoot.PresentDialog() approach of flattening dialog hierarchy.
I have tried this and it starts working as expected. No widgets in the hidden dialogs are intercepting user input any more. Great.

There is one major disadvantage to this approach though:
By presenting all dialogs in the context of the Application object, the tree of ownership gets lost.
I can programmatically unroll the stack of dialogs by one level only, using Owner.DismissDialog(this). I can’t unroll any further because Owner of any dialog is the Application object.

The structure of our application presented above is considerably simplified though. Actually, there are even more levels of dialogs, e.g. descending from the Cx level to Dx.
With the proposed strategy, I can’t unroll from Dx to A, can I?

Thanks,
Steffen

by

Hello Steffen,

ok, you want multiple dialogs to be dismissed at once, right? For example: the user performs following navigation A -> B1 -> B2 -> B3 -> E1 -> E2 -> E3. Consequently the current (the interactive) dialog is E3. Now the user wants to unroll all E3, E2 and E1 dialogs and return to the dialog from the preceding level B3.

In such case, please see the chapter Enumerate and search for existing Dialogs. It explains how to find and access existing dialogs. In practice you will implement a loop to search for the dialogs and dismiss them until a dedicated stop condition is fulfilled:

var Core::Root theRoot = GetRoot();

// Find the current dialog
var Core::Group dialog = theRoot.FindCurrentDialog();

// As long as a dialog was found and it does meet the condition ...
while (( dialog != null ) && Does_The_Dialog_Meet_The_Condition( dialog ))
{
  // ... dismiss it and ...
  dialog.Owner.DismissDialog( dialog, null, null, null, null, null, false );

  // ... search for the next dialog.
  dialog = theRoot.FindCurrentDialog();
}

How to implement the condition will depend on your implementation. For example, you could manage in all dialogs a variable indicating a level (Ex, Cx, ...) the dialog belongs to. Then compare the levels.

Or you ensure that all dialogs belonging to the same level descend from one and the same common class. For example, dialogs E1, E2, E3 descends from the class EX. Then you can test the dialog whether it is an instance descending from the class. The following code dismisses thus all recently presented dialogs, which descend from the EX class:

var Core::Root theRoot = GetRoot();

// Find the current dialog
var Core::Group dialog = theRoot.FindCurrentDialog();

// As long as a dialog was found and it does descend from the EX class ...
while ((YourUnit::EX)dialog != null )
{
  // ... dismiss it and ...
  dialog.Owner.DismissDialog( dialog, null, null, null, null, null, false );

  // ... search for the next dialog.
  dialog = theRoot.FindCurrentDialog();
}

If the dialogs perform animations during the dismiss operation, pass in the last parameter of DismissDialog() the value true to group the animations together. See also Perform several Dialog transitions simultaneously.

Is this a working approach for you?

Best regards

Paul Banach

by
Hi Paul,

Thanks for explaining dialog enumeration. Essentially, this is what I was looking for.
I have opted for the Common Class approach, and it works perfect for me.
I will streamline our code some further, but essentially I think this solves my problem.

Doing so, I have learned about the Class Runtime Cast, which is a cool feature, too.

Thanks for taking your time to provide in-depth support,
Steffen

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

...