Windows Phone 7 Controls Project Update

I have published an update to the calendar control on my CodePlex project dedicated to building common controls for Windows Phone 7.

I added a number of new features.  The biggest improvement added is the ability to use context menu from Silverlight Toolkit for Windows Phone 7.  In order to provide this feature, I added style property to Calendar control that allows you to specify a custom template for calendar item.  Of course, you have to be careful not to override parts of the template that are responsible for providing color information for the calendar.  Namely, in your template you have to have TemplateBinding for Foreground and Background.  Other than that, you can modify the default template as you see fit.  Then, you can set CalendarItemsStyle property on the calendar to force it to use different template.  Here is the example of the XAML for the screen that adds context menu to the calendar items.  As you see below I specify style for CalendarItem in the resources of the page, then use this style and assign it to CalendarItemStyle property of the calendar control.

<phone:PhoneApplicationPage 
  
x:Class="Sample.MainPage"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
   xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
   xmlns:controls="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls"
   xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 
  
xmlns:wi="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    
  
xmlns:i="clr-namespace:Microsoft.Practices.Prism.Interactivity;assembly=Microsoft.Practices.Prism.Interactivity"
 
  
xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
   xmlns:mvvmLight="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WP7"
   xmlns:wpControls="clr-namespace:WPControls;assembly=WPControls"
   mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
 
  
FontFamily="{StaticResource PhoneFontFamilyNormal}"
   FontSize="{StaticResource PhoneFontSizeNormal}"
   Foreground="{StaticResource PhoneForegroundBrush}"
   SupportedOrientations="Portrait"  Orientation="Portrait"
   shell:SystemTray.IsVisible="True">
    <phone:PhoneApplicationPage.Resources>
        <Style TargetType="wpControls:CalendarItem" x:Key="ItemStyle">
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate  TargetType="wpControls:CalendarItem">
                        <Grid x:Name="OuterGrid" HorizontalAlignment="Stretch"
 

VerticalAlignment="Stretch">
                            <Border
 
                          
BorderThickness="2"
                           HorizontalAlignment="Stretch"
                           VerticalAlignment="Stretch"
                           BorderBrush="{StaticResource PhoneForegroundBrush}">
                                <Grid Height="60" HorizontalAlignment="Stretch"
 

VerticalAlignment="Stretch" >
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="Auto"/>
                                        <RowDefinition Height="*"/>
                                    </Grid.RowDefinitions>
                                    <Rectangle Grid.RowSpan="2"
 

x:Name="BackgroundRectangle" Fill="{TemplateBinding Background}" />
                                    <TextBlock
 
                                  
x:Name="DayNumberBlock"
                                   Text="{Binding Path=DayNumber,
 

RelativeSource={RelativeSource TemplatedParent}}" 
                                  
Foreground="{TemplateBinding Foreground}"
 
                                  
FontWeight="ExtraBold"
                                   HorizontalAlignment="Left"
 
                                  
VerticalAlignment="Top"
 
                                  
Margin="4,2,0,0"/>
 
                                </Grid>
                            </Border>
                            <toolkit:ContextMenuService.ContextMenu>
                                <toolkit:ContextMenu>
                                    <toolkit:MenuItem Header="Mark">
                                        <wi:Interaction.Triggers>
                                            <wi:EventTrigger EventName="Click">
                                                <mvvmLight:EventToCommand
 

Command="{Binding Path=MarkCommand, Mode=OneWay}" 

CommandParameter="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ItemDate}" />
                                            </wi:EventTrigger>
                                        </wi:Interaction.Triggers>
                                    </toolkit:MenuItem>
                                    <toolkit:MenuItem Header="Clear">
                                        <wi:Interaction.Triggers>
                                            <wi:EventTrigger EventName="Click">
                                                <mvvmLight:EventToCommand
 

Command="{Binding Path=ClearCommand, Mode=OneWay}" 

CommandParameter="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ItemDate}" />
                                            </wi:EventTrigger>
                                        </wi:Interaction.Triggers>
                                    </toolkit:MenuItem>
                                </toolkit:ContextMenu>
                            </toolkit:ContextMenuService.ContextMenu>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </phone:PhoneApplicationPage.Resources>
 
    <!–LayoutRoot is the root grid where all page content is placed–>
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <!–Pivot Control–>
        <controls:Pivot Title="Sample">
 
            <controls:PivotItem Header="calendar">
                <Grid>
                    <wpControls:Calendar x:Name="mainCalendar"
 
                                       
SelectedDate="{Binding Path=SelectedDate, Mode=TwoWay}"
 
                                       
CalendarItemStyle="{StaticResource ItemStyle}"
                                        DatesSource="{Binding Path=Dates}"
                                        ColorConverter="{Binding}"/>
                </Grid>
 
            </controls:PivotItem>
 
            <controls:PivotItem Header="other">
                <Grid>
 
                </Grid>
            </controls:PivotItem>
        </controls:Pivot>
    </Grid>
 

 
</phone:PhoneApplicationPage
>

 

To make this XAML work, you have to also add references to MVVM Light framework for the phone in addition to Silverlight toolkit for the phone.  ViewModel is very simple, but I want to paste entire code to make it very clear how it is used.

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows.Media;
using GalaSoft.MvvmLight.Command;
using PeriodTracker.ViewModels;
using
WPControls;
 
 

namespace
Sample
{
   
public class MainViewModel : INotifyPropertyChanged, IDateToBrushConverter
    {
       
public
MainViewModel()
        {
            Dates =
new ObservableCollection<ISupportCalendarItem
>();
            Dates.Add(
new DateInfo() { CalendarItemDate = DateTime
.Today });
            Dates.Add(
new DateInfo() { CalendarItemDate = DateTime
.Today.AddDays(1) });
            MarkCommand =
new RelayCommand<object
>(OnMark);
            ClearCommand =
new RelayCommand<object
>(OnClear);
        }
 
       
public RelayCommand<object> MarkCommand { get; set
; }
       
public void OnMark(object
item)
        {
           
if (!Dates.Where(one => one.CalendarItemDate == (DateTime
)item).Any())
            {
                Dates.Add(
new DateInfo() { CalendarItemDate = (DateTime
)item });
            }
        }
 
       
public RelayCommand<object> ClearCommand { get; set
; }
       
public void OnClear(object
item)
        {
           
ISupportCalendarItem info = Dates.FirstOrDefault(one => one.CalendarItemDate == (DateTime
)item);
           
if (info != null
)
            {
                Dates.Remove(info);
            }
        }
 
       
private DateTime
selectedDate;
 
       
public DateTime
SelectedDate
        {
           
get { return
selectedDate; }
           
set { selectedDate = value; NotifyPropertyChanged("SelectedDate"
); }
        }
 
       
public event PropertyChangedEventHandler
PropertyChanged;
       
private void NotifyPropertyChanged(String
propertyName)
        {
 
           
if (PropertyChanged != null
)
            {
                PropertyChanged(
this, new PropertyChangedEventArgs
(propertyName));
            }
        }
 
       
private ObservableCollection<ISupportCalendarItem
> dates;
 
       
public ObservableCollection<ISupportCalendarItem
> Dates
        {
           
get { return
dates; }
           
set { dates = value; NotifyPropertyChanged("Dates"
); }
        }
 
       
public Brush Convert(DateTime dateTime, bool isSelected, Brush defaultValue, BrushType
brushType)
        {
           
if (brushType == BrushType
.Background)
            {
               
if (Dates != null
&& Dates.Where(one => one.CalendarItemDate == dateTime).Any() && !isSelected)
                {
                   
return new SolidColorBrush(Colors
.Red);
                }
               
else
                {
                   
return
defaultValue;
                }
            }
           
else
            {
               
if (Dates != null
&& Dates.Where(one => one.CalendarItemDate == dateTime).Any() && isSelected)
                {
                   
return new SolidColorBrush(Colors
.Red);
                }
               
else
                {
                   
return defaultValue;
                }
            }
        }
    }
}

 

As you can see, I have a property that provides source with dates to the calendar.  This is also new in this release.  If you provide an Observable Collection where each item implements new interface ISupportCalendarItem, calendar can improve overall performance.  It also enables you not to explicitly raise property changed events, forcing control to repaint.  Having said that, you now have two ways to explicitly force repaint of the calendar.  You can call Refresh() method on it.  Or, in your view model you can raise property changed event for the property that is bound to the DatesSource property of the calendar.

Another change I introduce is consolidate date to brush converters into a single class.  This minimizes the code you have write as well as simplifies XAML at the same time.  Here is the new interface:

using System.Windows.Media;
using
System;
 

namespace
WPControls
{
   
/// <summary>
    /// This converter can be used to control day number color and background color
    /// for each day
    /// </summary>
    public interface IDateToBrushConverter
    {
       
/// <summary>
        /// Perform conversion of a date to color
        /// This can be used to color a cell based on a date passed it
        /// </summary>
        /// <param name="dateTime">Date for the calendar cell</param>
        /// <param name="isSelected">Indicates if a date is selected by the user</param>
        /// <param name="defaultValue">Brush that will be used by default
        /// if the converter did not exists
        /// </param>
        /// <param name="brushType">Type of conversion to perform – foreground or background</param>
        /// <returns>New Brush to color day number or calendar cell background</returns>
        Brush Convert(DateTime dateTime, bool isSelected, Brush defaultValue, BrushType brushType);
    }
}

 

Brush type is the enumeration that consists of background and foreground:

namespace WPControls
{
   
/// <summary>
    /// Type of brush to driver color converter
    /// </summary>
    public enum BrushType
    {
       
/// <summary>
        /// Calendar item background conversion
        /// </summary>
        Background,
       
/// <summary>
        /// Day number of calendar item
        /// </summary>
        Foreground
    }
}

 

This concludes the list of changes in this release.  Please let me know if you have any questions at all regarding the use of the control