Friday, July 29, 2011

How To Create Circular Loader Using XAML


Most of the time we need to show some "Loading..." text to the user to make him understand that something is being getting Loaded or to restrict the user from clicking more several times thinking that application is not getting responded. Showing some "Loading..." type of text was used long back before .NET framework 3.0. Now with improved Framework and better configuration users would be more likely to see some sort of animations to quickly identify what it is intended to do. To cut long story short we need to build some smart Loader for the user in various XAML Applications - WPF, Silverlight & even for WP7.

                                    In this article, we will learn how to create a circular Loader as shown in the image above. I will not use a single line of C# code to create this loader. Everything will be in the XAML file itself to keep the code behind file clean. Read to know more about it step-by-step and after reading this article, you will be able to create and use a circular loader like this. Source code is also available for download at the end of the page.

Follow below mentioned steps to create your own Circular Loader - 

Setup the Project


First we need to create a WPF project. Here we will create a WPF 4 application project.



This will create a new solution project. Once done with this step, it's time to create the UI of the loader control. 

Create the UI of the Circular Loader


                                                               Now time to create the XAML of the loader. Remember that, we will not have a single line of C# code. Everything will be done in the XAML. If you are expert with Expression Blend, you can create it very easily. But here we will use direct XAML code to demonstrate the same. It will be helpful for you to understand properly.
                                               
                                                               Let's open the MainWindow xaml file and replace the original Grid control with a ViewBox control. You may ask one question here: Why should I use a ViewBox? ViewBox will allow you to resize the content of the panel with respect to parent container. This means if you explicitly increase or decrease the size of the user control, the inner content will proportionally resize as per the container size.

                                                              Now we will add a Canvas inside the ViewBox, so that, we can properly place the circles inside it in proper (x, y) coordinates.After this step, we need to add some Ellipse control inside the Canvas panel to create a Circle. We will fill the color of the Ellipse with the Black. We will use proper binding to do this, as shown below:

<Ellipse Fill="Black" 
                    Height="71" Canvas.Left="121" Canvas.Top="21" Width="69" Opacity="0.3"/>

                                                              In the above code snippet, you can see that, we added Canvas.Left and Canvas.Top to position the circle. Similarly, we will add some more circles using the Ellipse control and position them properly to create a circular path.

Setting the Opacity of the Circles


                                                              We need to set the opacity of the circles to create the similar look as we wanted to develop. Opacity will ensure that, we will use the same color but different transparency to create the UI.

Find the below XAML code for reference:

            <Ellipse Fill="Black" Height="71" Width="69" Canvas.Left="121" Canvas.Top="21" Opacity="0.3"/>

            <Ellipse Fill="Black" Width="69" Height="71" Canvas.Left="221" Canvas.Top="123" Opacity="0.5"/>
            <Ellipse Fill="Black" Width="69" Height="71" Canvas.Left="190" Canvas.Top="198" Opacity="0.6"/>
            <Ellipse Fill="Black" Height="71" Width="69" Canvas.Left="121" Canvas.Top="226" Opacity="0.7"/>
            <Ellipse Fill="Black" Width="69" Height="71" Canvas.Left="48" Canvas.Top="194" Opacity="0.8"/>
            <Ellipse Fill="Black" Height="71" Width="69" Canvas.Left="17" Canvas.Top="123" Opacity="0.9"/>
            <Ellipse Fill="Black" Height="71" Width="69" Canvas.Left="48" Canvas.Top="52" Opacity="1.0"/>

                                                              Have a look into the above XAML code. You will see how we placed the circles in proper coordinate positions. Also check the opacity that we set for each individual circles.


Find the complete code of the UI here, in case you need proper visibility on the code that we created just now:

<Window x:Class="CircularLoader.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="325">
    <Viewbox x:Name="LayoutRoot" HorizontalAlignment="Left" VerticalAlignment="Top">
        <Canvas x:Name="canvas" Height="300" Width="300">
            <Ellipse Fill="Black" Height="71" Canvas.Left="121" Canvas.Top="21" Width="69" Opacity="0.3"/>
            <Ellipse Fill="Black" Height="71" Width="69" Canvas.Left="194" Canvas.Top="52" Opacity="0.4"/>
            <Ellipse Fill="Black" Width="69" Height="71" Canvas.Left="221" Canvas.Top="123" Opacity="0.5"/>
            <Ellipse Fill="Black" Width="69" Height="71" Canvas.Left="190" Canvas.Top="198" Opacity="0.6"/>
            <Ellipse Fill="Black" Height="71" Canvas.Left="121" Canvas.Top="226" Width="69" Opacity="0.7"/>
            <Ellipse Fill="Black" Width="69" Height="71" Canvas.Left="48" Canvas.Top="194" Opacity="0.8"/>
            <Ellipse Fill="Black" Height="71" Width="69" Canvas.Left="17" Canvas.Top="123" Opacity="0.9"/>
            <Ellipse Fill="Black" Height="71" Width="69" Canvas.Left="48" Canvas.Top="52" Opacity="1.0"/>
        Canvas>
    Viewbox>
Window>

                        Run the application to get the below shown output. Note the output we wanted is still not completed but a step ahead to achieve what we wanted.


Create the Storyboard


                                                          Now what we need in this step? We need to create a smooth animation which we can achieve using the StoryBoard. We will create a Storyboard now to rotate the circles in the orbit. You can use the Expression Blend to create the animation easily. Below is sample code for part of animation of ellipse.

<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="ellipse">
                                <EasingDoubleKeyFrame KeyTime="0" Value="0.3"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.1" Value="1"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0.9"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="0.8"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0.7"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.6" Value="0.6"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0.5"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.8" Value="0.4"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.9" Value="0.3"/>
                        DoubleAnimationUsingKeyFrames>

What the above code do? The storyboard just plays with the opacity property. Let me be elaborate more –

<EasingDoubleKeyFrame KeyTime="0" Value="0.3"/>
                                                 Above line indicates the opacity of ellipse at time “0” which is “0.3”.

<EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0.5"/>
                                                 Similarly above line indicates the opacity of “0.5” at time “0.7 seconds”

On similar ground I have written the code for all the ellipses (use it for your reference) –

<Storyboard x:Key="OnLoaded1" RepeatBehavior="Forever">
                        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="ellipse">
                                <EasingDoubleKeyFrame KeyTime="0" Value="0.3"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.1" Value="1"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0.9"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="0.8"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0.7"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.6" Value="0.6"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0.5"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.8" Value="0.4"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.9" Value="0.3"/>
                        DoubleAnimationUsingKeyFrames>
                        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="ellipse7">
                                <EasingDoubleKeyFrame KeyTime="0" Value="1"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.1" Value="0.9"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0.8"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="0.7"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0.6"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.6" Value="0.5"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0.4"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.8" Value="0.3"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.9" Value="1"/>
                        DoubleAnimationUsingKeyFrames>
                        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="ellipse6">
                                <EasingDoubleKeyFrame KeyTime="0" Value="0.9"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.1" Value="0.8"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0.7"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="0.6"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0.5"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.6" Value="0.4"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0.3"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.8" Value="1"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.9" Value="0.9"/>
                        DoubleAnimationUsingKeyFrames>
                        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="ellipse5">
                                <EasingDoubleKeyFrame KeyTime="0" Value="0.8"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.1" Value="0.7"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0.6"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="0.5"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0.4"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.6" Value="0.3"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="1"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.8" Value="0.9"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.9" Value="0.8"/>
                        DoubleAnimationUsingKeyFrames>
                        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="ellipse4">
                                <EasingDoubleKeyFrame KeyTime="0" Value="0.7"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.1" Value="0.6"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0.5"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="0.4"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0.3"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.6" Value="1"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0.9"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.8" Value="0.8"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.9" Value="0.7"/>
                        DoubleAnimationUsingKeyFrames>
                        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="ellipse3">
                                <EasingDoubleKeyFrame KeyTime="0" Value="0.6"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.1" Value="0.5"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0.4"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="0.3"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.6" Value="0.9"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0.8"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.8" Value="0.7"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.9" Value="0.6"/>
                        DoubleAnimationUsingKeyFrames>
                        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="ellipse2">
                                <EasingDoubleKeyFrame KeyTime="0" Value="0.5"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.1" Value="0.4"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0.3"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="1"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0.9"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.6" Value="0.8"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0.7"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.8" Value="0.6"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.9" Value="0.5"/>
                        DoubleAnimationUsingKeyFrames>
                        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="ellipse1">
                                <EasingDoubleKeyFrame KeyTime="0" Value="0.4"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.1" Value="0.3"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="1"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="0.9"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0.8"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.6" Value="0.7"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0.6"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.8" Value="0.5"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.9" Value="0.4"/>
                        DoubleAnimationUsingKeyFrames>
                Storyboard>

Note - The storyboard will rotate the canvas to 360 degree to give a circular motion to the circles. Also we will set the RepeatBehavior to indefinite by setting the value "Forever". Here the storyboard will be available as a Resource to the Window. Hence we can easily access it whenever require but in the same window as the resource defined in the same window are bound or accessible through the same window only or move it to the application level to make available throughout the application.

                                                        Once the storyboard has been created, we need to run it on load. The simplest approach here will be calling the Begin() method of the storyboard from code behind class. As I told, we will not write a single line in the code behind, we need to do something from the XAML which will cause the storyboard to run. 

Below is the code to achieve the above task –

<Window.Triggers>
                <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                        <BeginStoryboard Storyboard="{StaticResource OnLoaded1}"/>
                EventTrigger>
        Window.Triggers>



See it in Action




                                                            If you resize the window, it will change the size of the circular loader in proportion to the parent panel. This is because of the “Viewbox” We used in our XAML code.

Hope, you enjoyed reading the article. This helped you to understand the same in depth. Now you will be able to modify the same code and implement additional behavior. You can also expose some APIs on need basis.

Download Source Code

Do you need the source code of the project for your reference? Yes, you can download it from here:


No comments:

Post a Comment