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

Comments

  • Anonymous
    August 25, 2008
    PingBack from http://hubsfunnywallpaper.cn/?p=2299

  • Anonymous
    August 26, 2008
    Hideous fonts. yuk.... horrible. no silverlight until fonts fixed... yuk!

  • Anonymous
    August 26, 2008
    Scott Barnes on Submitting SL Bugs, Shawn Wildermuth on SL Firestarter in NYC, Jason Cooke Templatinging

  • Anonymous
    August 27, 2008
    The comment has been removed

  • Anonymous
    January 22, 2009
    Is this still required?  You mention that this will be easier in SL2 -- I still can't seem to work out how to do it though...  Thanks.

  • Anonymous
    February 12, 2009
    Working with the final release of Silverlight 2 I'm trying to template a calendar.  More specifically I'm trying to set the background color of the CalendarDayButton if there is an event scheduled for that day. Is there an easy way to get this done now? I found this link and have modified the code slightly I can get the font to Bold but It won't let me set background or foreground colors. https://silverlight.net/forums/t/18744.aspx private void MonthView_Loaded(object sender, RoutedEventArgs e)        {            Grid g = (Grid)sender;            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(g); i++)            {                CalendarDayButton day = VisualTreeHelper.GetChild(g, i) as CalendarDayButton;                if (day != null)                {                    DateTime t = (DateTime)day.DataContext;                    if (t.Date == DateTime.Today.AddDays(1))                    {                        day.Background = new SolidColorBrush(Color.FromArgb(0, 255, 255, 0));                        //day.Content = "Tomorrow";                        day.FontWeight = FontWeights.Bold;                        day.Foreground = new SolidColorBrush(Color.FromArgb(0, 255, 255, 0));                    }                }            }        }

  • Anonymous
    March 11, 2009
    Are you able to even set data driven events for the dates?