Welcome to Pete Brown's 10rem.net

First time here? If you are a developer or are interested in Microsoft tools and technology, please consider subscribing to the latest posts.

You may also be interested in my blog archives, the articles section, or some of my lab projects such as the C64 emulator written in Silverlight.

(hide this)

Introducing the WPF 4 Calendar control

Pete Brown - 26 October 2009

WPF 4 now includes, in the core runtime libraries, the calendar control. This control previously existed only in the WPF Toolkit, but has been promoted in the latest WPF release. Inclusion in the core runtime library makes it easier to develop with  as there is no additional namespace declaration or reference required.

The Calendar control is located in the System.Windows.Controls namespace in the PresentationFramework DLL.

Let’s start with the key properties and then move on to a couple common tasks such as scaling and binding.

Key Properties

Note that all date properties use the DateTime Xaml Syntax for parsing. Not also that unless noted, the control is shown with FlowDirection set to LeftToRight and FirstDateOfWeek set to Sunday. Both may be overridden if desired.

DisplayDate, SelectedDate and SelectedDates

When the control is first displayed, a date is shown highlighted – the DisplayDate. By default, that date is Today’s date. But you may change it to whatever date you would like.

Use the SelectedDate scalar property when the calendar is in SingleDate selection mode. Use the SelectedDates collection property when the calendar is in any of the multi-select modes.

The DisplayDate is shown on the right, the SelectedDate is shown on the left

image

<Grid>
    <Calendar x:Name="CalendarControl"
              HorizontalAlignment="Center"
              VerticalAlignment="Center"
              
              SelectionMode="MultipleRange"
              DisplayMode="Month">
    </Calendar>

    <TextBlock Text="{Binding SelectedDate, ElementName=CalendarControl}"
               VerticalAlignment="Bottom"
               HorizontalAlignment="Left" />

    <TextBlock Text="{Binding DisplayDate, ElementName=CalendarControl}"
               VerticalAlignment="Bottom"
               HorizontalAlignment="Right" />
</Grid>

The above example also shows simple element binding using the SelectedDate (and DisplayDate) properties

DisplayMode

The DisplayMode property sets the calendar to display in one of three different modes: Decade, which shows the current decade and the year before and after, for three years total, Year which displays the months of the current year, and Month which displays the usual tabular calendar view of days in the month. At runtime, broader display modes drill-down into more specific display modes as described below.

Not that in all modes, Control + Up Arrow will drill back up the DisplayMode and Control + Down Arrow will drill down into the next mode. For example, if in Year Mode, Control + Up will show Decade and Control + Down will show Month.

Decade

image

In decade mode, clicking on a year will drill down into Year display mode, and will not affect SelectedDate.

In decade mode, the arrow key changes the year of the DisplayDate, the Home key changes the DisplayDate to the first year of the decade, and the End key changes the DisplayDate to the last year of the decade. The spacebear drills into the selected year. In all cases, the SelectedDate is not changed.

Year

image

In year mode, clicking on a month will drill down into Month display mode, and will not affect SelectedDate

In year mode, the arrow key changes the month of the DisplayDate, the Home key changes the DisplayDate to the first month of the year, and the End key changes the DisplayDate to the last month of the year. The spacebar drills into the selected month. In all cases, the SelectedDate is not changed.

Month

image

In month mode, clicking on a day sets the SelectedDate, or SelectedDates depending on the value for the SelectionMode property.

In month mode, the arrow key changes the SelectedDate property, Shift+Arrow will add/select an adjacent day if the SelectionMode allows multi-select. Home changes the selected date to the first day of the month, and End changes it to the last.

SelectionMode

Selection mode controls the selection behavior for the dates in the Month mode of the calendar.

None

The calendar is effectively read-only. No dates may be selected. Clicking on a date changes the focus, but does not change the SelectedDate

image

SingleDate

Allows selection of a single date. This is the default mode. Clicking on a date selects the date.

image

SingleRange

Allows selection of a single contiguous range of dates. Click on the start date, then shift-click on the end date.

image

SelectedDate controls the first selected date, SelectedDates contains the full set of selected dates.

MultipleRange

Allows selection of multiple non-contiguous dates or ranges of dates. Click on one date, shift-click to set a range if desired, then control-click on other dates to add them.

image 

SelectedDate contains the first date selected (not necessarily the oldest date, just the first one selected), SelectedDates contains the full set of selected dates.

DisplayDateStart / DisplayDateEnd

If you wish to limit the date range that may be picked using the control, set the bounds using the DisplayDateStart and End properties. Those set a date window out of which the user may not navigate using the UI.

BlackoutDates

Another way to restrict date selection is to use the BlackoutDates property. This property allows you to mark specific date ranges as non-selectable in the UI. In the default control template, this is represented by an X over the date.

<Calendar x:Name="CalendarControl"
          HorizontalAlignment="Center"
          VerticalAlignment="Center"
          DisplayMode="Month">
    <Calendar.BlackoutDates>
        <CalendarDateRange Start="10/4/2009"
                           End="10/15/2009" />
        <CalendarDateRange Start="10/23/2009"
                           End="10/28/2009" />
    </Calendar.BlackoutDates>
</Calendar>

image

When the user clicks on a blackout date, the SelectedDate property is not changed.

FlowDirection

Like most other control, the Calendar control respects the FlowDirection property in order to support right-to-left or left-to-right text directions. Below is an example of the control with FlowDirection set to RightToLeft

image

Scaling the Control

The Calendar control includes no built-in scaling. If you set the size of the control, or put it in a grid, it will clip but it will not expand. To visually resize, you’ll need to scale it using a ScaleTransform or by putting the control into a ViewBox.

Scaling with a ScaleTransform

The Calendar control scales like any other WPF control, using a simple ScaleTransform.

<Calendar x:Name="CalendarControl" 
          HorizontalAlignment="Left" 
          VerticalAlignment="Top"
          DisplayMode="Month">
    <Calendar.RenderTransform>
        <ScaleTransform ScaleX="1.5"
                        ScaleY="1.5" />
    </Calendar.RenderTransform>
</Calendar>

The result looks like this:

image 

Scaling with a ViewBox

A viewbox is simply a convenient way to abstract a ScaleTransform. It handles automatic centering and bounds checking for you, as well as dynamic resizing, so it’s a good way to handle scaling.

<Viewbox>
    <Calendar x:Name="CalendarControl"
              HorizontalAlignment="Left"
              VerticalAlignment="Top"
              DisplayMode="Month">
    </Calendar>
</Viewbox>

image

image

image

 

Binding to SelectedDate

When working in WPF and Silverlight, I write my code and structure my applications using variations on the ViewModel (or MVVM) pattern. I’ll use a very simple form of that pattern here to show how to bind to the SelectedDate property of the control.

First, add a folder named ViewModels and then an appropriately named ViewModel class in that folder. I typically name the VMs after their screen, so in this case it is simply MainWindowViewModel.

image

The ViewModel code itself is pretty simple. We have a class that implement INotifyPropertyChanged in order to inform the binding system that a value has changed, then we have a public property as our binding source.

class MainWindowViewModel : INotifyPropertyChanged
{
    private DateTime _dateOfBirth;
    public DateTime DateOfBirth
    {
        get { return _dateOfBirth; }
        set { _dateOfBirth = value; NotifyPropertyChanged("DateOfBirth"); }
    }

    public event PropertyChangedEventHandler  PropertyChanged;
    protected void NotifyPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

}

The property changed event handler allows us to set the property value from code or from other controls and still ensure that all bound targets know the value changed.

Next we need to set the ViewModel as the datacontext for our window. You can do this a number of ways including newing up the VM in the code-behind and setting the DataContext there, or using dependency injection to create the relationship between the two. In this case, I’m going to set it right in the xaml as a resource. To do that, I need to add a namespace and a resource to the xaml

<Window x:Class="WpfApplication6.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:viewModels="clr-namespace:WpfApplication6.ViewModels"
        Title="Calendar Control Demo" Height="300" Width="425">
    <Window.Resources>
        <viewModels:MainWindowViewModel x:Key="vm" />
    </Window.Resources>

You can see the viewModel namespace declaration tied to the window class, and the reference to the viewmodel itself right inside the Window.Resources section.

Next, I set the Grid’s DataContext property to be the static resource

<Grid DataContext="{StaticResource vm}">

Finally, set the SelectedDate property of the calendar to bind to the DateOfBirth property of the view model.

<Calendar x:Name="CalendarControl"
          HorizontalAlignment="Center"
          VerticalAlignment="Center"
          SelectedDate="{Binding DateOfBirth}"
          DisplayMode="Month">
</Calendar>

If you now run the application and set a breakpoint on the property setter for DateOfBirth in the ViewModel, you’ll see the binding in action.

Working with SelectedDates

The SelectedDates property is a little trickier. Rather than a simple scalar property like SelectedDate, this is a read-only property that returns a collection which may be modified, but not bound to.

If you working from code-behind and using the control as the source of record for the data, then it is pretty easy. If you’re using a pattern like the VM pattern, you’ll need to have an adapter that sits between the control and the viewmodel and updates each end with the other changes. Essentially, your own custom mini binding system.

If you use the ViewModel/MVVM pattern and have a different or better approach to handling the binding, please share.

For more information, please visit www.windowsclient.net . I’ll have a video version of this article up there soon.

Update 11/25/2009 The video is located here.

     
posted by Pete Brown on Monday, October 26, 2009
filed under:      

5 comments for “Introducing the WPF 4 Calendar control”

  1. Maxsays:
    About the DatePicker control I solved with this:

    <Style TargetType="my:Calendar" x:Key="CalenderStyleTouch">
    <Setter Property="LayoutTransform">
    <Setter.Value>
    <ScaleTransform ScaleY="2" ScaleX="2"></ScaleTransform>
    </Setter.Value>
    </Setter>
    </Style>

    and


    <my:DatePicker Name="from" Margin="5" Width="230" FontSize="16" CalendarStyle="{DynamicResource CalenderStyleTouch}" />
  2. Greg Bacchussays:
    For binding to a single selection range, it seems the only way is to inherit:


    public static class CalendarHelper
    {
    public static DateTime? SelectionMin( Calendar calendar )
    {
    if( calendar.SelectedDates != null && calendar.SelectedDates.Any() )
    {
    return calendar.SelectedDates.Min();
    }
    return null;
    }

    public static DateTime? SelectionMax( Calendar calendar )
    {
    if( calendar.SelectedDates != null && calendar.SelectedDates.Any() )
    {
    return calendar.SelectedDates.Max();
    }
    return null;
    }

    }

    public class CalendarEx : Calendar
    {
    private bool _suppressBind;

    #region SelectionMin

    public DateTime? SelectionMin
    {
    get { return (DateTime?)GetValue( SelectionMinProperty ); }
    set { SetValue( SelectionMinProperty, value ); }
    }

    /// <summary>
    /// Backing <see cref="DependencyProperty"/> for <see cref="SelectionMin"/>
    /// </summary>
    public static readonly DependencyProperty SelectionMinProperty = DependencyProperty.Register( "SelectionMin", typeof( DateTime? ), typeof( CalendarEx ), new FrameworkPropertyMetadata( default( DateTime? ), OnSelectionMinChanged ) );

    private static void OnSelectionMinChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
    var control = d as CalendarEx;
    if( control == null || control._suppressBind ) return;

    var minDate = (DateTime?)e.NewValue;
    var maxDate = CalendarHelper.SelectionMax( control );

    control.SetRange( minDate, maxDate );
    }

    #endregion

    #region SelectionMax

    public DateTime? SelectionMax
    {
    get { return (DateTime?)GetValue( SelectionMaxProperty ); }
    set { SetValue( SelectionMaxProperty, value ); }
    }

    /// <summary>
    /// Backing <see cref="DependencyProperty"/> for <see cref="SelectionMax"/>
    /// </summary>
    public static readonly DependencyProperty SelectionMaxProperty = DependencyProperty.Register( "SelectionMax", typeof( DateTime? ), typeof( CalendarEx ), new FrameworkPropertyMetadata( default( DateTime? ), OnSelectionMaxChanged ) );

    private static void OnSelectionMaxChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
    var control = d as CalendarEx;
    if( control == null || control._suppressBind ) return;

    var minDate = CalendarHelper.SelectionMin( control );
    var maxDate = (DateTime?)e.NewValue;

    control.SetRange( minDate, maxDate );
    }

    #endregion

    protected override void OnSelectedDatesChanged( SelectionChangedEventArgs e )
    {
    if( _suppressBind ) return;

    _suppressBind = true;
    try
    {
    var minDate = CalendarHelper.SelectionMin( this );
    var maxDate = CalendarHelper.SelectionMax( this );

    SelectionMin = minDate;
    SelectionMax = maxDate;

    base.OnSelectedDatesChanged( e );
    }
    finally
    {
    _suppressBind = false;
    }
    }

    public void SetRange( DateTime? minDate, DateTime? maxDate )
    {
    if( minDate.HasValue && maxDate.HasValue )
    SelectedDates.AddRange( minDate.Value, maxDate.Value );
    else if( minDate.HasValue )
    SelectedDate = minDate.Value;
    else if( maxDate.HasValue )
    SelectedDate = maxDate.Value;
    else
    SelectedDate = null;
    }
    }

Comment on this Post

Remember me

4 trackbacks for “Introducing the WPF 4 Calendar control”

  1. uberVU - social commentssays:
    This post was mentioned on Twitter by Pete_Brown: Blogged: Introducing the WPF 4 Calendar Control http://bit.ly/OiXG6
  2. ComponentGear.com Feedsays:
    This week on Channel 9, Dan is joined by Asli Bilgin to discuss the week's top developer news including
  3. Windows Client Newssays:
    WPF 4 now includes the calendar control in the core runtime. This video and the accompanying blog post