Jarloo

Menu

An Editable WPF Calendar Control

The Calendar that comes with .NET is good but there are so many times I wanted a calendar control that lets the user add entries right on the calendar. The ability to add text to a day on a calendar is important and makes the computer version seem much more like the paper one. It annoyed me that .NET didn’t have one and after a search I couldn’t seem to find anything worth using so as a good little programmer I wrote my own.

Here is what it looks like: (click for a bigger version)

Jarloo Open Source Calendar for WPF

The current day is shown in blue, and any day with an event automatically gets an orange border. The user can just click on a day and start typing. When a user edits a day like this it fires an event you can monitor and save the information to a file or database etc…

The control is “lookless”, so if you don’t like how i’ve skinned it you can easily modify the theme file and provide your own skin.

When I first started writing this project I thought it would take quite a bit of code, now that i’m done I’m surprised as how simple and small the project turned out to be.

Download Source Code

 

How the Calendar Works

The Jarloo Calendar is a WPF Control. The majority of it is really in a single small method called BuildCalendar:

public ObservableCollection<Day> Days { get; set; }
...
public void BuildCalendar(DateTime targetDate)
{
    Days.Clear();

    //Calculate when the first day of the month is and work out an
    //offset so we can fill in any boxes before that.
    DateTime d = new DateTime(targetDate.Year, targetDate.Month, 1);
    int offset = DayOfWeekNumber(d.DayOfWeek);
    if (offset != 1) d = d.AddDays(-offset);

    //Show 6 weeks each with 7 days = 42
    for (int box = 1; box <= 42; box++)
    {
        Day day = new Day {Date = d, Enabled = true, IsTargetMonth = targetDate.Month == d.Month};
        day.PropertyChanged += Day_Changed;
        day.IsToday = d == DateTime.Today;
        Days.Add(day);
        d = d.AddDays(1);
    }
}

&#91;/csharp&#93;

<h3>Day Class</h3>
The Day class is a simple state bag that implements INotifyPropertyChanged.


using System;
using System.ComponentModel;

namespace Jarloo.Calendar
{
    public class Day : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private DateTime date;
        private string notes;
        private bool enabled;
        private bool isTargetMonth;
        private bool isToday;

        public bool IsToday
        {
            get { return isToday; }
            set
            {
                isToday = value;
                if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsToday"));
            }
        }

        public bool IsTargetMonth
        {
            get { return isTargetMonth; }
            set
            {
                isTargetMonth = value;
                if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsTargetMonth"));
            }
        }

        public bool Enabled
        {
            get { return enabled; }
            set
            {
                enabled = value;
                if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Enabled"));
            }
        }

        public string Notes
        {
            get { return notes; }
            set
            {
                notes = value;
                if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Notes"));
            }
        }

        public DateTime Date
        {
            get { return date; }
            set
            {
                date = value;
                if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Date"));
            }
        }
    }
}

There isn’t much to making a Calendar in WPF, the harder part is really the skinning. Here is the Theme I used to skin the control. Remember this is just one theme, you can modify it or add your own.

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Jarloo="clr-namespace:Jarloo.Calendar" xmlns:Converters="clr-namespace:Jarloo.Calendar.Converters">

    <Converters:DateConverter x:Key="DateConverter"></Converters:DateConverter>
    <Converters:DayBorderColorConverter x:Key="DayBorderColorConverter"></Converters:DayBorderColorConverter> 

    <Style TargetType="{x:Type Jarloo:Calendar}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Jarloo:Calendar}">

                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">

                        <DockPanel>
                            <TextBlock Text="{Binding Date}" />

                            <Grid Height="30" DockPanel.Dock="Top">

                            </Grid>

                            <ItemsControl ItemsSource="{Binding DayNames}" DockPanel.Dock="Top">
                                <ItemsControl.ItemTemplate>
                                    <DataTemplate>
                                        <TextBlock TextAlignment="Center" Text="{Binding}">
                                            <TextBlock.Background>
                                                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
					                                <GradientStop Color="#FF171717" Offset="0"/>
					                                <GradientStop Color="#FF040404" Offset="1"/>
				                                </LinearGradientBrush>
                                            </TextBlock.Background>
                                        </TextBlock>
                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                                <ItemsControl.ItemsPanel>
                                    <ItemsPanelTemplate>
                                        <UniformGrid Rows="1" Columns="7" />
                                    </ItemsPanelTemplate>
                                </ItemsControl.ItemsPanel>
                            </ItemsControl>

                            <ItemsControl ItemsSource="{Binding Days}">
                                <ItemsControl.ItemTemplate>
                                    <DataTemplate>
                                        <!--Box-->
                                        <Border BorderBrush="Black" BorderThickness="1" Padding="0">

                                            <Border Name="InnerBorder" BorderBrush="{Binding Path=Notes, Converter={StaticResource DayBorderColorConverter}}" BorderThickness="2">                                                

                                                <Border.Style>
                                                    <Style TargetType="{x:Type Border}">
                                                        <Style.Triggers>
                                                            <!--Current Day-->
                                                            <DataTrigger Binding="{Binding IsToday}" Value="true">
                                                                <Setter Property="Border.Background">
                                                                    <Setter.Value>
                                                                        <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                                                                            <GradientStop Color="#FF1EA6C8" Offset="0"/>
                                                                            <GradientStop Color="#FF0691B3" Offset="1"/>
                                                                        </LinearGradientBrush>
                                                                    </Setter.Value>
                                                                </Setter>
                                                            </DataTrigger>
                                                        </Style.Triggers>
                                                    </Style>
                                                </Border.Style>

                                                <DockPanel>
                                                    <!--Day Number-->
                                                    <StackPanel Orientation="Horizontal" DockPanel.Dock="Top" FlowDirection="RightToLeft">
                                                        <TextBlock TextAlignment="Right" Text="{Binding Date, Converter={StaticResource DateConverter}, ConverterParameter=DAY}" FontSize="14" Margin="5,5,5,5" >
                                                            <TextBlock.Style>
                                                                <Style TargetType="{x:Type TextBlock}">
                                                                    <Style.Triggers>
                                                                        <DataTrigger Binding="{Binding IsTargetMonth}" Value="false">
                                                                            <Setter Property="TextBlock.Foreground" Value="Gray"></Setter>
                                                                        </DataTrigger>
                                                                    </Style.Triggers>
                                                                </Style>
                                                            </TextBlock.Style>
                                                        </TextBlock>
                                                    </StackPanel>

                                                    <TextBox IsEnabled="{Binding IsEnabled}" Text="{Binding Notes, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" AcceptsReturn="True" TextWrapping="Wrap" BorderThickness="0" ScrollViewer.VerticalScrollBarVisibility="Auto" Background="{x:Null}" Foreground="White" />
                                                </DockPanel>
                                            </Border>
                                        </Border>

                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                                <ItemsControl.ItemsPanel>
                                    <ItemsPanelTemplate>
                                        <UniformGrid Rows="6" Columns="7" />
                                    </ItemsPanelTemplate>
                                </ItemsControl.ItemsPanel>
                            </ItemsControl>
                        </DockPanel>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

You’ll notice I use an ItemsControl, this is to iterate the ObservableCollection of day objects, and I’ve changed the default ItemsPanelTemplate to use a UniformGrid so that as you resize the control the days all stay the same height and width.

The day uses a simple converter to display the information:

using System;
using System.Globalization;
using System.Windows.Data;

namespace Jarloo.Calendar.Converters
{
    public class DateConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            DateTime date = (DateTime) value;

            string param = (string) parameter;

            switch(param.ToUpper())
            {
                case "MONTH":
                    return date.Month;
                case "YEAR":
                    return date.Year;
                case "DAY":
                    return date.Day;
                default:
                    return date.ToString();
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

The colors displayed in the WPF Calendar control are controlled by a trigger for the current day as shown above, and for entries with notes there is another simple converter that handles this.

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;

namespace Jarloo.Calendar.Converters
{
    public class DayBorderColorConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            string notes = (string)value;

            if (string.IsNullOrEmpty(notes)) return null;

            if (notes.Length > 0) return new LinearGradientBrush(Color.FromRgb(220, 74, 56), Color.FromRgb(198, 56, 40), new Point(0.5, 0), new Point(0.5, 1));

            return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

Keep in mind as the comments in the files suggest this does not support globalization, so in places such as Australia where the week begins on Monday instead of Sunday it will not work. It should be easy enough to remedy though if your interested.

Also notice this control has no error handling. If you plan to use this in production you will want to add some. While your at it you might need to test it on odd scenarios such as leap-years as I have not done so.

Categories:   Code

Tags:  , , ,

Comments

Sorry, comments are closed for this item.