Article Series
This article is part one of a series of articles on developing an advanced WPF control designed to function like the Calendar view in Microsoft Outlook, shown below:
Introduction
About six months ago, I began on a initiative to learn Windows Presentation Foundation (WPF). As part of that initiative, I decided I needed a project to apply my skills against, one with sufficient variation of issues to solve, but one which would also minimize the amount of non-WPF code I would have to write. I decided to write a WPF based on Microsoft Outlook, utilizing the Outlook COM API as the underlying domain layer. At some point, I decided to build the necessary controls and components to display the Calendar view. In this series of articles, I will provide the design of the control both for purposes of publishing the code and to help others developing custom controls by explaining some of the design decisions made during the process.
Requirements
In developing the control, I decided upon a number of requirements:
- Support multiple views of the appointment data (one day, one week, etc.)
- Support binding the underlying data to an arbitrary collection of objects rather than require utilization of a pre-defined
Appointment
class
- Support "look-less" control design
- Support custom styling of all visual aspects of the control
- Support overlapping appointments (for example, an appointment from 1 PM - 3 PM and an appointment from 2 PM - 4 PM)
- Support displaying more than one day within a single view (similar to the "Week View" in Microsoft Outlook)
- Support reuse of the underlying classes in other applicable scenarios (to avoid making all the code specific to the
Appointment
View)
- Support arbitrary beginning and end of the calendar period(s) shown
For example, rather than support showing one day as midnight to midnight, allow 9 AM - 6 PM or even a 48 hour period across 2 days.
Background
This article series depends on the reader having advanced knowledge of Windows Presentation Foundation as it delves into topics such as item container generators, custom layout controls, etc.
Step 1 - Basic Control Class Design
The first step was to figure out the basic class design for the control itself and what properties would be needed. The two main options I had were to use the ListView
/ViewBase
combination or to inherit from Selector
(the parent class of controls such as ListBox
and ComboBox
). To support multiple views, I decided to make use of the ListView
component, which supports multiple views of the underlying data view the ListView.View
property (which is of type ViewBase
). The most common view used with a ListView
is the GridView
. There is a sample in the MSDN Library about creating a custom ViewBase
class here. Because of the usage of ListView
/ViewBase, which is different in some ways from inheriting from a class such as Selector
, not all the design points are directly applicable to more standard custom control creation.
The next step is to define the properties the class will need. To fulfill the requirement to display multiple days within a single view, I followed a design similar to GridView
, which has a Columns
collection containing individual GridViewColumn
instances. Similarly, the CalendarView
class will need a CalendarViewPeriod
data class and CalendarViewPeriodCollection
collection class. Again, I used the GridView
helper classes to determine the appropriate base classes to use. Inheriting CalendarViewPeriod
from DependencyObject
is necessary to permit data binding to the various properties of the class. Inheriting CalendarViewPeriodCollection
from ObservableCollection
automatically provides capability to listen for modifications to the list (add/remove/insert/replace/etc.), which will be needed later.
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace OutlookWpfCalendar.Windows.Controls
{
public class CalendarView: ViewBase
{
private CalendarViewPeriodCollection periods =
new CalendarViewPeriodCollection();
public CalendarViewPeriodCollection Periods
{
get { return periods; }
}
}
public class CalendarViewPeriod: DependencyObject
{
}
public class CalendarViewPeriodCollection: ObservableCollection<CalendarViewPeriod>
{
}
}
Each period will need properties to define the beginning and end of itself, as well as the header (which won't be used at this time). Each of these will be defined as dependency properties to support data binding.
public class CalendarViewPeriod: DependencyObject
{
public static readonly DependencyProperty BeginProperty =
DependencyProperty.Register("Begin", typeof(DateTime),
typeof(CalendarViewPeriod));
public static readonly DependencyProperty EndProperty =
DependencyProperty.Register("End", typeof(DateTime),
typeof(CalendarViewPeriod));
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header", typeof(object),
typeof(CalendarViewPeriod));
public DateTime Begin
{
get { return (DateTime)this.GetValue(BeginProperty); }
set { this.SetValue(BeginProperty, value); }
}
public DateTime End
{
get { return (DateTime)this.GetValue(EndProperty); }
set { this.SetValue(EndProperty, value); }
}
public object Header
{
get { return (object)this.GetValue(HeaderProperty); }
set { this.SetValue(HeaderProperty, value); }
}
}
To support binding to an arbitrary collection instead of a collection of fixed appointment classes, it is necessary to provide a method to ask each object within the ItemsSource
what the begin and end time for that appointment is. This will be done by asking for a binding, much like how the GridViewColumn
class requires a binding (DisplayMemberBinding
) to extract the value from a particular item to be displayed for that column within the GridView
.
public class CalendarView: ViewBase
{
public BindingBase ItemBeginBinding { get; set; }
public BindingBase ItemEndBinding { get; set; }
}
Step 2 - Basic Visual Tree
The next step is to define the styles to be used when rendering the ViewBase
's underlying visual tree as well as the style to be used when rendering each ListViewItem
(each ListViewItem
is a control meant to render a single data object contained with the ItemsSource
of the ListView
). This is done via overriding of two ViewBase
properties within the CalendarView
class and adding two styles to the Themes/Generic.xaml file. Note the use of the ComponentResourceKey
class. While it would be legitimate to just return a raw string
, there could be the potential for a name conflict down the road. A ComponentResourceKey
essentially adds a qualifier of a Type
instance, permitting multiple styles with the same "name" (or in this case, Resource ID). The first style targets the ListView
as a whole and for now renders just a StackPanel
, with the individual ListViewItem
instances contained inside it (via the IsItemsHost
being set to true
). The second style targets each ListViewItem
and renders a TextBlock
bound to the underlying data object itself (in this case, the appointment), which will render the result of invoking the ToString()
method on the data object.
public class CalendarView: ViewBase
{
protected override object DefaultStyleKey
{
get { return new ComponentResourceKey(this.GetType(), "DefaultStyleKey"); }
}
protected override object ItemContainerDefaultStyleKey
{
get { return new ComponentResourceKey(this.GetType(),
"ItemContainerDefaultStyleKey"); }
}
}
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:OutlookWpfCalendar.Windows.Controls">
<Style x:Key="{ComponentResourceKey TypeInTargetAssembly=
{x:Type controls:CalendarView}, ResourceId=DefaultStyleKey}"
TargetType="{x:Type ListView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListView}">
<StackPanel IsItemsHost="True" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{ComponentResourceKey TypeInTargetAssembly=
{x:Type controls:CalendarView},
ResourceId=ItemContainerDefaultStyleKey}"
TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<TextBlock Text="{Binding}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Step 3 - Display Initial View with Customized Item Template
Included with the download is a sample application that includes a simple Appointment
class and the following instance of the CalendarView
class (and the underlying result):
<Window x:Class="OutlookWpfCalendar.UI.CalendarViewWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:OutlookWpfCalendar.UI"
xmlns:controls="clr-namespace:OutlookWpfCalendar.Windows.Controls;
assembly=OutlookWpfCalendar"
Title="Calendar View Window" Height="500" Width="400">
<Grid>
<ListView>
<ListView.View>
<controls:CalendarView ItemBeginBinding="{Binding Path=Start}"
ItemEndBinding="{Binding Path=Finish}">
<controls:CalendarView.Periods>
<controls:CalendarViewPeriod Header="Monday"
Begin="03/02/2009 12:00 AM" End="03/03/2009 12:00 AM" />
<controls:CalendarViewPeriod Header="Tuesday"
Begin="03/03/2009 12:00 AM" End="03/04/2009 12:00 AM" />
<controls:CalendarViewPeriod Header="Wednesday"
Begin="03/04/2009 12:00 AM" End="03/05/2009 12:00 AM" />
<controls:CalendarViewPeriod Header="Thursday"
Begin="03/05/2009 12:00 AM" End="03/06/2009 12:00 AM" />
<controls:CalendarViewPeriod Header="Friday"
Begin="03/06/2009 12:00 AM" End="03/07/2009 12:00 AM" />
</controls:CalendarView.Periods>
</controls:CalendarView>
</ListView.View>
<ListView.Items>
<local:Appointment Start="03/02/2009 2:00 AM"
Finish="03/02/2009 3:00 AM" Subject="Meet with John"
Location="Southwest Meeting Room" Organizer="Jim Smith" />
<local:Appointment Start="03/03/2009 4:00 AM"
Finish="03/03/2009 5:00 AM" Subject="Meet with Rick"
Location="Southwest Meeting Room" Organizer="Jim Smith" />
<local:Appointment Start="03/04/2009 6:00 AM"
Finish="03/04/2009 6:30 AM" Subject="Meet with Dave"
Location="Southwest Meeting Room" Organizer="Jim Smith" />
<local:Appointment Start="03/02/2009 1:30 AM"
Finish="03/02/2009 5:00 AM" Subject="Meet with Larry"
Location="Southwest Meeting Room" Organizer="Jim Smith" />
<local:Appointment Start="03/03/2009 4:30 AM"
Finish="03/03/2009 7:30 AM" Subject="Meet with Jim"
Location="Southwest Meeting Room" Organizer="Jim Smith" />
</ListView.Items>
</ListView>
</Grid>
</Window>
Obviously, the appointments could use a little better styling, which requires a style to be defined for the individual ListViewItem
controls that encompass each Appointment
. This could be a global style or defined, as shown below, within the context of the ListView
itself via the ItemContainerStyle
property:
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Border BorderBrush="#5076A7" BorderThickness="1,1,1,1"
CornerRadius="4,4,4,4">
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#FFFFFF" Offset="0.0" />
<GradientStop Color="#C0D3EA" Offset="1.0" />
</LinearGradientBrush>
</Border.Background>
<StackPanel TextElement.FontFamily="Segoe UI"
TextElement.FontSize="12">
<TextBlock FontWeight="Bold" Padding="3,0,0,0"
Text="{Binding Path=Subject}" />
<TextBlock Padding="3,0,0,0"
Text="{Binding Path=Location}" />
<TextBlock Padding="3,0,0,0"
Text="{Binding Path=Organizer}" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</ListView.ItemContainerStyle>
Next Steps
In this article, the initial class structure has been designed and implemented. The focus has been more so on the class design over the visual layout. Other than the look of the individual appointment items, which is simply a style that could be applied in a normal ListBox
control, there really is nothing to suggest the Microsoft Outlook Calendar UI. In the following articles, build out of the actual controls necessary to create the true look and feel will occur. This matches the way in which I developed the control, first focusing on the XAML that would provide the necessary flexibility (which drove the class and property design), followed by determining the individual pieces of the visual layout that make up the whole.
History
- 04/15/2009: Initial version
- 04/21/2009: Updated article