Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Highlight dates on a WPF Calendar

0.00/5 (No votes)
20 Feb 2013 1  
Highligting and marking indvidual dates in a WPF Calendar

Introduction 

I wanted to use a WPF Calendar and highlight and mark individual dates of interest. I searched the web, but couldn't find any working solutions. I found "RedLetterDays" from Microsoft: http://msdn.microsoft.com/en-us/magazine/dd882520.aspx#id0430067, but that only works with WPF Toolkit and it is not very configurable. This solution doesn't rely on modifying the calendar, instead it modifies the background.  

Using the code 

Using the code is quite easy and is done in four steps: 

  1. Setting up the Calendar 
  2. Setting up icons
  3. Setting dates 
  4. Updating background when changing date   

The XAML part is nothing more than a basic Calendar.   

<Grid> 
    <Calendar Name="Kalender"  HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
</Grid>

The CalendarBackground is declared in the class. The background class is initialized with a reference to the Calendar class. This is done to be able to access DisplayDate

private readonly CalenderBackground background;  
background = new CalenderBackground(Kalender); 

First you must configure the icons you whish to use in the background. AddOverlay is called with an ID and a filename for the image. The images are 21x16 pixels with transparent backgrounds. The spacing between the rows in the Calendar varies between 15 and 16 pixels.   

background.AddOverlay("circle", "circle.png");
background.AddOverlay("tjek", "tjek.png");
background.AddOverlay("cross", "cross.png");
background.AddOverlay("box", "box.png");
background.AddOverlay("gray", "gray.png");   

Next you can add dates and the ID for the image you want to show on that date. It is possible to add more than one icon to one date. The overlay is done semi transparent so the icons are visible even if stacked. 

background.AddDate(new DateTime(2013, 02, 20), "tjek"); 
background.AddDate(new DateTime(2013, 02, 17), "tjek");
background.AddDate(new DateTime(2013, 02, 12), "tjek");
background.AddDate(new DateTime(2013, 02, 13), "tjek");
background.AddDate(new DateTime(2013, 02, 14), "tjek"); 
background.AddDate(new DateTime(2013, 02, 15), "tjek");
background.AddDate(new DateTime(2013, 02, 15), "circle");
background.AddDate(new DateTime(2013, 03, 01), "circle");
background.AddDate(new DateTime(2013, 03, 02), "circle");
background.AddDate(new DateTime(2013, 02, 10), "cross"); 

If you want you can set an option to mark the weekends. 

background.grayoutweekends = "gray";     

Assign the output from the class as the background for the Calendar. Create a DisplayDateChanged eventhandler to handle the update of the background when changing dates in the Calendar.  

Kalender.Background = background.GetBackground();
// Update background when changing the shown month
Kalender.DisplayDateChanged += KalenderOnDisplayDateChanged;

private void KalenderOnDisplayDateChanged(object sender, CalendarDateChangedEventArgs calendarDateChangedEventArgs)
{
  Kalender.Background = background.GetBackground();
}

The real "magic" takes place in the  CalenderBackground class.

First I must calculate the first shown date (January 28. in the screenshot above). The code handles Monday and Sunday as first day of week. As I know on which weekday the first day of the month is, I am able to calculate which days is the first shown. 

DateTime displaydate = _calendar.DisplayDate;
var firstdayofmonth = new DateTime(displaydate.Year, displaydate.Month, 1);
var dayofweek = (int) firstdayofmonth.DayOfWeek;
if (dayofweek == 0) dayofweek = 7; // set sunday to day 7.
if (dayofweek == (int)_calendar.FirstDayOfWeek) dayofweek = 8; // show a whole week ahead
if (_calendar.FirstDayOfWeek == DayOfWeek.Sunday) dayofweek += 1;
DateTime firstdate = firstdayofmonth.AddDays(-((Double) dayofweek) + 1);  

I create a default background with shading behind the month/year. 

var rtBitmap = new RenderTargetBitmap( 178 /* PixelWidth */, 160 /* PixelHeight */, 
        96 /* DpiX */, 96 /* DpiY */, PixelFormats.Default);
var drawVisual = new DrawingVisual();
using (DrawingContext dc = drawVisual.RenderOpen())
{
  var backGroundBrush = new LinearGradientBrush();
  backGroundBrush.StartPoint = new Point(0.5, 0);
  backGroundBrush.EndPoint = new Point(0.5, 1);
  backGroundBrush.GradientStops.Add(new GradientStop((Color) ColorConverter.ConvertFromString  ("#FFE4EAF0"), 0.0));
  backGroundBrush.GradientStops.Add(new GradientStop((Color) ColorConverter.ConvertFromString ("#FFECF0F4"), 0.16));
  backGroundBrush.GradientStops.Add(new GradientStop((Color) ColorConverter.ConvertFromString  ("#FFFCFCFD"), 0.16));
  backGroundBrush.GradientStops.Add(new GradientStop((Color) ColorConverter.ConvertFromString  ("#FFFFFFFF"), 1));
  dc.DrawRectangle(backGroundBrush, null, new Rect(0, 0, rtBitmap.Width, rtBitmap.Height));
}
rtBitmap.Render(drawVisual);

The final and most important part iterates through the 7 columns and 6 rows of the Calendar. As I know the first shown date, I  can traverse the dates and when I have a match  in the "datelist" I can add the overlay at the calculated position. This is done drawing a rectangle with the content from the overlay.

using (DrawingContext dc = drawVisual.RenderOpen())
{
    for (int y = 0; y < 6; y++)
        for (int x = 0; x < 7; x++)
        {
           int xpos = x*21 + 17;
           int ypos = y*16 + 50;

           foreach (string overlayid in datelist.Where(c => c.date == firstdate).Select(c => c.overlay))
           {
               if (overlayid != null)
               {
                   overlay overlay = overlays.Where(c => c.id == overlayid).FirstOrDefault();
                   dc.DrawRectangle(overlay.Brush, null /* no pen */,
                                         new Rect(xpos, ypos, overlay.BitMap.Width, overlay.BitMap.Height));
               }
           }
           firstdate = firstdate.AddDays(1);
       }
}   
rtBitmap.Render(drawVisual);
var brush = new ImageBrush(rtBitmap); // create a brush using the BitMap
return brush;

Voila. An ease way to make a custom calendar. 

Future improvements

One nice feature to add would be a mouseover tooltip with an explanation on why the date is highlighted.

History

  • 1.0: Initial POC.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here