XAML|C# Step By Step : UserControl – Wait Spinner

Waiting by moonux, on Flickr
Waiting, a photo by moonux on Flickr.

Something is cooking in the background? Do not forget to inform your user. A modern, flat, light weight, wait spinner is an animated and prominent way to inform user about running background processes (you would not have long running processes on the UI thread, right? See how to effectively free up UI). You would choose to show this control in that area of the screen to which the information, related to which the BG work is taking place – loading/processing etc, belongs. You may have multiple information points on screen for which background work is running.

For this step-by-step we will create this example:

A practical example of implementation of this spinner is this panorama app there are two wait spinners being used, one in Quick Stopwatch pano, and other in Recent pano. When data related to Quick stopwatch is loading, the wait spinner shows beside the title “Quick”, and when data related to Recent is loading, the wait spinner shows near the pano title “Recent”. This way user is informed about which section of the app is doing something in the background.

WaitSpinnerExample

Don’t have this app on your Windows Phone? Visit here

Let’s go through step-by-step of creating a WaitSpinner UserControl and using it in a page.

Step 1 : Create Solution and Project

  1. Create a project in VS with the name “WaitSpinner”. The solution gets created automatically.
  2. Right click on the solution in Solution Explorer, and choose “Add”>”New Project”.
  3. Select Class Library type project and name it “XAMLControls”.

Step 2 : Create UserControl

  1. Right click on XAMLControls and select “Add”>”New item”.
  2. Choose User Control and name it “UCWaitSpinner”.
  3. A new UCWaitSpinner.xaml and its code behind is created.

Step 3 : Open in Blend

Right click on UCWaitSpinner.xaml and choose to open in Blend. Depending on the version of Visual Studio the view you get might be a little different, but most of the UI will be similar. You will get something like this:
01_OpenUCInBlend

Step 4 : Change the type of Layout control

Change the type of Layout from Grid to ViewBox.
02_ChangeLayoutType

Step 5 : Add a new Grid to Layout ViewBox

  1. Choose Grid from control and right click and drag in XAML design view area.
  2. Update Layout properties; Height and Width to 50, HorizontalAlignment and VerticalAlignment to Stretch.

03_AddGridToLayoutRoot

Step 6 : Add Outer border

  1. Choose Ellipse tool from Toolbox and create a circle in the design area. Don’t worry about size and fill at this point.
  2. Make sure Ellipse is selected.
  3. In Properties.Brushes;
    • Set Fill to “No Brush”.
    • Set Stroke to “PhoneForegroundBrush”.
  4. In Layout section;
    • Set HorizontalAlignment to stretch.
    • Set VerticalAlignment to stretch.
    • Set all margins to 0.

04_CreateACircle

Step 7 : Add axis for hands

  1. Choose Ellipse tool from Toolbox again and create a circle in the middle of XAML design. Don’t bother about size at this point.
  2. Make sure this ellipse is selected.
  3. In Properties.Brushes;
    • Set Fill to “PhoneForegroundBrush”.
    • Set Stroke to “No Brush”.
  4. In Layout section;
    • Set Width and Height to 5.
    • Set HorizontalAlignment to center.
    • Set VerticalAlignment to center.
    • Set all margins to 0.

05_CreateAxisForHands

Step 8 : Add minute and hour hands

  1. Choose Rectangle tool from Toolbox and create a rectangle in the design area. Don’t worry about size and placement at this point.
  2. Rename the [Rectangle] to “MinuteHand”.
  3. Make sure you have MinuteHand selected.
  4. In Properties.Brushes;
    • Set Fill to “PhoneForegroundBrush”.
    • Set Stroke to “No Brush”.
  5. In Layout section;
    • Set Width to 2 and Height to 20.
    • Set HorizontalAlignment to Center.
    • Set VerticalAlignment to Bottom.
    • Set Bottom margin to 25 and all others to 0.
  6. In Properties.Transform;
    • Select Center Point tab.
    • Set X to 0.5.
    • Set Y to 1.
  7. Copy and paste “MinuteHand” control and name it “HourHand”. Keep everything same and change only the Properties.Layout.Height to 13.

06_CreateMinuteHand

Step 9 : Create Storyboard for moving hands

  1. In Objects and Timeline, click on “+” sign to add a new storyboard.
  2. Name the storyboard as “MoveHands”.

07_CreateStoryBoard

Step 10 : Create animation for minute hand

  1. In timeline window slide the marker to 3 second.
  2. Select MinuteHand control.
  3. In Properties.Transform select Rotate tab and set Angle property to 1440.

07b_CreateStoryBoard

Step 11 : Create animation for hour hand

  1. Select HourHand control.
  2. In Properties.Transform select Rotate tab and set Angle property to 360.
  3. c. In timeline windows click on play button to see hands are rotating properly (Hour hand completes 1 rotation and minute hand completes 4 rotations in 3 seconds).

07c_CreateStoryBoard

Step 11B : Follow me on Twitter 😀

Step 12 : Review XAML code

Close Blend and go back to XAML view of UCWaitSpinner in VS. You should see following XAML code:

<UserControl x:Class="XAMLControls.UCWaitSpinner"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    d:DesignHeight="480" d:DesignWidth="480">
	<UserControl.Resources>
		<Storyboard x:Name="MoveHands">
			<DoubleAnimation Duration="0:0:3" To="360" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.Rotation)" Storyboard.TargetName="HourHand" d:IsOptimized="True"/>
			<DoubleAnimation Duration="0:0:3" To="1440" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.Rotation)" Storyboard.TargetName="MinuteHand" d:IsOptimized="True"/>
		</Storyboard>
    </UserControl.Resources>

    <Viewbox x:Name="LayoutRoot">
    	<Grid Width="50" Height="50">
    		<Ellipse Margin="0">
    			<Ellipse.Stroke>
    				<SolidColorBrush Color="{StaticResource PhoneForegroundColor}"/>
    			</Ellipse.Stroke>
    		</Ellipse>
    		<Ellipse Margin="0" Width="4" Height="4" HorizontalAlignment="Center" VerticalAlignment="Center">
    			<Ellipse.Fill>
    				<SolidColorBrush Color="{StaticResource PhoneForegroundColor}"/>
    			</Ellipse.Fill>
    		</Ellipse>
    		<Rectangle x:Name="MinuteHand" Height="20" Margin="0,0,0,25" Width="2" StrokeThickness="0" HorizontalAlignment="Center" VerticalAlignment="Bottom" RenderTransformOrigin="0.5,1">
    			<Rectangle.RenderTransform>
    				<CompositeTransform/>
    			</Rectangle.RenderTransform>
    			<Rectangle.Fill>
    				<SolidColorBrush Color="{StaticResource PhoneContrastBackgroundColor}"/>
    			</Rectangle.Fill>
    		</Rectangle>
    		<Rectangle x:Name="HourHand" Height="13" Margin="0,0,0,25" Width="2" StrokeThickness="0" HorizontalAlignment="Center" VerticalAlignment="Bottom" RenderTransformOrigin="0.5,1">
    			<Rectangle.RenderTransform>
    				<CompositeTransform/>
    			</Rectangle.RenderTransform>
    			<Rectangle.Fill>
    				<SolidColorBrush Color="{StaticResource PhoneContrastBackgroundColor}"/>
    			</Rectangle.Fill>
    		</Rectangle>
    	</Grid>
    </Viewbox>
</UserControl>

Step 13 : Add appear disappear animations

Add AppearClock and DisappearClock animations just below MoveHands inside UserControl.Resource, like so:

        <Storyboard x:Name="AppearClock">
            <DoubleAnimation Duration="0:0:0.3" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="LayoutRoot" d:IsOptimized="True"/>
        </Storyboard>
        <Storyboard x:Name="DisappearClock">
            <DoubleAnimation Duration="0:0:2" To="0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="LayoutRoot" d:IsOptimized="True"/>
        </Storyboard>

Step 14 : Update code behind

Open code behind UCWaitSpinner.xaml.cs and add following lines to its constructor:

        public UCWaitSpinner()
        {
            InitializeComponent();
            MoveHands.RepeatBehavior = RepeatBehavior.Forever;
            LayoutRoot.Opacity = 0d;
            DisappearClock.Completed += (object sender, EventArgs e) => { MoveHands.Stop(); };
        }

Step 15 : Add start functionality

Add a public Start method to the class:

        public void Start()
        {
            MoveHands.Stop();
            AppearClock.Begin();
            MoveHands.Begin();
        }

Step 16 : Add stop functionality

Add a public Stop methods to the class:

        public void Stop()
        {
            DisappearClock.Begin();
        }

Step 17 : Build

Build XAMLControls project. If your project builds properly you should see UCWaitSpinner listed in your Toolbox in XAML Controls.

Step 18 : Add spinner to form

Open MainPage.xaml from the main project WaitSpinner and drag UCWaitSpinner from the Toolbox to the page. Open MainPage.xaml in code view and your will find the newly added control in ContentPanel:

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
                <my:UCWaitSpinner/>
        </Grid>

Step 19 : Edit control properties

In MainPage.xaml name UCWaitSpinner control as “waitSpinner”, add Height and Width properties with a value of 120 in both, and wrap the control in a StackPanel:

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <StackPanel>
                <my:UCWaitSpinner x:Name="waitSpinner" Height="120" Width="120" />
            </StackPanel>
        </Grid>

Step 20 : Add buttons to control test

Add two buttons, just below WaitSpinner, Start and Stop with Click handlers to control WaitSpinner:

        <my:UCWaitSpinner x:Name="waitSpinner" Height="120" Width="120" />
        <Button x:Name="buttonStart" Content="Start" Click="buttonStart_Click"/>
        <Button x:Name="buttonStop" Content="Stop"  Click="buttonStop_Click"/>

Step 21 : Write code in click handlers

Open MainPage.xaml.cs and call Start and Stop of WaitSpinner in buttons’ click handlers:

        private void buttonStart_Click(object sender, RoutedEventArgs e)
        {
            waitSpinner.Start();
        }

        private void buttonStop_Click(object sender, RoutedEventArgs e)
        {
            waitSpinner.Stop();
        }

You can run your code and click on Start button to start spinner. Click on Stop button and spinner will slowly fade out.

Let’s see the example code to integrate the control with background processes.

Make following changes to MainPage.xaml’s ContentPanel control:

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <StackPanel>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition/>
                        <ColumnDefinition/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Column="0" x:Name="textBlockSeconds" Text="..."/>
                    <my:UCWaitSpinner Grid.Column="1" x:Name="waitSpinner2" Height="90" Width="90" />
                </Grid>
                <my:UCWaitSpinner x:Name="waitSpinner" Height="120" Width="120" />
                <Button x:Name="buttonStart" Content="Start" Click="buttonStart_Click"/>
                <Button x:Name="buttonStop" Content="Stop"  Click="buttonStop_Click"/>
                <Button x:Name="buttonBackground" Content="Background" Click="buttonBackground_Click"/>
            </StackPanel>
        </Grid>

And add following code to the code behind MainPage.xaml.cs:

    public partial class MainPage : PhoneApplicationPage
    {
        // Constructor
        Thread bgThread;
        public MainPage()
        {
            InitializeComponent();
            bgThread = new Thread(new ThreadStart(() => 
            {
                Dispatcher.BeginInvoke(new Action(() => { waitSpinner2.Start(); }));
                for (int cnt = 0; cnt < 5; cnt++)
                {
                    Dispatcher.BeginInvoke(new Action(() => { textBlockSeconds.Text = string.Format("Step - {0}/4", cnt); }));
                    Thread.CurrentThread.Join(2000);
                }
                Dispatcher.BeginInvoke(new Action(() => { waitSpinner2.Stop(); }));
            }));
        }

        private void buttonStart_Click(object sender, RoutedEventArgs e)
        {
            waitSpinner.Start();
        }

        private void buttonStop_Click(object sender, RoutedEventArgs e)
        {
            waitSpinner.Stop();
        }

        private void buttonBackground_Click(object sender, RoutedEventArgs e)
        {
            buttonBackground.IsEnabled = false;
            bgThread.Start();
        }
    }

F5. You should see something like this:

Download entire code here.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s