Click here to Skip to main content
15,867,939 members
Articles / Desktop Programming / Windows Forms

Day, Week, and Month Calendar Controls

Rate me:
Please Sign up or sign in to vote.
4.81/5 (20 votes)
9 May 2011LGPL35 min read 125K   12.1K   93   31
Windows forms calendar controls for showing a series of appointments similar to Outlook
screen1.png - Click to enlarge image

Introduction

This control suite is a prototype set of calendar controls for a Windows Forms .NET application. They were created as a proof-of-concept that an Outlook style calendar could be integrated into a large Windows Forms application without the overhead of using a third party library.
Three controls are included - DayScheduleControl, WeekScheduleControl and MonthScheduleControl. The controls render a series of appointments in different layouts. I am unlikely to have time to enhance it in the near future, so I will release them in the hope that they will be useful to others.

DayScheduleControl is the closest in appearance to Outlook. It supports one-day and five-day views.

WeekScheduleControl displays a 7-day week. Unlike DayScheduleControl, it does not show hour slots.

MonthScheduleControl displays a whole month of appointments.

Using the Code

The class Form1 in the included test project contains sample usage of the controls. First, it has code to create a list of random appointments, and set the controls to show the current date. The CreateRandomAppointments method loads up a couple of months worth of random dummy data.

C#
DateTime weekstart = DateTime.Now;
AppointmentList appts = CreateRandomAppointments(weekstart);
weekView1.Date = weekstart;
weekView1.Appointments = appts;
monthView1.Date = weekstart;
monthView1.Appointments = appts;
dayView1.Date = weekstart;
dayView1.Appointments = appts;
dayView2.Date = weekstart;
dayView2.Appointments = appts;

Rather than CreateRandomAppointments, in a normal app, you would build up an AppointmentList and add Appointment objects something like the following, using your own data as a source.

C#
var appts = new AppointmentList(); 
ExtendedAppointment app = new ExtendedAppointment();
app.ColorBlockBrush = Brushes.Red;
app.Subject = "A sample appointment";
app.DateStart = DateTime.Now.AddMinutes(30);
app.DateEnd = DateTime.Now.AddMinutes(60);  
appts.Add(app); 

Second, the create, move and edit events are wired up to demo methods.

C#
weekView1.AppointmentCreate += calendar_AppointmentAdd;
monthView1.AppointmentCreate += calendar_AppointmentAdd;
dayView1.AppointmentCreate += calendar_AppointmentAdd;
dayView2.AppointmentCreate += calendar_AppointmentAdd;

weekView1.AppointmentMove += calendar_AppointmentMove;
monthView1.AppointmentMove += calendar_AppointmentMove;
dayView1.AppointmentMove += calendar_AppointmentMove;
dayView2.AppointmentMove += calendar_AppointmentMove;

weekView1.AppointmentEdit += calendar_AppointmentEdit;
monthView1.AppointmentEdit += calendar_AppointmentEdit;
dayView1.AppointmentEdit += calendar_AppointmentEdit;
dayView2.AppointmentEdit += calendar_AppointmentEdit;

When the user interacts with a calendar appointment, the event is fired so we pop up a custom dialog to deal with it. NewAppointment in this case is a dialog for entering the title, start and end dates of an appointment. The MoveAppointment and EditAppointment dialogs are pretty similar in what they do.

C#
private void calendar_AppointmentAdd(object sender, AppointmentCreateEventArgs e)
{
    //show a dialog to add an appointment
    using (NewAppointment dialog = new NewAppointment())
    {
        if (e.Date != null)
        {
            dialog.AppointmentDateStart = e.Date.Value;
            dialog.AppointmentDateEnd = e.Date.Value.AddMinutes(15);
        }
        DialogResult result = dialog.ShowDialog();
        if (result == DialogResult.OK)
        {
            //if the user clicked 'save', save the new appointment 
            string title = dialog.AppointmentTitle;
            DateTime dateStart = dialog.AppointmentDateStart;
            DateTime dateEnd = dialog.AppointmentDateEnd;
            e.Control.Appointments.Add(new ExtendedAppointment() { 
            Subject = title, DateStart = dateStart, DateEnd = dateEnd });

            //have to tell the controls to refresh appointment display
            weekView1.RefreshAppointments();
            monthView1.RefreshAppointments();
            dayView1.RefreshAppointments();
            dayView2.RefreshAppointments();

            //get the controls to repaint 
            weekView1.Invalidate();
            monthView1.Invalidate();
            dayView1.Invalidate();
            dayView2.Invalidate();
        }
    }
}

The ExtendedAppointment class is for adding additional properties to appointments. Because this is located in the test application along with the dialogs for create/move/edit, you can add more fields to the dialogs without having to go into the guts of the SheduleControls assembly.

How It Works

The requirements for these controls were:

  • Support Windows 7 styles
  • Support keyboard access
  • Be accessible to the disabled
  • Don't use much memory

As much of the code for the three controls is similar, they all inherit from BaseScheduleControl. This base control handles drag and drop, the hidden DataGridView of appointments, and mouse click events. Much of the code is for linking keyboard operations in the hidden DataGridView to the UI display (e.g., a selected appointment), and vice versa with mouse operations.

C#
namespace Syd.ScheduleControls
{
    /// <summary>
    /// The BaseScheduleControl defines properties common to 
    /// the three schedule controls. 
    /// </summary>
    public  partial class BaseScheduleControl : Control, 
    System.ComponentModel.ISupportInitialize
    { 

BaseScheduleControl has a DataGridView control in order to save time setting up the keyboard access and the accessibility features. The grid object is of type HiddenGrid, which extends DataGridView but overrides the OnPaint and OnPaintBackground events so that the control is not visible. The grid is exposed to child controls as the AppointmentGrid property.

C#
internal class HiddenGrid : DataGridView
{
    protected override void OnPaintBackground(PaintEventArgs pevent)
    {
        //Don't paint anything
    }
    protected override void OnPaint(PaintEventArgs e)
    {
        //Don't paint anything
    }
}

The VisualStyleRenderer is used to pick up the current Windows theme and display with the colours from that. If visual styles are disabled, the regular windows colours are used. All rendering operations are wrapped up in the RendererCache class, which paints everything including the days, their titles to the appointments. RendererCache holds a series of IRenderer objects which are objects that can draw boxes with text or borders (everything painted on the controls is basically a box).

C#
internal class RendererCache
{
    //...
    private readonly IRenderer bigHeaderRenderWrap = null;
    private readonly IRenderer headerRenderWrap = null;
    private readonly IRenderer headerRenderSelWrap = null;
    private readonly IRenderer appointmentRenderWrap = null;
    private readonly IRenderer appointmentRenderSelWrap = null;
    private readonly IRenderer controlRenderWrap = null;
    private readonly IRenderer bodyRenderWrap = null;
    private readonly IRenderer bodyLightRenderWrap = null;

There are different implementations of IRenderer depending on whether visual styles are enabled. If the code was running with visual styles turned on, the IRenderer for a header would be initialised as follows (VisualStyleWrapper implements IRenderer):

C#
       private readonly VisualStyleRenderer headerRender = null;
       private readonly VisualStyleElement headerElement =
       VisualStyleElement.ExplorerBar.NormalGroupHead.Normal;
//...
       headerRender = new VisualStyleRenderer(headerElement);
       headerRenderWrap = new VisualStyleWrapper
       (headerRender, SystemPens.ControlDarkDark);

However if visual styles were turned off, a simpler implementation of IRenderer called NonVisualStyleWrapper would be used. The NonVisualStyleWrapper just uses system colour brushes and maybe a bit of gradient fill to render days and appointments on the control.

C#
headerRenderWrap = new NonVisualStyleWrapper(SystemColors.ControlText, 
        SystemBrushes.ControlText, 
        SystemColors.Control, 
        SystemBrushes.Control, 
        SystemColors.ControlLightLight, 
        SystemBrushes.ControlLightLight, 
        SystemPens.ControlText);
       ((NonVisualStyleWrapper)headerRenderWrap).NoGradientBlend=true;

RendererCache is a singleton and is used by all three controls in the OnPaint event. The following is a sample of how it is used to draw a header box for a day.

C#
RendererCache.Current.Header.DrawBox
(e.Graphics, Font, day.TitleBounds, day.FormattedName);

Speed was the primary consideration in the design of the app, so there isn't much usage of events. Likewise, day and appointment items are not controls themselves - that would be slow to render.

Instead of using lots of controls, the lists of days/hours and appointments have their screen real estate calculated in one hit, and they are all painted by the parent cont. Days are wrapped up in DayRegion objects, which contain the name of the day and its bounds. Appointments are wrapped up in AppointmentRegion objects, which contain the Appointment and its bounds. The IRegion interface defines the common property to all these regions - the bounds.

C#
internal interface IRegion
{
    Rectangle Bounds { get; set; }
}

The size and shape of the day and hour regions is figured out by the CalculateTimeSlotBounds method. This method is called from OnPaint only when the property BoundsValidTimeSlot is set to false (this property is set to false in cases such as when the control has been resized or the date shown has changed). It is overridden in all three controls, as all three have different layouts of days.

C#
protected override void CalculateTimeSlotBounds(Graphics g)

The size and shape of the appointments is figured out by the CalculateAppointmentBounds method. This ensures that all the appointments fit into their owning day or hour, if possible, and handles any other calculations such as overlaps. This method is called from OnPaint only when the property BoundsValidAppointment is set to false (this property is set to false in cases such as when the control has been resized or the appointment list has changed).

C#
protected override void CalculateAppointmentBounds(Graphics g)

The controls don't include default dialogs for creating/moving/editing appointments, but the demo project includes sample dialogs wired up to those three events.

Future Enhancements

Missing features in the current version:

  • Lots more stuff on the controls should be configurable in properties
  • Support for full-day or multiple-day appointments
  • Tooltips when you hover over an appointment (to display the full subject)
  • XP, high contrast mode, high DPI support
  • DayScheduleControl doesn't support weekends, out-of-hours appointments, or scrolling
  • Controls haven't been tested under a screen reader, the DataGridView may not have the properties set for it to be readable
  • The DayScheduleControl handles overlapping appointments, but the maths it uses isn't very good
  • Time slot navigation/selection with keyboard or mouse
  • Better highlight of the current day

History

  • Initial version

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Australia Australia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionError when adding 5 or 10 minute appointments Pin
TrevorWJ18-Jun-20 11:50
TrevorWJ18-Jun-20 11:50 
QuestionAdd resource column to each day. Pin
TrevorWJ18-Jun-20 11:49
TrevorWJ18-Jun-20 11:49 
QuestionIs it possible to show a multi day event with a bar like iCal Pin
Murulimadhav121-Feb-16 6:23
Murulimadhav121-Feb-16 6:23 
QuestionAdding items - No entries appear - Drawing error - red box Pin
Member 114515695-Aug-15 4:09
Member 114515695-Aug-15 4:09 
QuestionExtended appointment for 2 days or more Pin
jose pablo m27-Feb-15 21:27
jose pablo m27-Feb-15 21:27 
Questionappointment backcolor Pin
oxo115-Dec-14 11:21
oxo115-Dec-14 11:21 
Bugwhole day appointment Pin
herman60216-Sep-14 20:53
herman60216-Sep-14 20:53 
QuestionNice post Pin
Tridip Bhattacharjee12-Dec-13 21:57
professionalTridip Bhattacharjee12-Dec-13 21:57 
QuestionHow to delete appointment? Pin
Josip849-Nov-13 20:46
Josip849-Nov-13 20:46 
QuestionCalendar Visualization not seen Pin
Member 102004999-Aug-13 15:47
Member 102004999-Aug-13 15:47 
I tried running the sample Form1.cs and could see the visualization. How do I render the calendar grids etc on the window? What class do I need to call?
QuestionGreat Control Pin
YAllTaken16-Jun-13 22:52
YAllTaken16-Jun-13 22:52 
AnswerRe: Great Control Pin
jibola17-Jul-13 4:25
jibola17-Jul-13 4:25 
GeneralRe: Great Control Pin
YAllTaken18-Jul-13 23:27
YAllTaken18-Jul-13 23:27 
QuestionSelectedAppointment Fix Pin
cjb11011-Feb-13 23:19
cjb11011-Feb-13 23:19 
Generalopening the project Pin
vjkmr575718-Dec-12 20:08
vjkmr575718-Dec-12 20:08 
GeneralRe: opening the project Pin
YAllTaken19-Jun-13 5:36
YAllTaken19-Jun-13 5:36 
GeneralMy vote of 3 Pin
shareque27-Jun-12 20:51
shareque27-Jun-12 20:51 
Questionselected appointment Pin
hozone3-Apr-12 2:04
hozone3-Apr-12 2:04 
QuestionAppointmentRegion Bounds Pin
cjb11015-Feb-12 2:27
cjb11015-Feb-12 2:27 
GeneralMy vote of 5 Pin
cjb11015-Feb-12 2:22
cjb11015-Feb-12 2:22 
QuestionWhy am I getting index out of bounds errors Pin
Jim Wilkie8-Feb-12 10:07
Jim Wilkie8-Feb-12 10:07 
GeneralMy vote of 5 Pin
enxx19847-Feb-12 20:35
enxx19847-Feb-12 20:35 
QuestionHow to use this control with DB Pin
MostafaHamdy29-Jan-12 3:38
MostafaHamdy29-Jan-12 3:38 
AnswerRe: How to use this control with DB Pin
chumairr18-Jun-13 20:15
chumairr18-Jun-13 20:15 
GeneralRe: How to use this control with DB Pin
jibola17-Jul-13 4:29
jibola17-Jul-13 4:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.