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 its 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 looks like this:
- Validate the input
- If all fields are correct, start saving the data
- 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:
- Validate the input
- If all fields are correct, check if the dialog is needed
- If the dialog is needed, show the dialog
- Once the user has made a choice and wants to continue, start saving the data. If the user does not want to continue, stop.
- 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 way 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:
4: _dialog = new DialogWindow();
5:
6:
7: ThreadPool.QueueUserWorkItem(SaveData, this);
8: }
9:
10: private void SaveData(object stateInfo)
11: {
12: if (DialogWindow.ShowDialog(_dialog, stateInfo))
13: {
14:
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
method:
1: public static bool ShowDialog(DialogWindow window, object stateInfo)
2: {
3: bool result = false;
4: DependencyObject dependencyObject = stateInfo as DependencyObject;
5:
6:
7:
8: if (dependencyObject == null dependencyObject.Dispatcher.CheckAccess())
9: {
10: window.Show();
11: return false;
12: }
13:
14: Action action = new Action(window.Show);
15:
16: dependencyObject.Dispatcher.BeginInvoke(action);
17:
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.