Introduction
I was in search for a calendar where appointments can be added. I've seen a great
CodeProject article by kirkaiya where he achieved this by creating a UserControl. It's very good but
I thought I could achieve almost the same thing by styling the WPF Calendar.
Figure 1 shows the WPF calendar.

Figure 1. WPF Calendar in its Default Style
My first impression is that it might not be possible. Anyway, I wanted to give it
shot. I came across another article at MSDN Magazine, this time by Charles Petzold. He explained in great detail the
different parts of the Calendar's template. After reading the article, it seems
to me that it is not enough to change the Calendar's template. A custom control
that inherits from Calendar should be created to support appointments.
Getting Started
The best way to style a WPF control is to start from the default style. Fortunately,
we don't need any tools to get the default style. The source code is freely available
at CodePlex, so I recommend downloading it first. Upon extracting the zip file,
you can get the Calendar's default style from WPFToolkitBinariesAndSource\Toolkit-release\Calendar\Themes\Generic.xaml.
This file has a resource dictionary containing 4 styles for the following controls:
Calendar, CalendarItem, CalendarDayButton and CalendarButton.
The Calendar's control template only consists of a CalendarItem control inside a
StackPanel. This means that the default appearance of the Calendar is mostly
defined by the CalendarItem's control template. However, you can still add other
controls to the Calendar's template.
The CalendarItem control is basically divided into the following parts: previous
button, next button, header button, month content grid, and year content grid.
The appearance of the CalendarItem depends on the selected display mode: month,
year or decade.
The month content grid is used when the display mode is month while the year content
grid is used for the other two display modes. The CalendarItem fills up the month
content grid with CalendarDayButton controls. Meanwhile, the year content grid
is filled up with CalendarButton controls. Since we are only interested in the
month view of the calendar, we don't need to edit the CalendarButton template.
Let's create a WPF application that shows a calendar in the main window. Copy the
default styles for Calendar, CalendarItem and CalendarDayButton controls in the
window's resources and add the required assemblies and namespaces. If you are
using Visual Studio 2008 and .NET 3.5 SP1, you might get the following error:
"The attachable property 'VisualStateGroups' was not found in type 'VisualStateManager'.
This is a known bug and you won't be able to see the Calendar in the WPF designer.
However, the application should be able to run without any errors.
Resizing the Calendar
The Calendar should fill the entire grid and resized when the main window is resized.
This can't be done just by setting the width and height of the Calendar. First,
let's change the Calendar's control template. I changed the StackPanel into a
Grid and removed the HorizontalAlignment.
<Grid Name="PART_Root">
<primitives:CalendarItem
Name="PART_CalendarItem"
Style="{TemplateBinding CalendarItemStyle}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
/>
</Grid>
Listing 1. Calendar Control Template
Although the CalendarItem fills the grid, the CalendarItem's content (the navigation
buttons, header button and month content grid) is aligned to the top left corner
of the grid. So we need to edit the CalendarItem's template so that the content
is distributed evenly. But first, we should give a key to the CalendarItem style
and use it as the value of the Calendar's CalendarItemStyle property. After that,
look for the grid that has 2 rows and 3 columns in the CalendarItem's control
template. The first row contains the navigation and header buttons. The second
row contains the month content grid. The first column contains the previous button,
second column contains the header button and third column contains the next column.
The month content grid spans 3 columns. Let's add another column that will occupy
the remaining space. The month content grid will now span 4 columns.
We should also change the row and column definitions of the month content grid. This
grid contains 7 rows and 7 columns, where the Height and Width properties are
set to Auto. The first row corresponds to the day title (Su, Mo, Tu, etc.) while
the other 6 rows contain the days of the month (1, 2, 3 and so on). The 7 columns
correspond to the 7 days of the week. Let's set the Height of the last 6 rows
and the Width of all columns to *. These changes result in the following figure.

Figure 2. Resized Calendar
Styling the Navigation and Header Buttons
The navigation buttons in Outlook are displayed as arrows inside circles. The displayed
month is also not in the middle of the navigation buttons, but located at the
right. The font properties are also a bit different. Let's start first with the
circle background of the navigation buttons.
<Style x:Key="NavigationEllipseStyle" TargetType="{x:Type Ellipse}">
<Setter Property="Width" Value="20"/>
<Setter Property="Height" Value="20"/>
<Setter Property="Stroke" Value="#FF8EB0DC"/>
<Setter Property="StrokeThickness" Value="1"/>
<Setter Property="Fill">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#FFFAFCFF" Offset="0"/>
<GradientStop Color="#FFFAFCFF" Offset="0.5"/>
<GradientStop Color="#FFCCE2FF" Offset="0.5"/>
<GradientStop Color="#FFCCE2FF" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
Listing 2. The Navigation Button's Ellipse
I added this style in the resources section of the grid containing the navigation
buttons. The following code shows the inner grid of the updated previous button
template which uses the style shown in Listing 2. The path objects that make
up the arrow and other properties like margin and width are also updated.
<Grid>
<Ellipse Style="{StaticResource NavigationEllipseStyle}"/>
<Path Margin="4,0,0,0" Height="10" Width="6" VerticalAlignment="Center" HorizontalAlignment="Left" Stretch="Fill" Data="M288.75,232.25 L283,236.625 L288.75,240.625" StrokeThickness="2">
<Path.Stroke>
<SolidColorBrush x:Name="TextColor" Color="#FF406CA6" />
</Path.Stroke>
</Path>
<Path Margin="4,0,0,0" Height="10" Width="12" VerticalAlignment="Center" HorizontalAlignment="Left" Stretch="Fill" Data="M283,236.625 L297,236.625" Stroke="#FF406CA6" StrokeThickness="2"/>
</Grid>
Listing 3. Part of Updated Previous Button Template
In the following code, I added a margin around the previous button and set its width
to 20 instead. I just applied the same thing with the next button, with the arrow's
paths reversed.
<Button x:Name="PART_PreviousButton"
Margin="4"
Grid.Row="0" Grid.Column="0"
Template="{StaticResource PreviousButtonTemplate}"
Height="20" Width="20"
HorizontalAlignment="Left"
Focusable="False"
/>
Listing 4. Updated Previous Button
Finally, I changed the font weight and size properties of the header button. Also
notice that the header and next buttons have switched places.
<Button x:Name="PART_HeaderButton"
Grid.Row="0" Grid.Column="2"
Template="{StaticResource HeaderButtonTemplate}"
HorizontalAlignment="Center" VerticalAlignment="Center"
FontWeight="Normal" FontSize="20"
Focusable="False"
/>
Listing 5. Updated Header Button
The following figure shows the updated Calendar.

Figure 3. Updated Navigation and Header Buttons
Changing the Day Title Format
As you see, the format of each day title is set to the shortest day name like Su
for Sunday, Mo for Monday, and so on. Unfortunately, the Calendar or the CalendarItem
does not expose any property that let's us change the day title format easily.
The CalendarItem uses only the ShortestDayNames property of the current date
format. However, we can still change the format by using an IValueConverter as
you will see later. The following code shows the updated DayTitleTemplate.
<local:DayNameConverter x:Key="DayNameConverter"/>
<!-- Start: Data template for header button -->
<DataTemplate x:Key="DayTitleTemplate">
<Grid>
<TextBlock
FontWeight="Normal"
FontFamily="Arial"
FontSize="10.5"
Foreground="#FF7C93D7"
HorizontalAlignment="Center"
Text="{Binding Converter={StaticResource DayNameConverter}}"
Margin="0,6,0,6"
VerticalAlignment="Center"/>
</Grid>
</DataTemplate>
Listing 6. Updated Day Title Template
The Text property of the TextBlock is bound to a string representing the shortest
day name. So if we want to change Su to Sunday, Mo to Monday and so on, we need
to use an IValueConverter, like the one shown below.
/// <summary>
/// Converts the specified short day name to its normal counterpart.
/// </summary>
[ValueConversion(typeof(string), typeof(string))]
public class DayNameConverter : IValueConverter
{
object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
DateTimeFormatInfo dateTimeFormat = GetCurrentDateFormat();
string[] shortestDayNames = dateTimeFormat.ShortestDayNames;
string[] dayNames = dateTimeFormat.DayNames;
for (int i = 0; i < shortestDayNames.Count(); i++)
{
if (shortestDayNames[i] == value.ToString())
{
return dayNames[i];
}
}
return null;
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
private static DateTimeFormatInfo GetCurrentDateFormat()
{
if (CultureInfo.CurrentCulture.Calendar is GregorianCalendar)
{
return CultureInfo.CurrentCulture.DateTimeFormat;
}
foreach (Calendar cal in CultureInfo.CurrentCulture.OptionalCalendars)
{
if (cal is GregorianCalendar)
{
DateTimeFormatInfo dtfi = new CultureInfo(CultureInfo.CurrentCulture.Name).DateTimeFormat;
dtfi.Calendar = cal;
return dtfi;
}
}
DateTimeFormatInfo dt = new CultureInfo(CultureInfo.InvariantCulture.Name).DateTimeFormat;
dt.Calendar = new GregorianCalendar();
return dt;
}
}
Listing 7. Day Name Converter
This IValueConverter converts the shortest day name to its normal day name equivalent.
The Convert method uses the GetCurrentDateFormat method in getting the current
date format. This is the same method used by the CalendarItem in generating the
day titles. The following figure shows the updated Calendar. Notice that I also
changed the text color.

Figure 4. Updated Day Titles
Styling the CalendarDayButton
In Outlook, the days are located on the top left corner of each cell. Also, there
is a filled rectangle on top of each cell. To imitate this in our Calendar, we'll
need to update the CalendarDayButton template. The following XAML code is a part
of the updated CalendarDayButton control template.
<Rectangle x:Name="SelectedBackground" Grid.Row="1" RadiusX="1" RadiusY="1" Opacity="0" Fill="{TemplateBinding Background}"/>
<Rectangle x:Name="Background" Grid.Row="1" RadiusX="1" RadiusY="1" Opacity="0" Fill="{TemplateBinding Background}" />
<Rectangle x:Name="InactiveBackground" Grid.Row="1" RadiusX="1" RadiusY="1" Opacity="0" Fill="#FFA5BFE1"/>
<Border>
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop x:Name="StartGradient" Color="#FFDFE8F5" Offset="0"/>
<GradientStop Color="{Binding ElementName=StartGradient, Path=Color}" Offset="0.5"/>
<GradientStop x:Name="EndGradient" Color="#FFC9D9ED" Offset="0.5"/>
<GradientStop Color="{Binding ElementName=EndGradient, Path=Color}" Offset="1"/>
</LinearGradientBrush>
</Border.Background>
<ContentPresenter
x:Name="NormalText"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="5,1,5,1">
<TextElement.Foreground>
<SolidColorBrush x:Name="selectedText" Color="#FF333333"/>
</TextElement.Foreground>
</ContentPresenter>
</Border>
<Rectangle x:Name="Border" StrokeThickness="0.5" Grid.RowSpan="2" SnapsToDevicePixels="True">
<Rectangle.Stroke>
<SolidColorBrush x:Name="BorderBrush" Color="#FF5D8CC9"/>
</Rectangle.Stroke>
</Rectangle>
<Path x:Name="Blackout" Grid.Row="1" Opacity="0" Margin="3" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RenderTransformOrigin="0.5,0.5" Fill="#FF000000" Stretch="Fill" Data="M8.1772461,11.029181 L10.433105,11.029181 L11.700684,12.801641 L12.973633,11.029181
L15.191895,11.029181 L12.844727,13.999395 L15.21875,17.060919 L12.962891,17.060919
L11.673828,15.256231 L10.352539,17.060919 L8.1396484,17.060919 L10.519043,14.042364
z"/>
<Rectangle Width="0" x:Name="DayButtonFocusVisual" Grid.Row="1" Visibility="Collapsed" IsHitTestVisible="false" RadiusX="1" RadiusY="1" Stroke="#FF45D6FA"/>
Listing 8. Part of Updated CalendarDayButton Template
If you look at the default style, there is a Rectangle labeled TodayBackground. You
can see this as the gray background of the grid containing the current day. I've
removed this since Outlook indicates the current day differently. I've also added
another row so that the first row contains the day and has a blue background
(this is the LinearGradientBrush in the above XAML) while the second row has
a white background and will show the appointments for that day. More on that
later.
When the current VisualState is set to Today, the following animations take place.
Basically, it changes the background color of the rectangle containing the day
and the border's color and stroke thickness.
<vsm:VisualState x:Name="Today">
<Storyboard>
<ColorAnimation Duration="0" Storyboard.TargetName="StartGradient" Storyboard.TargetProperty="Color" To="#FFF7D275"></ColorAnimation>
<ColorAnimation Duration="0" Storyboard.TargetName="EndGradient" Storyboard.TargetProperty="Color" To="#FFF3B84B"></ColorAnimation>
<ColorAnimation Duration="0" Storyboard.TargetName="BorderBrush" Storyboard.TargetProperty="Color" To="#FFF3B84B"></ColorAnimation>
<DoubleAnimation Duration="0" Storyboard.TargetName="Border" Storyboard.TargetProperty="StrokeThickness" To="2"></DoubleAnimation>
</Storyboard>
</vsm:VisualState>
Listing 9. StoryBoard when VisualState is Today
I've also added a rectangle and named it InactiveBackground. Its opacity is set to
1 when the VisualState is Inactive. You can see this background for days that
are shown on the calendar but is not included in the currently displayed month.
For example, Oct. 31 is shown but is inactive when the current month displayed
is November.
<vsm:VisualState x:Name="Inactive">
<Storyboard>
<ColorAnimation Duration="0" Storyboard.TargetName="selectedText" Storyboard.TargetProperty="Color" To="#FF777777"></ColorAnimation>
<DoubleAnimation Duration="0" Storyboard.TargetName="InactiveBackground" Storyboard.TargetProperty="Opacity" To="1"></DoubleAnimation>
</Storyboard>
</vsm:VisualState>
Listing 10. StoryBoard when VisualState is Inactive
The following figure shows the updated Calendar.

Figure 5. Styled CalendarDayButton
Finalizing the Style
There are some minor things that still need to be done, like setting the margins,
borders, background etc. The most noticeable is the light-blue background at
the top of the Calendar. The background's height resizes proportionally with
the height of the window. This is not the behavior we want. We can remove this
by not specifying the background of the CalendarItem in the Calendar’s control
template. The following figure shows the final Calendar after applying or updating
some margins and adding background to some grid rows.

Figure 6. Final Calendar Style
Adding Appointments
To add appointments, we need to create a custom control that will inherit from the
Calendar control. This will mean that we should move the style definitions from
the window’s resources section into the custom control’s resource dictionary.
This custom control, shown in the code below, will have a variable that will
contain the list of appointments.
/// <summary>
/// Custom calendar control that supports appointments.
/// </summary>
public class MonthViewCalendar : Calendar
{
public static DependencyProperty AppointmentsProperty =
DependencyProperty.Register
(
"Appointments",
typeof(ObservableCollection<Appointment>),
typeof(Calendar)
);
/// <summary>
/// The list of appointments. This is a dependency property.
/// </summary>
public ObservableCollection<Appointment> Appointments
{
get { return (ObservableCollection<Appointment>)GetValue(AppointmentsProperty); } set { SetValue(AppointmentsProperty, value); }
}
static MonthViewCalendar()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MonthViewCalendar), new FrameworkPropertyMetadata(typeof(MonthViewCalendar)));
}
public MonthViewCalendar()
: base()
{
SetValue(AppointmentsProperty, new ObservableCollection<Appointment>());
}
}
Listing 11. Custom Calendar Control
The Appointments collection accepts objects of type Appointment, which is shown below.
/// <summary>
/// The appointment data.
/// </summary>
public class Appointment
{
public string Subject { get; set; }
public DateTime Date { get; set; }
}
Listing 12. Appointment Class
This class contains only a few details. I won’t imitate Outlook’s implementation
of adding appointments. In this implementation, when a user double-clicks on
a CalendarDayButton, the appointment dialog will be shown. The following figure
shows the appointment dialog.

Figure 7. Appointment Dialog
My first idea was to derive from CalendarDayButton. But it impossible as the CalendarDayButton
is a sealed class. After reading Charles Petzold’s article I mentioned in the
Introduction, my only choice is to override the OnMouseDoubleClick method of
the Calendar class. The following shows the overridden method.
protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
{
base.OnMouseDoubleClick(e);
FrameworkElement element = e.OriginalSource as FrameworkElement;
if (element.DataContext is DateTime)
{
AppointmentWindow appointmentWindow = new AppointmentWindow
(
(Appointment appointment) =>
{
Appointments.Add(appointment);
}
);
appointmentWindow.Show();
}
}
Listing 13. The OnMouseDoubleClick Method
The tricky part is how we will know if the user really clicked a CalendarDayButton.
The OriginalSource property of the MouseButtonEventArgs object will not return
an object of type CalendarDayButton. It might return any object in the CalendarDayButton’s
control template, like rectangle or path. Fortunately, the DataContext value
of the CalendarDayButton is inherited by controls deeper in the visual tree.
Thus, if the DataContext property is of type DateTime, we know that the user
clicked on a CalendarDayButton.
The next thing to do is display the appointments. This can be achieved by updating
the CalendarDayButton control template. The basic idea is to add a ListBox where
its ItemsSource property is bound to the Appointments property of the MonthViewCalendar
control. We also need to filter the appointments based on the date of the CalendarDayButton.
I used an IMultiValueConverter for this purpose. The following XAML code is a
part of the updated CalendarDayButton template.
<!-- Appointments -->
<ListBox
x:Name="appointmentsLbx"
Grid.Row="1"
Background="Transparent"
BorderBrush="Transparent"
HorizontalContentAlignment="Stretch"
>
<ListBox.ItemsSource>
<MultiBinding Converter="{StaticResource AppointmentsConverter}">
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MonthViewCalendar}}" Path="Appointments"/>
<Binding RelativeSource="{RelativeSource Mode=Self}" Path="DataContext"/>
</MultiBinding>
</ListBox.ItemsSource>
<ListBox.ItemTemplate>
<DataTemplate>
<Border Background="#FFDFE8F5" BorderBrush="#FF5D8CC9" BorderThickness="1" CornerRadius="4">
<TextBlock HorizontalAlignment="Center" Text="{Binding Subject}"/>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
<ControlTemplate.Triggers>
<Trigger SourceName="appointmentsLbx" Property="HasItems" Value="False">
<Setter TargetName="appointmentsLbx" Property="Visibility" Value="Hidden"/>
</Trigger>
</ControlTemplate.Triggers>
Listing 14. CalendarDayButton’s Appointments ListBox
I used multi-binding to filter the appointments based on the date on the CalendarDayButton.
This is because we need to pass 2 objects to the IMultiValueConverter: the appointments
collection and the date. The following shows the IMultiValueConverter code.
/// <summary>
/// Gets the appointments for the specified date.
/// </summary>
[ValueConversion(typeof(ObservableCollection<Appointment>), typeof(ObservableCollection<Appointment>))]
public class AppointmentsConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
DateTime date = (DateTime)values[1];
ObservableCollection<Appointment> appointments = new ObservableCollection<Appointment>();
foreach (Appointment appointment in (ObservableCollection<Appointment>)values[0])
{
if (appointment.Date.Date == date)
{
appointments.Add(appointment);
}
}
return appointments;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
Listing 15. Appointments Converter
Everything should work now except for 1 minor glitch. The list box’s items do not
get updated immediately after adding an appointment to the collection. This can
be fixed by implementing the INotifyPropertyChanged interface on the MonthViewCalendar
class and raising the PropertyChanged event when an appointment is added. The
following figure shows the calendar with some appointments.

Figure 9. Calendar with Appointments
The Visual Studio 2008 solution can be downloaded here. Note that I haven’t implemented updating an appointment.