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

  • Posted: March 14, 2012 22:29

    ct

    Very beautiful calendar!
  • Posted: March 16, 2012 17:26

    Waldner

    Hello, its a nice control ! can you give me a example about the save method ?
    • Posted: March 16, 2012 17:47

      admin

      The save is quite app specific. You basically just need to listen to the DayChanged event. This gives you a Day object that contains the current edits. The Notes Property has the edits, and it's just a string. So you can take it and save it to a database along with the Date property on the Day object. Then when the app starts again you can pass that info back to have the control repopulate the info.
  • Posted: March 30, 2012 16:55

    Andy

    Hi, good job. may u replace int month = DateTime.ParseExact(cboMonth.SelectedItem as string, "MMMM", CultureInfo.CurrentCulture).Month; with int month = cboMonth.SelectedIndex + 1; also month "may" is missing in List months
    • Posted: March 30, 2012 21:32

      admin

      @Andy Thanks, I've implemented both fixes. Can't believe i forgot a month. lol.
  • Posted: May 1, 2012 04:20

    Madura

    this WPF application can do C# winform also..?
    • Posted: May 1, 2012 13:30

      admin

      No WPF controls will not work in WinForms.
  • Posted: May 2, 2012 02:11

    Madura

    please tel me how to get selected date time in this calender..? (any method)
    • Posted: May 2, 2012 10:35

      admin

      The calendar does not have a selected state. You could add one, but the purpose of the calendar was to provide a way to associate text with a date, instead of a DatePicker style of control.
  • Posted: May 3, 2012 04:01

    Madura

    Thank you, ur response
  • Posted: May 17, 2012 15:35

    Bracher

    Hi, thanks for this great post. I'm rather new to WPF. I just wan't to know how exactly do you implement a selected event on the Days? Do you have to change the the Day class to be more of a control?
  • Posted: June 11, 2012 06:09

    Madura

    How to dynamically change this calendar Day Border Color in different events..
    • Posted: June 11, 2012 07:44

      admin

      The control is "lookless", meaning the look is completely controlled through the theme file. You can change the color there if you want a different one. If you want to use more then one, I would suggest setting up DataTriggers to change the color.
  • Posted: June 12, 2012 00:03

    Madura

    Thank you sir. sir i have data base values (event), i want to this values to put in this calendar and put different colors in particular event... how to i do it...?
  • Posted: June 13, 2012 23:58

    Madura

    How pass object in XAML textbox please help me
  • Posted: July 20, 2012 16:25

    Ted

    Greetings, Can this control be used to read appointments from a SQL Server database. I could probably read the code and figure it out, but its Friday, and I am feeling a little lazy..... Anyway, it is a great looking calendar.....
    • Posted: July 20, 2012 20:45

      admin

      The control won't load the data for you, but it can display the appointments
  • Posted: September 28, 2012 02:27

    maria

    how it will save the appointment dates. Actually i am new to coding. Plz guide me
  • Posted: October 2, 2012 08:17

    Bjarke Vad

    Hey, thanks for this great calendar! I'm making a Metro theme for with Danish localisation(Monday is the first day of the week etc.), and I'm turning each day into a select-able item with a listview, making a week-view - as opposed to only month-view, and finally adding the ability to add events to each day(Which will be a bit more complex than just a string - NYI). If anyone is interested I will upload it when I'm done! This is the result so far: http://imgur.com/9Ace7
    • Posted: December 6, 2015 08:36

      Tim

      Hi there. Just a check here. Did you ever get around to completing your changes? 'You mentioned if anyone's interested' and I suppose I make 1.
      • Posted: December 16, 2015 21:04

        Kelly Elias

        Sorry no I don't. I have way too much on my plate currently. I started a small commodity trading company with others and we've grown so fast that I'm swamped. (good problem to have, just hectic)
  • Posted: October 2, 2012 08:21

    Bjarke Vad

    Forgot to say that this is very much WIP :)
  • Posted: October 10, 2012 23:52

    hiba

    @BJARKE VAD : Kindly upload it here. I need it Thank you so mch. But plz make the method for saving the events...
  • Posted: October 26, 2012 14:12

    Cardinal177Man

    Hello. Found something interesting. When I navigate to Oct 2012, the start of month is off by one day. i.e. 01-Oct-2012 is on a Sunday, when it's actually on a Monday. FYI, to all.
  • Posted: December 27, 2012 01:33

    pisey

    I want to save even to persistent storage, how can i? anyone have idea.
  • Posted: January 17, 2013 06:12

    Muhammad Alaa

    great work I searched for this calendar for days and really found your calendar a great work. Thanks
  • Posted: March 30, 2013 09:37

    spoon

    I got rong day to date mapping in month april and july. I think there is a bug in Calendar.cs -> BuildCalendar. wrong: if (offset != 1) d = d.AddDays(-offset); working: if (offset != 0) d = d.AddDays(-offset); Can somebody confirm taht this is correct and will have no other sideeffects?
  • Posted: April 10, 2013 07:58

    Sh

    Great work! May i use your work for commercial program? Ill modify and add some features Let me know if it's ok
    • Posted: April 10, 2013 10:08

      Kelly Elias

      Yes it is free to use in commercial products, but keep in mind this software is given "as is" with no support, and I take no responsibility for any issues that may occur.
  • Posted: May 31, 2013 09:59

    Andrea

    There's a way to make days info (like Notes property) bindable?
    • Posted: May 31, 2013 10:36

      Kelly Elias

      You will need to change it to a dependency property to make it bindable.
  • Posted: July 23, 2013 19:25

    AVE

    How do you change the culture of the calendar? Mine's off by one day
    • Posted: November 27, 2015 04:09

      Gee

      One bit of a correction to make those months which start on a Monday have an offset of 1. In line No. 11 of the calendar.cs class 'if (offset != 1) d = d.AddDays(-offset);' Change the value '1' to '0' so it becomes 'if (offset != 0) d = d.AddDays(-offset);'
  • Posted: August 25, 2013 13:11

    Gabe

    I've gone through your comments and am trying to figure out how to capture a mouse down event on a specific day in the calendar control. I've tried various ways, making changes to the Day class, but have not been able to have any luck yet so far. I've even tried to wire a mousedown event from within the generic.xaml file, but that wasn't allowing me to complile the solution at all. Any thoughts on an easy way to do this? I am simply trying to determine which day is selected when clicked on, on this control in WPF. Thanks!
  • Posted: September 9, 2013 02:05

    Moazzam Waheed

    Can anyone help on multiple event orange border color on calender?
  • Posted: November 6, 2013 01:22

    Tad

    I too(Like many others) am trying to find a way to capture the mouse down and get the selected date from it. I'm still very new to WPF so it is very difficult for me, if anyone has figured it out for this calendar, that would be amazing! Hope someone replies..
  • Posted: November 24, 2013 09:53

    A-Max Lee

    Hi, I found that your project is quite interesting & may I ask that got the version which got the save method that will save all the user type in? Hope to receive reply. Thank you :)
  • Posted: April 22, 2014 09:38

    Jhonathan Maia

    Hello! You have a tutorial teaching how to create a layout like this? very beatiful calendar! Best Regards.
  • Posted: September 3, 2014 00:43

    Justin

    You have my thanks. Simple calendar layout that can be altered for different functions. You are a ninja
  • Posted: October 17, 2014 14:59

    Mickael Martins

    Hey, how can I save and load the texts?
  • Posted: November 9, 2014 03:55

    Sony

    Great article, but i how can i attach a click event to the jarloo.calendar.I tried, but failed can you show an example
  • Posted: January 12, 2016 08:29

    Allan

    Hi, There is a bug, when the 1st day of the month == monday, the calendar does not display properly, noticed it when i was doing testing on feb 2016. Any idea what is going on?
  • Posted: February 2, 2016 14:57

    Josh Loschen

    There is also a bug where every 7 leap years the calendar is off by 1 day in February. This bug only occurs if the offset in the BuildCalendar() method is calculated to be 1. It occurs February 2016, February 2044, February 2072. I never would have noticed but it just happens to currently be one of times the calendar will be wrong.
  • Posted: February 4, 2016 06:41

    Nico

    Hi, at first well done work. I tried to adapt your ideas and replace the "notes" Textbox with a Listbox and bind it to an ObservableCollection. As far as good and all is displayed fine. Now I want to add objects via user input with an contextmenu. Therefor i added a Contextmenu to the listbox control. Where i´m struggling at the moment is to bind a action/command to the menuitem. About any help i would be Happy. br Nico
    • Posted: February 4, 2016 08:29

      cocoon

      @Nico: don't know if you mean that, but if you want to let something happen when clicking on en contect menu entry, something like this? //code behind private void DoSomething_Click(object sender, RoutedEventArgs e) { // do something here }
  • Posted: February 4, 2016 08:31

    cocoon

    Sorry, code got deleted in previous message: http://pastebin.com/mQ1z8Btz
  • Posted: April 28, 2016 08:09

    Stoyan

    Hi, this project is awesome ! But can someone help me with the save method for the text edits to persistent storage, please. Best Regards.
  • Posted: May 27, 2016 04:57

    fumahou

    This may be kinda late but I hope this helps someone who needs it. To make a default month to be selected when initialized you have to invert the order in the file where "months" is created like this: cboMonth.SelectionChanged += (o, e) => RefreshCalendar(); cboYear.SelectionChanged += (o, e) => RefreshCalendar(); cboMonth.SelectedItem = months.First(); cboYear.SelectedItem = DateTime.Today.Year; It's up to you to select the month you want to select.
  • Posted: July 15, 2016 07:52

    Ritsu

    How can I save the text inside the calendar box... for example I want to see the text the next time I open the window..?