Wednesday, August 10, 2011

Customizing WPF ListView (Part 1)

                                    It is fun experience trying to customize the look and behaviour of the standard listview included in WPF (see the figure below).



                                      In this article, we will learn how to create a Customize ListView as shown in the image above. More Important is to understand the XAML file. Read to know more about it step-by-step and after reading this article, you will be able to create and use a customize listview like this. Source code is also available for download at the end of the page.


Setup the Project



                                    Setting up the project is all done in same way as mentioned in previous blog on Circular Loader.



Creating UI

                                   
                                    Creating a UI for the listview must be quite straight forward to most of the developers. So would jump directly into code:


<ListView x:Name="lvCustomiseListView" ItemsSource="{Binding}">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Server Name" DisplayMemberBinding="{Binding ServerName}"/>
                    <GridViewColumn Header="Server Status" DisplayMemberBinding="{Binding ServerStatus}"/>
                    <GridViewColumn Header="Network Status" DisplayMemberBinding="{Binding NetworkStatus}"/>
                GridView>
            ListView.View>
        ListView>

                            With the above code following will be the output(shown in figure below).


                           This was not the output intended; so we need to customize or change some part of the code in XAML file. How do we achieve the output? For this you should be clear with the concept of DataTemplate.



What is DataTemplate and Where it is Used?



                                        DataTemplate is used whenever you want to modify the default template of any WPF control in our case it’s a WPF ListView control. WPF ListView can contain 'N' number of columns and each column can have defined their own datatemplate or in simple terms the representation format (Ultimately it’s all about Presentation!!!). 



How to use DataTemplate


                          DataTemplate can be defined in the resource section of window if the datatemplate isto be used over multiple listview or it can be defined as a part of listview column. To keep code simpler and for easily readable I have made all three datatemplates as a part of window resources ( for our customize ListView).


Structure of DataTemplate



                                        How does the datatemplate looks like? Below is the sample code:

<DataTemplate x:Key="hpLinkServerName">
            <TextBlock>
                <Hyperlink>
                    <Run Text="{Binding ServerName}"/>
                Hyperlink>
            TextBlock>
        DataTemplate>

                          DataTemplate if defined as part of window resources will require unique key to identify the resource. Note- Key should be unique within the same window only, can be same across windows. Datatemplate can contain multiple controls but should follow the rules of WPF controls.


DataTemplate Xaml Code


<Window.Resources>
        <DataTemplate x:Key="hpLinkServerName">
            <TextBlock>
                <Hyperlink>
                    <Run Text="{Binding ServerName}"/>
                Hyperlink>
            TextBlock>
        DataTemplate>
       
        <DataTemplate x:Key="ServerStatus">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding ServerStatus}" VerticalAlignment="Center"/>
                <Image Source="Resources\Check.png" Height="25" Width="25" Margin="5,0,0,0"/>
            StackPanel>
        DataTemplate>

        <DataTemplate x:Key="NetworkStatus">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding NetworkStatus}" VerticalAlignment="Center"/>
                <Image Source="Resources\Cancel.png" Height="25" Width="25" Margin="5,0,0,0"/>
            StackPanel>
        DataTemplate>
    Window.Resources>

                             Here is the code for customising the listview based on the datatemplates we created above for each of the column-

<ListView x:Name="lvCustomiseListView" ItemsSource="{Binding}">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Server Name" CellTemplate="{StaticResource hpLinkServerName}"/>
                    <GridViewColumn Header="Server Status" CellTemplate="{StaticResource ServerStatus}"/>
                    <GridViewColumn Header="Network Status" CellTemplate="{StaticResource NetworkStatus}"/>
                GridView>
            ListView.View>
        ListView>


See it in Action



                                       Run the application to get the desired output:



Download Source Code

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


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: