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

28 Comments

  1. Hi Sergey,

    this is really a great control! But, it is possible to remove the upper two ‘previous’ and ‘next` buttons and instead and integrate their functions for example into the buttons of the application bar? And how can I do that?

    Kind regards,
    Joerg

  2. Hey, Joerg,
    You could do that by specyfing a custom template for calendar control. Just copy the one from the project and remove the buttons. Also, if you see that this is useful, please add this as a suggestion on the CodePlex. That would make it easier to track for me. I could probably just add a property to suppress the two buttons and expose two commands (next and previous), which would make it a better control

  3. Hi Sergey.
    I am struggling with an implementation of your calendar control.
    Specifically, I am trying to create a DatesSource from a list of holiday dates.
    The problem I have is with your example, specifically with:

    Dates.Add(new DateInfo() { CalendarItemDate = DateTime.Today });

    I do not seem to be able to find the DateInfo class anywhere. Can you please help?

    Paul Mariotti

  4. Hi again and again Sergey
    I am sorry, I need to ask you another question. In the calendar I am implementing I want to show all Sundays and holidays from a national holiday list as white on red, and some other holidays from an optional holiday list as black on orange.
    I have implemented the appropriate converter class (see code below) but the setting of the colors is sporadic – some get set and some don’t.
    I do not seem to understand when and how the converter gets called (I thought it was called every time a cell is drawn).
    The code:
    public class ColorConverter : IDateToBrushConverter
    {
    public Brush Convert(DateTime dateTime, bool isSelected, Brush defaultValue, BrushType bType)
    {
    if (bType == BrushType.Background)
    {
    if (dateTime.DayOfWeek == DayOfWeek.Sunday)
    return new SolidColorBrush(Colors.Red);

    IEnumerable hol = (from c in App.currentHolidays
    where (c.holidayDate == dateTime)
    select c);
    if (hol.Count() != 0)
    {
    if (hol.First().holidayType == “Y”)
    return new SolidColorBrush(Colors.Red);
    else
    return new SolidColorBrush(Colors.Orange);
    }

    if (dateTime == new DateTime(DateTime.Today.Year, DateTime.Today.Month, 5))
    {
    return new SolidColorBrush(Colors.Yellow);
    }
    else
    {
    return defaultValue;
    }
    }
    else
    {
    if (dateTime.DayOfWeek == DayOfWeek.Sunday)
    return new SolidColorBrush(Colors.White);

    IEnumerable hol = (from c in App.currentHolidays
    where (c.holidayDate == dateTime)
    select c);

    if (hol.Count() != 0)
    {
    if (hol.First().holidayType == “Y”)
    return new SolidColorBrush(Colors.White);
    else
    return new SolidColorBrush(Colors.Black);
    }

    if (dateTime == new DateTime(DateTime.Today.Year, DateTime.Today.Month, 5))
    {
    return new SolidColorBrush(Colors.Yellow);
    }
    else
    {
    return defaultValue;
    }
    }
    }
    }

    Can you help, please?

    Paul Mariotti

    • Yes, it does get called for every cell as it is updated. I have not seen the issue like yours before, and my app in the app store is using this functionality. Maybe there are logic problems and your dates do not get cought by your conditions? You could setup a breakpoint inside the converter, then navigate to a month with issues, and simply trace through the logic for the date that does not work. If you would like to email me a sample project with repro steps, I could take a look.
      Thanks.
      Sergey

  5. Hi!

    This is a great control. I am currently working on a tool for our consultants so they can report their time on their wp7 phones instead of the bulky website we have. It is not fun to use in the phone browser.

    This calendar control is almost exactly what I had in mind. However, I am wondering if you have plans to update it further, if you do, I would love to see these “improvements”

    1) Remove the buttons for stepping through years and months. Gestures is more easy to use on a wp7 Also saves screen space

    2) Add a property on the calendar to display week numbers on the left side of the calendar. This is an extremly useful feature in Business Administration…

    3) Make it possible to multi select different calendar cells. For example, if the user hold down the finger on monday, and drag the finger to friday and release the finger, these days are selected, and it should be possible in code to get a collection of selected days based on that event. Also useful if you want to make a booking etc.

    Thanks for making a very nice control! It is wierd that MS didnt release it, since they already have built one in their own calendar.

    Henrik

  6. hi Sergey, i’m new to windows phone development
    regarding to the sample project i downloaded, WPControls…..when i run the sample project it doesn’t perform the function that you’ve mentioned above….all the .cs class not used??

  7. Hi Sergey. I am trying to do the selected date data binding where they can enter details for each date selected. How do I start on that? :S Is there an article on selected date data binding?

  8. For example, you can do something like this


    This is actually an example from the examples project included with source code on CodePlex. Of course, you can bind to your view model the same way.
    Thanks.

  9. in this project i am changing the color for text if any data available on this date. i am checking the data from database

    my code is

    namespace NotepadTesting
    {
    public class ColorConverter : IDateToBrushConverter
    {
    private const string Con_String = @”isostore:/amit.sdf”;
    public Brush Convert(DateTime dateTime, bool isSelected, Brush defaultValue, BrushType brushType)
    {
    if (brushType == BrushType.Background)
    {
    if (dateTime == new DateTime(DateTime.Today.Year, DateTime.Today.Month, DateTime.Today.Day))
    {
    return new SolidColorBrush(Colors.Orange);
    }
    else
    {
    return defaultValue;
    }
    }
    else
    {
    for (int i = 0; i < 32; i++)
    {
    String date = (i + 1) + "/" + DateTime.Today.Month + "/" + DateTime.Today.Year;
    NotePad context = new NotePad(Con_String);
    IQueryable query = from c in context.CalendarDetails select c;
    {
    foreach (one db in query)
    {
    string dbdate = db.E_EventDate;
    if (dbdate == date)
    {
    return new SolidColorBrush(Colors.Red);
    }
    else
    {
    return defaultValue;
    }
    }
    }
    }
    }
    }
    }
    }

    but it says that error like not all code paths returns the value

    my question is how to check the date with database date in this class file?

    help me out

  10. You are missing return statements in all your if block, so you do not return value under all conditions. You should also avoid hitting db in converter – it will be too slow I think.

    public class ColorConverter// : IDateToBrushConverter
    {
    private const string Con_String = @”isostore:/amit.sdf”;
    public Brush Convert(DateTime dateTime, bool isSelected, Brush defaultValue, BrushType brushType)
    {
    if (brushType == BrushType.Background)
    {
    if (dateTime == new DateTime(DateTime.Today.Year, DateTime.Today.Month, DateTime.Today.Day))
    {
    return new SolidColorBrush(Colors.Orange);
    }
    else
    {
    return defaultValue;
    }
    }
    else
    {
    for (int i = 0; i < 32; i++) { String date = (i + 1) + "/" + DateTime.Today.Month + "/" + DateTime.Today.Year; NotePad context = new NotePad(Con_String); var query = from c in context.CalendarDetails select c; { foreach (var db in query) { string dbdate = db.E_EventDate; if (dbdate == date) { return new SolidColorBrush(Colors.Red); } else { return defaultValue; } } } } } return defaultValue; } }

  11. I want to do the same as pari to do, but i just not figured all about to do it, could you please explain me the detail to do it? check the database then how to contruct the class of “ColorConverter” ? if you can please send me the project about the explanation and little example to my email. thanks 😀

  12. On codeplex site you can download the sample project. I believe it has a converter in it do demonstrate the usage. Hope it will help you, Also, code above is another example.

  13. just did the same about what you’d explained right aove my first comment. i replace whlo coloerconverter.cs with these. but nothing changed. the code means to check every 1-31 date in every changed month then change the color when it has task in it, isn’t it? i did it correctly but nothing changed?

  14. Did you set the converter property on the calendar itself? Can you put some breakpoints inside your converter and run the project to see if the breakpoints is hit? This will tell you if you assigned converter to the calendar.

  15. now it gives me an error. how to set the converter xaml files except with staticresource?
    ColorConverter = “{staticresource ColorConverter}” give me an error

    then foreach statement also give me another error -__-

  16. error message in “foreach(var db in query)” statement says “An exception of type ‘System.Data.SqlServerCe.SqlCeException’ occurred in Microsoft.Phone.Data.Internal.ni.dll but was not handled in user code” what should i do then?

  17. you mean in colorconverter.cs right? of course it works! then when i try to print the query to select the 1-31 date from month, it also work. but i think when the program called for the first time it can’t find the database file, what then?

  18. You have to wait then until your app is fully initialized before you do any work. Maybe in app.xaml.cs you can open and verify the databases, then you can use single db instance in app object for all the work. Should work better.

Leave a Reply to Sergey Barskiy Cancel reply

Your email address will not be published. Required fields are marked *