Click here to Skip to main content
6,629,885 members and growing! (20,023 online)
Email Password   helpLost your password?
Web Development » Silverlight » HowTo     Beginner License: The Microsoft Public License (Ms-PL)

Step by Step Creating a Vista Busy Cursor Like Silverlight Control

By cokkiy

From this article you can learn how to create a Vista busy cursor like Silverlight control step by step.
C# (C# 2.0, C# 3.0), .NET (.NET 2.0, .NET 3.0, .NET 3.5), Silverlight, Dev, Design
Posted:1 Dec 2008
Views:9,967
Bookmarked:13 times
Unedited contribution
Announcements
Loading...
 
Search    
Advanced Search
Add to IE Search
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
7 votes for this article.
Popularity: 3.89 Rating: 4.61 out of 5

1

2

3
2 votes, 28.6%
4
5 votes, 71.4%
5

Screen Capture of Busy Icon

Introduction

In many times, in your Silverlight application, you need download something or do something need a period of time. At this time you may want to display an indicator to indicate the user your application is in busy. So you may want to create such a control can be used in your application. In this article I'll step by step creating a Vista busy cursor like control.

Part 1. Cteating a Silverlight App

Why we first creating a SL application? The reason is the Expression Blend 2 (with SP1) can't directly design the SL control's default template. So we first creating a application using Belend to design the visual of the control.

Step 1.1 Creating a Silverlight Application In VS 2008

Open Visual Studio 2008, creating a Silverlight application. Right clicking the Page.xaml file, selecting "Open in Expression Blend", the project will be opened with Blend 2.

Step 1.2 Designing the Visual of the Control

In Blend, the Page.xaml already opened. In "Objects and Timeline" panel, select the LayoutRoot element. Double clicking it let's in selected state.

Select the LayoutRoot

Then, selecting a Grid control from the "ToolBox", double clicking it add to the LayoutRoot. In "Properties" panel, set the Grid's Width and Height to Auto, and the HorizontalAlignment and the VerticalAlignment to Center.

Set Grid properties

In "Objects and Timeline" panel, double clicking the Grid just added make it as selected. Adding a Ellipse from "ToolBox" panel inside into it. Set the Width and the Height properties to 20. The Fill to None and the Stroke to a Gradient Brush and the StrokeThickness to 6. And set the Opacity to 0.

Ellipse Properties

The final Visual of the control look like the following:

Final Visual of the Control

Step 1.3 Creating the VisualStates

Now we creating VisualStates, VisualState "represents the visual appearance of the control when it is in a specific state" (from MSDN). Our control can be in one of two states: BusyState and IdleState. In BusyState the control will be visible and an animation will be display. In IdleState the control will be hide.

In "States" panel, click the "Add states group" button add a states group.

Add states group

Rename the "VisualStateGroup" to "BusyIdleStates". Clicking the "Add State" button in right of the "BusyIdleStates", add two VisualState named it "BusyState" and "IdleState".

Add two state

Selecting the "BusyState" in "States" Panel. Then Opening the Timeline panel and in "Objects and Timeline" panel selecting the Ellipse element. Then selecting the Stroke property of the Ellipse element. In "ToolBox" selecting "Brush Transform" tool, move the key timeline to "0:00.300", using the "Brush Transform" tool rotate the Stroke brush to 45 degree.

Selecting the ellipse The stroke property

The "Brush Transform" tool and using it.

The Brush Transform tool

Move the timeline to "0:00.600", using the "Brush Transform" tool rotating the Brush to 90 degree. Repeat this step until rotating a whole circle.

In the end, selecting the Opacity property, move the Timeline to "0:00.000", set it to 100%. This make the control to visible. After this step, close the "BusyState" by clicking the "Base" in State Panel.

Step 1.4 Testing

Adding following code to the Page.xaml.cs file.

 public Page()
{
   InitializeComponent();
   // add a event handle to Loaded event
   this.Loaded += new RoutedEventHandler(Page_Loaded);
}

void Page_Loaded(object sender, RoutedEventArgs e)
{
    // Go to BusyState
   VisualStateManager.GoToState(this, "BusyState", false);
}                 

Right clicking the project in VS 2008, selecting "Debug" then "Running a new instance". You will see the effects of the application.

Part 2. Creating the Control

Step 2.1 File Structure of Our Control

In this part, we changing our just created application to a control. So you can easy using this control in any application. First, add a "Silverlight Class Library" project to the current solution, named it "WaitingIcon". Rename the class1.cs to WaitingIcon.cs, also rename the class1 class to WaitingIcon, and make it derived from Control class. Then add a folder to the project named it "themes", add a new text file to themes folder and named it "generic.xaml", our default control template will be here.

Project Art

Selecting the Generic.xaml file and right click it, then selecting the Property menu item. In the Property window, set the Build Action to Resource and delete the text in the Custom Tool box.

Generic file property

Step 2.2 Creating Default Control Template of Our Control

Opening the Generic.xaml file, add following code to the file.

 <ResourceDictionary  xmlns="http://schemas.microsoft.com/client/2007"
                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                     xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows" 
                     xmlns:controls="clr-namespace:Cokkiy.Display">       
    <Style TargetType="controls:WaitingIcon">        
        <Setter Property="Template">
            <Setter.Value>
                <--Control Template for the WaitingIcon-->
                <ControlTemplate TargetType="controls:WaitingIcon">   
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary> 

Opening the Page.xaml file we created in Part1, copy all code between "<Grid>" and "</Grid>" to Generic.xaml file and insert it into after "<ControlTemplate TargetType="controls:WaitingIcon">".

 <ControlTemplate TargetType="controls:WaitingIcon">                    
    <Grid>                        
        <Ellipse StrokeThickness="{TemplateBinding StrokeThickness}" x:Name="ellipse" 
           Stroke="{TemplateBinding Background}" Opacity="0">
        </Ellipse>
    </Grid>
</ControlTemplate>    

In the application, we directly set the Ellipse StrokeThickness to 6. But in our control, we set it to TemplateBinding so the end user can set the width. And changing the Stroke property to TemplatingBinding so the end user can set a different Brush for our control. We can add a default Brush for the Stroke by add following code after <Style TargetType="controls:WaitingIcon"> and a default width for the StrokeThickness property.

 <Style TargetType="controls:WaitingIcon">
   <Setter Property="StrokeThickness" Value="6"/>
   <Setter Property="Background">
      <Setter.Value>
         <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
             <GradientStop Color="#FF0A0E94" Offset="0.576"/>
             <GradientStop Color="#FF0FFF1B" Offset="1"/>
         </LinearGradientBrush>
      </Setter.Value>
   </Setter>    

Copy the "VisualStates" define from the Page.xmal to Generic.xaml and insert it into after <Grid>.

 <Grid>
   <vsm:VisualStateManager.VisualStateGroups>
         <vsm:VisualStateGroup x:Name="BusyIdleStates">
               <vsm:VisualState x:Name="BusyState">
                    <Storyboard AutoReverse="False" RepeatBehavior="Forever">
                        <PointAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="ellipse" 
                                 Storyboard.TargetProperty="(Shape.Stroke).(LinearGradientBrush.StartPoint)">
                            <SplinePointKeyFrame KeyTime="00:00:00.25" Value="0.868,0.161"/>
                            <SplinePointKeyFrame KeyTime="00:00:00.5" Value="0.997,0.44"/>
                            <SplinePointKeyFrame KeyTime="00:00:00.75" Value="0.845,0.863"/>
                            <SplinePointKeyFrame KeyTime="00:00:01" Value="0.545,0.999"/>
                            <SplinePointKeyFrame KeyTime="00:00:01.2500000" Value="0.166,0.873"/>
                            <SplinePointKeyFrame KeyTime="00:00:01.5" Value="0.001,0.536"/>
                            <SplinePointKeyFrame KeyTime="00:00:01.7500000" Value="0.084,0.222"/>
                            <SplinePointKeyFrame KeyTime="00:00:02" Value="0.462,0.001"/>
                        </PointAnimationUsingKeyFrames>
                        <PointAnimationUsingKeyFrames BeginTime="00:00:00" 
                              Storyboard.TargetName="ellipse" 
                               Storyboard.TargetProperty="(Shape.Stroke).(LinearGradientBrush.EndPoint)">
                             <SplinePointKeyFrame KeyTime="00:00:00.25" Value="0.132,0.839"/>
                             <SplinePointKeyFrame KeyTime="00:00:00.5" Value="0.003,0.56"/>
                             <SplinePointKeyFrame KeyTime="00:00:00.75" Value="0.155,0.137"/>
                             <SplinePointKeyFrame KeyTime="00:00:01" Value="0.455,0.001"/>
                             <SplinePointKeyFrame KeyTime="00:00:01.2500000" Value="0.834,0.127"/>
                             <SplinePointKeyFrame KeyTime="00:00:01.5" Value="0.999,0.464"/>
                             <SplinePointKeyFrame KeyTime="00:00:01.7500000" Value="0.916,0.778"/>
                             <SplinePointKeyFrame KeyTime="00:00:02" Value="0.538,0.999"/>
                        </PointAnimationUsingKeyFrames>
                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="ellipse"
                                  Storyboard.TargetProperty="Opacity">
                             <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
                        </DoubleAnimationUsingKeyFrames>
                    </Storyboard>
               </vsm:VisualState>
           <vsm:VisualState x:Name="IdleState"/>
       </vsm:VisualStateGroup>
   </vsm:VisualStateManager.VisualStateGroups>       

Step 2.3 Creating Code for Our Control

We just creating file structure and Control Template for our control, now we should add code to it. In the control template we binding the Ellipse's StrokeThickness to a property named StrokeThickness, so we first add the StrokeThickness to our control code.

 #region StrokeThickness Property
/// <summary>
/// Gets or sets the width of the <see cref="WaitingIcon"/> stroke outline. 
/// </summary>
/// <value>The width of the <see cref="WaitingIcon"/>  outline, in pixels. 
/// The default value is 0. </value>
public double StrokeThickness
{
    get { return (double)GetValue(StrokeThicknessProperty); }
    set { SetValue(StrokeThicknessProperty, value); }
}

/// <summary>
/// Identifies the <see cref="StrokeThickness"/> dependency property. 
/// </summary>
public static readonly DependencyProperty StrokeThicknessProperty =
      DependencyProperty.Register("StrokeThickness", typeof(double), typeof(WaitingIcon),
            new PropertyMetadata(6.0)); 
#endregion       

The purpose of we creating this control  is to indicate the application is busy doing something, so our control should have a property indicating whether in busy or not.

 #region IsBusy Property
/// <summary>
/// Gets or sets a value indicating is busy or not
/// </summary>
/// <value>A value indicating whether the control is in busy state or not.
/// <para>The default value is <c>false</c>.</para></value>
public bool IsBusy
{
    get { return (bool)GetValue(IsBusyProperty); }
    set { SetValue(IsBusyProperty, value); }
}

/// <summary>
/// Identifies the <see cref="IsBusy"/> dependency property. 
/// </summary>
public static readonly DependencyProperty IsBusyProperty =
       DependencyProperty.Register("IsBusy", typeof(bool), 
          typeof(WaitingIcon),
            new PropertyMetadata(false, IsBusyPropertyChanged));

/// <summary>
/// The <see cref="IsBusy"/> property changed callback function.
/// </summary>
/// <param name="d">The <see cref="WaitingIcon"/> control whosevsee cref="IsBusy"/> property changed.</param>
/// <param name="e">The DependencyPropertyChangedEventArgs contains old and new value.</param>
private static void IsBusyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
     WaitingIcon wi = d as WaitingIcon;
     wi.IsBusyChanged((bool)e.OldValue, (bool)e.NewValue);
} 
#endregion   

When the IsBusy property set to true, our control should be visible and display the animation we created. It's simple we just go to the "BusyState".

 /// <summary>
/// The <see cref="IsBusy "/> property changed.
/// </summary>
/// <param name="oldValue">The old value of the <see cref="IsBusy"/> property.</param>
/// <param name="newValue">The new  value of the <see cref="IsBusy"/> property.</param>
protected virtual void IsBusyChanged(bool oldValue, bool newValue)
{
    if (newValue)
    {
         VisualStateManager.GoToState(this, WaitingIcon.BusyStateName, false);
    }
    else
    {
         VisualStateManager.GoToState(this, WaitingIcon.IdleStateName, false);
    }
}    

The final step is apply the default control template to our control. In constructor, set the DefaultStyleKey to the type of our control.

/// <summary>
/// Initialize a new instance of <see cref="WaitingIcon"/> class.
/// </summary>
public WaitingIcon()
{
    // The default style key
    this.DefaultStyleKey = typeof(WaitingIcon);
}

/// <summary>
/// Apply new template
/// </summary>
public override void OnApplyTemplate()
{
    base.OnApplyTemplate();
    
    if (this.IsBusy)
    {
         // if set to busy in XAML, we must go to BusyState here
         VisualStateManager.GoToState(this, WaitingIcon.BusyStateName, false);
    }
}

Compiling the project of we just created, our control ready to use.

Part 3. Using the Control

Create a new Silverlight application, add reference to our control assembly. The in the Page.xaml file, put our control where you want and set a backgroud and storke thickness or just using the default.

<UserControl x:Class="WaitingTest.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:cdc="clr-namespace:Cokkiy.Display;assembly=Cokkiy.Display.WaitingIcon"
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="#FF090808">
        <cdc:WaitingIcon Width="20" Height="20" IsBusy="True">
        	<cdc:WaitingIcon.Background>
        		<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
        			<GradientStop Color="#FF070B9C" Offset="0.57599997520446777"/>
        			<GradientStop Color="#FFFFFFFF" Offset="1"/>
        		</LinearGradientBrush>
        	</cdc:WaitingIcon.Background>
        </cdc:WaitingIcon>
   </Grid>

The IsBusy property you may want set in code when your application in really busy state.

Points of Interest

You may notice both in IsBusyChanged function and OnApplyTemplate I do same checking the IsBusy property value and go to "BusyState" when it set to True. The reason is when you set IsBusy to True in XAML, the IsBusyChanged function called before the Template applied. At that time, the "BusyState" VisualState not exists at all and nothing will happen at all. So you need recheck when template applied, if the value is true, you should go "BusyState" here.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

About the Author

cokkiy


Member
The God created the world.
The programer made the world easy.
I am a programer like c# and c++, so I am writting application in it.
Creating beautiful application make life easy.
If you have a project and looking for a man, I'll be say "hi, I am just the man you are looking for."
Occupation: Team Leader
Location: China China

Other popular Silverlight articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 2 of 2 (Total in Forum: 2) (Refresh)FirstPrevNext
GeneralWorks great Pinmemberdefwebserver11:46 25 Oct '09  
GeneralVery Cool PinmemberPaul Conrad8:14 1 Dec '08  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 1 Dec 2008
Editor:
Copyright 2008 by cokkiy
Everything else Copyright © CodeProject, 1999-2009
Web20 | Advertise on the Code Project