DevCrux Blog - Create. Learn. Make Money
DevCrux Blog - Create. Learn. Make Money
  • General
  • Programming
  • Website Design
  • Apps
  • 3D Modelling
  • Revenue
  • Tutorials
Programming, Tutorials

Styling the Chart Control in the Silverlight 4 Toolkit

Download source code – 2.41 MB

Introduction

I noticed that many people, including myself, have had some difficulties in styling the Chart control that comes with the Silverlight 4 Toolkit. It is relatively hard to go down the visual tree of the control in order to style the control in the Design view of Expression Blend so as to have a good look and feel. I searched the internet and I got little information on how to go about this process and I decided to work it through myself. I succeeded in restyling the chart control and the majority of the working is done in XAML. I think this article will ease the challenges of other developers/designers facing this same issue.

There are a series of Chart controls that come with the Silverlight 4 Toolkit, and some of these are listed below:

  • BarSeries
  • ColumnSeries
  • StackedColumnSeries
  • BubbleSeries
  • LineSeries
  • PieSeries
  • ScatterSeries

I will be discussing two of these series:

  • ColumnSeries
  • StackedColumnSeries

Note: You can follow the steps in this article both in Expression Blend 4 and Visual Studio 2010, but I will use Expression Blend 4 in this article. If you do not have the toolkit, it is available at Silverlight Toolkit – CodePlex.

ColumnSeries

With a new Silverlight Application created, I added a Chart control and edited the Template. In this article, I decided to delete the Legend and Chart Title from the Template as I will not need them. The Chart Title can still be of use so you can retain yours but the Legend is not useful as we will be using different colors for each column. The chart control now looks like this:

Chart1.png

Next, edit the XAML in order to bind the DependentValueBinding, IndependentValueBinding properties of the Chart control to a class which controls each column. The DataPointStyle property is also bound to a style created to control how the columns look. The XAML is:

<toolkit:Chart x:Name="GasChart" Style="{StaticResource GasChartStyle}" 
    BorderBrush="{x:Null}" Margin="4,141,0,60">
    <toolkit:ColumnSeries 
    DependentValueBinding="{Binding GasValue}" 
    IndependentValueBinding="{Binding GasName}" 
    DataPointStyle="{StaticResource ColorByGradeColumn}" 
    AnimationSequence="FirstToLast"/>
</toolkit:Chart>

The DataPointStyle is set to a StaticResource that sets the ControlTemplate for the ColumnDataPoint. At this point, we’re changing how the columns will look. This look is a gloss-like column compared to the default ugly look. Paying close attention to the XAML code below, you will notice that the Background property of theBorder in the ControlTemplate is bound to one of the properties (GasColor) of a class. This enables changing the column color dynamically:

<Style x:Key="ColorByGradeColumn" TargetType="toolkit:ColumnDataPoint">
    <Setter Property="Background" Value="DarkGray"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="toolkit:ColumnDataPoint">
                <Border  Background="{Binding GasColor}" 
                         BorderBrush="{Binding GasColor}" 
                         BorderThickness="0.5">
                    <Grid Background="{x:Null}">
                        <ToolTipService.ToolTip>
                        <!--Provides the tooltip for each column -->
                            <StackPanel>
                                <ContentControl 
                                  Content ="{ TemplateBinding IndependentValue }"/>
                                <StackPanel Orientation="Horizontal">
                                      <ContentControl 
                                        Content ="{ TemplateBinding 
                                             FormattedDependentValue }"/>
                                    <ContentControl Content ="ppm"/>
                                </StackPanel>
                            </StackPanel>
                        </ToolTipService.ToolTip>
                        <Border BorderBrush="{x:Null}">
                            <Border.Background>
                                <!--This gradiesnt provides 
                                           the gloss-like feature for each-->
                                <LinearGradientBrush EndPoint="1.344,0.5" 
                                          StartPoint="-0.344,0.5">
                                    <GradientStop Color="White" Offset="0"/>
                                    <GradientStop Color="{Binding GasColor}" 
                                          Offset="0.5"/>
                                </LinearGradientBrush>
                            </Border.Background>
                        </Border>
                    </Grid>
                </Border>            
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Now, the class whose properties are bound to the Chart control is:

public class GasNameValue
{
    public double GasValue { get; set; }
    public string GasName { get; set; }
    public Brush GasColor { get; set; }
}

The method below creates new objects of the class above in a List where the values for each column are set. The number of objects created will determine the number of columns that your Chart will contain; most times your data comes from a database or cloud and this process will be dynamic rather than manual. The last line of code sets the ItemsSource of the Chart control to the List object.

private void SetData()
{
    List<GasNameValue> gasList = new List<GasNameValue>
    {
        new GasNameValue
        {
            GasName = "CO2",
            GasValue = 850.0,
            GasColor = new SolidColorBrush(Colors.Red)
        },
        new GasNameValue
        {
            GasName = "SO2",
            GasValue = 700.0,
            GasColor = new SolidColorBrush(Colors.Blue)
        },
        new GasNameValue
        {
            GasName = "CH4",
            GasValue = 820.0,
            GasColor = new SolidColorBrush(Colors.Green)
        },
        new GasNameValue
        {
            GasName = "NO2",
            GasValue = 600.0,
            GasColor = new SolidColorBrush(Colors.Yellow)
        },
        new GasName_Value
        {
            GasName = "F11",
            GasValue = 910.o,
            GasColor = new SolidColorBrush(Colors.Purple)
        },
        new GasNameValue
        {
            GasName = "F12",
            GasValue = 760.0,
            GasColor = new SolidColorBrush(Colors.Orange)
        },
    };
    ((ColumnSeries)GasChart.Series[0]).ItemsSource = gasList;
}

The final ColumnSeries looks like this:

Chart2.png

StackedColumnSeries

The default StackedColumnSeries looks like this (and it looks ugly to me):

Chart4.png

Now, when working with the StackedColumnSeries, after adding the Chart control and editing the Templateas appropriate, you need to add the SeriesDefinition for each Stacked Column. The number ofSeriesDefinitions that you add determines the number of partitions that you will end up having on each Stacked Column. The XAML below consists of six (6) partitions on each Stacked Column with properties bound to a class:

<toolkit:StackedColumnSeries>
    <toolkit:SeriesDefinition
        Title="CO2"
        ItemsSource="{Binding}"
        DependentValueBinding="{Binding GasValue1}" 
        IndependentValueBinding="{Binding Year}"/>
    <toolkit:SeriesDefinition
        Title="SO2"
        ItemsSource="{Binding}"
        DependentValueBinding="{Binding GasValue2}" 
        IndependentValueBinding="{Binding Year}"/>
    <toolkit:SeriesDefinition
        Title="NO2"
        ItemsSource="{Binding}"
        DependentValueBinding="{Binding GasValue3}" 
        IndependentValueBinding="{Binding Year}"/>
        <toolkit:SeriesDefinition
        Title="CH4"
        ItemsSource="{Binding}"
        DependentValueBinding="{Binding GasValue4}" 
        IndependentValueBinding="{Binding Year}"/>
    <toolkit:SeriesDefinition
        Title="F11"
        ItemsSource="{Binding}"
        DependentValueBinding="{Binding GasValue5}" 
        IndependentValueBinding="{Binding Year}"/>
    <toolkit:SeriesDefinition
        Title="F12"
        ItemsSource="{Binding}"
        DependentValueBinding="{Binding GasValue6}" 
        IndependentValueBinding="{Binding Year}"/>
</toolkit:StackedColumnSeries>

This time around, in order to create a new color for each partition, a ResourceDictionaryCollection must be added to the chart palette; this ResourceDictionaryCollection contains six (6) Resource Dictionaries. Each ResourceDictionary provides the color for each partition on a stack. The XAML is like this:

<toolkit:Chart.Palette>
    <toolkit:ResourceDictionaryCollection>
        <ResourceDictionary>
            <Style x:Key="DataPointStyle" TargetType="toolkit:ColumnDataPoint">
                <Setter Property="Background" Value="Red"/>
                <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="toolkit:ColumnDataPoint">
                        <Border Background="Red" BorderBrush="Red" BorderThickness="0.5">
                            <Grid Background="{x:Null}">
                                <ToolTipService.ToolTip>
                                        <StackPanel Orientation="Horizontal">
                                              <ContentControl 
                                              Content ="{ TemplateBinding 
                                                 FormattedDependentValue }"/>
                                            <ContentControl Content =" ppm"/>
                                        </StackPanel>
                                </ToolTipService.ToolTip>
                                <Border BorderBrush="{x:Null}">
                                    <Border.Background>
                                        <LinearGradientBrush EndPoint="1.344,0.5" 
                                            StartPoint="-0.344,0.5">
                                            <GradientStop Color="White" Offset="0"/>
                                            <GradientStop Color="Red" Offset="0.5"/>
                                        </LinearGradientBrush>
                                    </Border.Background>
                                </Border>
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            </Style>
        </ResourceDictionary>
        <ResourceDictionary>
            <Style x:Key="DataPointStyle" TargetType="Control">
                <Setter Property="Background" Value="Blue"/>
                <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="toolkit:ColumnDataPoint">
                        <Border Background="Blue" 
                              BorderBrush="Blue" BorderThickness="0.5">
                            <Grid Background="{x:Null}">
                                <ToolTipService.ToolTip>
                                        <StackPanel Orientation="Horizontal">
                                              <ContentControl 
                                              Content ="{ TemplateBinding 
                                                  FormattedDependentValue }"/>
                                            <ContentControl Content =" ppm"/>
                                        </StackPanel>
                                </ToolTipService.ToolTip>
                                <Border BorderBrush="{x:Null}">
                                    <Border.Background>
                                        <LinearGradientBrush EndPoint="1.344,0.5" 
                                            StartPoint="-0.344,0.5">
                                            <GradientStop Color="White" Offset="0"/>
                                            <GradientStop Color="Blue" Offset="0.5"/>
                                        </LinearGradientBrush>
                                    </Border.Background>
                                </Border>
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            </Style>
        </ResourceDictionary>
        <ResourceDictionary>
            <Style x:Key="DataPointStyle" TargetType="Control">
                <Setter Property="Background" Value="Green"/>
                <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="toolkit:ColumnDataPoint">
                        <Border Background="Green" 
                               BorderBrush="Green" BorderThickness="0.5">
                            <Grid Background="{x:Null}">
                                <ToolTipService.ToolTip>
                                        <StackPanel Orientation="Horizontal">
                                              <ContentControl 
                                              Content ="{ TemplateBinding 
                                                   FormattedDependentValue }"/>
                                            <ContentControl Content =" ppm"/>
                                        </StackPanel>
                                </ToolTipService.ToolTip>
                                <Border BorderBrush="{x:Null}">
                                    <Border.Background>
                                        <LinearGradientBrush EndPoint="1.344,0.5" 
                                        StartPoint="-0.344,0.5">
                                            <GradientStop Color="White" Offset="0"/>
                                            <GradientStop Color="Green" Offset="0.5"/>
                                        </LinearGradientBrush>
                                    </Border.Background>
                                </Border>
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            </Style>
        </ResourceDictionary><ResourceDictionary>
            <Style x:Key="DataPointStyle" TargetType="Control">
                <Setter Property="Background" Value="Yellow"/>
                <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="toolkit:ColumnDataPoint">
                        <Border Background="Yellow" 
                                BorderBrush="Yellow" BorderThickness="0.5">
                            <Grid Background="{x:Null}">
                                <ToolTipService.ToolTip>
                                        <StackPanel Orientation="Horizontal">
                                              <ContentControl 
                                              Content ="{ TemplateBinding 
                                                  FormattedDependentValue }"/>
                                            <ContentControl Content =" ppm"/>
                                        </StackPanel>
                                </ToolTipService.ToolTip>
                                <Border BorderBrush="{x:Null}">
                                    <Border.Background>
                                        <LinearGradientBrush EndPoint="1.344,0.5" 
                                            StartPoint="-0.344,0.5">
                                            <GradientStop Color="White" Offset="0"/>
                                            <GradientStop Color="Yellow" Offset="0.5"/>
                                        </LinearGradientBrush>
                                    </Border.Background>
                                </Border>
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            </Style>
        </ResourceDictionary>
        <ResourceDictionary>
            <Style x:Key="DataPointStyle" TargetType="Control">
                <Setter Property="Background" Value="Purple"/>
                <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="toolkit:ColumnDataPoint">
                        <Border Background="Purple" 
                              BorderBrush="Purple" BorderThickness="0.5">
                            <Grid Background="{x:Null}">
                                <ToolTipService.ToolTip>
                                        <StackPanel Orientation="Horizontal">
                                              <ContentControl 
                                              Content ="{ TemplateBinding 
                                                FormattedDependentValue }"/>
                                            <ContentControl Content =" ppm"/>
                                        </StackPanel>
                                </ToolTipService.ToolTip>
                                <Border BorderBrush="{x:Null}">
                                    <Border.Background>
                                        <LinearGradientBrush EndPoint="1.344,0.5" 
                                            StartPoint="-0.344,0.5">
                                            <GradientStop Color="White" Offset="0"/>
                                            <GradientStop Color="Purple" Offset="0.5"/>
                                        </LinearGradientBrush>
                                    </Border.Background>
                                </Border>
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            </Style>
        </ResourceDictionary>
        <ResourceDictionary>
            <Style x:Key="DataPointStyle" TargetType="Control">
                <Setter Property="Background" Value="Orange"/>
                <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="toolkit:ColumnDataPoint">
                        <Border Background="Orange" 
                                 BorderBrush="Orange" 
                                 BorderThickness="0.5">
                            <Grid Background="{x:Null}">
                                <ToolTipService.ToolTip>
                                        <StackPanel Orientation="Horizontal">
                                              <ContentControl 
                                                Content ="{ TemplateBinding 
                                                   FormattedDependentValue }"/>
                                            <ContentControl Content =" ppm"/>
                                        </StackPanel>
                                </ToolTipService.ToolTip>
                                <Border BorderBrush="{x:Null}">
                                    <Border.Background>
                                        <LinearGradientBrush EndPoint="1.344,0.5" 
                                            StartPoint="-0.344,0.5">
                                            <GradientStop Color="White" Offset="0"/>
                                            <GradientStop Color="Orange" Offset="0.5"/>
                                        </LinearGradientBrush>
                                    </Border.Background>
                                </Border>
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            </Style>
        </ResourceDictionary>
    </toolkit:ResourceDictionaryCollection>
</toolkit:Chart.Palette>

Note that from the above code, the Chart is bound to a class but this time around, the colors are fixed, although it is still possible to dynamically change the colors by databinding the Background properties in theControlTemplate.

The class to which the chart is bound is:

public class GasName_Value
{
    public string Year { get; set; }
    public double GasValue1 { get; set; }
    public double GasValue2 { get; set; }
    public double GasValue3 { get; set; }
    public double GasValue4 { get; set; }
    public double GasValue5 { get; set; }
    public double GasValue6 { get; set; }
}

The final StackedColumnSeries looks like this:

Chart3.png

These techniques can also be applied to other StackedColumn Series such as Stacked100ColumnSeries,StakedBarSeries, and Stacked100BarSeries, with very little manipulations. Happy Styling!

Download source code – 2.41 MB

August 31, 2015by Oludayo Alli
Programming, Tutorials

Creating a custom TimePicker control in WPF 4.0

Download source code – 127 KB

Introduction

I was working on an application, Time Watcher 5.0, and I needed to use a TimePicker control which was not provided along with controls in WPF 4.0. There are several third party TimePicker controls but I was not satisfied with their “mechanism of action and behaviour”. Below are the steps I took to create my own custom TimePicker control knowing that it will benefit a lot of people by sharing the process.

Please note this!

Please note that this is an introductory article on how to create custom controls for your application from a designer’s point of view. This article will give an insight on how to use existing controls to design your own custom controls, and recommendations on how to create reusable custom controls (with registered properties) are provided at the end of this article.

In this demonstration, I’ll be using Expression Blend 4. Now let’s get to business!

Step 1 – Designing the control

In Expression Blend 4, do the following:

Layout

  1. Add a Grid to the LayoutRoot (This is the parent container)
  2. Set the background colour of the Grid to “#000000”
  3. Add a StackPanel to the Grid you just added
  4. Set the orientation to “horizontal”
  5. Set the vertical alignment and horizontal alignment to “stretch”
  6. Add a ToggleButton to the StackPanel

Your layout should look similar to this:

Layout.jpg

Figure 1

ToggleButton – Edit Template

  1. Right click on the ToggleButton and click Edit Template -> Edit a copy.
  2. Select ContentPresenter and cut, select the Chrome, and delete.
  3. Select Template in the Timeline and add a Grid to it from Asset.
  4. Select the Grid and paste the ContentPresenter inside it (your Object and Timeline panel should look like figure 2(a)).
  5. Make sure the Template node is selected in the Timeline.
  6. Go to the Trigger tab, add a new property (see Figure 2b).
  7. Change the property to IsChecked and set the value to “true” (see Figure 2b).
  8. Now select the Grid inside the Template and change the background colour to “#FF919191”.
  9. Return scope to Window.

Figure_2a.jpgFigure_2b.png

Figure 2

Layout – A finishing touch

  1. Rename the ToggleButton to ‘HrBtn‘.
  2. Select the ToggleButton and add a TextBlock to it.
  3. Rename the TextBlock to HrTxt and set the foreground colour to “#FFFFFFFF”.
  4. Select the ToggleButton and copy.
  5. Paste three copies of the ToggleButton inside the StackPanel.
  6. Rename the new copies of the ToggleButton as MinBtn, SecBtn, and AmPmBtn.
  7. Rename the TextBlock inside each ToggleButton as MinTxt, SecTxt, and AmPmTxt.
  8. Add two other TextBlocks to the StackPanel and set their Text property as ‘:’.
  9. Add two Button controls to the StackPanel and rename them as upBtn and downBtn.
  10. Set the Text property of these Button controls to ‘Up’ and ‘Down’, respectively.
  11. Rearrange your timeline to resemble the one shown below:

Timeline.jpg

Your control should look similar to this:

Control.jpg

Step 2 – Coding your control

Under the upBtn_Click Event Handler, add the following code:

/*The code below check to see which node is selected in the control, 
get the current value of the node, increase the value by one 
and display back to the user. This get fired every time the user clicks the 'Up' Button*/

if (HrBtn.IsChecked == true) //If the hour node is selected in the control
{
    int hour = int.Parse(HrTxt.Text); //Converts the Text to an Integer
    
    if (hour == 12)
        hour = hour - hour;
        // This ensures that hour does not exceed
        // 12 since hour ranges from 1 to 12
    
    hour++; //Increase the hour by 1
    
    HrTxt.Text = hour.ToString();
    //Convert the resulting hour back to string format.
}
else if (MinBtn.IsChecked == true) 
{
    int min = int.Parse(MinTxt.Text); //Converts the Text to an Integer
    
    if (min == 59)
        min = -1; //This ensures that minute does not exceed
                  //60 since minute ranges from 0 to 59
    
    min++;
    
    if (min.ToString().Length == 1)
    // This ensures that the minute text maintain a standard length of 2
    {
        MinTxt.Text = "0" + min.ToString();
    }
    else
    {
        MinTxt.Text = min.ToString();
    }
}
else if (SecBtn.IsChecked == true)
{
    int sec = int.Parse(SecTxt.Text);
    
    if (sec == 59)
        sec = -1;
    
    sec++;
    
    if (sec.ToString().Length == 1)
    {
        SecTxt.Text = "0" + sec.ToString();
    }
    else
    {
        SecTxt.Text = sec.ToString();
    }
}
else if (AmPmBtn.IsChecked == true)
{
    //If the Am/Pm node is selected,
    //the code below alternates between Am and Pm.
    if(AmPmTxt.Text == "AM")
    {
        AmPmTxt.Text = "PM";
    }
    else
    {
        AmPmTxt.Text = "AM";
    }
}

Under the downBtn_Click Event Handler, add the following code:

/*The code below check to see which node is selected in the control, 
get the current value of the node, decrease the value by one 
and display back to the user. This get fired every time 
the user clicks the 'Down' Button*/

if (HrBtn.IsChecked == true)
{
    int hour = int.Parse(HrTxt.Text);
    
    if (hour == 1)
        hour = 13;
    
    hour--;
    
    HrTxt.Text = hour.ToString();
}
else if (MinBtn.IsChecked == true)
{
    int min = int.Parse(MinTxt.Text);
    
    if (min == 0)
        min = 60;
    
    min--;
    
    
    if (min.ToString().Length == 1)
    {
        MinTxt.Text = "0" + min.ToString();
    }
    else
    {
        MinTxt.Text = min.ToString();
    }
}
else if (SecBtn.IsChecked == true)
{
    int sec = int.Parse(SecTxt.Text);
    
    if (sec == 0)
        sec = 60;
    
    sec--;
    
    
    if (sec.ToString().Length == 1)
    {
        SecTxt.Text = "0" + sec.ToString();
    }
    else
    {
        SecTxt.Text = sec.ToString();
    }
}
else if (AmPmBtn.IsChecked == true)
{
    if(AmPmTxt.Text == "AM")
    {
        AmPmTxt.Text = "PM";
    }
    else
    {
        AmPmTxt.Text = "AM";
    }
}

If you have good hands in design especially in Expression Blend, you can skin your control as it suits you. The final control of Time Watcher 5.0 application looks like this:

Final.jpg

On a final note

When creating a reusable control, it must be “skinable”; the use of the WPF commanding model (see Commanding Overview) is also better than raising clicked events if you are creating a control for reuse. A bindable DateTime or TimeSpan property will also enable users of this control to bind to it.

Recommendations

For further readings on how to create a custom control and add dependency properties, I recommend Create a WPF Custom Control, Part 1 and Create a WPF Custom Control, Part 2 by David Veeneman.

Download source code – 127 KB

August 31, 2015by Oludayo Alli

Get Notification For New Content

Avatars by Sterling Adventures