Templating the Silverlight Calendar [Jason Cooke]

<Editorial Note>
I am delighted to have Jason to write this awesome blog showing everyone how to retemplate the Calendar using Silverlight Beta 2. Let us know what you think!
</Editorial Note>

My name is Jason Cooke. I work as tester for Silverlight's Calendar and DatePicker controls. Kathy has asked me to write some more details about a workaround I found for a Calendar templating issue. Corina Barber’s wonderful  Beta 2 templates use an earlier version of this workaround.

First, a little history. In Silverlight 2 Beta 1, you could template the appearance of the days and months in the Calendar control by setting the DayStyle and MonthStyle properties. You would only get one chance to set the template, because the styles can only be set once in Silverlight.

For Silverlight 2 Beta 2, we wanted to make the templating more flexible, so we removed those properties. However, we forgot to add the new way for setting the templates. Using XAML alone, it is not possible to template the Calendar's days and months. (We intend to fix this in the final version of Silverlight 2.) For example, Corina’s Red template would look like this, using the default day button styles:

clip_image002

One way you can work around this limitation is by changing the Calendar's template to use a custom grid for holding the days. This custom grid lets the Calendar set the days using the default template, but then re-templates the days using the template you specify. (This approach has some performance issues, and might not behave as expected in all circumstances.) With the workaround, her calendar looks like this:

clip_image004

The following instructions assume that you already have set up a custom template for the Calendar, based on the ones from MSDN. For more information about this, see the MSDN reference at Calendar Styles and Templates or Corrina's blog at ux musings.

1. In your Silverlight project, add a new class file named GridHook.cs, which defines a derived grid control named GridHook:

using System;

using System.Linq;

using System.Windows;

using System.Windows.Controls;

[TemplatePart(Name = GridHook.ElementButtonTemplate,

    Type = typeof(ControlTemplate))]

public class GridHook : Grid

{

    private const string ElementButtonTemplate =

        "ButtonTemplate";

    private ControlTemplate _buttonTemplate;

    public GridHook() : base()

    {

        // Use LayoutUpdated event to hook into Calendar

        this.LayoutUpdated += this_LayoutUpdated;

    }

    void this_LayoutUpdated(object sender, EventArgs e)

    {

        // Load in the ButtonTemplate.

        _buttonTemplate =

            this.Resources[ElementButtonTemplate]

            as ControlTemplate;

        // Look in the parent chain for the Calendar.

        FrameworkElement par = this.Parent

            as FrameworkElement;

        while (par != null)

        {

            Calendar cal = par as Calendar;

            if (cal != null)

            {

                // Check the button templates when

                // the layout updates.

                cal.LayoutUpdated += cal_LayoutUpdated;

                // Don't care about grid's layout.

                this.LayoutUpdated -= this_LayoutUpdated;

                // Check the button templates now.

                RefreshButtonTemplates();

                break;

            }

            par = par.Parent as FrameworkElement;

        }

    }

    void cal_LayoutUpdated(object sender, EventArgs e)

    {

        RefreshButtonTemplates();

    }

    void RefreshButtonTemplates()

    {

        foreach (Control cb in

            this.Children.OfType<Control>())

        {

            // Only update if needed

            if (cb.Template != _buttonTemplate)

                cb.Template = _buttonTemplate;

        }

    }

}

2. In the Calendar template, replace the Grid named MonthView:

<Grid x:Name="MonthView" Grid.Row="1" Grid.ColumnSpan="3"

    Visibility="Collapsed" Margin="10,12,10,7"

    RenderTransformOrigin="0.5,0.5">

with this definition that uses GridHook and a resource section that defines the ButtonTemplate, using the template for the DayButton.

<local:GridHook x:Name="MonthView" Grid.Row="1" Grid.ColumnSpan="3"

    Visibility="Collapsed" Margin="10,12,10,7"

    RenderTransformOrigin="0.5,0.5">

  <Grid.Resources>

    <ControlTemplate x:Key="ButtonTemplate" >

      <!--Day Button template ... too long to list here-->

    </ControlTemplate>

  </Grid.Resources>

3. Customize the DayButton template.

4. You can do the same thing with the YearView grid, starting with the template for the CalendarButton.

And, just to be clear, this is a temporary workaround for Silverlight 2 Beta 2. We are working on ways to make templating even easier for the final version of Silverlight 2.

Enjoy your programming,

Jason