Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / UWP

Extending Content/Message Dialog Class in UWP to Avoid Crash

5.00/5 (1 vote)
31 Mar 2017CPOL3 min read 11.7K  
Showing multiple dialogs concurrently in UWP

Introduction

If you are familiar with controls used in UWP, then you must be knowing about the “MessageDialog” or “ContentDialog “control. As the name says, these controls are used to show pop-up dialogs to the users alike the “alert” message in JavaScript.

These dialog also can be customized as per developer’s need by creating a User Control and inheriting “ContentDialog”/”MessageDialog” class.

The Problem

Recently in my UWP project, we had this requirement of showing many dialogs at different point of events. We even had our own User Control for showing dialogs to the users at certain places.

During one of the new feature implementations, we faced application crash while trying to show ContentDialog to the user. Upon investigating, it was clear that in UWP, more than one dialog couldn’t be shown at the same point of time, trying to do so would result in crash of application.

The control being provided was managed by the UWP SDK, not much documentation was available for this. As designed by SDK, the UI thread could invoke only one dialog at a time.

The Solution

I found out 3 effective solutions for this problem:

  1. Don’t show another dialog when already in view – This can be done in various ways like setting a global Flag or another convenient way as per your requirement. However, this would lead to very unfriendly UX by not showing important dialogs at right time.
  2. Close the existing dialog before showing another one – I haven’t tried and am not sure how this could be implemented but it is possible. But again, bad UX since you may close a message before the user sees it.
  3. Wait for the existing dialog to be closed and show henceforth – This is the most convincing solution I found out. In this approach, if a dialog is already in view, the other one would wait for it to be closed. Once user dismisses the existing dialog, the next one would be shown to the user. A fine solution unless you have 100s of dialogs to a show at a certain point of time. ;)

I’ve implemented the third approach in my project and would brief the implementation precisely.

We will be creating extension methods to handle this problem without modifying anything in the existing code. You can find two different cases of implementation as shown below.

Case 1

Your app uses only ContentDialogs/MessageDialogs throughout the application. Thus your sole objective would be to handle one of these dialogs to avoid the crash.

C#
private static TaskCompletionSource<ContentDialog> _contentDialogShowRequest;

public static async Task<ContentDialogResult> ShowAsyncQueue(this ContentDialog dialog)
 {
     if (!Window.Current.Dispatcher.HasThreadAccess)
     {
        throw new InvalidOperationException("This method can only be invoked from UI thread.");
     }

     while (_contentDialogShowRequest != null)
     {
         await _contentDialogShowRequest.Task;
     }

     var request = _contentDialogShowRequest = new TaskCompletionSource<ContentDialog>();
     var result = await dialog.ShowAsync();
     _contentDialogShowRequest = null;
     request.SetResult(dialog);

     return result;
}

As you can understand, we have created a private instance of ContentDialog TaskCompletion which would make the new Dialog show request to wait till the existing dialog task gets completed.

Case 2

Consider your app uses MessageDialog at a few places and ContentDialog at a few other places. So we require to await the task completion of both these classes.

C#
private static TaskCompletionSource<ContentDialog> _contentDialogShowRequest;
private static TaskCompletionSource<MessageDialog> _messageDialogShowRequest;

public static async Task<ContentDialogResult> ShowAsyncQueue(this ContentDialog dialog)
{
    if (!Window.Current.Dispatcher.HasThreadAccess)
    {
        throw new InvalidOperationException("This method can only be invoked from UI thread.");
    }

    while (_contentDialogShowRequest != null)
    {
        await _contentDialogShowRequest.Task;
    }

    while (_messageDialogShowRequest != null)
    {
        await _messageDialogShowRequest.Task;
    }

    var request = _contentDialogShowRequest = new TaskCompletionSource<ContentDialog>();
    var result = await dialog.ShowAsync();
    _contentDialogShowRequest = null;
    request.SetResult(dialog);

    return result;
}

public static async Task<ContentDialogResult> ShowAsyncQueue(this MessageDialog dialog)
{
    if (!Window.Current.Dispatcher.HasThreadAccess)
    {
        throw new InvalidOperationException("This method can only be invoked from UI thread.");
    }

    while (_messageDialogShowRequest != null)
    {
        await _messageDialogShowRequest.Task;
    }

    while (_contentDialogShowRequest != null)
    {
        await _contentDialogShowRequest.Task;
    }

    var request = _messageDialogShowRequest = new TaskCompletionSource<MessageDialog>();
    var result = await dialog.ShowAsync();
    _messageDialogShowRequest = null;
    request.SetResult(dialog);

    return result;
}

The same implementation but here we are trying to check & await instances of both the classes thus writing extension methods for both the dialog classes. If we don’t check for both the classes, it would again result in a crash when there is a ContentDialog is in view and you are trying to show a MessageDialog to the user or vice-versa.

Quote:

Once the extension methods are written, instead of calling “ShowAsync()” to show the dialogs, call this extension method “ShowAsynQueue()” to show dialogs.

You can see the complete code snippet in the image below:

Click to enlarge image

Points of Interest

This is the problem that should have been handled by the UWP SDK itself. It's obvious that nobody would want to face a crash when trying to show a simple control to the end user. Maybe the UWP team didn’t implement it intentionally in order to enable developers to write their own way of handling this.

Maybe in the coming SDK releases, this would be handled! Till then, enjoy this solution!!

History

  • 31st March, 2017: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)