![]() |
Web Development »
Silverlight »
HowTo
Beginner
License: The Microsoft Public License (Ms-PL)
Step by Step Creating a Vista Busy Cursor Like Silverlight ControlBy cokkiyFrom 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
|
||||||||
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
![]()
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.
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.
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.
In Blend, the Page.xaml already opened. In "Objects and Timeline" panel, select the LayoutRoot element. Double clicking it let's in selected state.
![]()
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.
![]()
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.
![]()
The final Visual of the control look like the following:
![]()
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.
![]()
Rename the "VisualStateGroup" to "BusyIdleStates". Clicking the "Add State" button in right of the "BusyIdleStates", add two VisualState named it "BusyState" and "IdleState".
![]()
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.
![]()
The "Brush Transform" tool and using it.
![]()
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.
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.
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.
![]()
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.
![]()
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>
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.
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.
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.
| You must Sign In to use this message board. | |||||||||||||||
|
|||||||||||||||
|
|||||||||||||||
|
|||||||||||||||
|
|||||||||||||||
General
News
Question
Answer
Joke
Rant
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 |