Click here to Skip to main content
15,907,233 members
Articles / Desktop Programming / MFC

Calendar DayView Control

Rate me:
Please Sign up or sign in to vote.
4.86/5 (152 votes)
20 Jun 2006CPOL3 min read 1.7M   24.9K   375   510
A calendar DayView control.

Introduction

This article describes creating a day view control for visualizing schedule functions in required applications. I have cloned the Outlook day view appearance for similar use. Here is a screenshot:

Office 12 Theme:

Sample Image

Office XP Theme:

Sample Image

Background

Before writing this control, I was in need of a day view control that just looks like that in Outlook. I have found some commercial toolkits but none of them meets my requirements. Some of those want that all appointments be given before showing the control, some of those are not open source, etc. So I wrote this control in a "hurry development", and I think other people can use it. Pay back time for using the CodeProject :)

What we have?

  • You can create your appointment class to hold special information (other than start, end dates and title).
  • You don't need to read all appointments from the DB or something like that.
  • You can specify how much days will be shown.
  • You can colorize appointments to show different views.
  • In-place editing.
  • Drag drop operations.
  • No Win32 API.
  • Theme based rendering.

By the way, it's compiled under the final release of .NET 2.0. If you don't have it, you need to recompile the project.

Using the code

This control uses a class named "Appointment" to visualize the view. The DayView control doesn't pay attention to saving appointments to the DB or fetching them. So, you need to write your own DB logic and answer the events.

The control implements these events to interact with the hosting application:

  • dayView1.NewAppointment
  • dayView1.ResolveAppointments
  • dayView1.SelectionChanged

The sample application uses a list collection as the container for appointments. You may use a cached DB source too.

NewAppointment event

This is raised when the user wants to create an appointment. Event arguments contain start date, end date, and title values of the new appointment. You can create your appointment class that inherits from the DayView.Appointment base class.

The sample application just creates a new appointment, and adds it to the list collection.

C#
void dayView1_NewAppointment(object sender, NewAppointmentEventArgs args)
{
    Appointment m_Appointment = new Appointment();

    m_Appointment.StartDate = args.StartDate;
    m_Appointment.EndDate = args.EndDate;
    m_Appointment.Title = args.Title;

    m_Appointments.Add(m_Appointment);
}

ResolveAppointments event

This event is raised when the DayView control needs to show an appointment on a date. Event arguments contain the start date and end date of the required range of dates.

The sample application scans the list collection for a specified date range. You can fetch them from your own DB too.

C#
private void dayView1_ResolveAppointments(object sender, 
                      ResolveAppointmentsEventArgs args)
{
    List<Appointment> m_Apps = new List<Appointment>();

    foreach (Appointment m_App in m_Appointments)
        if ((m_App.StartDate >= args.StartDate) && 
            (m_App.StartDate <= args.EndDate))
            m_Apps.Add(m_App);

    args.Appointments = m_Apps;
}

Selection Changed event

This event is raised when the user selects an appointment.

C#
private void dayView1_SelectionChanged(object sender, EventArgs e)
{
    label3.Text = dayView1.SelectionStart.ToString() + 
                  ":" + dayView1.SelectionEnd.ToString();
}

The sample application shows the start date and the end date of the selected appointment in a label.

Points of interest

When I wrote this control, the hard part was sorting the appointments on screen without crossing over. The control internally uses an "AppointmentView" class to hold the state of appointments on screen. The rest of the code was drawing and isolating from the external application.

You may see some remarked codes about all-day events, but currently all-day events is not complete. When I finish it, I'll update this article. (Thanks to Claus Espersen for implementing the Office 12 theme and bug fixes.)

History

  • 14.07.2006
    • Bug fixes.
    • Start hour and start minute properties implemented.
    • Theme based rendering implemented.
    • AllowNew property implemented.
    • Mouse drag drop bugs fixed.
    • Internal changes for all day events.
    • Zoom feature implemented.
  • 11.09.2005
    • Initial release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


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

Comments and Discussions

 
NewsCodePlex Pin
Ertan Tike6-Mar-07 9:49
Ertan Tike6-Mar-07 9:49 
GeneralSave Appointment Module -- message to Batzo. Pin
11202002yme*5-Mar-07 16:33
11202002yme*5-Mar-07 16:33 
GeneralRe: Save Appointment Module -- message to Batzo. Pin
batzo5-Mar-07 17:17
batzo5-Mar-07 17:17 
GeneralRe: Save Appointment Module -- message to Batzo. Pin
0vermind15-Jan-08 15:00
0vermind15-Jan-08 15:00 
Generalsave appointments Pin
oCcSking27-Feb-07 8:31
oCcSking27-Feb-07 8:31 
GeneralRe: save appointments [modified] Pin
batzo28-Feb-07 17:18
batzo28-Feb-07 17:18 
GeneralRe: save appointments Pin
11202002yme*1-Mar-07 14:48
11202002yme*1-Mar-07 14:48 
GeneralRe: save appointments [modified] Pin
batzo4-Mar-07 22:26
batzo4-Mar-07 22:26 
Hello,

Sorry for replying too late. At first, add new property named scheduleRow and new function named refresh into the Appoinment class and change the get/set methods little. Sample is here:

public class Appointment
{
private DataRow scheduleRow=null; //New property

public Appointment()
{
initialize();
}

public Appointment(DataRow dr)
{
scheduleRow = dr;
refresh();
}


private void initialize()
{
Color = Color.White;
BorderColor = Color.Blue;
Title = "New Appointment";
}

//new function
public void refresh()
{
if (scheduleRow == null)
return;

System.Drawing.ColorConverter colorConverter = new ColorConverter();

color = (Color)colorConverter.ConvertFromString(scheduleRow["Color"].ToString());
borderColor = (Color)colorConverter.ConvertFromString(scheduleRow["BorderColor"].ToString());
title = scheduleRow["Title"].ToString();
startDate = (DateTime)scheduleRow["StartDate"];
endDate = (DateTime)scheduleRow["EndDate"];

locked = System.Convert.ToBoolean(scheduleRow["Locked"].ToString());
}

private string group;

public string Group
{
get { return group; }
set { group = value; }
}

private DateTime startDate;

public DateTime StartDate
{
get
{
return startDate;
}
set
{
startDate = value;
OnStartDateChanged();
}
}

protected virtual void OnStartDateChanged()
{
if (scheduleRow != null)
{
scheduleRow["StartDate"] = StartDate;
}
}

private DateTime endDate;

public DateTime EndDate
{
get
{
return endDate;
}
set
{
endDate = value;
OnEndDateChanged();
}
}

protected virtual void OnEndDateChanged()
{
//System.Diagnostics.Debug.WriteLine("OnEndDateChanged: "+this.EndDate.ToString("yyyy-MM-dd HH:mm:ss"));
if (scheduleRow != null)
{
scheduleRow["EndDate"] = EndDate;
}
}

private bool locked = false;

[System.ComponentModel.DefaultValue(false)]
public bool Locked
{
get { return locked; }
set
{
locked = value;
OnLockedChanged();
}
}

protected virtual void OnLockedChanged()
{
if (scheduleRow != null)
{
scheduleRow["Locked"] = Locked.ToString();
}
}

private Color color = Color.White;

public Color Color
{
get
{
return color;
}
set
{
color = value;
OnColorChanged();
}
}

private void OnColorChanged()
{
if (scheduleRow != null)
{
System.Drawing.ColorConverter colorConverter = new ColorConverter();
scheduleRow["Color"] = colorConverter.ConvertToString(Color);
}
}

private Color textColor = Color.Black;

public Color TextColor
{
get { return textColor; }
set { textColor = value; }
}

private Color borderColor = Color.Blue;

public Color BorderColor
{
get
{
return borderColor;
}
set
{
borderColor = value;
OnBorderColorChanged();
}
}

private void OnBorderColorChanged()
{
if (scheduleRow != null)
{
System.Drawing.ColorConverter colorConverter = new ColorConverter();
scheduleRow["BorderColor"] = colorConverter.ConvertToString(BorderColor);
}
}


private string title = "";

[System.ComponentModel.DefaultValue("")]
public string Title
{
get
{
return title;
}
set
{
title = value;
OnTitleChanged();
}
}
protected virtual void OnTitleChanged()
{
//System.Diagnostics.Debug.WriteLine("OnTitleChanged: " + this.Title);

if (scheduleRow != null)
{
scheduleRow["Title"] = Title;
}
}

internal int conflictCount;
internal bool allDayEvent;
}

Then, you should change DayView Class as following code. (In DrawDay() function, call refresh() function of Appointment class)

private void DrawDay(PaintEventArgs e, Rectangle rect, DateTime time)
{
//renderer.DrawDayBackground(e.Graphics, rect);

Rectangle workingHoursRectangle = GetHourRangeRectangle(workStart, workEnd, rect);

if (workingHoursRectangle.Y < this.HeaderHeight)
workingHoursRectangle.Y = this.HeaderHeight;

if (!((time.DayOfWeek == DayOfWeek.Saturday) || (time.DayOfWeek == DayOfWeek.Sunday))) //weekends off -> no working hours
renderer.DrawHourRange(e.Graphics, workingHoursRectangle, false, false);

if ((selection == SelectionType.DateRange) && (time.Day == selectionStart.Day))
{
Rectangle selectionRectangle = GetHourRangeRectangle(selectionStart, selectionEnd, rect);

renderer.DrawHourRange(e.Graphics, selectionRectangle, false, true);
}

e.Graphics.SetClip(rect);

for (int hour = 0; hour < 24 * 2; hour++)
{
int y = rect.Top + (hour * halfHourHeight) - scrollbar.Value;

using (Pen pen = new Pen(((hour % 2) == 0 ? renderer.HourSeperatorColor : renderer.HalfHourSeperatorColor)))
e.Graphics.DrawLine(pen, rect.Left, y, rect.Right, y);

if (y > rect.Bottom)
break;
}

renderer.DrawDayGripper(e.Graphics, rect, appointmentGripWidth);

e.Graphics.ResetClip();

/* BEGIN */
AppointmentList appointments = (AppointmentList)cachedAppointments[time.Day];

if (appointments != null)
{
List<string> groups = new List<string>();

foreach (Appointment app in appointments)
{
if (!groups.Contains(app.Group))
groups.Add(app.Group);

app.refresh();
}

Rectangle rect2 = rect;
rect2.Width = rect2.Width / groups.Count;

groups.Sort();

foreach (string group in groups)
{
DrawAppointments(e, rect2, time, group);

rect2.X += rect2.Width;
}
}

/* END */
}


Next, Add new property into the container of DayView control. For example: Form1 class.

partial class Form1
{
private System.Data.DataSet scheduleDataSet;
......
.....

}

Now, you should get data from Database or xml file. I wrote some functions you need.

private void CreateTable()
{
this.scheduleDataSet = new System.Data.DataSet();

System.Data.DataTable scheduleDataTable = new System.Data.DataTable();
System.Data.DataColumn TitleColumn = new System.Data.DataColumn();
System.Data.DataColumn StartDateColumn = new System.Data.DataColumn();
System.Data.DataColumn EndDateColumn = new System.Data.DataColumn();
System.Data.DataColumn BorderColorColumn = new System.Data.DataColumn();
System.Data.DataColumn ColorColumn = new System.Data.DataColumn();
System.Data.DataColumn LockedColumn = new System.Data.DataColumn();

//
// scheduleDataSet
//
this.scheduleDataSet.DataSetName = "scheduleDataSet";
this.scheduleDataSet.Tables.AddRange(new System.Data.DataTable[] {
scheduleDataTable});
//
// scheduleDataTable
//
scheduleDataTable.Columns.AddRange(new System.Data.DataColumn[] {
TitleColumn,
StartDateColumn,
EndDateColumn,
BorderColorColumn,
ColorColumn,
LockedColumn});
scheduleDataTable.TableName = "schedule";
//
// TitleColumn
//
TitleColumn.ColumnName = "Title";
//
// StartDateColumn
//
StartDateColumn.ColumnName = "StartDate";
StartDateColumn.DataType = typeof(System.DateTime);
//
// EndDateColumn
//
EndDateColumn.ColumnName = "EndDate";
EndDateColumn.DataType = typeof(System.DateTime);
//
// BorderColorColumn
//
BorderColorColumn.ColumnName = "BorderColor";
//
// ColorColumn
//
ColorColumn.ColumnName = "Color";
//
// LockedColumn
//
LockedColumn.ColumnName = "Locked";
}

private void createData()
{
CreateTable();
System.Data.DataRow dr = scheduleDataSet.Tables["schedule"].NewRow();

DateTime dateTime = DateTime.Now;

dr["Title"] = "Appointment1 (Purevsuren)";
dr["StartDate"] = dateTime;
dr["EndDate"] = dateTime.AddMinutes(15);
dr["Color"] = "White";
dr["BorderColor"] = "Blue";
dr["Locked"] = "false";
scheduleDataSet.Tables["schedule"].Rows.Add(dr);

dr = scheduleDataSet.Tables["schedule"].NewRow();
dateTime = dateTime.AddMinutes(30);

dr["Title"] = "Appointment2 (Zorigt)";
dr["StartDate"] = dateTime;
dr["EndDate"] = dateTime.AddMinutes(30);
dr["Color"] = "White";
dr["BorderColor"] = "Blue";
dr["Locked"] = "false";
scheduleDataSet.Tables["schedule"].Rows.Add(dr);

dr = scheduleDataSet.Tables["schedule"].NewRow();
dateTime = dateTime.AddHours(3);

dr["Title"] = "Appointment3 (Batzorigt)";
dr["StartDate"] = dateTime;
dr["EndDate"] = dateTime.AddHours(1);
dr["Color"] = "Red";
dr["BorderColor"] = "Blue";
dr["Locked"] = "true";
scheduleDataSet.Tables["schedule"].Rows.Add(dr);

dr = scheduleDataSet.Tables["schedule"].NewRow();
dateTime = dateTime.AddHours(3);

dr["Title"] = "Flying to Mongolia";
dr["StartDate"] = dateTime;
dr["EndDate"] = dateTime.AddHours(1);
dr["Color"] = "Red";
dr["BorderColor"] = "Blue";
dr["Locked"] = "true";
scheduleDataSet.Tables["schedule"].Rows.Add(dr);

writeToXml("schedule.xml");
}


private void readFromXml(String fileName)
{
//add your own code here
scheduleDataSet.ReadXml(fileName);
}

private void writeToXml(String fileName)
{
//add your own code here
scheduleDataSet.WriteXml(fileName);
}

In constructor function I added some code to display data that read from xml file.
PS: I inserted the DataGridView1 and Save button into the form.

private void initDayView()
{
m_Appointments = new List<appointment>();

for (int i = 0; i < scheduleDataSet.Tables["schedule"].Rows.Count; i++)
{
Appointment app = new Appointment(scheduleDataSet.Tables["schedule"].Rows[i]);
m_Appointments.Add(app);
}
}

public Form1()
{
InitializeComponent();

//if there is no xml file, create xml file
createData(); //else comment out this line

//read from xml file
//readFromXml("schedule.xml"); //if there is xml file

this.dataGridView1.DataSource = scheduleDataSet;
this.dataGridView1.DataMember = "schedule";

initDayView();

dayView1.StartDate = DateTime.Now;
dayView1.NewAppointment += new NewAppointmentEventHandler(dayView1_NewAppointment);
dayView1.SelectionChanged += new EventHandler(dayView1_SelectionChanged);
dayView1.ResolveAppointments += new Calendar.ResolveAppointmentsEventHandler(this.dayView1_ResolveAppointments);

dayView1.MouseMove += new System.Windows.Forms.MouseEventHandler(this.dayView1_MouseMove);

comboBox1.SelectedIndex = 1;
}

To save data into xml file on click of Save button:

private void btnSave_Click(object sender, EventArgs e)
{
scheduleDataSet.WriteXml("schedule.xml");
}

If you wish to update DayView control on changing of datagrid, you should add CellEndEdit event.
So, you can use following code:

private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
dayView1.Invalidate();
}

Good Luck!


-- modified at 4:58 Monday 5th March, 2007
GeneralRe: save appointments Pin
11202002yme*5-Mar-07 12:45
11202002yme*5-Mar-07 12:45 
GeneralRe: save appointments -- Pin
11202002yme*5-Mar-07 14:09
11202002yme*5-Mar-07 14:09 
GeneralRe: save appointments -- Pin
batzo5-Mar-07 15:09
batzo5-Mar-07 15:09 
GeneralRe: save appointments Pin
Ho Quyen18-May-10 1:45
Ho Quyen18-May-10 1:45 
QuestionLatest version? Pin
Mattias Olgerfelt27-Feb-07 5:38
Mattias Olgerfelt27-Feb-07 5:38 
AnswerRe: Latest version? Pin
Gimlei27-Feb-07 12:37
Gimlei27-Feb-07 12:37 
GeneralRe: Latest version? Pin
Mattias Olgerfelt27-Feb-07 12:50
Mattias Olgerfelt27-Feb-07 12:50 
GeneralRe: Latest version? Pin
AndyYerg28-Feb-07 10:12
AndyYerg28-Feb-07 10:12 
GeneralRe: Latest version? Pin
11202002yme*4-Mar-07 8:06
11202002yme*4-Mar-07 8:06 
GeneralAdd an Icon to an Appointment [modified] Pin
pracsec22-Feb-07 5:01
pracsec22-Feb-07 5:01 
GeneralRe: Add an Icon to an Appointment Pin
Gimlei22-Feb-07 6:57
Gimlei22-Feb-07 6:57 
GeneralFix Non-Working Hours Bottom Shading Pin
Curtis Schlak.20-Feb-07 16:28
Curtis Schlak.20-Feb-07 16:28 
GeneralException from Scrollbar Pin
flow196519-Feb-07 10:11
flow196519-Feb-07 10:11 
GeneralDelete appointment Pin
ap8216-Feb-07 2:48
ap8216-Feb-07 2:48 
AnswerRe: Delete appointment Pin
benoit_lamare@yahoo.fr16-Feb-07 2:57
benoit_lamare@yahoo.fr16-Feb-07 2:57 
GeneralRe: Delete appointment Pin
ap8216-Feb-07 4:36
ap8216-Feb-07 4:36 
GeneralDelete appointment Pin
ap8216-Feb-07 2:40
ap8216-Feb-07 2:40 

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.