Silverlight Application Busy Cursor
I'm doing a lot of Silverlight work and one issue I've encountered is how to set the cursor to an application wide busy wait icon, and disable all the controls in the application, while a web service call is made. The first technique I tried was to wrap all the application content in a ControlControl and set the latter's IsEnabled property to false while the service call is made, also setting the cursor to Wait on the main UserControl. This doesn't look good though because the application is greyed out while it is disabled, which seems too visually aggressive.
A better technique is to place a transparent rectangle over the application content during the service call. For example, in the following XAML the Rectangle element is defined with its Cursor set to "Wait" and its Visibility set to "Collapsed" so that when the application runs the Rectangle will not affect its appearance:
<UserControl x:Class="TestBusyCursor1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid x:Name="LayoutRoot" Background="LightGray" >
<Border BorderBrush="Black" Cursor="Arrow" >
<StackPanel Margin="50">
<TextBlock Margin="10" Width="300" Text="Busy Cursor 1"/>
<TextBox Margin="10" Width="300"/>
<Button Content="Do Something" Click="Button_Click"
Margin="10" HorizontalAlignment="Right"/>
</StackPanel>
</Border>
<Rectangle x:Name="WaitCursor" Fill="Transparent"
Visibility="Collapsed" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" Cursor="Wait"/>
</Grid>
</UserControl>
In the code-behind the click handler sets the Visibility of the Rectangle to Visible and so because the Rectangle is defined after the other content in the Grid control it effectively overlays the other content and prevents the user from clicking on any controls underneath the Rectangle. A worker thread sleeps for 5 seconds to simulate the long-running service call, and then when this completes the visibility of the Rectangle is set to Collapsed again so the Rectangle is hidden:
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
namespace TestBusyCursor1
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs args)
{
WaitCursor.Visibility = Visibility.Visible;
var worker = new BackgroundWorker();
worker.DoWork += (s, e) =>
{
// simulate making a slow web service call
Thread.Sleep(10000);
};
worker.RunWorkerCompleted += (s, e) =>
{
Dispatcher.BeginInvoke(() =>
{
WaitCursor.Visibility = Visibility.Collapsed;
});
};
worker.RunWorkerAsync();
}
}
}
Click on the button here to see the Wait cursor and how the button and textbox cannot be clicked during the simulation of the service call:
One problem with this is that it is still possible to tab into the text box, and enter text there, while clicking on the content is prevented by the Rectangle. This can be worked around by finding all the child controls which have their IsTabStop property set to true and setting it to false for the duration of the service call:
private void Button_Click(object sender, RoutedEventArgs args)
{
WaitCursor.Visibility = Visibility.Visible;
var tabStops = LayoutRoot.GetVisuals().OfType<Control>()
.Where(c => c.IsTabStop).ToArray();
foreach (var tabStop in tabStops)
tabStop.IsTabStop = false;
var worker = new BackgroundWorker();
worker.DoWork += (s, e) =>
{
// simulate making a slow web service call
Thread.Sleep(10000);
};
worker.RunWorkerCompleted += (s, e) =>
{
Dispatcher.BeginInvoke(() =>
{
foreach (var control in tabStops)
control.IsTabStop = true;
WaitCursor.Visibility = Visibility.Collapsed;
});
};
worker.RunWorkerAsync();
}
So in this version it is not possible to tab into the text box during the service call:
There is one problem remaining: the visual state of the cursor is not changed until the mouse is moved. This can be confusing if you click on the button and move the mouse for a moment so that the Wait cursor is displayed, but then don't move the mouse after the service call. From the user's point of view it looks like the service call is continuing indefinitely. For this reason it may look better to instead use some sort of spinning icon, web browser style, to indicate that the application is busy.