Tuesday, November 16, 2010

Adventures while building a Silverlight Enterprise application part #38

In this post I want to look into a problem most of us will encounter while building a Line Of Business application in Silverlight. It involves UI paradigms combined with the asynchronous communication model.

The problem

We have all run into it at some point. You have a form that a user needs to fill out, which contains a couple of fields and something like a save button. When the user clicks the save button you validate their input and if the validation fails, you block the save action.

All is well. The user can correct the input and try again at will. However, validation has a downside. It’s limited to either true or false and no way in between.

Let’s consider a scenario where you would like to have confirmation from the user. Now you could hack this into the validation system by using a flag and if the user doesn’t change the value and saves a second time it must be right. However I don’t think this is a very good solution and it would confuse users.

I need a dialog!

So we want some other way of asking the user for input. The usual way of doing this would be to present the user with a dialog where the user can press a button to choose whether or not it’s ok to continue. We worked with them for many years and they are intuitive to use.

In Silverlight however they pose a problem. Of course you can use the dialog included in the framework. However this uses the browser to present the dialog and it’s not a good looking thing. More importantly it’s very limited in it’s functionality. Fortunately it’s not that big of a deal to just build a user control that provides the functionality we want. We can even make it modal by just placing a Canvas over the entire UI and then putting the user control in a Popup (or use a childwindow for that matter).

Then the issues start. If you are to build a user control to display dialogs, like we have done, you most likely implemented it using a callback to signal the application that the user has clicked a button in your dialog and you’re done. Although this is a classic asynchronous model, working with it can become problematic.

Splitting code

Let’s go back to our scenario. We have a form with some fields. Some fields get validated and some can lead to a dialog. The steps we would take to save our data before introducing the dialog look like this:

  1. Validate the input
  2. If all fields are correct start saving the data
  3. Once the save is completed, refresh the current data

Now if we introduce a single dialog that allows the user to choose between stop and continue the steps are like this:

  1. Validate the input
  2. If all fields are correct, check if the dialog is needed
  3. If the dialog is needed, show the dialog
  4. Once the user has made a choice and wants to continue, start saving the data. If the user does not want to continue, stop.
  5. Once the save is completed, refresh the current data

Looking at these steps, we go from one event handler for the save completion, to adding a callback for the dialog. So instead of having two methods with code for saving, we now have three methods. And with every dialog we add, we add another callback, thus another method containing code for our save action. You can see how this gets out of hand quickly.

So make it synchronous…

Well, that would solve our problem, however… Silverlight only has one dispatcher for the UI-thread. This means that as soon as you block the UI-thread, your application becomes unresponsive (basically it just hangs until you unblock). While displaying a dialog that is not a very practical thing to do, as the user would not be able to click a button (defeating the whole purpose, right?).

In effect this means there is no straight forward to provide synchronous UI interaction. It’s going to be event based and thus asynchronous. There is a way around this, however. You can’t block the UI thread, but you can block any other thread, so if we move all the code involved in saving to a background thread we can eliminate a callback for the dialog. Here is some example code to show you how calling such a dialog would look:

   1: private void saveButton_Click(object sender, RoutedEventArgs e)



   2: {

   3:     // Make sure to create your dialog while still running on the UI thread

   4:     _dialog = new DialogWindow();

   5:     

   6:     // Pass off the save logic to another thread

   7:     ThreadPool.QueueUserWorkItem(SaveData, this);

   8: }

   9:  

  10: private void SaveData(object stateInfo)

  11: {

  12:     if (DialogWindow.ShowDialog(_dialog, stateInfo))

  13:     {

  14:         // Actually save the data

  15:     }

  16: }



So basically you run anything involved with the save action (except for validation, perhaps) on a background thread. I also pass a reference to the calling UI control. The reason for that will become clear soon.

While running in the background thread, you can call my DialogWindow.ShowDialog method and it will block your thread. This means you can use its result right away.

Run code on the UI thread

In order for this to work we obviously need to be able to run some code on the UI thread and then block our thread while waiting for a result. Here is the code for the ShowDialog mehtod:



   1: public static bool ShowDialog(DialogWindow window, object stateInfo)

   2: {

   3:     bool result = false;

   4:     DependencyObject dependencyObject = stateInfo as DependencyObject;

   5:  

   6:     // If we got passed a dependency object and we have access 

   7:     // to it's dispatcher then we are on the UI thread and we can't block

   8:     if (dependencyObject == null  dependencyObject.Dispatcher.CheckAccess())

   9:     {

  10:         window.Show();

  11:         return false;

  12:     }

  13:  

  14:     Action action = new Action(window.Show);

  15:     // Run the Show method on the UI thread

  16:     dependencyObject.Dispatcher.BeginInvoke(action);

  17:     // Block this thread until we have a result

  18:     while (!window.DialogResult.HasValue)

  19:     {

  20:         Thread.Sleep(50);

  21:     }

  22:     result = window.DialogResult.Value;

  23:  

  24:     return result;

  25: }


First thing we do is check if we are actually on the UI thread. If we are, we don’t want to block. Be aware that the CheckAccess method is not shown by IntelliSense for some reason. It compiles without any problems though.

If we are not on the UI thread, we can actually use the dispatcher of the dependency object passed to us, to actually run the Show method on the UI thread. Then we simply wait for the result and pass it back.

Conclusion

In order to have a synchronous dialog we simply introduced a thread to run our code on which runs some code back on the UI thread and waits for it to complete. This should make it easier for us to compose more complex flows in our code.

You can download the code here.

No comments:

Post a Comment