Home > Silverlight > Creating a Custom Silverlight Task Dashboard Control (Part 1)

Creating a Custom Silverlight Task Dashboard Control (Part 1)

September 11, 2010 Leave a comment Go to comments

Recently I was tasked with working on a Silverlight application where we were pulling data from SharePoint. One of the things we were pulling was tasks from a SharePoint tasks list. I wanted to display a simple dashboard giving a breakdown of tasks by status (i.e. In Progress, Not Started, Completed, etc.) with visual progress bar indicators of the percentage for each like shown below:

I got the idea from a white paper I read once about creating dashboard web parts using XSLT in SharePoint Designer a while back. Anyhow, I digress, but basically I needed to create a custom control that I can plug into any Silverlight app, set some properties, and viola! Since this was my first attempt at creating a custom Silverlight control, I thought why not write a blog about it.

I went ahead and started by creating a Silverlight control library in Visual Studio. I added a class which I called TaskDashboard and derived it from the base Control class. At this point all I have is this:

public class TaskDashboard: Control
{

}

Now, I need to expose properties so that the control user can set the values for each of the task statuses.  Although there are 5 different statuses for a SharePoint task, I only care about 3; Completed, In Progress, and Not Started. To expose these, I  created 3 dependency properties. Now, they don’t have to be dependency properties but, they come in handy when you want to add things like data binding, styles,  and animations (as you’ll see later). And, since most of the properties exposed by Silverlight elements are dependency properties, why go against the current. This is what that looks like:

public static readonly DependencyProperty CompletedProgressValueProperty = DependencyProperty.Register("CompletedProgressValue", typeof(double), typeof(TaskDashboard), null);
public double CompletedProgressValue
{
    get { return (double)base.GetValue(CompletedProgressValueProperty); }
    set { base.SetValue(CompletedProgressValueProperty, value); }
}
public static readonly DependencyProperty InProgressProgressValueProperty = DependencyProperty.Register("InProgressProgressValue", typeof(double), typeof(TaskDashboard), null);
public double InProgressProgressValue
{
    get { return (double)base.GetValue(InProgressProgressValueProperty); }
    set { base.SetValue(InProgressProgressValueProperty, value); }
}
public static readonly DependencyProperty NotStartedProgressValueProperty = DependencyProperty.Register("NotStartedProgressValue", typeof(double), typeof(TaskDashboard), null);
public double NotStartedProgressValue
{
    get { return (double)base.GetValue(NotStartedProgressValueProperty); }
    set { base.SetValue(NotStartedProgressValueProperty, value); }
}

At this point I need to create the default control template that will define how the control will look. For ASP.NET developers, this is similar to defining your UI markup in an .ascx file when creating a custom web user control. However, unlike ASP.NET, the wiring is not implicit and there are certain rules to follow:

  1. The control template definition must be placed in a file named generic.xaml. If you’re creating more than one control, all of their templates must be defined there.
  2. Also, the generic.xaml file itself must reside in a folder named Themes; this is because Silverlight is very closely related with WPF and that’s the way the cookie crumbles for lack of a better term.


In generic.xaml, you need to have a resource dictionary with a style for each control in the control library, if you’re only working with one control, just declare one style. In the style, you set the Template property for your control as follows:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:SLCustomControls;assembly=SLCustomControls">
 
    <Style TargetType="local:TaskDashboard">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:TaskDashboard">
                    <!-- Define the template by adding elements here -->                  
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

Notice the mapping of the namespace prefix which has the namespace and the assembly name. Also, notice the TargetType property of the Style tag; it specifies the namespace prefix which you use to bring in your assembly (“local” in my case), colon, and the class name for the control. One other part that’s needed is to set the control’s DefaultStyleKey property in the constructor as follows:

// Constructor
public TaskDashboard()
{
 DefaultStyleKey = typeof(TaskDashboard);
}

So now that I have my basic pieces, I need to define what goes inside the ControlTemplate; this is what gives the control the basic layout and structure. In this case, it consists of a Border with a Grid that contains the TextBlocks and ProgressBars that I need in order to make this work.

Defining the Layout

The layout consists of a Border which has a Grid. The Grid in turn contains all of the child elements such as TextBlocks and ProgressBars that I need to display the content of the custom control.

<Border BorderBrush="{TemplateBinding BorderBrush}" 
      BorderThickness="TemplateBinding BorderThickness}"
      Background="{TemplateBinding Background}"
      CornerRadius="{TemplateBinding CornerRadius}"
      Width="TemplateBinding Width}">
                        
    <Grid x:Name="RootElement">
        <Grid.Resources>
            <Storyboard x:Name="CompletedStoryboard">
                <DoubleAnimation Storyboard.TargetName="ProgressBarCompleted" 
                            Storyboard.TargetProperty="Value" Duration="0:0:1"  
                            From="0" To="{TemplateBinding CompletedProgressValue}">
                </DoubleAnimation>
            </Storyboard>
            <Storyboard x:Name="InProgressStoryboard">
                <DoubleAnimation Storyboard.TargetName="ProgressBarInProgress" 
                            Storyboard.TargetProperty="Value" Duration="0:0:1" 
                            From="0" To="{TemplateBinding InProgressProgressValue}">
                </DoubleAnimation>
            </Storyboard>
            <Storyboard x:Name="NotStartedStoryboard">
                <DoubleAnimation Storyboard.TargetName="ProgressBarNotStarted"
                            Storyboard.TargetProperty="Value" Duration="0:0:1"   
                            From="0" To="{TemplateBinding NotStartedProgressValue}">
                </DoubleAnimation>
            </Storyboard>
        </Grid.Resources>
        <VisualStateManager.VisualStateGroups>
 
        </VisualStateManager.VisualStateGroups>
 
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="20"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"></ColumnDefinition>
            <ColumnDefinition Width="Auto"></ColumnDefinition>
            <ColumnDefinition Width="Auto"></ColumnDefinition>
        </Grid.ColumnDefinitions>
 
        <!-- Child controls -->
                  
    </Grid>
</Border>

I used the TemplateBinding expressions in order to set some of the properties of the Border. You’re probably wondering where do these properties such as BorderBrush, Background, and Width come from. Recall that the control’s class inherits from the base Control class and that’s where they’re either defined or inherited from a higher level class like FrameworkElement. One thing to note though; the base Control class does not have a property for CornerRadius, in this case, I just created a dependency property in the TaskDashboard class and I bind it here to the Border’s CornerRadius property. This gives me the nice rounded effect around the control.
I also wanted to give this control a simple animation so that when the control loads, the ProgressBars start filling up with their assigned values. To achieve this, I simply added Storyboards as resources under the Grid, one for each ProgressBar. Nothing fancy here,  I’m just animating the Value property of the respective ProgressBar for 1 second from 0 to whatever value the control user sets for each of the task status dependency properties defined earlier.

The Child Controls
These are the elements that make up the control’s default template. You may need to access these elements in order to set properties, handlers, states or whatever but in order to do that, you must first wire these up so that they can be accessed from the control’s code behind(see next section).

<TextBlock x:Name="TextBlockTitle" Grid.Row="0" Grid.ColumnSpan="3" Margin="10,10,10,0"
       Text="{TemplateBinding Title}" Foreground="{TemplateBinding TextLabelForeground}"
       FontWeight="Bold" FontSize="12" />
 
<Border Grid.Row="1" Grid.ColumnSpan="3" BorderBrush="{TemplateBinding BorderBrush}"
   BorderThickness="0.5"  Width="{TemplateBinding TitleUnderlineWidth}"  
   Margin="10,0" />                               
 
 
<!-- Completed Tasks -->
<TextBlock x:Name="TextBlockCompleted" Tag="Completed" Grid.Row="2" Grid.Column="0"
       Margin="10,10" Text="Completed:" FontWeight="Bold"
       Foreground="{TemplateBinding TextLabelForeground}" />
 
<ProgressBar x:Name="ProgressBarCompleted" Margin="10,10"
         Width="{TemplateBinding ProgressBarWidth}" Height="20" HorizontalAlignment="Stretch"
         IsEnabled="True" Grid.Row="2" Grid.Column="1"
         Background="{TemplateBinding CompletedProgressBackground}"
         Foreground="{TemplateBinding CompletedProgressForeground}" Maximum="100" Minimum="0"
         Value="{TemplateBinding CompletedProgressValue}"  />
 
<TextBlock x:Name="TextBlockCompletedPercent" Margin="10,10" Grid.Row="2" Grid.Column="2"
       Text="{TemplateBinding CompletedProgressValue}" FontWeight="Bold"
       Foreground="{TemplateBinding TextLabelForeground}" />
 
<!-- In Progress Tasks -->
<TextBlock x:Name="TextBlockInProgress" Tag="InProgress" Grid.Row="3" Grid.Column="0"
       Margin="10,10" Text="In Progress:" FontWeight="Bold"
       Foreground="{TemplateBinding TextLabelForeground}" />
 
<ProgressBar x:Name="ProgressBarInProgress" Margin="10,10"
        Width="{TemplateBinding ProgressBarWidth}" Height="20" IsEnabled="True" Grid.Row="3"
        Grid.Column="1" Background="{TemplateBinding InProgressProgressBackground}"
        Foreground="{TemplateBinding InProgressProgressForeground}" Maximum="100"
        Minimum="0" Value="{TemplateBinding InProgressProgressValue}"  />
 
<TextBlock x:Name="TextBlockInProgressPercent" Margin="10,10" Grid.Row="3" Grid.Column="2" Text=""
       FontWeight="Bold" Foreground="{TemplateBinding TextLabelForeground}" />
 
<!-- Not Started Tasks -->
<TextBlock x:Name="TextBlockNotStarted" Tag="NotStarted" Grid.Row="4" Grid.Column="0"
       Margin="10,10" Text="Not Started:" FontWeight="Bold"
       Foreground="{TemplateBinding TextLabelForeground}" />
 
<ProgressBar x:Name="ProgressBarNotStarted" Margin="10,10"
        Width="{TemplateBinding ProgressBarWidth}" Height="20" IsEnabled="True" Grid.Row="4"
        Grid.Column="1" Background="{TemplateBinding NotStartedProgressBackground}"
        Foreground="{TemplateBinding NotStartedProgressForeground}"
        Maximum="100" Minimum="0" Value="{TemplateBinding NotStartedProgressValue}"  />
 
<TextBlock x:Name="TextBlockNotStartedPercent" Margin="10,10" Grid.Row="4" Grid.Column="2" Text=""
       FontWeight="Bold" Foreground="{TemplateBinding TextLabelForeground}" />

This is it for now. In (Part 2) of this post I will go into detail of how the wiring of these elements get done with the control’s code behind. If anyone has any questions, feel free to email me or post any comments.

Go to Part 2

Leave a comment