Bug in DateTimePicker.cs

  • Thread starter Thread starter Robert Lalouche
  • Start date Start date
R

Robert Lalouche

I appreciate having the managed DTP control, including source code. I found
a bug to report. If you select day 31 in a 31 day month, then change the
month to a month which has less than 31 days, the code crashes - apparently
try to leave the days at 31 in a month that doesn't have 31 days. Please
help get this inoformation to the person who can fix it - thanks.
 
Change the following code in DateTimePicker.cs:

private void OnMonthMenuClick(System.Object sender, System.EventArgs e)
{
// determine what menu item was clicked
MenuItem item = sender as MenuItem;
if (item != null)
{
// update the current date selection
DateTime newDate = DateTime.Parse(
string.Format("{0}, {1} {2}",
item.Text, m_curSel.Day, m_curSel.Year));
UpdateCurSel(newDate);
}
}

in

private void OnMonthMenuClick(System.Object sender, System.EventArgs e)
{
// determine what menu item was clicked
MenuItem item = sender as MenuItem;
if (item != null)
{
// update the current date selection
DateTime newDate = DateTime.Parse(
string.Format("{0}, {1} {2}",
item.Text, 1, m_curSel.Year));
UpdateCurSel(newDate);
}
}
The behaviour changes a little meaning that if you change the month using the drop down list then the selected day of the new month will be 1. In case you want to keep the day selected then simply keep the call with 1 instead of m_curSel.Day and then try to add to the new date m_curSel.Day-1. If the month changes then that month doesn't have m_curSel.Day days but less. You can also make a function that returns you the number of days in a month and see if the m_curSelDay is greater that what your function returned than add dayMonth-1 to the newly created date.
 
Using the original code that I downloaded from Microsoft I could not
reproduce this problem
Here is a link to the project (with Original DateTimePicker.msi and
DateTimePicker.cs) and
my language/culture supported Version.

http://www.mj10777.de/NETFramework/Compact/DateTimePicker/index.htm

Please check to see if you are using the same version.
If not : try this version.
Otherwise you could send me your .cs version and I will try it out here.
Are you recieving some exception Error?

Mark Johnson, Berlin Germany
(e-mail address removed)
 
You have the error. To reproduce it:
1.Select the year 2004 - month march
2. With the cursor pad move the selection to the day 31
3.From the dropdown menu for the month select february

It must crach because: m_curSel.Day is not a valid day for february and the parse function will raise an exception. DateTime newDate = DateTime.Parse(string.Format("{0}, {1} {2}",
item.Text, m_curSel.Day, m_curSel.Year));
 
here is the fix I had made for this bug... don't forget that this is also
the case of year when on Feb29

éric

private void OnMonthMenuClick(System.Object sender, System.EventArgs e)

{

// determine what menu item was clicked

MenuItem item = sender as MenuItem;

if (item != null)

{

try

{

// update the current date selection

DateTime newDate = DateTime.Parse(

string.Format("{0}, {1} {2}",

item.Text, m_curSel.Day, m_curSel.Year));

UpdateCurSel(newDate);

}

catch

{

int d = 30;

if (item.Text == "February")

{

// could check if 29 exists that year but why bother

d = 28;

}

// update the current date selection

DateTime newDate = DateTime.Parse(

string.Format("{0}, {1} {2}",

item.Text, d, m_curSel.Year));

UpdateCurSel(newDate);

}

}

}
 
same as MS but with Max date and Min Date as well as using yyyy-MM-dd and
fix the bug with picking months on 31st day

using System;

using System.Windows.Forms;

using System.Drawing;



namespace Controls

{

#region DateTimePickerFormat enumeration

/// <summary>

/// Specifies the date and time format the DateTimePicker control displays.

/// </summary>

internal enum DateTimePickerFormat

{

Long,

Short,

Custom

}

#endregion

#region DateTimePicker class

/// <summary>

/// Manged DateTimePicker control. User can select a day from popup
calendar.

/// </summary>

internal class DateTimePicker : Control

{

class Const

{

// size of the drop arrow on far right

internal static Size DropArrowSize = new Size(7,4);

}


// offscreen bitmap

Bitmap m_bmp;

Graphics m_graphics;

// gdi objects

SolidBrush m_brushFore;

SolidBrush m_brushDisabled;

SolidBrush m_brushFrame;

Pen m_penFrame;


// down arrow coordinates

Point[] m_arrowPoints = new Point[3];


// day picker, displays popup month calendar

DayPickerPopup m_dayPicker = new DayPickerPopup();


// date format, short or long

DateTimePickerFormat m_format = DateTimePickerFormat.Long;

// exposed events

internal event EventHandler ValueChanged;

internal event EventHandler CloseUp;

internal event EventHandler DropDown;



// properties



/// <summary>

/// Gets the maximum date value.

/// </summary>

///

private DateTime minDate = DateTime.MinValue;

private DateTime maxDate = DateTime.MaxValue;

internal DateTime MaxDateTime

{

get { return this.maxDate; }

set

{

if (value <= DateTime.MaxValue)

{

this.maxDate = value;

this.m_dayPicker.MaxDate = value;

}

else

{

this.maxDate = DateTime.MaxValue;

this.m_dayPicker.MaxDate = DateTime.MaxValue;

}

}

}

/// <summary>

/// Gets the minimum date value.

/// </summary>

internal DateTime MinDateTime

{

get { return this.minDate; }

set

{

if (value >= DateTime.MinValue)

{

this.minDate = value;

this.m_dayPicker.MinDate = value;

}

else

{

this.minDate = DateTime.MinValue;

this.m_dayPicker.MinDate = DateTime.MinValue;

}

}

}

/// <summary>

/// Gets or sets the format of the date displayed in the control.

/// </summary>


private string m_CustomFormat = "yyyy-MM-dd";

internal string CustomFormat

{

get { return m_CustomFormat; }

set

{

m_CustomFormat = value;

this.m_dayPicker.DateFormat = value;

Invalidate();

}

}

internal DateTimePickerFormat Format

{

get { return m_format; }

set

{

// update format and repaint

m_format = value;

Invalidate();

}

}

/// <summary>

/// Gets or sets the date value assigned to the control.

/// </summary>

internal DateTime Value

{

// setting the picker value raises the ValueChanged

// event which causes the control to repaint

get { return m_dayPicker.Value; }

set { m_dayPicker.Value = value; }

}

/// <summary>

/// Gets or sets the text associated with this control. Throws a

/// FormatException if the specified text is not a valid date.

/// </summary>

public override String Text

{

get

{

// return date as string in the correct format

switch (m_format)

{

case DateTimePickerFormat.Short :

return this.Value.ToShortDateString();

//break;

case DateTimePickerFormat.Long :

return this.Value.ToLongDateString();

//break;

case DateTimePickerFormat.Custom :

try

{

return this.Value.ToString(this.m_CustomFormat);

}

catch(System.Exception)

{

return this.Value.ToShortDateString();

}

//break;

default :

{

return this.Value.ToShortDateString();

}

}

}

set

{

// update the datetime value

this.Value = DateTime.Parse(value);

}

}



/// <summary>

/// Constructor. Initializes a new instance of the DateTimePicker class.

/// </summary>

internal DateTimePicker()

{

// hookup day picker events

m_dayPicker.CloseUp += new EventHandler(OnDayPickerCloseUp);

m_dayPicker.ValueChanged += new EventHandler(OnDayPickerValueChanged);

}



// drawing methods

protected override void OnEnabledChanged(EventArgs e)

{

this.Refresh();

base.OnEnabledChanged (e);

}

protected override void OnPaint(PaintEventArgs e)

{

// draw to memory bitmap

CreateMemoryBitmap();

CreateGdiObjects();

// init background

m_graphics.Clear(this.BackColor);

// label

Size size = m_graphics.MeasureString(this.Text, this.Font).ToSize();

m_graphics.DrawString(this.Text, this.Font,

this.Enabled ? m_brushFore : m_brushDisabled,

4, (this.Height - size.Height)/2);

// drop arrow

m_graphics.FillPolygon(m_brushFrame, m_arrowPoints);

// frame around control

m_graphics.DrawRectangle(m_penFrame, 0, 0, this.Width-1, this.Height-1);


// blit memory bitmap to screen

e.Graphics.DrawImage(m_bmp, 0, 0);

}

protected override void OnPaintBackground(PaintEventArgs e)

{

// don't pass to base since we paint everything, avoid flashing

}



// events



/// <summary>

/// Show or hide the day picker popup control. Determine the

/// best location to display the day picker.

/// </summary>

protected override void OnMouseDown(MouseEventArgs e)

{

base.OnMouseDown(e);


// add day picker control to the toplevel window

// this allows the control to display on top of

// tabs and other controls

if (m_dayPicker.Parent == null)

this.TopLevelControl.Controls.Add(m_dayPicker);

// intelligently calculate where the day picker should be displayed,

// try to display below the label control, display above the control

// if there is not enough room

Point pos = new Point(this.Left, this.Bottom+1);

// map points to top level window

Point parentPos = this.Parent.PointToScreen(this.Parent.Location);

Point topParentPos =
this.TopLevelControl.PointToScreen(this.Parent.Location);

pos.Offset(parentPos.X - topParentPos.X, parentPos.Y - topParentPos.Y);

// see if there is enough room to display day picker below label

if ((pos.Y + m_dayPicker.Size.Height + 320 -
ECASOFT.Controls.Static.SIP.VisibleDesktop.Bottom) >

this.TopLevelControl.ClientRectangle.Height)

{

// there is not enough room, try displaying above the label

pos.Y -= (this.Height + m_dayPicker.Size.Height + 2);

if (pos.Y < 0)

{

// there was not enough room, display at bottom of screen

pos.Y = (this.TopLevelControl.ClientRectangle.Height -

m_dayPicker.Size.Height - 320 +
ECASOFT.Controls.Static.SIP.VisibleDesktop.Bottom +

(26 * Convert.ToInt32(ECASOFT.Controls.Static.SIP.Enabled)));

}

}

try

{

if (((System.Windows.Forms.Form) this.TopLevelControl).FormBorderStyle ==

System.Windows.Forms.FormBorderStyle.None)

{

pos.Y = 0;

}

}

catch{}


// try displaying aligned with the label control

if ((pos.X + m_dayPicker.Size.Width) >
this.TopLevelControl.ClientRectangle.Width)

pos.X = (this.TopLevelControl.ClientRectangle.Width -
m_dayPicker.Size.Width);

// display or hide the day picker control

m_dayPicker.Display(

!m_dayPicker.Visible, pos.X, pos.Y,

this.BackColor, this.ForeColor);


// raise the DropDown or CloseUp event

if (m_dayPicker.Visible && this.DropDown != null)

this.DropDown(this, EventArgs.Empty);


if (!m_dayPicker.Visible && this.CloseUp != null)

this.CloseUp(this, EventArgs.Empty);

}


/// <summary>

/// CloseUp event from the day picker control

/// </summary>

private void OnDayPickerCloseUp(object sender, System.EventArgs e)

{

// pass to our container

if (this.CloseUp != null)

this.CloseUp(this, EventArgs.Empty);

}

/// <summary>

/// ValueChanged event from the day picker control

/// </summary>

private void OnDayPickerValueChanged(object sender, System.EventArgs e)

{

// repaint to display the new value

Invalidate();


// pass along to our container

if (this.ValueChanged != null)

this.ValueChanged(this, e);

}



// helper methods



/// <summary>

/// Create offsceeen bitmap. This bitmap is used for double-buffering

/// to prevent flashing.

/// </summary>

private void CreateMemoryBitmap()

{

if (m_bmp == null || m_bmp.Width != this.Width || m_bmp.Height !=
this.Height)

{

// memory bitmap

m_bmp = new Bitmap(this.Width, this.Height);

m_graphics = Graphics.FromImage(m_bmp);

// calculate down arrow points

m_arrowPoints[0].X = this.Width - Const.DropArrowSize.Width - 4;

m_arrowPoints[0].Y = (this.Height - Const.DropArrowSize.Height+1) / 2;

m_arrowPoints[1].X = m_arrowPoints[0].X + Const.DropArrowSize.Width;

m_arrowPoints[1].Y = m_arrowPoints[0].Y;

m_arrowPoints[2].X = m_arrowPoints[0].X + (Const.DropArrowSize.Width/2);

m_arrowPoints[2].Y = m_arrowPoints[0].Y + Const.DropArrowSize.Height;

}

}


/// <summary>

/// Create GDI objects required to paint the control.

/// </summary>

private void CreateGdiObjects()

{

// window frame brush

if (m_brushFrame == null)

m_brushFrame = new SolidBrush(SystemColors.WindowFrame);


// window frame pen

if (m_penFrame == null)

m_penFrame = new Pen(SystemColors.WindowFrame);

// fore color brush, the .Net CF does not support OnForeColorChanged,

// so we detect if the forecolor changed here

if (m_brushFore == null || m_brushFore.Color != this.ForeColor)

m_brushFore = new SolidBrush(this.ForeColor);


// disabled brush

if (m_brushDisabled == null)

m_brushDisabled = new SolidBrush(SystemColors.GrayText);

}

}


#endregion


#region DayPickerPopup class

/// <summary>

/// Displays a calendar that allows user to select a new date.

/// Displays box around today and user can hover over dates.

/// Allows quick access to month with month context menu and year

/// with numeric updown control.

/// </summary>

class DayPickerPopup : Control

{

class Const

{

// font for caption, days of week and days

internal const string FontName = "Arial";

internal const int FontSize = 9;


// location and size of different elements in calendar

internal const int ControlWidth = 164;

internal const int CaptionHeight = 28;

internal static Point DaysGrid = new Point(6, 43);

internal static Size DaysCell = new Size(23, 14);

internal const int NumCols = 7;

internal const int NumRows = 6;


// arrow buttons

internal static Size ArrowButtonOffset = new Size(6, 6);

internal static Size ArrowButtonSize = new Size(20, 15);

internal static Size ArrowPointsOffset = new Size(13, 9);

internal static Size ArrowPointsSize = new Size(5, 10);


// bottom today label

internal static Point BottomLabelsPos = new Point(6, 135);

internal const int BottomLabelHeight = 12;

}

// exposed events

internal event EventHandler CloseUp;

internal event EventHandler ValueChanged;


// memory bitmap to prevent flashing

Bitmap m_bmp;

Graphics m_graphics;

// gdi objects

Font m_font;

// days

SolidBrush m_brushCur;

SolidBrush m_brushOther;

SolidBrush m_brushCurRed;

SolidBrush m_brushOtherRed;

SolidBrush m_brushSelBack;

SolidBrush m_brushSelText;

Pen m_penHoverBox;

// caption

Font m_fontCaption;

SolidBrush m_brushCaptionBack;

SolidBrush m_brushCaptionText;


// general

SolidBrush m_brushBack;

Pen m_penBack;

SolidBrush m_brushFrame;

Pen m_penFrame;

bool AllowClose = true;


// store dates; today, current selection, current hover

// and the first date in the calendar

DateTime m_today = DateTime.Today;

DateTime m_curSel = DateTime.Today;

DateTime m_hoverSel = DateTime.Today;

DateTime m_firstDate;

DateTime m_maxDate = DateTime.MaxValue;

DateTime m_minDate = DateTime.MinValue;

string m_format = "yyyy-MM-dd";

// if capturing mouse events (hovering over days)

bool m_captureMouse=false;

// cache calendar for better performance, each DateTime

// structure if only 8 bytes

int m_curMonth = -1;

int m_curYear = -1;

DateTime[] m_days = new DateTime[42];

// caption controls; user can click on month and year

// in caption to quickly change values

ContextMenu m_monthMenu;

NumericUpDown m_yearUpDown;

// hit testing

Rectangle m_rcLeftButton = Rectangle.Empty;

Rectangle m_rcRightButton = Rectangle.Empty;

Rectangle m_rcMonth = Rectangle.Empty;

Rectangle m_rcYear = Rectangle.Empty;


// arrow button coordinates

Point[] m_leftArrowPoints = new Point[3];

Point[] m_rightArrowPoints = new Point[3];



// properties



/// <summary>

/// Selected date.

/// </summary>

internal DateTime Value

{

get { return m_curSel; }

set

{

if (value != m_curSel)

UpdateCurSel(value);

}

}

internal string DateFormat

{

get { return m_format; }

set { m_format = value; }

}

internal DateTime MinDate

{

get {return this.m_minDate;}

set

{

this.m_minDate = value;

this.m_yearUpDown.Minimum = value.Year;

}

}


internal DateTime MaxDate

{

get {return this.m_maxDate;}

set

{

this.m_maxDate = value;

this.m_yearUpDown.Maximum = value.Year;

}

}


/// <summary>

/// Constructor.

/// </summary>

internal DayPickerPopup()

{

this.Initialize();

}

// internal DayPickerPopup(DateTime minDate, DateTime maxDate)

// {

// this.Initialize();

// }

protected override void OnLostFocus(EventArgs e)

{

base.OnLostFocus (e);

this.AllowClose = true;

this.Close();

}

private void Initialize()

{

// init controls that popup when click on the

// month or year in the caption

InitMonthContextMenu();

InitYearUpDown();


// init display properties

this.Visible = false;

this.Location = new Point(0, 0);

this.Size = new Size(Const.ControlWidth,

Const.BottomLabelsPos.Y + Const.BottomLabelHeight + 5);

this.m_yearUpDown.ReadOnly = true;

this.m_yearUpDown.Maximum = this.m_maxDate.Year;

this.m_yearUpDown.Minimum = this.m_minDate.Year;

}




// internal methods



/// <summary>

/// Show or hide the calendar.

/// </summary>

internal void Display(bool visible, int x, int y, Color backColor, Color
foreColor)

{

if (visible)

{

// initialize properties if being displayed

m_captureMouse = false;

m_yearUpDown.Hide();


this.BackColor = backColor;

this.ForeColor = foreColor;

this.Left = x;

this.Top = y;

this.BringToFront();

this.Focus();


// default to hovering over the current selection

m_hoverSel = m_curSel;

}

// hide or show the calendar

this.Visible = visible;

}



// drawing methods



protected override void OnPaint(PaintEventArgs e)

{

// draw to memory bitmap

CreateMemoryBitmap();

CreateGdiObjects();

// calculate the fist date in the days grid, this is used

// to draw the previous month days, the current month days,

// and any days in the next month

CalculateFirstDate();

// init the background

m_graphics.Clear(this.BackColor);

// draw elements of the calendar

// the caption and days of week

DrawCaption(m_graphics);

DrawDaysOfWeek(m_graphics);


// the days grid and different selections

DrawDays(m_graphics);

DrawCurSelection(m_graphics);

DrawHoverSelection(m_graphics, m_hoverSel, true);

DrawTodaySelection(m_graphics);

// the today label at the bottom

DrawBottomLabels(m_graphics);

// frame around the control

m_graphics.DrawRectangle(m_penFrame, 0, 0,

this.Width-1, this.Height-1);


// blit memory bitmap to screen

e.Graphics.DrawImage(m_bmp, 0, 0);

}

protected override void OnPaintBackground(PaintEventArgs e)

{

// don't pass to base since we paint everything, avoid flashing

}


/// <summary>

/// Draw caption; current month, current year,

/// left and right arrow buttons.

/// </summary>

private void DrawCaption(Graphics g)

{

// back area

g.FillRectangle(m_brushCaptionBack, 0, 0, this.Width, Const.CaptionHeight);


// draw the caption centered in the area

string text = m_curSel.ToString("MMMM yyyy");

Size totalSize = g.MeasureString(text, m_fontCaption).ToSize();

int x = (this.Width - totalSize.Width) / 2;

int y = (Const.CaptionHeight - totalSize.Height) / 2;

g.DrawString(text, m_fontCaption, m_brushCaptionText, x, y);

// calculate the bounding rectangle for each element (the

// month and year) so we can detect if the user clicked on

// either element later


// calculate the month bounding rectangle

text = m_curSel.ToString("MMMM");

Size size = g.MeasureString(text, m_fontCaption).ToSize();

m_rcMonth.X = x;

m_rcMonth.Y = y;

m_rcMonth.Width = size.Width;

m_rcMonth.Height = size.Height;


// calculate the year bounding rectangle

text = m_curSel.ToString("yyyy");

size = g.MeasureString(text, m_fontCaption).ToSize();

m_rcYear.X = x + totalSize.Width - size.Width;

m_rcYear.Y = y;

m_rcYear.Width = size.Width;

m_rcYear.Height = size.Height;


// draw the left arrow button

g.FillRectangle(m_brushBack, m_rcLeftButton);

g.DrawRectangle(m_penFrame, m_rcLeftButton);

g.FillPolygon(m_brushFrame, m_leftArrowPoints);


// draw the right arrow button

g.FillRectangle(m_brushBack, m_rcRightButton);

g.DrawRectangle(m_penFrame, m_rcRightButton);

g.FillPolygon(m_brushFrame, m_rightArrowPoints);

}

/// <summary>

/// Draw days of week header.

/// </summary>

private void DrawDaysOfWeek(Graphics g)

{

const string dow = "SMTWTFS";


// calculate where to draw days of week

Point pos = new Point(Const.DaysGrid.X+3, Const.CaptionHeight);


// go through and draw each character

foreach (char c in dow)

{

g.DrawString(c.ToString(), m_fontCaption, m_brushCaptionBack, pos.X, pos.Y);

pos.X += Const.DaysCell.Width;

}


// separator line

g.DrawLine(m_penFrame, Const.DaysGrid.X, Const.DaysGrid.Y-1,

this.Width - Const.DaysGrid.X, Const.DaysGrid.Y-1);

}

/// <summary>

/// Draw days in the grid. Recalculate and cache days if the

/// month or year changed.

/// </summary>

private void DrawDays(Graphics g)

{

// see if need to calculate new set of days

if (m_curSel.Month != m_curMonth || m_curSel.Year != m_curYear)

{

// the month of year changed, calculate and cache new set of days

CalculateDays();

m_curMonth = m_curSel.Month;

m_curYear = m_curSel.Year;

}

// starting point of grid

Point pos = Const.DaysGrid;


// any extra pixels (used for single digit numbers)

int extra;


// loop through and draw each day in the grid

for (int y=0; y < Const.NumRows; y++)

{

for (int x=0; x < Const.NumCols; x++)

{

// get the date from the cache

DateTime display = m_days[(y*7)+x];


// see if requires extra pixels (single digit day)

extra = (display.Day < 10) ? 4 : 0;


if (display > this.m_maxDate || display < this.m_minDate)

{

g.DrawString(display.Day.ToString(), m_font,

(display.Month == m_curMonth) ? m_brushCurRed : m_brushOtherRed,

pos.X + extra, pos.Y);

}

else

{

g.DrawString(display.Day.ToString(), m_font,

(display.Month == m_curMonth) ? m_brushCur : m_brushOther,

pos.X + extra, pos.Y);

}

// update position within the grid

pos.X += Const.DaysCell.Width;

}


// update position within the grid

pos.X = Const.DaysGrid.X;

pos.Y += Const.DaysCell.Height + 1;

}

}


/// <summary>

/// Draw the specified day.

/// </summary>

private void DrawDay(Graphics g, DateTime day, bool selected)

{

// get the position of this cell in the grid

int index = GetDayIndex(day);

Point pos = GetDayCellPosition(index);

// cell background

g.FillRectangle(selected ? m_brushSelBack : m_brushBack,

pos.X - 5, pos.Y, Const.DaysCell.Width, Const.DaysCell.Height);


// extra space if single digit

if (day.Day < 10)

pos.X += 4;


// the day

g.DrawString(day.Day.ToString(), m_font,

selected ? m_brushSelText : m_brushCur,

pos.X, pos.Y);

}


/// <summary>

/// Draw the currently selected day.

/// </summary>

private void DrawCurSelection(Graphics g)

{

// calculate the coordinates of the current cell

int index = GetDayIndex(m_curSel);

Point pos = GetDayCellPosition(index);

// background

m_graphics.FillRectangle(m_brushSelBack, pos.X - 5, pos.Y,

Const.DaysCell.Width, Const.DaysCell.Height);


// extra space for single digit days

if (m_curSel.Day < 10)

pos.X += 4;


// the day

m_graphics.DrawString(m_curSel.Day.ToString(),

m_font, m_brushSelText, pos.X, pos.Y);

}

/// <summary>

/// Draws of erases the hover selection box.

/// </summary>

private void DrawHoverSelection(Graphics g, DateTime date, bool draw)

{

// see if hovering over a cell, return right away

// if outside of the grid area

int index = GetDayIndex(date);

if (index < 0 || index >= m_days.Length)

return;

// get the coordinates of cell

Point pos = GetDayCellPosition(index);


// draw or erase the hover selection

g.DrawRectangle(draw ? m_penHoverBox : m_penBack,

pos.X - 5, pos.Y, Const.DaysCell.Width, Const.DaysCell.Height);

}

/// <summary>

/// Draw box around today on grid.

/// </summary>

private void DrawTodaySelection(Graphics g)

{

// see if today is visible in the current grid

int index = GetDayIndex(m_today);

if (index < 0 || index >= m_days.Length)

return;


// only draw on current month

if (m_today.Month != m_curSel.Month)

return;

// today is visible, draw box around cell

Point pos = GetDayCellPosition(index);

g.DrawRectangle(m_penFrame, pos.X-5, pos.Y,

Const.DaysCell.Width, Const.DaysCell.Height);

g.DrawRectangle(m_penFrame, pos.X-4, pos.Y+1,

Const.DaysCell.Width-2, Const.DaysCell.Height-2);

}

/// <summary>

/// Draw the today label at bottom of calendar.

/// </summary>

private void DrawBottomLabels(Graphics g)

{

// draw today string, don't store bounding rectangle since

// hit testing is the entire width of the calendar

try

{

string text = string.Format("Today: {0}", m_today.ToString(m_format));

g.DrawString(text, this.m_fontCaption, this.m_brushCur,

Const.BottomLabelsPos.X, Const.BottomLabelsPos.Y);

}

catch (System.Exception)

{

string text = string.Format("Today: {0}", m_today.ToShortDateString());

g.DrawString(text, this.m_fontCaption, this.m_brushCur,

Const.BottomLabelsPos.X, Const.BottomLabelsPos.Y);

}


}



// events



/// <summary>

/// Determine what area was taped (clicked) and take the appropriate

/// action. If no items were taped, see if should start tracking mouse.

/// </summary>

protected override void OnMouseDown(MouseEventArgs e)

{

base.OnMouseDown(e);


// see if should hide the year updown control

if (m_yearUpDown.Visible)

{

if (!m_yearUpDown.Bounds.Contains(e.X, e.Y))

{

// user clicked outside of updown control,

// update grid with the new year specified

// in the updown control

OnYearUpDownValueChanged(null, EventArgs.Empty);

m_yearUpDown.Hide();

this.Focus();

}

}


// left arrow button

if (m_rcLeftButton.Contains(e.X, e.Y))

{

// display previous month

UpdateCurSel(m_curSel.AddMonths(-1));

return;

}

// right arrow button

if (m_rcRightButton.Contains(e.X, e.Y))

{

// display the next month

UpdateCurSel(m_curSel.AddMonths(1));

return;

}

// month part of caption

if (m_rcMonth.Contains(e.X, e.Y))

{

// display the context menu, the days grid is updated

// if the user selects a new month

DisplayMonthMenu(e.X, e.Y);

return;

}

// year part of caption

if (m_rcYear.Contains(e.X, e.Y))

{

// display the number updown year control, the days

// grid is updated if the user selects a new year

DisplayYearUpDown(e.X, e.Y);

return;

}


// today label

if (e.Y >= Const.BottomLabelsPos.Y)

{

// select today in grid

UpdateCurSel(m_today);

this.Close();

return;

}


// otherwise, start tracking mouse movements

m_captureMouse = true;

UpdateHoverCell(e.X, e.Y);

}


/// <summary>

/// User is done hovering over days. Set the current day

/// if they stopped on a day, otherwise they let up outside

/// of the day grid.

/// </summary>

protected override void OnMouseUp(MouseEventArgs e)

{

base.OnMouseUp(e);

if (m_captureMouse)

{

// done capturing mouse movements

m_captureMouse = false;


// update the current selection to the day

// last hovered over

int index = GetDayIndex(m_hoverSel);

if (index >= 0 && index < m_days.Length)

{

UpdateCurSel(m_hoverSel);

this.Close();


}

else

{

// canceled hovering by moving outside of grid

UpdateCurSel(m_curSel);

}

}

}


/// <summary>

/// Update the hover cell (mouse-over) if necessary.

/// </summary>

protected override void OnMouseMove(MouseEventArgs e)

{

base.OnMouseMove(e);

// update the hover cell

if (m_captureMouse)

UpdateHoverCell(e.X, e.Y);

}

/// <summary>

/// User can navigate days with the hardware device jog buttons.

/// </summary>

protected override void OnKeyUp(KeyEventArgs e)

{

// holds the number of days to change

int days = 0;

switch (e.KeyCode)

{

case Keys.Left:

days = -1;

break;

case Keys.Right:

days = 1;

break;

case Keys.Up:

days = -7;

break;

case Keys.Down:

days = 7;

break;

case Keys.Return:

this.Close();

break;

}


// see if pressed any of the jog buttons

if (days != 0)

{

// calculate the new day that should be selected

DateTime newDay = m_curSel.AddDays(days);

if (m_curSel.Month != newDay.Month)

{

// user navigated to previous or next month

UpdateCurSel(newDay);

}

else

{

// the month did not change so update the current

// selection by calling CreateGraphics (instead of

// invalidating and repainting) for better performance

Graphics g = this.CreateGraphics();

DrawDay(g, m_curSel, false);

DrawDay(g, newDay, true);

g.Dispose();

m_curSel = newDay;


// update hover selection

UpdateHoverCell(GetDayIndex(m_curSel));


// raise the ValueChanged event

if (this.ValueChanged != null)

ValueChanged(this, EventArgs.Empty);

}

}

}

/// <summary>

/// Event from the month context menu. Put a checkmark

/// next to the currently selected month.

/// </summary>

private void OnMonthMenuPopup(System.Object sender, System.EventArgs e)

{

// clear all checks

foreach (MenuItem item in m_monthMenu.MenuItems)

item.Checked = false;


// check the current month

if (m_curMonth > 0 && m_curMonth <= 12)

m_monthMenu.MenuItems[m_curMonth-1].Checked = true;

}

/// <summary>

/// Event from the month context menu. Update the current selection

/// to the month that was clicked.

/// </summary>

private void OnMonthMenuClick(System.Object sender, System.EventArgs e)

{

// determine what menu item was clicked

MenuItem item = sender as MenuItem;

if (item != null)

{

try

{

// update the current date selection

DateTime newDate = DateTime.Parse(

string.Format("{0}, {1} {2}",

item.Text, m_curSel.Day, m_curSel.Year));

UpdateCurSel(newDate);

}

catch

{

int d = 30;

if (item.Text == "February")

{

// could check if 29 exists that year but why bother

d = 28;

}

// update the current date selection

DateTime newDate = DateTime.Parse(

string.Format("{0}, {1} {2}",

item.Text, d, m_curSel.Year));

UpdateCurSel(newDate);

}

}

}

/// <summary>

/// Event from year updown control. Update current selection

/// with the year in the updown control.

/// </summary>

private void OnYearUpDownValueChanged(System.Object sender, System.EventArgs
e)

{

int d = this.Value.Day;

try

{

try

{

DateTime newDate = new DateTime(

(int)m_yearUpDown.Value,

m_curSel.Month, m_curSel.Day);

}

catch

{

if (this.Value.Month == 2 & d == 29)

{

d = 28;

}

}


// only want to update the current selection

// when the user is interacting with the

// control (when it's visible)

if (m_yearUpDown.Visible)

{

if (Convert.ToDateTime(Convert.ToString(this.m_yearUpDown.Value) + "-" +

Convert.ToString(this.Value.Month) + "-" + Convert.ToString(d)) >

this.m_maxDate)

{

this.m_yearUpDown.Hide();

this.UpdateCurSel(this.m_maxDate);


}

else if (Convert.ToDateTime(Convert.ToString(this.m_yearUpDown.Value) + "-"
+

Convert.ToString(this.Value.Month) + "-" + Convert.ToString(d)) <

this.m_minDate)

{

this.m_yearUpDown.Hide();

this.UpdateCurSel(this.m_minDate);


}

else

{

// update the current selection to the year

DateTime newDate = new DateTime(

(int)m_yearUpDown.Value,

m_curSel.Month, d);


UpdateCurSel(newDate);

}

}

}

catch

{


// catch if the user entered an invalid year

// in the control

m_yearUpDown.Value = m_curSel.Year;

}

}


// helper methods



/// <summary>

/// Initialize the numeric updown control that is displayed

/// when the year part of the caption is clicked.

/// </summary>

private void InitYearUpDown()

{

// create the numeric updown control

m_yearUpDown = new NumericUpDown();

this.Controls.Add(m_yearUpDown);


// hookup the valuechanged event

m_yearUpDown.ValueChanged += new EventHandler(OnYearUpDownValueChanged);

// init other properties

m_yearUpDown.Minimum = DateTime.MinValue.Year;

m_yearUpDown.Maximum = DateTime.MaxValue.Year;

m_yearUpDown.Visible = false;

}


/// <summary>

/// Display the numeric updown year control.

/// </summary>

private void DisplayYearUpDown(int x, int y)

{

// init year to currently selected year

m_yearUpDown.Text = m_curSel.Year.ToString();

// init the position and size of the control

m_yearUpDown.Left = m_rcYear.Left - 3;

m_yearUpDown.Top = m_rcYear.Top - 3;

m_yearUpDown.Width = m_rcYear.Width + 30;

m_yearUpDown.Height = m_rcYear.Height + 6;


m_yearUpDown.Show();

}

/// <summary>

/// Initialize the context menu that is displayed when the

/// user clicks the month part of the caption.

/// </summary>

private void InitMonthContextMenu()

{

// create a menu that contains list of months

m_monthMenu = new ContextMenu();

for (int i=1; i <= 12; i++)

{

// create new menu item and hookup the click event

MenuItem item = new MenuItem();

m_monthMenu.MenuItems.Add(item);

item.Click += new EventHandler(OnMonthMenuClick);

item.Text = DateTime.Parse(

string.Format("{0}/1/2000", i)).ToString("MMMM");

}

// hookup popup event so can check the current month

m_monthMenu.Popup += new EventHandler(OnMonthMenuPopup);

}

/// <summary>

/// Show the month context menu. The current month

/// is checked in the popup event.

/// </summary>

private void DisplayMonthMenu(int x, int y)

{

m_monthMenu.Show(this, new Point(x, y));

}


/// <summary>

/// Calculates the date for the first cell in the days

/// grid. Always show at least one day of previous month.

/// </summary>

private void CalculateFirstDate()

{

m_firstDate = new DateTime(m_curSel.Year, m_curSel.Month, 1);

if (m_firstDate.DayOfWeek == DayOfWeek.Sunday)

m_firstDate = m_firstDate.AddDays(-7);

else

m_firstDate = m_firstDate.AddDays(-(int)m_firstDate.DayOfWeek);

}


/// <summary>

/// Calculate and cache the days that are displayed in the calendar.

/// The days are cached for better performance, each day is only 8 bytes.

/// </summary>

private void CalculateDays()

{

for (int i=0; i < m_days.Length; i++)

m_days = m_firstDate.AddDays(i);

}


/// <summary>

/// Return the upper left x / y coordinates for the specified index.

/// </summary>

private Point GetDayCellPosition(int index)

{

// calculate the x and y coordinates for the specified index

return new Point(

Const.DaysGrid.X + (((int)index % Const.NumCols) * Const.DaysCell.Width),

Const.DaysGrid.Y + (((int)index / Const.NumCols) *
(Const.DaysCell.Height+1)));

}


/// <summary>

/// Create memory bitmap for double-buffering.

/// </summary>

private void CreateMemoryBitmap()

{

// see if need to create memory bitmap

if (m_bmp == null || m_bmp.Width != this.Width || m_bmp.Height !=
this.Height)

{

// create the memory bitmap

m_bmp = new Bitmap(this.Width, this.Height);

m_graphics = Graphics.FromImage(m_bmp);


// calculate the coordinates of the left and right

// arrow buttons now instead of each time paint


// left button

m_rcLeftButton = new Rectangle(

Const.ArrowButtonOffset.Width, Const.ArrowButtonOffset.Height,

Const.ArrowButtonSize.Width, Const.ArrowButtonSize.Height);


// right button

m_rcRightButton = new Rectangle(

this.Width - Const.ArrowButtonOffset.Width - Const.ArrowButtonSize.Width -
1,

Const.ArrowButtonOffset.Height, Const.ArrowButtonSize.Width,
Const.ArrowButtonSize.Height);


// left arrow in button

m_leftArrowPoints[0].X = Const.ArrowPointsOffset.Width;

m_leftArrowPoints[0].Y = Const.ArrowPointsOffset.Height +
(Const.ArrowPointsSize.Height/2);

m_leftArrowPoints[1].X = m_leftArrowPoints[0].X +
Const.ArrowPointsSize.Width;

m_leftArrowPoints[1].Y = Const.ArrowPointsOffset.Height;

m_leftArrowPoints[2].X = m_leftArrowPoints[1].X;

m_leftArrowPoints[2].Y = m_leftArrowPoints[1].Y +
Const.ArrowPointsSize.Height;

// right arrow in button

m_rightArrowPoints = (Point[])m_leftArrowPoints.Clone();

m_rightArrowPoints[0].X = this.Width - Const.ArrowPointsOffset.Width;

m_rightArrowPoints[1].X = m_rightArrowPoints[2].X =

m_rightArrowPoints[0].X - Const.ArrowPointsSize.Width;

}

}


/// <summary>

/// Create any gdi objects required for drawing.

/// </summary>

private void CreateGdiObjects()

{

if (m_font == null)

m_font = new Font(Const.FontName, Const.FontSize, FontStyle.Regular);

// days grid


if (m_brushCur == null || m_brushCur.Color != this.ForeColor)

{

m_brushCur = new SolidBrush(this.ForeColor);

}

if (m_brushCurRed == null)

m_brushCurRed = new SolidBrush(System.Drawing.Color.Red);

if (m_brushOther == null)

m_brushOther = new SolidBrush(SystemColors.GrayText);

if (m_brushOtherRed == null)

m_brushOtherRed = new SolidBrush(System.Drawing.Color.LightSalmon);


if (m_brushSelBack == null)

m_brushSelBack = new SolidBrush(SystemColors.Highlight);


if (m_brushSelText == null)

m_brushSelText = new SolidBrush(SystemColors.HighlightText);


if (m_penHoverBox == null)

m_penHoverBox = new Pen(SystemColors.GrayText);

// caption

if (m_brushCaptionBack == null)

m_brushCaptionBack = new SolidBrush(SystemColors.ActiveCaption);

if (m_brushCaptionText == null)

m_brushCaptionText = new SolidBrush(SystemColors.ActiveCaptionText);

if (m_fontCaption == null)

m_fontCaption = new Font(Const.FontName, Const.FontSize, FontStyle.Bold);


// general

if (m_brushBack == null || m_brushBack.Color != this.BackColor)

m_brushBack = new SolidBrush(this.BackColor);

if (m_penBack == null || m_penBack.Color != this.BackColor)

m_penBack = new Pen(this.BackColor);


if (m_brushFrame == null)

m_brushFrame = new SolidBrush(SystemColors.WindowFrame);


if (m_penFrame == null)

m_penFrame = new Pen(SystemColors.WindowFrame);

}

/// <summary>

/// Update the current selection with the specified date.

/// </summary>

private void UpdateCurSel(DateTime newDate)

{

if (newDate < this.m_minDate)

{

newDate = this.m_minDate;

AllowClose = false;

}

else if (newDate > this.m_maxDate)

{

newDate = this.m_maxDate;

AllowClose = false;

}

else

{

AllowClose = true;

}

// see if should raise ValueChanged event

bool raiseEvent = (m_curSel != newDate) ? true : false;


// store new date selection

m_curSel = newDate;

m_hoverSel = m_curSel;


// repaint

Invalidate();

Update();

// raise ValueChanged event

if (this.ValueChanged != null && raiseEvent)

ValueChanged(this, EventArgs.Empty);


}


/// <summary>

/// Return index into days array for the specified date.

/// </summary>

private int GetDayIndex(DateTime date)

{

TimeSpan span = date.Subtract(m_firstDate);

return (int)span.TotalDays;

}

/// <summary>

/// Return index into the days array for the specified coordinates.

/// </summary>

private int GetDayIndex(int x, int y)

{

// see if in the day grid bounding rectangle

Rectangle rc = new Rectangle(

0, Const.DaysGrid.Y,

Const.NumCols * Const.DaysCell.Width,

Const.BottomLabelsPos.Y);

if (!rc.Contains(x, y))

return -1;


// calculate the index

return (x / Const.DaysCell.Width) +

(((y-Const.DaysGrid.Y) / (Const.DaysCell.Height+1)) * Const.NumCols);

}

/// <summary>

/// Update the cell that has the hover mark.

/// </summary>

private void UpdateHoverCell(int x, int y)

{

// calculate index into grid and then update the cell

int index = GetDayIndex(x, y);

UpdateHoverCell(index);

}


/// <summary>

/// Update the cell that has the hover mark. Call CreateGraphics

/// instead of invalidating for better performance.

/// </summary>

private void UpdateHoverCell(int newIndex)

{

// see if over the days grid

if (newIndex < 0 || newIndex >= m_days.Length)

{

// outside of grid, erase current hover mark

Graphics g = this.CreateGraphics();

DrawHoverSelection(g, m_hoverSel, false);

DrawTodaySelection(g);

g.Dispose();


m_hoverSel = DateTime.MinValue;

return;

}

// see if hover date has changed

if (m_hoverSel != m_days[newIndex])

{

// earase old hover mark and draw new mark

Graphics g = this.CreateGraphics();

DrawHoverSelection(g, m_hoverSel, false);

DrawHoverSelection(g, m_days[newIndex], true);

DrawTodaySelection(g);

g.Dispose();


// store current hover date

m_hoverSel = m_days[newIndex];

}

}


/// <summary>

/// Close the control. Raise the CloseUp event.

/// </summary>

private void Close()

{

if (this.AllowClose == true)

{

if (this.m_yearUpDown.Visible == false)

{

this.Hide();

// raise the CloseUp event

if (this.CloseUp != null)

CloseUp(this, EventArgs.Empty);

}

}

}

}


#endregion

}
 
let me know if you want an updated version of the ms datepicker
added max and min date and custom format
fix month pick while day = 31

eric[@]westgen.com
 
Yes, I could reproduce this with my Version (with International support).
I will look at this tomorrow - the given source sent is badly formatted an
this will take some time.

The Original version does not run correctly on a German Mashine
- you only see a list of Months all filled with January - which is why I
made the changes

Mark Johnson, Berlin Germany
(e-mail address removed)

Dan Ardelean said:
You have the error. To reproduce it:
1.Select the year 2004 - month march
2. With the cursor pad move the selection to the day 31
3.From the dropdown menu for the month select february

It must crach because: m_curSel.Day is not a valid day for february and
the parse function will raise an exception. DateTime newDate =
DateTime.Parse(string.Format("{0}, {1} {2}",
 
This will not work on non-english Mashines:
if (item.Text == "February")

This does:
if (item.Text == m_monthMenu.MenuItems[1].Text) // Feburary in supported
Language

This does

// could check if 29 exists that year but why bother

Well Feburary 2004 shows a good reason why.

Mark Johnson, Berlin Germany

(e-mail address removed)
 
this implementation of the date picker is english only

Mark Johnson said:
This will not work on non-english Mashines:
if (item.Text == "February")

This does:
if (item.Text == m_monthMenu.MenuItems[1].Text) // Feburary in supported
Language

This does

// could check if 29 exists that year but why bother

Well Feburary 2004 shows a good reason why.

Mark Johnson, Berlin Germany

(e-mail address removed)



éric said:
here is the fix I had made for this bug... don't forget that this is also
the case of year when on Feb29

éric

private void OnMonthMenuClick(System.Object sender, System.EventArgs e)

{

// determine what menu item was clicked

MenuItem item = sender as MenuItem;

if (item != null)

{

try

{

// update the current date selection

DateTime newDate = DateTime.Parse(

string.Format("{0}, {1} {2}",

item.Text, m_curSel.Day, m_curSel.Year));

UpdateCurSel(newDate);

}

catch

{

int d = 30;

if (item.Text == "February")

{

// could check if 29 exists that year but why bother

d = 28;

}

// update the current date selection

DateTime newDate = DateTime.Parse(

string.Format("{0}, {1} {2}",

item.Text, d, m_curSel.Year));

UpdateCurSel(newDate);

}

}

}
 
why bother means you will end up with 28... better then an error?

Mark Johnson said:
This will not work on non-english Mashines:
if (item.Text == "February")

This does:
if (item.Text == m_monthMenu.MenuItems[1].Text) // Feburary in supported
Language

This does

// could check if 29 exists that year but why bother

Well Feburary 2004 shows a good reason why.

Mark Johnson, Berlin Germany

(e-mail address removed)



éric said:
here is the fix I had made for this bug... don't forget that this is also
the case of year when on Feb29

éric

private void OnMonthMenuClick(System.Object sender, System.EventArgs e)

{

// determine what menu item was clicked

MenuItem item = sender as MenuItem;

if (item != null)

{

try

{

// update the current date selection

DateTime newDate = DateTime.Parse(

string.Format("{0}, {1} {2}",

item.Text, m_curSel.Day, m_curSel.Year));

UpdateCurSel(newDate);

}

catch

{

int d = 30;

if (item.Text == "February")

{

// could check if 29 exists that year but why bother

d = 28;

}

// update the current date selection

DateTime newDate = DateTime.Parse(

string.Format("{0}, {1} {2}",

item.Text, d, m_curSel.Year));

UpdateCurSel(newDate);

}

}

}
 
Yes, this would interest me (E-Mail is below).
Since the Original version does not work at all on an non US-Cultures (like
Canada/UK-English,German) where the short date is not MM/DD/YYY, I would
like to keep this up to date (the month list shows 12 months with "January"
in the native Language).
This is why my last version can be downloaded from
http://www.mj10777.de/NETFramework/Compact/DateTimePicker/index.htm

I just saw that Canada has a special rule which made it not work in my
version.
The correction of this even simplyfies to code using
uici.DateTimeFormat.MonthNames; to fill the Month MenuItems which would
solve most of problems of the non-US Cultures if used in the original code.
The only thing that must be hardcoded is "Today" and the FirstDayof Week
(when not Sunday) due to
DateTimeFormatInfo.CurrentInfo.FirstDayOfWeek not working on Compact.
My Version also supports Mo, Tu etc. in the native Language when desired.

Mark Johnson, Berlin Germany
(e-mail address removed)


éric said:
if you send me an email I will email you my implementation...
I use min and max date to set maximum or minimum dates for users
Dates beyond the min max appear in red and can not be clicked
I also just added code to check for feb29 as you requested and changed the
text="february"
custom format can be any format you want (within reason) of date format.

Regards,

éric
(e-mail address removed)


Mark Johnson said:
As far as I understand your code:
you are creating two extra Fields with MaxData and MinDate.
you use these to set m_yearUpDown.Minimum and m_yearUpDown.Maximum in
InitYearUpDown.
Otherwise it does not seem to be used.
Since the original code set these valuse with DateTime.MinValue.Year and
DateTime.MaxValue.Year anyway
I don't understand the need of these two extra Fields.
using yyyy-MM-dd
is used only to show the Date in the International format when the Maschine
does not support this, correct?

Mark Johnson, Berlin Germany
(e-mail address removed)

éric said:
same as MS but with Max date and Min Date as well as using yyyy-MM-dd and
fix the bug with picking months on 31st day

using System;

using System.Windows.Forms;

using System.Drawing;



namespace Controls

{

#region DateTimePickerFormat enumeration

/// <summary>

/// Specifies the date and time format the DateTimePicker control displays.

/// </summary>

internal enum DateTimePickerFormat

{

Long,

Short,

Custom

}

#endregion

#region DateTimePicker class

/// <summary>

/// Manged DateTimePicker control. User can select a day from popup
calendar.

/// </summary>

internal class DateTimePicker : Control

{

class Const

{

// size of the drop arrow on far right

internal static Size DropArrowSize = new Size(7,4);

}


// offscreen bitmap

Bitmap m_bmp;

Graphics m_graphics;

// gdi objects

SolidBrush m_brushFore;

SolidBrush m_brushDisabled;

SolidBrush m_brushFrame;

Pen m_penFrame;


// down arrow coordinates

Point[] m_arrowPoints = new Point[3];


// day picker, displays popup month calendar

DayPickerPopup m_dayPicker = new DayPickerPopup();


// date format, short or long

DateTimePickerFormat m_format = DateTimePickerFormat.Long;

// exposed events

internal event EventHandler ValueChanged;

internal event EventHandler CloseUp;

internal event EventHandler DropDown;



// properties



/// <summary>

/// Gets the maximum date value.

/// </summary>

///

private DateTime minDate = DateTime.MinValue;

private DateTime maxDate = DateTime.MaxValue;

internal DateTime MaxDateTime

{

get { return this.maxDate; }

set

{

if (value <= DateTime.MaxValue)

{

this.maxDate = value;

this.m_dayPicker.MaxDate = value;

}

else

{

this.maxDate = DateTime.MaxValue;

this.m_dayPicker.MaxDate = DateTime.MaxValue;

}

}

}

/// <summary>

/// Gets the minimum date value.

/// </summary>

internal DateTime MinDateTime

{

get { return this.minDate; }

set

{

if (value >= DateTime.MinValue)

{

this.minDate = value;

this.m_dayPicker.MinDate = value;

}

else

{

this.minDate = DateTime.MinValue;

this.m_dayPicker.MinDate = DateTime.MinValue;

}

}

}

/// <summary>

/// Gets or sets the format of the date displayed in the control.

/// </summary>


private string m_CustomFormat = "yyyy-MM-dd";

internal string CustomFormat

{

get { return m_CustomFormat; }

set

{

m_CustomFormat = value;

this.m_dayPicker.DateFormat = value;

Invalidate();

}

}

internal DateTimePickerFormat Format

{

get { return m_format; }

set

{

// update format and repaint

m_format = value;

Invalidate();

}

}

/// <summary>

/// Gets or sets the date value assigned to the control.

/// </summary>

internal DateTime Value

{

// setting the picker value raises the ValueChanged

// event which causes the control to repaint

get { return m_dayPicker.Value; }

set { m_dayPicker.Value = value; }

}

/// <summary>

/// Gets or sets the text associated with this control. Throws a

/// FormatException if the specified text is not a valid date.

/// </summary>

public override String Text

{

get

{

// return date as string in the correct format

switch (m_format)

{

case DateTimePickerFormat.Short :

return this.Value.ToShortDateString();

//break;

case DateTimePickerFormat.Long :

return this.Value.ToLongDateString();

//break;

case DateTimePickerFormat.Custom :

try

{

return this.Value.ToString(this.m_CustomFormat);

}

catch(System.Exception)

{

return this.Value.ToShortDateString();

}

//break;

default :

{

return this.Value.ToShortDateString();

}

}

}

set

{

// update the datetime value

this.Value = DateTime.Parse(value);

}

}



/// <summary>

/// Constructor. Initializes a new instance of the DateTimePicker class.

/// </summary>

internal DateTimePicker()

{

// hookup day picker events

m_dayPicker.CloseUp += new EventHandler(OnDayPickerCloseUp);

m_dayPicker.ValueChanged += new EventHandler(OnDayPickerValueChanged);

}



// drawing methods

protected override void OnEnabledChanged(EventArgs e)

{

this.Refresh();

base.OnEnabledChanged (e);

}

protected override void OnPaint(PaintEventArgs e)

{

// draw to memory bitmap

CreateMemoryBitmap();

CreateGdiObjects();

// init background

m_graphics.Clear(this.BackColor);

// label

Size size = m_graphics.MeasureString(this.Text, this.Font).ToSize();

m_graphics.DrawString(this.Text, this.Font,

this.Enabled ? m_brushFore : m_brushDisabled,

4, (this.Height - size.Height)/2);

// drop arrow

m_graphics.FillPolygon(m_brushFrame, m_arrowPoints);

// frame around control

m_graphics.DrawRectangle(m_penFrame, 0, 0, this.Width-1, this.Height-1);


// blit memory bitmap to screen

e.Graphics.DrawImage(m_bmp, 0, 0);

}

protected override void OnPaintBackground(PaintEventArgs e)

{

// don't pass to base since we paint everything, avoid flashing

}



// events



/// <summary>

/// Show or hide the day picker popup control. Determine the

/// best location to display the day picker.

/// </summary>

protected override void OnMouseDown(MouseEventArgs e)

{

base.OnMouseDown(e);


// add day picker control to the toplevel window

// this allows the control to display on top of

// tabs and other controls

if (m_dayPicker.Parent == null)

this.TopLevelControl.Controls.Add(m_dayPicker);

// intelligently calculate where the day picker should be displayed,

// try to display below the label control, display above the control

// if there is not enough room

Point pos = new Point(this.Left, this.Bottom+1);

// map points to top level window

Point parentPos = this.Parent.PointToScreen(this.Parent.Location);

Point topParentPos =
this.TopLevelControl.PointToScreen(this.Parent.Location);

pos.Offset(parentPos.X - topParentPos.X, parentPos.Y - topParentPos.Y);

// see if there is enough room to display day picker below label

if ((pos.Y + m_dayPicker.Size.Height + 320 -
ECASOFT.Controls.Static.SIP.VisibleDesktop.Bottom) >

this.TopLevelControl.ClientRectangle.Height)

{

// there is not enough room, try displaying above the label

pos.Y -= (this.Height + m_dayPicker.Size.Height + 2);

if (pos.Y < 0)

{

// there was not enough room, display at bottom of screen

pos.Y = (this.TopLevelControl.ClientRectangle.Height -

m_dayPicker.Size.Height - 320 +
ECASOFT.Controls.Static.SIP.VisibleDesktop.Bottom +

(26 * Convert.ToInt32(ECASOFT.Controls.Static.SIP.Enabled)));

}

}

try

{

if (((System.Windows.Forms.Form) this.TopLevelControl).FormBorderStyle ==

System.Windows.Forms.FormBorderStyle.None)

{

pos.Y = 0;

}

}

catch{}


// try displaying aligned with the label control

if ((pos.X + m_dayPicker.Size.Width) >
this.TopLevelControl.ClientRectangle.Width)

pos.X = (this.TopLevelControl.ClientRectangle.Width -
m_dayPicker.Size.Width);

// display or hide the day picker control

m_dayPicker.Display(

!m_dayPicker.Visible, pos.X, pos.Y,

this.BackColor, this.ForeColor);


// raise the DropDown or CloseUp event

if (m_dayPicker.Visible && this.DropDown != null)

this.DropDown(this, EventArgs.Empty);


if (!m_dayPicker.Visible && this.CloseUp != null)

this.CloseUp(this, EventArgs.Empty);

}


/// <summary>

/// CloseUp event from the day picker control

/// </summary>

private void OnDayPickerCloseUp(object sender, System.EventArgs e)

{

// pass to our container

if (this.CloseUp != null)

this.CloseUp(this, EventArgs.Empty);

}

/// <summary>

/// ValueChanged event from the day picker control

/// </summary>

private void OnDayPickerValueChanged(object sender, System.EventArgs e)

{

// repaint to display the new value

Invalidate();


// pass along to our container

if (this.ValueChanged != null)

this.ValueChanged(this, e);

}



// helper methods



/// <summary>

/// Create offsceeen bitmap. This bitmap is used for double-buffering

/// to prevent flashing.

/// </summary>

private void CreateMemoryBitmap()

{

if (m_bmp == null || m_bmp.Width != this.Width || m_bmp.Height !=
this.Height)

{

// memory bitmap

m_bmp = new Bitmap(this.Width, this.Height);

m_graphics = Graphics.FromImage(m_bmp);

// calculate down arrow points

m_arrowPoints[0].X = this.Width - Const.DropArrowSize.Width - 4;

m_arrowPoints[0].Y = (this.Height - Const.DropArrowSize.Height+1) / 2;

m_arrowPoints[1].X = m_arrowPoints[0].X + Const.DropArrowSize.Width;

m_arrowPoints[1].Y = m_arrowPoints[0].Y;

m_arrowPoints[2].X = m_arrowPoints[0].X + (Const.DropArrowSize.Width/2);

m_arrowPoints[2].Y = m_arrowPoints[0].Y + Const.DropArrowSize.Height;

}

}


/// <summary>

/// Create GDI objects required to paint the control.

/// </summary>

private void CreateGdiObjects()

{

// window frame brush

if (m_brushFrame == null)

m_brushFrame = new SolidBrush(SystemColors.WindowFrame);


// window frame pen

if (m_penFrame == null)

m_penFrame = new Pen(SystemColors.WindowFrame);

// fore color brush, the .Net CF does not support OnForeColorChanged,

// so we detect if the forecolor changed here

if (m_brushFore == null || m_brushFore.Color != this.ForeColor)

m_brushFore = new SolidBrush(this.ForeColor);


// disabled brush

if (m_brushDisabled == null)

m_brushDisabled = new SolidBrush(SystemColors.GrayText);

}

}


#endregion


#region DayPickerPopup class

/// <summary>

/// Displays a calendar that allows user to select a new date.

/// Displays box around today and user can hover over dates.

/// Allows quick access to month with month context menu and year

/// with numeric updown control.

/// </summary>

class DayPickerPopup : Control

{

class Const

{

// font for caption, days of week and days

internal const string FontName = "Arial";

internal const int FontSize = 9;


// location and size of different elements in calendar

internal const int ControlWidth = 164;

internal const int CaptionHeight = 28;

internal static Point DaysGrid = new Point(6, 43);

internal static Size DaysCell = new Size(23, 14);

internal const int NumCols = 7;

internal const int NumRows = 6;


// arrow buttons

internal static Size ArrowButtonOffset = new Size(6, 6);

internal static Size ArrowButtonSize = new Size(20, 15);

internal static Size ArrowPointsOffset = new Size(13, 9);

internal static Size ArrowPointsSize = new Size(5, 10);


// bottom today label

internal static Point BottomLabelsPos = new Point(6, 135);

internal const int BottomLabelHeight = 12;

}

// exposed events

internal event EventHandler CloseUp;

internal event EventHandler ValueChanged;


// memory bitmap to prevent flashing

Bitmap m_bmp;

Graphics m_graphics;

// gdi objects

Font m_font;

// days

SolidBrush m_brushCur;

SolidBrush m_brushOther;

SolidBrush m_brushCurRed;

SolidBrush m_brushOtherRed;

SolidBrush m_brushSelBack;

SolidBrush m_brushSelText;

Pen m_penHoverBox;

// caption

Font m_fontCaption;

SolidBrush m_brushCaptionBack;

SolidBrush m_brushCaptionText;


// general

SolidBrush m_brushBack;

Pen m_penBack;

SolidBrush m_brushFrame;

Pen m_penFrame;

bool AllowClose = true;


// store dates; today, current selection, current hover

// and the first date in the calendar

DateTime m_today = DateTime.Today;

DateTime m_curSel = DateTime.Today;

DateTime m_hoverSel = DateTime.Today;

DateTime m_firstDate;

DateTime m_maxDate = DateTime.MaxValue;

DateTime m_minDate = DateTime.MinValue;

string m_format = "yyyy-MM-dd";

// if capturing mouse events (hovering over days)

bool m_captureMouse=false;

// cache calendar for better performance, each DateTime

// structure if only 8 bytes

int m_curMonth = -1;

int m_curYear = -1;

DateTime[] m_days = new DateTime[42];

// caption controls; user can click on month and year

// in caption to quickly change values

ContextMenu m_monthMenu;

NumericUpDown m_yearUpDown;

// hit testing

Rectangle m_rcLeftButton = Rectangle.Empty;

Rectangle m_rcRightButton = Rectangle.Empty;

Rectangle m_rcMonth = Rectangle.Empty;

Rectangle m_rcYear = Rectangle.Empty;


// arrow button coordinates

Point[] m_leftArrowPoints = new Point[3];

Point[] m_rightArrowPoints = new Point[3];



// properties



/// <summary>

/// Selected date.

/// </summary>

internal DateTime Value

{

get { return m_curSel; }

set

{

if (value != m_curSel)

UpdateCurSel(value);

}

}

internal string DateFormat

{

get { return m_format; }

set { m_format = value; }

}

internal DateTime MinDate

{

get {return this.m_minDate;}

set

{

this.m_minDate = value;

this.m_yearUpDown.Minimum = value.Year;

}

}


internal DateTime MaxDate

{

get {return this.m_maxDate;}

set

{

this.m_maxDate = value;

this.m_yearUpDown.Maximum = value.Year;

}

}


/// <summary>

/// Constructor.

/// </summary>

internal DayPickerPopup()

{

this.Initialize();

}

// internal DayPickerPopup(DateTime minDate, DateTime maxDate)

// {

// this.Initialize();

// }

protected override void OnLostFocus(EventArgs e)

{

base.OnLostFocus (e);

this.AllowClose = true;

this.Close();

}

private void Initialize()

{

// init controls that popup when click on the

// month or year in the caption

InitMonthContextMenu();

InitYearUpDown();


// init display properties

this.Visible = false;

this.Location = new Point(0, 0);

this.Size = new Size(Const.ControlWidth,

Const.BottomLabelsPos.Y + Const.BottomLabelHeight + 5);

this.m_yearUpDown.ReadOnly = true;

this.m_yearUpDown.Maximum = this.m_maxDate.Year;

this.m_yearUpDown.Minimum = this.m_minDate.Year;

}




// internal methods



/// <summary>

/// Show or hide the calendar.

/// </summary>

internal void Display(bool visible, int x, int y, Color backColor, Color
foreColor)

{

if (visible)

{

// initialize properties if being displayed

m_captureMouse = false;

m_yearUpDown.Hide();


this.BackColor = backColor;

this.ForeColor = foreColor;

this.Left = x;

this.Top = y;

this.BringToFront();

this.Focus();


// default to hovering over the current selection

m_hoverSel = m_curSel;

}

// hide or show the calendar

this.Visible = visible;

}



// drawing methods



protected override void OnPaint(PaintEventArgs e)

{

// draw to memory bitmap

CreateMemoryBitmap();

CreateGdiObjects();

// calculate the fist date in the days grid, this is used

// to draw the previous month days, the current month days,

// and any days in the next month

CalculateFirstDate();

// init the background

m_graphics.Clear(this.BackColor);

// draw elements of the calendar

// the caption and days of week

DrawCaption(m_graphics);

DrawDaysOfWeek(m_graphics);


// the days grid and different selections

DrawDays(m_graphics);

DrawCurSelection(m_graphics);

DrawHoverSelection(m_graphics, m_hoverSel, true);

DrawTodaySelection(m_graphics);

// the today label at the bottom

DrawBottomLabels(m_graphics);

// frame around the control

m_graphics.DrawRectangle(m_penFrame, 0, 0,

this.Width-1, this.Height-1);


// blit memory bitmap to screen

e.Graphics.DrawImage(m_bmp, 0, 0);

}

protected override void OnPaintBackground(PaintEventArgs e)

{

// don't pass to base since we paint everything, avoid flashing

}


/// <summary>

/// Draw caption; current month, current year,

/// left and right arrow buttons.

/// </summary>

private void DrawCaption(Graphics g)

{

// back area

g.FillRectangle(m_brushCaptionBack, 0, 0, this.Width, Const.CaptionHeight);


// draw the caption centered in the area

string text = m_curSel.ToString("MMMM yyyy");

Size totalSize = g.MeasureString(text, m_fontCaption).ToSize();

int x = (this.Width - totalSize.Width) / 2;

int y = (Const.CaptionHeight - totalSize.Height) / 2;

g.DrawString(text, m_fontCaption, m_brushCaptionText, x, y);

// calculate the bounding rectangle for each element (the

// month and year) so we can detect if the user clicked on

// either element later


// calculate the month bounding rectangle

text = m_curSel.ToString("MMMM");

Size size = g.MeasureString(text, m_fontCaption).ToSize();

m_rcMonth.X = x;

m_rcMonth.Y = y;

m_rcMonth.Width = size.Width;

m_rcMonth.Height = size.Height;


// calculate the year bounding rectangle

text = m_curSel.ToString("yyyy");

size = g.MeasureString(text, m_fontCaption).ToSize();

m_rcYear.X = x + totalSize.Width - size.Width;

m_rcYear.Y = y;

m_rcYear.Width = size.Width;

m_rcYear.Height = size.Height;


// draw the left arrow button

g.FillRectangle(m_brushBack, m_rcLeftButton);

g.DrawRectangle(m_penFrame, m_rcLeftButton);

g.FillPolygon(m_brushFrame, m_leftArrowPoints);


// draw the right arrow button

g.FillRectangle(m_brushBack, m_rcRightButton);

g.DrawRectangle(m_penFrame, m_rcRightButton);

g.FillPolygon(m_brushFrame, m_rightArrowPoints);

}

/// <summary>

/// Draw days of week header.

/// </summary>

private void DrawDaysOfWeek(Graphics g)

{

const string dow = "SMTWTFS";


// calculate where to draw days of week

Point pos = new Point(Const.DaysGrid.X+3, Const.CaptionHeight);


// go through and draw each character

foreach (char c in dow)

{

g.DrawString(c.ToString(), m_fontCaption, m_brushCaptionBack, pos.X, pos.Y);

pos.X += Const.DaysCell.Width;

}


// separator line

g.DrawLine(m_penFrame, Const.DaysGrid.X, Const.DaysGrid.Y-1,

this.Width - Const.DaysGrid.X, Const.DaysGrid.Y-1);

}

/// <summary>

/// Draw days in the grid. Recalculate and cache days if the

/// month or year changed.

/// </summary>

private void DrawDays(Graphics g)

{

// see if need to calculate new set of days

if (m_curSel.Month != m_curMonth || m_curSel.Year != m_curYear)

{

// the month of year changed, calculate and cache new set of days

CalculateDays();

m_curMonth = m_curSel.Month;

m_curYear = m_curSel.Year;

}

// starting point of grid

Point pos = Const.DaysGrid;


// any extra pixels (used for single digit numbers)

int extra;


// loop through and draw each day in the grid

for (int y=0; y < Const.NumRows; y++)

{

for (int x=0; x < Const.NumCols; x++)

{

// get the date from the cache

DateTime display = m_days[(y*7)+x];


// see if requires extra pixels (single digit day)

extra = (display.Day < 10) ? 4 : 0;


if (display > this.m_maxDate || display < this.m_minDate)

{

g.DrawString(display.Day.ToString(), m_font,

(display.Month == m_curMonth) ? m_brushCurRed : m_brushOtherRed,

pos.X + extra, pos.Y);

}

else

{

g.DrawString(display.Day.ToString(), m_font,

(display.Month == m_curMonth) ? m_brushCur : m_brushOther,

pos.X + extra, pos.Y);

}

// update position within the grid

pos.X += Const.DaysCell.Width;

}


// update position within the grid

pos.X = Const.DaysGrid.X;

pos.Y += Const.DaysCell.Height + 1;

}

}


/// <summary>

/// Draw the specified day.

/// </summary>

private void DrawDay(Graphics g, DateTime day, bool selected)

{

// get the position of this cell in the grid

int index = GetDayIndex(day);

Point pos = GetDayCellPosition(index);

// cell background

g.FillRectangle(selected ? m_brushSelBack : m_brushBack,

pos.X - 5, pos.Y, Const.DaysCell.Width, Const.DaysCell.Height);


// extra space if single digit

if (day.Day < 10)

pos.X += 4;


// the day

g.DrawString(day.Day.ToString(), m_font,

selected ? m_brushSelText : m_brushCur,

pos.X, pos.Y);

}


/// <summary>

/// Draw the currently selected day.

/// </summary>

private void DrawCurSelection(Graphics g)

{

// calculate the coordinates of the current cell

int index = GetDayIndex(m_curSel);

Point pos = GetDayCellPosition(index);

// background

m_graphics.FillRectangle(m_brushSelBack, pos.X - 5, pos.Y,

Const.DaysCell.Width, Const.DaysCell.Height);


// extra space for single digit days

if (m_curSel.Day < 10)

pos.X += 4;


// the day

m_graphics.DrawString(m_curSel.Day.ToString(),

m_font, m_brushSelText, pos.X, pos.Y);

}

/// <summary>

/// Draws of erases the hover selection box.

/// </summary>

private void DrawHoverSelection(Graphics g, DateTime date, bool draw)

{

// see if hovering over a cell, return right away

// if outside of the grid area

int index = GetDayIndex(date);

if (index < 0 || index >= m_days.Length)

return;

// get the coordinates of cell

Point pos = GetDayCellPosition(index);


// draw or erase the hover selection

g.DrawRectangle(draw ? m_penHoverBox : m_penBack,

pos.X - 5, pos.Y, Const.DaysCell.Width, Const.DaysCell.Height);

}

/// <summary>

/// Draw box around today on grid.

/// </summary>

private void DrawTodaySelection(Graphics g)

{

// see if today is visible in the current grid

int index = GetDayIndex(m_today);

if (index < 0 || index >= m_days.Length)

return;


// only draw on current month

if (m_today.Month != m_curSel.Month)

return;

// today is visible, draw box around cell

Point pos = GetDayCellPosition(index);

g.DrawRectangle(m_penFrame, pos.X-5, pos.Y,

Const.DaysCell.Width, Const.DaysCell.Height);

g.DrawRectangle(m_penFrame, pos.X-4, pos.Y+1,

Const.DaysCell.Width-2, Const.DaysCell.Height-2);

}

/// <summary>

/// Draw the today label at bottom of calendar.

/// </summary>

private void DrawBottomLabels(Graphics g)

{

// draw today string, don't store bounding rectangle since

// hit testing is the entire width of the calendar

try

{

string text = string.Format("Today: {0}", m_today.ToString(m_format));

g.DrawString(text, this.m_fontCaption, this.m_brushCur,

Const.BottomLabelsPos.X, Const.BottomLabelsPos.Y);

}

catch (System.Exception)

{

string text = string.Format("Today: {0}", m_today.ToShortDateString());

g.DrawString(text, this.m_fontCaption, this.m_brushCur,

Const.BottomLabelsPos.X, Const.BottomLabelsPos.Y);

}


}



// events



/// <summary>

/// Determine what area was taped (clicked) and take the appropriate

/// action. If no items were taped, see if should start tracking mouse.

/// </summary>

protected override void OnMouseDown(MouseEventArgs e)

{

base.OnMouseDown(e);


// see if should hide the year updown control

if (m_yearUpDown.Visible)

{

if (!m_yearUpDown.Bounds.Contains(e.X, e.Y))

{

// user clicked outside of updown control,

// update grid with the new year specified

// in the updown control

OnYearUpDownValueChanged(null, EventArgs.Empty);

m_yearUpDown.Hide();

this.Focus();

}

}


// left arrow button

if (m_rcLeftButton.Contains(e.X, e.Y))

{

// display previous month

UpdateCurSel(m_curSel.AddMonths(-1));

return;

}

// right arrow button

if (m_rcRightButton.Contains(e.X, e.Y))

{

// display the next month

UpdateCurSel(m_curSel.AddMonths(1));

return;

}

// month part of caption

if (m_rcMonth.Contains(e.X, e.Y))

{

// display the context menu, the days grid is updated

// if the user selects a new month

DisplayMonthMenu(e.X, e.Y);

return;

}

// year part of caption

if (m_rcYear.Contains(e.X, e.Y))

{

// display the number updown year control, the days

// grid is updated if the user selects a new year

DisplayYearUpDown(e.X, e.Y);

return;

}


// today label

if (e.Y >= Const.BottomLabelsPos.Y)

{

// select today in grid

UpdateCurSel(m_today);

this.Close();

return;

}


// otherwise, start tracking mouse movements

m_captureMouse = true;

UpdateHoverCell(e.X, e.Y);

}


/// <summary>

/// User is done hovering over days. Set the current day

/// if they stopped on a day, otherwise they let up outside

/// of the day grid.

/// </summary>

protected override void OnMouseUp(MouseEventArgs e)

{

base.OnMouseUp(e);

if (m_captureMouse)

{

// done capturing mouse movements

m_captureMouse = false;


// update the current selection to the day

// last hovered over

int index = GetDayIndex(m_hoverSel);

if (index >= 0 && index < m_days.Length)

{

UpdateCurSel(m_hoverSel);

this.Close();


}

else

{

// canceled hovering by moving outside of grid

UpdateCurSel(m_curSel);

}

}

}


/// <summary>

/// Update the hover cell (mouse-over) if necessary.

/// </summary>

protected override void OnMouseMove(MouseEventArgs e)

{

base.OnMouseMove(e);

// update the hover cell

if (m_captureMouse)

UpdateHoverCell(e.X, e.Y);

}

/// <summary>

/// User can navigate days with the hardware device jog buttons.

/// </summary>

protected override void OnKeyUp(KeyEventArgs e)

{

// holds the number of days to change

int days = 0;

switch (e.KeyCode)

{

case Keys.Left:

days = -1;

break;

case Keys.Right:

days = 1;

break;

case Keys.Up:

days = -7;

break;

case Keys.Down:

days = 7;

break;

case Keys.Return:

this.Close();

break;

}


// see if pressed any of the jog buttons

if (days != 0)

{

// calculate the new day that should be selected

DateTime newDay = m_curSel.AddDays(days);

if (m_curSel.Month != newDay.Month)

{

// user navigated to previous or next month

UpdateCurSel(newDay);

}

else

{

// the month did not change so update the current

// selection by calling CreateGraphics (instead of

// invalidating and repainting) for better performance

Graphics g = this.CreateGraphics();

DrawDay(g, m_curSel, false);

DrawDay(g, newDay, true);

g.Dispose();

m_curSel = newDay;


// update hover selection

UpdateHoverCell(GetDayIndex(m_curSel));


// raise the ValueChanged event

if (this.ValueChanged != null)

ValueChanged(this, EventArgs.Empty);

}

}

}

/// <summary>

/// Event from the month context menu. Put a checkmark

/// next to the currently selected month.

/// </summary>

private void OnMonthMenuPopup(System.Object sender, System.EventArgs e)

{

// clear all checks

foreach (MenuItem item in m_monthMenu.MenuItems)

item.Checked = false;


// check the current month

if (m_curMonth > 0 && m_curMonth <= 12)

m_monthMenu.MenuItems[m_curMonth-1].Checked = true;

}

/// <summary>

/// Event from the month context menu. Update the current selection

/// to the month that was clicked.

/// </summary>

private void OnMonthMenuClick(System.Object sender, System.EventArgs e)

{

// determine what menu item was clicked

MenuItem item = sender as MenuItem;

if (item != null)

{

try

{

// update the current date selection

DateTime newDate = DateTime.Parse(

string.Format("{0}, {1} {2}",

item.Text, m_curSel.Day, m_curSel.Year));

UpdateCurSel(newDate);

}

catch

{

int d = 30;

if (item.Text == "February")

{

// could check if 29 exists that year but why bother

d = 28;

}

// update the current date selection

DateTime newDate = DateTime.Parse(

string.Format("{0}, {1} {2}",

item.Text, d, m_curSel.Year));

UpdateCurSel(newDate);

}

}

}

/// <summary>

/// Event from year updown control. Update current selection

/// with the year in the updown control.

/// </summary>

private void OnYearUpDownValueChanged(System.Object sender, System.EventArgs
e)

{

int d = this.Value.Day;

try

{

try

{

DateTime newDate = new DateTime(

(int)m_yearUpDown.Value,

m_curSel.Month, m_curSel.Day);

}

catch

{

if (this.Value.Month == 2 & d == 29)

{

d = 28;

}

}


// only want to update the current selection

// when the user is interacting with the

// control (when it's visible)

if (m_yearUpDown.Visible)

{

if (Convert.ToDateTime(Convert.ToString(this.m_yearUpDown.Value) + "-" +

Convert.ToString(this.Value.Month) + "-" + Convert.ToString(d)) >

this.m_maxDate)

{

this.m_yearUpDown.Hide();

this.UpdateCurSel(this.m_maxDate);


}

else if (Convert.ToDateTime(Convert.ToString(this.m_yearUpDown.Value)
+
"-"
+

Convert.ToString(this.Value.Month) + "-" + Convert.ToString(d)) <

this.m_minDate)

{

this.m_yearUpDown.Hide();

this.UpdateCurSel(this.m_minDate);


}

else

{

// update the current selection to the year

DateTime newDate = new DateTime(

(int)m_yearUpDown.Value,

m_curSel.Month, d);


UpdateCurSel(newDate);

}

}

}

catch

{


// catch if the user entered an invalid year

// in the control

m_yearUpDown.Value = m_curSel.Year;

}

}


// helper methods



/// <summary>

/// Initialize the numeric updown control that is displayed

/// when the year part of the caption is clicked.

/// </summary>

private void InitYearUpDown()

{

// create the numeric updown control

m_yearUpDown = new NumericUpDown();

this.Controls.Add(m_yearUpDown);


// hookup the valuechanged event

m_yearUpDown.ValueChanged += new EventHandler(OnYearUpDownValueChanged);

// init other properties

m_yearUpDown.Minimum = DateTime.MinValue.Year;

m_yearUpDown.Maximum = DateTime.MaxValue.Year;

m_yearUpDown.Visible = false;

}


/// <summary>

/// Display the numeric updown year control.

/// </summary>

private void DisplayYearUpDown(int x, int y)

{

// init year to currently selected year

m_yearUpDown.Text = m_curSel.Year.ToString();

// init the position and size of the control

m_yearUpDown.Left = m_rcYear.Left - 3;

m_yearUpDown.Top = m_rcYear.Top - 3;

m_yearUpDown.Width = m_rcYear.Width + 30;

m_yearUpDown.Height = m_rcYear.Height + 6;


m_yearUpDown.Show();

}

/// <summary>

/// Initialize the context menu that is displayed when the

/// user clicks the month part of the caption.

/// </summary>

private void InitMonthContextMenu()

{

// create a menu that contains list of months

m_monthMenu = new ContextMenu();

for (int i=1; i <= 12; i++)

{

// create new menu item and hookup the click event

MenuItem item = new MenuItem();

m_monthMenu.MenuItems.Add(item);

item.Click += new EventHandler(OnMonthMenuClick);

item.Text = DateTime.Parse(

string.Format("{0}/1/2000", i)).ToString("MMMM");

}

// hookup popup event so can check the current month

m_monthMenu.Popup += new EventHandler(OnMonthMenuPopup);

}

/// <summary>

/// Show the month context menu. The current month

/// is checked in the popup event.

/// </summary>

private void DisplayMonthMenu(int x, int y)

{

m_monthMenu.Show(this, new Point(x, y));

}


/// <summary>

/// Calculates the date for the first cell in the days

/// grid. Always show at least one day of previous month.

/// </summary>

private void CalculateFirstDate()

{

m_firstDate = new DateTime(m_curSel.Year, m_curSel.Month, 1);

if (m_firstDate.DayOfWeek == DayOfWeek.Sunday)

m_firstDate = m_firstDate.AddDays(-7);

else

m_firstDate = m_firstDate.AddDays(-(int)m_firstDate.DayOfWeek);

}


/// <summary>

/// Calculate and cache the days that are displayed in the calendar.

/// The days are cached for better performance, each day is only 8 bytes.

/// </summary>

private void CalculateDays()

{

for (int i=0; i < m_days.Length; i++)

m_days = m_firstDate.AddDays(i);

}


/// <summary>

/// Return the upper left x / y coordinates for the specified index.

/// </summary>

private Point GetDayCellPosition(int index)

{

// calculate the x and y coordinates for the specified index

return new Point(

Const.DaysGrid.X + (((int)index % Const.NumCols) * Const.DaysCell.Width),

Const.DaysGrid.Y + (((int)index / Const.NumCols) *
(Const.DaysCell.Height+1)));

}


/// <summary>

/// Create memory bitmap for double-buffering.

/// </summary>

private void CreateMemoryBitmap()

{

// see if need to create memory bitmap

if (m_bmp == null || m_bmp.Width != this.Width || m_bmp.Height !=
this.Height)

{

// create the memory bitmap

m_bmp = new Bitmap(this.Width, this.Height);

m_graphics = Graphics.FromImage(m_bmp);


// calculate the coordinates of the left and right

// arrow buttons now instead of each time paint


// left button

m_rcLeftButton = new Rectangle(

Const.ArrowButtonOffset.Width, Const.ArrowButtonOffset.Height,

Const.ArrowButtonSize.Width, Const.ArrowButtonSize.Height);


// right button

m_rcRightButton = new Rectangle(

this.Width - Const.ArrowButtonOffset.Width - Const.ArrowButtonSize.Width -
1,

Const.ArrowButtonOffset.Height, Const.ArrowButtonSize.Width,
Const.ArrowButtonSize.Height);


// left arrow in button

m_leftArrowPoints[0].X = Const.ArrowPointsOffset.Width;

m_leftArrowPoints[0].Y = Const.ArrowPointsOffset.Height +
(Const.ArrowPointsSize.Height/2);

m_leftArrowPoints[1].X = m_leftArrowPoints[0].X +
Const.ArrowPointsSize.Width;

m_leftArrowPoints[1].Y = Const.ArrowPointsOffset.Height;

m_leftArrowPoints[2].X = m_leftArrowPoints[1].X;

m_leftArrowPoints[2].Y = m_leftArrowPoints[1].Y +
Const.ArrowPointsSize.Height;

// right arrow in button

m_rightArrowPoints = (Point[])m_leftArrowPoints.Clone();

m_rightArrowPoints[0].X = this.Width - Const.ArrowPointsOffset.Width;

m_rightArrowPoints[1].X = m_rightArrowPoints[2].X =

m_rightArrowPoints[0].X - Const.ArrowPointsSize.Width;

}

}


/// <summary>

/// Create any gdi objects required for drawing.

/// </summary>

private void CreateGdiObjects()

{

if (m_font == null)

m_font = new Font(Const.FontName, Const.FontSize, FontStyle.Regular);

// days grid


if (m_brushCur == null || m_brushCur.Color != this.ForeColor)

{

m_brushCur = new SolidBrush(this.ForeColor);

}

if (m_brushCurRed == null)

m_brushCurRed = new SolidBrush(System.Drawing.Color.Red);

if (m_brushOther == null)

m_brushOther = new SolidBrush(SystemColors.GrayText);

if (m_brushOtherRed == null)

m_brushOtherRed = new SolidBrush(System.Drawing.Color.LightSalmon);


if (m_brushSelBack == null)

m_brushSelBack = new SolidBrush(SystemColors.Highlight);


if (m_brushSelText == null)

m_brushSelText = new SolidBrush(SystemColors.HighlightText);


if (m_penHoverBox == null)

m_penHoverBox = new Pen(SystemColors.GrayText);

// caption

if (m_brushCaptionBack == null)

m_brushCaptionBack = new SolidBrush(SystemColors.ActiveCaption);

if (m_brushCaptionText == null)

m_brushCaptionText = new SolidBrush(SystemColors.ActiveCaptionText);

if (m_fontCaption == null)

m_fontCaption = new Font(Const.FontName, Const.FontSize, FontStyle.Bold);


// general

if (m_brushBack == null || m_brushBack.Color != this.BackColor)

m_brushBack = new SolidBrush(this.BackColor);

if (m_penBack == null || m_penBack.Color != this.BackColor)

m_penBack = new Pen(this.BackColor);


if (m_brushFrame == null)

m_brushFrame = new SolidBrush(SystemColors.WindowFrame);


if (m_penFrame == null)

m_penFrame = new Pen(SystemColors.WindowFrame);

}

/// <summary>

/// Update the current selection with the specified date.

/// </summary>

private void UpdateCurSel(DateTime newDate)

{

if (newDate < this.m_minDate)

{

newDate = this.m_minDate;

AllowClose = false;

}

else if (newDate > this.m_maxDate)

{

newDate = this.m_maxDate;

AllowClose = false;

}

else

{

AllowClose = true;

}

// see if should raise ValueChanged event

bool raiseEvent = (m_curSel != newDate) ? true : false;


// store new date selection

m_curSel = newDate;

m_hoverSel = m_curSel;


// repaint

Invalidate();

Update();

// raise ValueChanged event

if (this.ValueChanged != null && raiseEvent)

ValueChanged(this, EventArgs.Empty);


}


/// <summary>

/// Return index into days array for the specified date.

/// </summary>

private int GetDayIndex(DateTime date)

{

TimeSpan span = date.Subtract(m_firstDate);

return (int)span.TotalDays;

}

/// <summary>

/// Return index into the days array for the specified coordinates.

/// </summary>

private int GetDayIndex(int x, int y)

{

// see if in the day grid bounding rectangle

Rectangle rc = new Rectangle(

0, Const.DaysGrid.Y,

Const.NumCols * Const.DaysCell.Width,

Const.BottomLabelsPos.Y);

if (!rc.Contains(x, y))

return -1;


// calculate the index

return (x / Const.DaysCell.Width) +

(((y-Const.DaysGrid.Y) / (Const.DaysCell.Height+1)) * Const.NumCols);

}

/// <summary>

/// Update the cell that has the hover mark.

/// </summary>

private void UpdateHoverCell(int x, int y)

{

// calculate index into grid and then update the cell

int index = GetDayIndex(x, y);

UpdateHoverCell(index);

}


/// <summary>

/// Update the cell that has the hover mark. Call CreateGraphics

/// instead of invalidating for better performance.

/// </summary>

private void UpdateHoverCell(int newIndex)

{

// see if over the days grid

if (newIndex < 0 || newIndex >= m_days.Length)

{

// outside of grid, erase current hover mark

Graphics g = this.CreateGraphics();

DrawHoverSelection(g, m_hoverSel, false);

DrawTodaySelection(g);

g.Dispose();


m_hoverSel = DateTime.MinValue;

return;

}

// see if hover date has changed

if (m_hoverSel != m_days[newIndex])

{

// earase old hover mark and draw new mark

Graphics g = this.CreateGraphics();

DrawHoverSelection(g, m_hoverSel, false);

DrawHoverSelection(g, m_days[newIndex], true);

DrawTodaySelection(g);

g.Dispose();


// store current hover date

m_hoverSel = m_days[newIndex];

}

}


/// <summary>

/// Close the control. Raise the CloseUp event.

/// </summary>

private void Close()

{

if (this.AllowClose == true)

{

if (this.m_yearUpDown.Visible == false)

{

this.Hide();

// raise the CloseUp event

if (this.CloseUp != null)

CloseUp(this, EventArgs.Empty);

}

}

}

}


#endregion

}


 
and again i cannot believe, that we have to code a new DT-Picker and have
to deal with such problems... we have the year 2004 :-) ... strange..
really

i said something about this before... there's a ready and stable DT-picker
on any device... but not in .NET ... and now... now it happens that the new
picker is working different, has bugs, etc..

i have fixed something to code it in german too (and to let the week start
with monday). If you change it to german format, you will get the 12 months
correctly. Also i have change the header from "SMTWTFS" to "MDMDFSS" to fit
the german names

of course it would be better to have ONE DT-Picker that will fit the
system-parameters (start of week, the language and the format) - but i
don't have to time to do it now

now i will look to fix that crash when using the the Month-Selector instead
of the foreward, backward buttons too... :-S ... crazy crazy...

Boris
 
Thanks for the correction of the class. I think there are more people who
corrected the problem with the Popup showing twelve Januaries.

The original version still has one more bug: The DateTimePopup does not
disappear without picking a date. This is espacially strange using
TabControls. I made my Popup disappering when losing focus, but now the user
can't switch the year with the small arrows which is no problem in our
application.

Marc

Mark Johnson said:
Yes, this would interest me (E-Mail is below).
Since the Original version does not work at all on an non US-Cultures (like
Canada/UK-English,German) where the short date is not MM/DD/YYY, I would
like to keep this up to date (the month list shows 12 months with "January"
in the native Language).
This is why my last version can be downloaded from
http://www.mj10777.de/NETFramework/Compact/DateTimePicker/index.htm

I just saw that Canada has a special rule which made it not work in my
version.
The correction of this even simplyfies to code using
uici.DateTimeFormat.MonthNames; to fill the Month MenuItems which would
solve most of problems of the non-US Cultures if used in the original code.
The only thing that must be hardcoded is "Today" and the FirstDayof Week
(when not Sunday) due to
DateTimeFormatInfo.CurrentInfo.FirstDayOfWeek not working on Compact.
My Version also supports Mo, Tu etc. in the native Language when desired.

Mark Johnson, Berlin Germany
(e-mail address removed)


éric said:
if you send me an email I will email you my implementation...
I use min and max date to set maximum or minimum dates for users
Dates beyond the min max appear in red and can not be clicked
I also just added code to check for feb29 as you requested and changed the
text="february"
custom format can be any format you want (within reason) of date format.

Regards,

éric
(e-mail address removed)


Mark Johnson said:
As far as I understand your code:
you are creating two extra Fields with MaxData and MinDate.
you use these to set m_yearUpDown.Minimum and m_yearUpDown.Maximum in
InitYearUpDown.
Otherwise it does not seem to be used.
Since the original code set these valuse with DateTime.MinValue.Year and
DateTime.MaxValue.Year anyway
I don't understand the need of these two extra Fields.
using yyyy-MM-dd
is used only to show the Date in the International format when the Maschine
does not support this, correct?

Mark Johnson, Berlin Germany
(e-mail address removed)

same as MS but with Max date and Min Date as well as using
yyyy-MM-dd
and
fix the bug with picking months on 31st day

using System;

using System.Windows.Forms;

using System.Drawing;



namespace Controls

{

#region DateTimePickerFormat enumeration

/// <summary>

/// Specifies the date and time format the DateTimePicker control
displays.

/// </summary>

internal enum DateTimePickerFormat

{

Long,

Short,

Custom

}

#endregion

#region DateTimePicker class

/// <summary>

/// Manged DateTimePicker control. User can select a day from popup
calendar.

/// </summary>

internal class DateTimePicker : Control

{

class Const

{

// size of the drop arrow on far right

internal static Size DropArrowSize = new Size(7,4);

}


// offscreen bitmap

Bitmap m_bmp;

Graphics m_graphics;

// gdi objects

SolidBrush m_brushFore;

SolidBrush m_brushDisabled;

SolidBrush m_brushFrame;

Pen m_penFrame;


// down arrow coordinates

Point[] m_arrowPoints = new Point[3];


// day picker, displays popup month calendar

DayPickerPopup m_dayPicker = new DayPickerPopup();


// date format, short or long

DateTimePickerFormat m_format = DateTimePickerFormat.Long;

// exposed events

internal event EventHandler ValueChanged;

internal event EventHandler CloseUp;

internal event EventHandler DropDown;



// properties



/// <summary>

/// Gets the maximum date value.

/// </summary>

///

private DateTime minDate = DateTime.MinValue;

private DateTime maxDate = DateTime.MaxValue;

internal DateTime MaxDateTime

{

get { return this.maxDate; }

set

{

if (value <= DateTime.MaxValue)

{

this.maxDate = value;

this.m_dayPicker.MaxDate = value;

}

else

{

this.maxDate = DateTime.MaxValue;

this.m_dayPicker.MaxDate = DateTime.MaxValue;

}

}

}

/// <summary>

/// Gets the minimum date value.

/// </summary>

internal DateTime MinDateTime

{

get { return this.minDate; }

set

{

if (value >= DateTime.MinValue)

{

this.minDate = value;

this.m_dayPicker.MinDate = value;

}

else

{

this.minDate = DateTime.MinValue;

this.m_dayPicker.MinDate = DateTime.MinValue;

}

}

}

/// <summary>

/// Gets or sets the format of the date displayed in the control.

/// </summary>


private string m_CustomFormat = "yyyy-MM-dd";

internal string CustomFormat

{

get { return m_CustomFormat; }

set

{

m_CustomFormat = value;

this.m_dayPicker.DateFormat = value;

Invalidate();

}

}

internal DateTimePickerFormat Format

{

get { return m_format; }

set

{

// update format and repaint

m_format = value;

Invalidate();

}

}

/// <summary>

/// Gets or sets the date value assigned to the control.

/// </summary>

internal DateTime Value

{

// setting the picker value raises the ValueChanged

// event which causes the control to repaint

get { return m_dayPicker.Value; }

set { m_dayPicker.Value = value; }

}

/// <summary>

/// Gets or sets the text associated with this control. Throws a

/// FormatException if the specified text is not a valid date.

/// </summary>

public override String Text

{

get

{

// return date as string in the correct format

switch (m_format)

{

case DateTimePickerFormat.Short :

return this.Value.ToShortDateString();

//break;

case DateTimePickerFormat.Long :

return this.Value.ToLongDateString();

//break;

case DateTimePickerFormat.Custom :

try

{

return this.Value.ToString(this.m_CustomFormat);

}

catch(System.Exception)

{

return this.Value.ToShortDateString();

}

//break;

default :

{

return this.Value.ToShortDateString();

}

}

}

set

{

// update the datetime value

this.Value = DateTime.Parse(value);

}

}



/// <summary>

/// Constructor. Initializes a new instance of the DateTimePicker class.

/// </summary>

internal DateTimePicker()

{

// hookup day picker events

m_dayPicker.CloseUp += new EventHandler(OnDayPickerCloseUp);

m_dayPicker.ValueChanged += new EventHandler(OnDayPickerValueChanged);

}



// drawing methods

protected override void OnEnabledChanged(EventArgs e)

{

this.Refresh();

base.OnEnabledChanged (e);

}

protected override void OnPaint(PaintEventArgs e)

{

// draw to memory bitmap

CreateMemoryBitmap();

CreateGdiObjects();

// init background

m_graphics.Clear(this.BackColor);

// label

Size size = m_graphics.MeasureString(this.Text, this.Font).ToSize();

m_graphics.DrawString(this.Text, this.Font,

this.Enabled ? m_brushFore : m_brushDisabled,

4, (this.Height - size.Height)/2);

// drop arrow

m_graphics.FillPolygon(m_brushFrame, m_arrowPoints);

// frame around control

m_graphics.DrawRectangle(m_penFrame, 0, 0, this.Width-1, this.Height-1);


// blit memory bitmap to screen

e.Graphics.DrawImage(m_bmp, 0, 0);

}

protected override void OnPaintBackground(PaintEventArgs e)

{

// don't pass to base since we paint everything, avoid flashing

}



// events



/// <summary>

/// Show or hide the day picker popup control. Determine the

/// best location to display the day picker.

/// </summary>

protected override void OnMouseDown(MouseEventArgs e)

{

base.OnMouseDown(e);


// add day picker control to the toplevel window

// this allows the control to display on top of

// tabs and other controls

if (m_dayPicker.Parent == null)

this.TopLevelControl.Controls.Add(m_dayPicker);

// intelligently calculate where the day picker should be displayed,

// try to display below the label control, display above the control

// if there is not enough room

Point pos = new Point(this.Left, this.Bottom+1);

// map points to top level window

Point parentPos = this.Parent.PointToScreen(this.Parent.Location);

Point topParentPos =
this.TopLevelControl.PointToScreen(this.Parent.Location);

pos.Offset(parentPos.X - topParentPos.X, parentPos.Y - topParentPos.Y);

// see if there is enough room to display day picker below label

if ((pos.Y + m_dayPicker.Size.Height + 320 -
ECASOFT.Controls.Static.SIP.VisibleDesktop.Bottom) >

this.TopLevelControl.ClientRectangle.Height)

{

// there is not enough room, try displaying above the label

pos.Y -= (this.Height + m_dayPicker.Size.Height + 2);

if (pos.Y < 0)

{

// there was not enough room, display at bottom of screen

pos.Y = (this.TopLevelControl.ClientRectangle.Height -

m_dayPicker.Size.Height - 320 +
ECASOFT.Controls.Static.SIP.VisibleDesktop.Bottom +

(26 * Convert.ToInt32(ECASOFT.Controls.Static.SIP.Enabled)));

}

}

try

{

if (((System.Windows.Forms.Form)
this.TopLevelControl).FormBorderStyle
==
System.Windows.Forms.FormBorderStyle.None)

{

pos.Y = 0;

}

}

catch{}


// try displaying aligned with the label control

if ((pos.X + m_dayPicker.Size.Width) >
this.TopLevelControl.ClientRectangle.Width)

pos.X = (this.TopLevelControl.ClientRectangle.Width -
m_dayPicker.Size.Width);

// display or hide the day picker control

m_dayPicker.Display(

!m_dayPicker.Visible, pos.X, pos.Y,

this.BackColor, this.ForeColor);


// raise the DropDown or CloseUp event

if (m_dayPicker.Visible && this.DropDown != null)

this.DropDown(this, EventArgs.Empty);


if (!m_dayPicker.Visible && this.CloseUp != null)

this.CloseUp(this, EventArgs.Empty);

}


/// <summary>

/// CloseUp event from the day picker control

/// </summary>

private void OnDayPickerCloseUp(object sender, System.EventArgs e)

{

// pass to our container

if (this.CloseUp != null)

this.CloseUp(this, EventArgs.Empty);

}

/// <summary>

/// ValueChanged event from the day picker control

/// </summary>

private void OnDayPickerValueChanged(object sender, System.EventArgs e)

{

// repaint to display the new value

Invalidate();


// pass along to our container

if (this.ValueChanged != null)

this.ValueChanged(this, e);

}



// helper methods



/// <summary>

/// Create offsceeen bitmap. This bitmap is used for double-buffering

/// to prevent flashing.

/// </summary>

private void CreateMemoryBitmap()

{

if (m_bmp == null || m_bmp.Width != this.Width || m_bmp.Height !=
this.Height)

{

// memory bitmap

m_bmp = new Bitmap(this.Width, this.Height);

m_graphics = Graphics.FromImage(m_bmp);

// calculate down arrow points

m_arrowPoints[0].X = this.Width - Const.DropArrowSize.Width - 4;

m_arrowPoints[0].Y = (this.Height - Const.DropArrowSize.Height+1) / 2;

m_arrowPoints[1].X = m_arrowPoints[0].X + Const.DropArrowSize.Width;

m_arrowPoints[1].Y = m_arrowPoints[0].Y;

m_arrowPoints[2].X = m_arrowPoints[0].X + (Const.DropArrowSize.Width/2);

m_arrowPoints[2].Y = m_arrowPoints[0].Y + Const.DropArrowSize.Height;

}

}


/// <summary>

/// Create GDI objects required to paint the control.

/// </summary>

private void CreateGdiObjects()

{

// window frame brush

if (m_brushFrame == null)

m_brushFrame = new SolidBrush(SystemColors.WindowFrame);


// window frame pen

if (m_penFrame == null)

m_penFrame = new Pen(SystemColors.WindowFrame);

// fore color brush, the .Net CF does not support OnForeColorChanged,

// so we detect if the forecolor changed here

if (m_brushFore == null || m_brushFore.Color != this.ForeColor)

m_brushFore = new SolidBrush(this.ForeColor);


// disabled brush

if (m_brushDisabled == null)

m_brushDisabled = new SolidBrush(SystemColors.GrayText);

}

}


#endregion


#region DayPickerPopup class

/// <summary>

/// Displays a calendar that allows user to select a new date.

/// Displays box around today and user can hover over dates.

/// Allows quick access to month with month context menu and year

/// with numeric updown control.

/// </summary>

class DayPickerPopup : Control

{

class Const

{

// font for caption, days of week and days

internal const string FontName = "Arial";

internal const int FontSize = 9;


// location and size of different elements in calendar

internal const int ControlWidth = 164;

internal const int CaptionHeight = 28;

internal static Point DaysGrid = new Point(6, 43);

internal static Size DaysCell = new Size(23, 14);

internal const int NumCols = 7;

internal const int NumRows = 6;


// arrow buttons

internal static Size ArrowButtonOffset = new Size(6, 6);

internal static Size ArrowButtonSize = new Size(20, 15);

internal static Size ArrowPointsOffset = new Size(13, 9);

internal static Size ArrowPointsSize = new Size(5, 10);


// bottom today label

internal static Point BottomLabelsPos = new Point(6, 135);

internal const int BottomLabelHeight = 12;

}

// exposed events

internal event EventHandler CloseUp;

internal event EventHandler ValueChanged;


// memory bitmap to prevent flashing

Bitmap m_bmp;

Graphics m_graphics;

// gdi objects

Font m_font;

// days

SolidBrush m_brushCur;

SolidBrush m_brushOther;

SolidBrush m_brushCurRed;

SolidBrush m_brushOtherRed;

SolidBrush m_brushSelBack;

SolidBrush m_brushSelText;

Pen m_penHoverBox;

// caption

Font m_fontCaption;

SolidBrush m_brushCaptionBack;

SolidBrush m_brushCaptionText;


// general

SolidBrush m_brushBack;

Pen m_penBack;

SolidBrush m_brushFrame;

Pen m_penFrame;

bool AllowClose = true;


// store dates; today, current selection, current hover

// and the first date in the calendar

DateTime m_today = DateTime.Today;

DateTime m_curSel = DateTime.Today;

DateTime m_hoverSel = DateTime.Today;

DateTime m_firstDate;

DateTime m_maxDate = DateTime.MaxValue;

DateTime m_minDate = DateTime.MinValue;

string m_format = "yyyy-MM-dd";

// if capturing mouse events (hovering over days)

bool m_captureMouse=false;

// cache calendar for better performance, each DateTime

// structure if only 8 bytes

int m_curMonth = -1;

int m_curYear = -1;

DateTime[] m_days = new DateTime[42];

// caption controls; user can click on month and year

// in caption to quickly change values

ContextMenu m_monthMenu;

NumericUpDown m_yearUpDown;

// hit testing

Rectangle m_rcLeftButton = Rectangle.Empty;

Rectangle m_rcRightButton = Rectangle.Empty;

Rectangle m_rcMonth = Rectangle.Empty;

Rectangle m_rcYear = Rectangle.Empty;


// arrow button coordinates

Point[] m_leftArrowPoints = new Point[3];

Point[] m_rightArrowPoints = new Point[3];



// properties



/// <summary>

/// Selected date.

/// </summary>

internal DateTime Value

{

get { return m_curSel; }

set

{

if (value != m_curSel)

UpdateCurSel(value);

}

}

internal string DateFormat

{

get { return m_format; }

set { m_format = value; }

}

internal DateTime MinDate

{

get {return this.m_minDate;}

set

{

this.m_minDate = value;

this.m_yearUpDown.Minimum = value.Year;

}

}


internal DateTime MaxDate

{

get {return this.m_maxDate;}

set

{

this.m_maxDate = value;

this.m_yearUpDown.Maximum = value.Year;

}

}


/// <summary>

/// Constructor.

/// </summary>

internal DayPickerPopup()

{

this.Initialize();

}

// internal DayPickerPopup(DateTime minDate, DateTime maxDate)

// {

// this.Initialize();

// }

protected override void OnLostFocus(EventArgs e)

{

base.OnLostFocus (e);

this.AllowClose = true;

this.Close();

}

private void Initialize()

{

// init controls that popup when click on the

// month or year in the caption

InitMonthContextMenu();

InitYearUpDown();


// init display properties

this.Visible = false;

this.Location = new Point(0, 0);

this.Size = new Size(Const.ControlWidth,

Const.BottomLabelsPos.Y + Const.BottomLabelHeight + 5);

this.m_yearUpDown.ReadOnly = true;

this.m_yearUpDown.Maximum = this.m_maxDate.Year;

this.m_yearUpDown.Minimum = this.m_minDate.Year;

}




// internal methods



/// <summary>

/// Show or hide the calendar.

/// </summary>

internal void Display(bool visible, int x, int y, Color backColor, Color
foreColor)

{

if (visible)

{

// initialize properties if being displayed

m_captureMouse = false;

m_yearUpDown.Hide();


this.BackColor = backColor;

this.ForeColor = foreColor;

this.Left = x;

this.Top = y;

this.BringToFront();

this.Focus();


// default to hovering over the current selection

m_hoverSel = m_curSel;

}

// hide or show the calendar

this.Visible = visible;

}



// drawing methods



protected override void OnPaint(PaintEventArgs e)

{

// draw to memory bitmap

CreateMemoryBitmap();

CreateGdiObjects();

// calculate the fist date in the days grid, this is used

// to draw the previous month days, the current month days,

// and any days in the next month

CalculateFirstDate();

// init the background

m_graphics.Clear(this.BackColor);

// draw elements of the calendar

// the caption and days of week

DrawCaption(m_graphics);

DrawDaysOfWeek(m_graphics);


// the days grid and different selections

DrawDays(m_graphics);

DrawCurSelection(m_graphics);

DrawHoverSelection(m_graphics, m_hoverSel, true);

DrawTodaySelection(m_graphics);

// the today label at the bottom

DrawBottomLabels(m_graphics);

// frame around the control

m_graphics.DrawRectangle(m_penFrame, 0, 0,

this.Width-1, this.Height-1);


// blit memory bitmap to screen

e.Graphics.DrawImage(m_bmp, 0, 0);

}

protected override void OnPaintBackground(PaintEventArgs e)

{

// don't pass to base since we paint everything, avoid flashing

}


/// <summary>

/// Draw caption; current month, current year,

/// left and right arrow buttons.

/// </summary>

private void DrawCaption(Graphics g)

{

// back area

g.FillRectangle(m_brushCaptionBack, 0, 0, this.Width,
Const.CaptionHeight);


// draw the caption centered in the area

string text = m_curSel.ToString("MMMM yyyy");

Size totalSize = g.MeasureString(text, m_fontCaption).ToSize();

int x = (this.Width - totalSize.Width) / 2;

int y = (Const.CaptionHeight - totalSize.Height) / 2;

g.DrawString(text, m_fontCaption, m_brushCaptionText, x, y);

// calculate the bounding rectangle for each element (the

// month and year) so we can detect if the user clicked on

// either element later


// calculate the month bounding rectangle

text = m_curSel.ToString("MMMM");

Size size = g.MeasureString(text, m_fontCaption).ToSize();

m_rcMonth.X = x;

m_rcMonth.Y = y;

m_rcMonth.Width = size.Width;

m_rcMonth.Height = size.Height;


// calculate the year bounding rectangle

text = m_curSel.ToString("yyyy");

size = g.MeasureString(text, m_fontCaption).ToSize();

m_rcYear.X = x + totalSize.Width - size.Width;

m_rcYear.Y = y;

m_rcYear.Width = size.Width;

m_rcYear.Height = size.Height;


// draw the left arrow button

g.FillRectangle(m_brushBack, m_rcLeftButton);

g.DrawRectangle(m_penFrame, m_rcLeftButton);

g.FillPolygon(m_brushFrame, m_leftArrowPoints);


// draw the right arrow button

g.FillRectangle(m_brushBack, m_rcRightButton);

g.DrawRectangle(m_penFrame, m_rcRightButton);

g.FillPolygon(m_brushFrame, m_rightArrowPoints);

}

/// <summary>

/// Draw days of week header.

/// </summary>

private void DrawDaysOfWeek(Graphics g)

{

const string dow = "SMTWTFS";


// calculate where to draw days of week

Point pos = new Point(Const.DaysGrid.X+3, Const.CaptionHeight);


// go through and draw each character

foreach (char c in dow)

{

g.DrawString(c.ToString(), m_fontCaption, m_brushCaptionBack, pos.X,
pos.Y);

pos.X += Const.DaysCell.Width;

}


// separator line

g.DrawLine(m_penFrame, Const.DaysGrid.X, Const.DaysGrid.Y-1,

this.Width - Const.DaysGrid.X, Const.DaysGrid.Y-1);

}

/// <summary>

/// Draw days in the grid. Recalculate and cache days if the

/// month or year changed.

/// </summary>

private void DrawDays(Graphics g)

{

// see if need to calculate new set of days

if (m_curSel.Month != m_curMonth || m_curSel.Year != m_curYear)

{

// the month of year changed, calculate and cache new set of days

CalculateDays();

m_curMonth = m_curSel.Month;

m_curYear = m_curSel.Year;

}

// starting point of grid

Point pos = Const.DaysGrid;


// any extra pixels (used for single digit numbers)

int extra;


// loop through and draw each day in the grid

for (int y=0; y < Const.NumRows; y++)

{

for (int x=0; x < Const.NumCols; x++)

{

// get the date from the cache

DateTime display = m_days[(y*7)+x];


// see if requires extra pixels (single digit day)

extra = (display.Day < 10) ? 4 : 0;


if (display > this.m_maxDate || display < this.m_minDate)

{

g.DrawString(display.Day.ToString(), m_font,

(display.Month == m_curMonth) ? m_brushCurRed : m_brushOtherRed,

pos.X + extra, pos.Y);

}

else

{

g.DrawString(display.Day.ToString(), m_font,

(display.Month == m_curMonth) ? m_brushCur : m_brushOther,

pos.X + extra, pos.Y);

}

// update position within the grid

pos.X += Const.DaysCell.Width;

}


// update position within the grid

pos.X = Const.DaysGrid.X;

pos.Y += Const.DaysCell.Height + 1;

}

}


/// <summary>

/// Draw the specified day.

/// </summary>

private void DrawDay(Graphics g, DateTime day, bool selected)

{

// get the position of this cell in the grid

int index = GetDayIndex(day);

Point pos = GetDayCellPosition(index);

// cell background

g.FillRectangle(selected ? m_brushSelBack : m_brushBack,

pos.X - 5, pos.Y, Const.DaysCell.Width, Const.DaysCell.Height);


// extra space if single digit

if (day.Day < 10)

pos.X += 4;


// the day

g.DrawString(day.Day.ToString(), m_font,

selected ? m_brushSelText : m_brushCur,

pos.X, pos.Y);

}


/// <summary>

/// Draw the currently selected day.

/// </summary>

private void DrawCurSelection(Graphics g)

{

// calculate the coordinates of the current cell

int index = GetDayIndex(m_curSel);

Point pos = GetDayCellPosition(index);

// background

m_graphics.FillRectangle(m_brushSelBack, pos.X - 5, pos.Y,

Const.DaysCell.Width, Const.DaysCell.Height);


// extra space for single digit days

if (m_curSel.Day < 10)

pos.X += 4;


// the day

m_graphics.DrawString(m_curSel.Day.ToString(),

m_font, m_brushSelText, pos.X, pos.Y);

}

/// <summary>

/// Draws of erases the hover selection box.

/// </summary>

private void DrawHoverSelection(Graphics g, DateTime date, bool draw)

{

// see if hovering over a cell, return right away

// if outside of the grid area

int index = GetDayIndex(date);

if (index < 0 || index >= m_days.Length)

return;

// get the coordinates of cell

Point pos = GetDayCellPosition(index);


// draw or erase the hover selection

g.DrawRectangle(draw ? m_penHoverBox : m_penBack,

pos.X - 5, pos.Y, Const.DaysCell.Width, Const.DaysCell.Height);

}

/// <summary>

/// Draw box around today on grid.

/// </summary>

private void DrawTodaySelection(Graphics g)

{

// see if today is visible in the current grid

int index = GetDayIndex(m_today);

if (index < 0 || index >= m_days.Length)

return;


// only draw on current month

if (m_today.Month != m_curSel.Month)

return;

// today is visible, draw box around cell

Point pos = GetDayCellPosition(index);

g.DrawRectangle(m_penFrame, pos.X-5, pos.Y,

Const.DaysCell.Width, Const.DaysCell.Height);

g.DrawRectangle(m_penFrame, pos.X-4, pos.Y+1,

Const.DaysCell.Width-2, Const.DaysCell.Height-2);

}

/// <summary>

/// Draw the today label at bottom of calendar.

/// </summary>

private void DrawBottomLabels(Graphics g)

{

// draw today string, don't store bounding rectangle since

// hit testing is the entire width of the calendar

try

{

string text = string.Format("Today: {0}", m_today.ToString(m_format));

g.DrawString(text, this.m_fontCaption, this.m_brushCur,

Const.BottomLabelsPos.X, Const.BottomLabelsPos.Y);

}

catch (System.Exception)

{

string text = string.Format("Today: {0}", m_today.ToShortDateString());

g.DrawString(text, this.m_fontCaption, this.m_brushCur,

Const.BottomLabelsPos.X, Const.BottomLabelsPos.Y);

}


}



// events



/// <summary>

/// Determine what area was taped (clicked) and take the appropriate

/// action. If no items were taped, see if should start tracking mouse.

/// </summary>

protected override void OnMouseDown(MouseEventArgs e)

{

base.OnMouseDown(e);


// see if should hide the year updown control

if (m_yearUpDown.Visible)

{

if (!m_yearUpDown.Bounds.Contains(e.X, e.Y))

{

// user clicked outside of updown control,

// update grid with the new year specified

// in the updown control

OnYearUpDownValueChanged(null, EventArgs.Empty);

m_yearUpDown.Hide();

this.Focus();

}

}


// left arrow button

if (m_rcLeftButton.Contains(e.X, e.Y))

{

// display previous month

UpdateCurSel(m_curSel.AddMonths(-1));

return;

}

// right arrow button

if (m_rcRightButton.Contains(e.X, e.Y))

{

// display the next month

UpdateCurSel(m_curSel.AddMonths(1));

return;

}

// month part of caption

if (m_rcMonth.Contains(e.X, e.Y))

{

// display the context menu, the days grid is updated

// if the user selects a new month

DisplayMonthMenu(e.X, e.Y);

return;

}

// year part of caption

if (m_rcYear.Contains(e.X, e.Y))

{

// display the number updown year control, the days

// grid is updated if the user selects a new year

DisplayYearUpDown(e.X, e.Y);

return;

}


// today label

if (e.Y >= Const.BottomLabelsPos.Y)

{

// select today in grid

UpdateCurSel(m_today);

this.Close();

return;

}


// otherwise, start tracking mouse movements

m_captureMouse = true;

UpdateHoverCell(e.X, e.Y);

}


/// <summary>

/// User is done hovering over days. Set the current day

/// if they stopped on a day, otherwise they let up outside

/// of the day grid.

/// </summary>

protected override void OnMouseUp(MouseEventArgs e)

{

base.OnMouseUp(e);

if (m_captureMouse)

{

// done capturing mouse movements

m_captureMouse = false;


// update the current selection to the day

// last hovered over

int index = GetDayIndex(m_hoverSel);

if (index >= 0 && index < m_days.Length)

{

UpdateCurSel(m_hoverSel);

this.Close();


}

else

{

// canceled hovering by moving outside of grid

UpdateCurSel(m_curSel);

}

}

}


/// <summary>

/// Update the hover cell (mouse-over) if necessary.

/// </summary>

protected override void OnMouseMove(MouseEventArgs e)

{

base.OnMouseMove(e);

// update the hover cell

if (m_captureMouse)

UpdateHoverCell(e.X, e.Y);

}

/// <summary>

/// User can navigate days with the hardware device jog buttons.

/// </summary>

protected override void OnKeyUp(KeyEventArgs e)

{

// holds the number of days to change

int days = 0;

switch (e.KeyCode)

{

case Keys.Left:

days = -1;

break;

case Keys.Right:

days = 1;

break;

case Keys.Up:

days = -7;

break;

case Keys.Down:

days = 7;

break;

case Keys.Return:

this.Close();

break;

}


// see if pressed any of the jog buttons

if (days != 0)

{

// calculate the new day that should be selected

DateTime newDay = m_curSel.AddDays(days);

if (m_curSel.Month != newDay.Month)

{

// user navigated to previous or next month

UpdateCurSel(newDay);

}

else

{

// the month did not change so update the current

// selection by calling CreateGraphics (instead of

// invalidating and repainting) for better performance

Graphics g = this.CreateGraphics();

DrawDay(g, m_curSel, false);

DrawDay(g, newDay, true);

g.Dispose();

m_curSel = newDay;


// update hover selection

UpdateHoverCell(GetDayIndex(m_curSel));


// raise the ValueChanged event

if (this.ValueChanged != null)

ValueChanged(this, EventArgs.Empty);

}

}

}

/// <summary>

/// Event from the month context menu. Put a checkmark

/// next to the currently selected month.

/// </summary>

private void OnMonthMenuPopup(System.Object sender, System.EventArgs e)

{

// clear all checks

foreach (MenuItem item in m_monthMenu.MenuItems)

item.Checked = false;


// check the current month

if (m_curMonth > 0 && m_curMonth <= 12)

m_monthMenu.MenuItems[m_curMonth-1].Checked = true;

}

/// <summary>

/// Event from the month context menu. Update the current selection

/// to the month that was clicked.

/// </summary>

private void OnMonthMenuClick(System.Object sender, System.EventArgs e)

{

// determine what menu item was clicked

MenuItem item = sender as MenuItem;

if (item != null)

{

try

{

// update the current date selection

DateTime newDate = DateTime.Parse(

string.Format("{0}, {1} {2}",

item.Text, m_curSel.Day, m_curSel.Year));

UpdateCurSel(newDate);

}

catch

{

int d = 30;

if (item.Text == "February")

{

// could check if 29 exists that year but why bother

d = 28;

}

// update the current date selection

DateTime newDate = DateTime.Parse(

string.Format("{0}, {1} {2}",

item.Text, d, m_curSel.Year));

UpdateCurSel(newDate);

}

}

}

/// <summary>

/// Event from year updown control. Update current selection

/// with the year in the updown control.

/// </summary>

private void OnYearUpDownValueChanged(System.Object sender,
System.EventArgs
e)

{

int d = this.Value.Day;

try

{

try

{

DateTime newDate = new DateTime(

(int)m_yearUpDown.Value,

m_curSel.Month, m_curSel.Day);

}

catch

{

if (this.Value.Month == 2 & d == 29)

{

d = 28;

}

}


// only want to update the current selection

// when the user is interacting with the

// control (when it's visible)

if (m_yearUpDown.Visible)

{

if (Convert.ToDateTime(Convert.ToString(this.m_yearUpDown.Value) +
"-"
(Convert.ToDateTime(Convert.ToString(this.m_yearUpDown.Value)
+
"-"
+

Convert.ToString(this.Value.Month) + "-" + Convert.ToString(d)) <

this.m_minDate)

{

this.m_yearUpDown.Hide();

this.UpdateCurSel(this.m_minDate);


}

else

{

// update the current selection to the year

DateTime newDate = new DateTime(

(int)m_yearUpDown.Value,

m_curSel.Month, d);


UpdateCurSel(newDate);

}

}

}

catch

{


// catch if the user entered an invalid year

// in the control

m_yearUpDown.Value = m_curSel.Year;

}

}


// helper methods



/// <summary>

/// Initialize the numeric updown control that is displayed

/// when the year part of the caption is clicked.

/// </summary>

private void InitYearUpDown()

{

// create the numeric updown control

m_yearUpDown = new NumericUpDown();

this.Controls.Add(m_yearUpDown);


// hookup the valuechanged event

m_yearUpDown.ValueChanged += new EventHandler(OnYearUpDownValueChanged);

// init other properties

m_yearUpDown.Minimum = DateTime.MinValue.Year;

m_yearUpDown.Maximum = DateTime.MaxValue.Year;

m_yearUpDown.Visible = false;

}


/// <summary>

/// Display the numeric updown year control.

/// </summary>

private void DisplayYearUpDown(int x, int y)

{

// init year to currently selected year

m_yearUpDown.Text = m_curSel.Year.ToString();

// init the position and size of the control

m_yearUpDown.Left = m_rcYear.Left - 3;

m_yearUpDown.Top = m_rcYear.Top - 3;

m_yearUpDown.Width = m_rcYear.Width + 30;

m_yearUpDown.Height = m_rcYear.Height + 6;


m_yearUpDown.Show();

}

/// <summary>

/// Initialize the context menu that is displayed when the

/// user clicks the month part of the caption.

/// </summary>

private void InitMonthContextMenu()

{

// create a menu that contains list of months

m_monthMenu = new ContextMenu();

for (int i=1; i <= 12; i++)

{

// create new menu item and hookup the click event

MenuItem item = new MenuItem();

m_monthMenu.MenuItems.Add(item);

item.Click += new EventHandler(OnMonthMenuClick);

item.Text = DateTime.Parse(

string.Format("{0}/1/2000", i)).ToString("MMMM");

}

// hookup popup event so can check the current month

m_monthMenu.Popup += new EventHandler(OnMonthMenuPopup);

}

/// <summary>

/// Show the month context menu. The current month

/// is checked in the popup event.

/// </summary>

private void DisplayMonthMenu(int x, int y)

{

m_monthMenu.Show(this, new Point(x, y));

}


/// <summary>

/// Calculates the date for the first cell in the days

/// grid. Always show at least one day of previous month.

/// </summary>

private void CalculateFirstDate()

{

m_firstDate = new DateTime(m_curSel.Year, m_curSel.Month, 1);

if (m_firstDate.DayOfWeek == DayOfWeek.Sunday)

m_firstDate = m_firstDate.AddDays(-7);

else

m_firstDate = m_firstDate.AddDays(-(int)m_firstDate.DayOfWeek);

}


/// <summary>

/// Calculate and cache the days that are displayed in the calendar.

/// The days are cached for better performance, each day is only 8 bytes.

/// </summary>

private void CalculateDays()

{

for (int i=0; i < m_days.Length; i++)

m_days = m_firstDate.AddDays(i);

}


/// <summary>

/// Return the upper left x / y coordinates for the specified index.

/// </summary>

private Point GetDayCellPosition(int index)

{

// calculate the x and y coordinates for the specified index

return new Point(

Const.DaysGrid.X + (((int)index % Const.NumCols) * Const.DaysCell.Width),

Const.DaysGrid.Y + (((int)index / Const.NumCols) *
(Const.DaysCell.Height+1)));

}


/// <summary>

/// Create memory bitmap for double-buffering.

/// </summary>

private void CreateMemoryBitmap()

{

// see if need to create memory bitmap

if (m_bmp == null || m_bmp.Width != this.Width || m_bmp.Height !=
this.Height)

{

// create the memory bitmap

m_bmp = new Bitmap(this.Width, this.Height);

m_graphics = Graphics.FromImage(m_bmp);


// calculate the coordinates of the left and right

// arrow buttons now instead of each time paint


// left button

m_rcLeftButton = new Rectangle(

Const.ArrowButtonOffset.Width, Const.ArrowButtonOffset.Height,

Const.ArrowButtonSize.Width, Const.ArrowButtonSize.Height);


// right button

m_rcRightButton = new Rectangle(

this.Width - Const.ArrowButtonOffset.Width - Const.ArrowButtonSize.Width -
1,

Const.ArrowButtonOffset.Height, Const.ArrowButtonSize.Width,
Const.ArrowButtonSize.Height);


// left arrow in button

m_leftArrowPoints[0].X = Const.ArrowPointsOffset.Width;

m_leftArrowPoints[0].Y = Const.ArrowPointsOffset.Height +
(Const.ArrowPointsSize.Height/2);

m_leftArrowPoints[1].X = m_leftArrowPoints[0].X +
Const.ArrowPointsSize.Width;

m_leftArrowPoints[1].Y = Const.ArrowPointsOffset.Height;

m_leftArrowPoints[2].X = m_leftArrowPoints[1].X;

m_leftArrowPoints[2].Y = m_leftArrowPoints[1].Y +
Const.ArrowPointsSize.Height;

// right arrow in button

m_rightArrowPoints = (Point[])m_leftArrowPoints.Clone();

m_rightArrowPoints[0].X = this.Width - Const.ArrowPointsOffset.Width;

m_rightArrowPoints[1].X = m_rightArrowPoints[2].X =

m_rightArrowPoints[0].X - Const.ArrowPointsSize.Width;

}

}


/// <summary>

/// Create any gdi objects required for drawing.

/// </summary>

private void CreateGdiObjects()

{

if (m_font == null)

m_font = new Font(Const.FontName, Const.FontSize, FontStyle.Regular);

// days grid


if (m_brushCur == null || m_brushCur.Color != this.ForeColor)

{

m_brushCur = new SolidBrush(this.ForeColor);

}

if (m_brushCurRed == null)

m_brushCurRed = new SolidBrush(System.Drawing.Color.Red);

if (m_brushOther == null)

m_brushOther = new SolidBrush(SystemColors.GrayText);

if (m_brushOtherRed == null)

m_brushOtherRed = new SolidBrush(System.Drawing.Color.LightSalmon);


if (m_brushSelBack == null)

m_brushSelBack = new SolidBrush(SystemColors.Highlight);


if (m_brushSelText == null)

m_brushSelText = new SolidBrush(SystemColors.HighlightText);


if (m_penHoverBox == null)

m_penHoverBox = new Pen(SystemColors.GrayText);

// caption

if (m_brushCaptionBack == null)

m_brushCaptionBack = new SolidBrush(SystemColors.ActiveCaption);

if (m_brushCaptionText == null)

m_brushCaptionText = new SolidBrush(SystemColors.ActiveCaptionText);

if (m_fontCaption == null)

m_fontCaption = new Font(Const.FontName, Const.FontSize, FontStyle.Bold);


// general

if (m_brushBack == null || m_brushBack.Color != this.BackColor)

m_brushBack = new SolidBrush(this.BackColor);

if (m_penBack == null || m_penBack.Color != this.BackColor)

m_penBack = new Pen(this.BackColor);


if (m_brushFrame == null)

m_brushFrame = new SolidBrush(SystemColors.WindowFrame);


if (m_penFrame == null)

m_penFrame = new Pen(SystemColors.WindowFrame);

}

/// <summary>

/// Update the current selection with the specified date.

/// </summary>

private void UpdateCurSel(DateTime newDate)

{

if (newDate < this.m_minDate)

{

newDate = this.m_minDate;

AllowClose = false;

}

else if (newDate > this.m_maxDate)

{

newDate = this.m_maxDate;

AllowClose = false;

}

else

{

AllowClose = true;

}

// see if should raise ValueChanged event

bool raiseEvent = (m_curSel != newDate) ? true : false;


// store new date selection

m_curSel = newDate;

m_hoverSel = m_curSel;


// repaint

Invalidate();

Update();

// raise ValueChanged event

if (this.ValueChanged != null && raiseEvent)

ValueChanged(this, EventArgs.Empty);


}


/// <summary>

/// Return index into days array for the specified date.

/// </summary>

private int GetDayIndex(DateTime date)

{

TimeSpan span = date.Subtract(m_firstDate);

return (int)span.TotalDays;

}

/// <summary>

/// Return index into the days array for the specified coordinates.

/// </summary>

private int GetDayIndex(int x, int y)

{

// see if in the day grid bounding rectangle

Rectangle rc = new Rectangle(

0, Const.DaysGrid.Y,

Const.NumCols * Const.DaysCell.Width,

Const.BottomLabelsPos.Y);

if (!rc.Contains(x, y))

return -1;


// calculate the index

return (x / Const.DaysCell.Width) +

(((y-Const.DaysGrid.Y) / (Const.DaysCell.Height+1)) * Const.NumCols);

}

/// <summary>

/// Update the cell that has the hover mark.

/// </summary>

private void UpdateHoverCell(int x, int y)

{

// calculate index into grid and then update the cell

int index = GetDayIndex(x, y);

UpdateHoverCell(index);

}


/// <summary>

/// Update the cell that has the hover mark. Call CreateGraphics

/// instead of invalidating for better performance.

/// </summary>

private void UpdateHoverCell(int newIndex)

{

// see if over the days grid

if (newIndex < 0 || newIndex >= m_days.Length)

{

// outside of grid, erase current hover mark

Graphics g = this.CreateGraphics();

DrawHoverSelection(g, m_hoverSel, false);

DrawTodaySelection(g);

g.Dispose();


m_hoverSel = DateTime.MinValue;

return;

}

// see if hover date has changed

if (m_hoverSel != m_days[newIndex])

{

// earase old hover mark and draw new mark

Graphics g = this.CreateGraphics();

DrawHoverSelection(g, m_hoverSel, false);

DrawHoverSelection(g, m_days[newIndex], true);

DrawTodaySelection(g);

g.Dispose();


// store current hover date

m_hoverSel = m_days[newIndex];

}

}


/// <summary>

/// Close the control. Raise the CloseUp event.

/// </summary>

private void Close()

{

if (this.AllowClose == true)

{

if (this.m_yearUpDown.Visible == false)

{

this.Hide();

// raise the CloseUp event

if (this.CloseUp != null)

CloseUp(this, EventArgs.Empty);

}

}

}

}


#endregion

}


 
Mark Johnson said:
Yes, this would interest me (E-Mail is below).
Since the Original version does not work at all on an non US-Cultures (like
Canada/UK-English,German) where the short date is not MM/DD/YYY, I would
like to keep this up to date (the month list shows 12 months with "January"
in the native Language).
This is why my last version can be downloaded from
http://www.mj10777.de/NETFramework/Compact/DateTimePicker/index.htm

I just saw that Canada has a special rule which made it not work in my
version.
The correction of this even simplyfies to code using
uici.DateTimeFormat.MonthNames; to fill the Month MenuItems which would
solve most of problems of the non-US Cultures if used in the original code.
The only thing that must be hardcoded is "Today" and the FirstDayof Week
(when not Sunday) due to
DateTimeFormatInfo.CurrentInfo.FirstDayOfWeek not working on Compact.
My Version also supports Mo, Tu etc. in the native Language when desired.

Mark Johnson, Berlin Germany
(e-mail address removed)


Do you plan on releasing a vb.net version.. This control seems so needed..
I hope microsoft release a proper version in the next CF.
 
Marc Ambrosius said:
Thanks for the correction of the class. I think there are more people who
corrected the problem with the Popup showing twelve Januaries.

The original version still has one more bug: The DateTimePopup does not
disappear without picking a date. This is espacially strange using
TabControls. I made my Popup disappering when losing focus, but now the user
can't switch the year with the small arrows which is no problem in our
application.

Marc

Hmm.. I just notice this too.. quite annoying.. You have the code the
LostFocus event?
 
To be frank, no.
I find VB very confusing and avoid using it.
I would not like to invest the time needed to insure proper wiriting of VB
code
Should someone who understands VB better than me do the basic work of
translating the relativly small portion of code needed, I wold test and
update the results to my homepage.
I hope microsoft release a proper version in the next CF.
Yes, I agree.

90% of my code are inside 2 Language Properties that I have added to the
Original code.
2 Methods have minimal changes, basicly using strings set in the Language
Properties instead of const Strings.
CalculateFirstDate() has been compleatly changed to support FirstDayof Week
logic.
Otherwise no changes were made to the Original code.

Here is the compleate code where changes have been made/included in C#

public class DateTimePicker : Control
{
public DateTimePicker()
{ // mj10777 Language Support
m_dayPicker = new DayPickerPopup(ip_Language);
// hookup day picker events
m_dayPicker.CloseUp += new EventHandler(OnDayPickerCloseUp);
m_dayPicker.ValueChanged += new EventHandler(OnDayPickerValueChanged);
} // public DateTimePicker()

// mj10777 Language Support
private int ip_Language = 1; // English
/// <summary>
/// mj10777 Language Support
/// - I am using the Telefon Codes for the Languages
/// - unsupported languages are defaulted to English
/// - Date Formatting is Maschine specific
/// - I hope that all formats are supported
/// </summary>
public int Language
{
get
{
return ip_Language;
}
set
{
ip_Language = value; // Supported Languages checked in DayPickerPopup
m_dayPicker = new DayPickerPopup(ip_Language);
// hookup day picker events
m_dayPicker.CloseUp += new EventHandler(OnDayPickerCloseUp);
m_dayPicker.ValueChanged += new EventHandler(OnDayPickerValueChanged);
}
}
}
class DayPickerPopup : Control
{
#region mj10777 Language Support
// mj10777 Language Support
private int ip_Language = 1; // English
private int ip_MonthDayPattern= 1; // DD.MM.YYY / DD/MM./YYYY
private string[] s_DayOfWeek;
private string[] s_MonthsOfYear;
public int DayOfWeekC = 2; // Amount of Chars for Day name
private string s_Today = "Today";
private DayOfWeek dow_FirstDayOfWeek;
/// <summary>
/// Language Support
/// - I am using the Telefon Codes for the Languages
/// - unsupported languages are defaulted to English
/// - Date Formatting is Maschine specific
/// - I hope that all formats are supported
/// </summary>
public int i_Language
{
get
{
return ip_Language;
}
set
{
//-- Support for MonthDayPattern needed in InitMonthContextMenu()
// - this is Maschine specific
System.Globalization.CultureInfo uici =
(CultureInfo)System.Globalization.CultureInfo.CurrentUICulture.Clone();
if (uici.DateTimeFormat.MonthDayPattern == "MMMM dd") // US-English and
Swahili(Kenia)
ip_MonthDayPattern= 0; // Month Day Year
if (uici.DateTimeFormat.MonthDayPattern == "dd MMMM") // Most of the
World
ip_MonthDayPattern= 1; // Day Month Year
if ((uici.DateTimeFormat.MonthDayPattern == "d MMMM") || // Polish
(uici.DateTimeFormat.MonthDayPattern == "MMMM d.") || // Hungray
(uici.DateTimeFormat.MonthDayPattern == "d. MMMM") || //
Lettland
(uici.DateTimeFormat.MonthDayPattern == "'den 'd MMMM")) // Sweden
ip_MonthDayPattern= 2; // Year Month Day
String s_Day = "";
// This should show Monday on a German Machine, but shows Sunday which
is not correct.
// - used only here and in CalculateFirstDate()
dow_FirstDayOfWeek = DateTimeFormatInfo.CurrentInfo.FirstDayOfWeek;
// Add your Language here to support the Default
if ((value == 1) || (value == 49))
{
ip_Language = value;
}
else
{ // Set to English if Language is not supported
ip_Language = 1;
}
// Add your Language here to support DayOfWeek and Today, no other
changes are needed.
if (ip_Language == 1) // English
{
s_Today = "Today";
}
if (ip_Language == 49) // German
{
s_Today = "Heute";
// DateTimeFormatInfo.CurrentInfo.FirstDayOfWeek does not work, so set
it Manually
/*
dow_FirstDayOfWeek = DayOfWeek.Tuesday;
dow_FirstDayOfWeek = DayOfWeek.Wednesday;
dow_FirstDayOfWeek = DayOfWeek.Thursday;
dow_FirstDayOfWeek = DayOfWeek.Friday;
dow_FirstDayOfWeek = DayOfWeek.Saturday;
dow_FirstDayOfWeek = DayOfWeek.Sunday;
*/
dow_FirstDayOfWeek = DayOfWeek.Monday;
}
// Set the Month to look at in the Year 2004.
int i_Month = 5;
if (dow_FirstDayOfWeek == DayOfWeek.Thursday)
{
i_Month = 1; // 01.01.2004
}
if (dow_FirstDayOfWeek == DayOfWeek.Friday)
{
i_Month = 10; // 01.10.2004
}
if (dow_FirstDayOfWeek == DayOfWeek.Saturday)
{
i_Month = 5; // 01.05.2004
}
if (dow_FirstDayOfWeek == DayOfWeek.Sunday)
{
i_Month = 2; // 01.02.2004
}
if (dow_FirstDayOfWeek == DayOfWeek.Monday)
{
i_Month = 3; // 01.03.2004
}
if (dow_FirstDayOfWeek == DayOfWeek.Tuesday)
{
i_Month = 6; // 01.06.2004
}
if (dow_FirstDayOfWeek == DayOfWeek.Wednesday)
{
i_Month = 9; // 01.09.2004
}
// Get the letters of the Days of the Week, accourding the
FirstDayofWeek Value
s_DayOfWeek = new string[7];
for (int i=1;i<=7;i++)
{
if (ip_MonthDayPattern == 0)
s_Day =
DateTime.Parse(string.Format("{0}/{1}/2004",i_Month,i)).ToString("dddd");
if (ip_MonthDayPattern == 1)
s_Day =
DateTime.Parse(string.Format("{1}/{0}/2004",i_Month,i)).ToString("dddd");
if (ip_MonthDayPattern == 2)
s_Day =
DateTime.Parse(string.Format("2004/{0}/{1}",i_Month,i)).ToString("dddd");
s_DayOfWeek[i-1] = s_Day.Substring(0,DayOfWeekC);
}
// Get the Months of Year , accourding the MonthNames Array
s_MonthsOfYear = new string[12];
for (int i=0;i<12;i++)
{
s_MonthsOfYear = uici.DateTimeFormat.MonthNames;
}
} // set
} // public int i_Language
#endregion
private void DrawDaysOfWeek(Graphics g)
{ // mj10777 Language support
// calculate where to draw days of week
Point pos = new Point(Const.DaysGrid.X+3, Const.CaptionHeight);
// go through and draw each character
for (int i=0;i<s_DayOfWeek.Length;i++)
{

g.DrawString(s_DayOfWeek,m_fontCaption,m_brushCaptionBack,pos.X,pos.Y);
pos.X += Const.DaysCell.Width;
}
// separator line
g.DrawLine(m_penFrame, Const.DaysGrid.X, Const.DaysGrid.Y-1,
this.Width - Const.DaysGrid.X, Const.DaysGrid.Y-1);
} // private void DrawDaysOfWeek(Graphics g)
private void DrawBottomLabels(Graphics g)
{// mj10777 Language Support
// draw today string, don't store bounding rectangle since
// hit testing is the entire width of the calendar
string text = string.Format("{0}: {1}",s_Today,
m_today.ToShortDateString());
g.DrawString(text, this.m_fontCaption, this.m_brushCur,
Const.BottomLabelsPos.X, Const.BottomLabelsPos.Y);
} // private void DrawBottomLabels(Graphics g)
private void InitMonthContextMenu()
{ // mj10777 Language Support
// create a menu that contains list of months
m_monthMenu = new ContextMenu();
for (int i=0;i<12;i++)
{
// create new menu item and hookup the click event
MenuItem item = new MenuItem();
m_monthMenu.MenuItems.Add(item);
item.Click += new EventHandler(OnMonthMenuClick);
item.Text = s_MonthsOfYear;
}

// hookup popup event so can check the current month
m_monthMenu.Popup += new EventHandler(OnMonthMenuPopup);
} // private void InitMonthContextMenu()
private void CalculateFirstDate()
{ // mj10777 Language Support for dow_FirstDayofWeek used only here,
Checked : So,Mo,Tu,We,Th,Fr,Sa
m_firstDate = new DateTime(m_curSel.Year,m_curSel.Month,1);
int i_CountDays = 0;
if (m_firstDate.DayOfWeek == dow_FirstDayOfWeek)
i_CountDays = -7;
else
{
int i_FirstDayOfWeek = (int)dow_FirstDayOfWeek;
if ((int)m_firstDate.DayOfWeek != 0) // Not Sunday
{
i_CountDays = -(int)m_firstDate.DayOfWeek+i_FirstDayOfWeek;
if (i_CountDays > 0)
i_CountDays = -7+i_CountDays;
if (i_CountDays <= -7)
i_CountDays += 7;
}
else // Sunday
i_CountDays = -7+i_FirstDayOfWeek;
}
m_firstDate = m_firstDate.AddDays(i_CountDays); // Set the FirstDate
} // private void CalculateFirstDate()

}

Mark Johnson, Berlin Germany
(e-mail address removed)




bic said:
"Mark Johnson" <[email protected]> wrote in message
Yes, this would interest me (E-Mail is below).
Since the Original version does not work at all on an non US-Cultures (like
Canada/UK-English,German) where the short date is not MM/DD/YYY, I would
like to keep this up to date (the month list shows 12 months with "January"
in the native Language).
This is why my last version can be downloaded from
http://www.mj10777.de/NETFramework/Compact/DateTimePicker/index.htm

I just saw that Canada has a special rule which made it not work in my
version.
The correction of this even simplyfies to code using
uici.DateTimeFormat.MonthNames; to fill the Month MenuItems which would
solve most of problems of the non-US Cultures if used in the original code.
The only thing that must be hardcoded is "Today" and the FirstDayof Week
(when not Sunday) due to
DateTimeFormatInfo.CurrentInfo.FirstDayOfWeek not working on Compact.
My Version also supports Mo, Tu etc. in the native Language when desired.

Mark Johnson, Berlin Germany
(e-mail address removed)


Do you plan on releasing a vb.net version.. This control seems so needed..
I hope microsoft release a proper version in the next CF.
 
bic said:
"Marc Ambrosius" <[email protected]> wrote in message

Hmm.. I just notice this too.. quite annoying.. You have the code the
LostFocus event?
I just did something very simple:
Protected Overrides Sub OnLostFocus(ByVal e As EventArgs)
Close()
End Sub

As the control closes losing the focus, the YearUpDown control doesn't make
sense any more. So I had to get rid of anything that deals with
m_yearUpDown.

Marc
 
Back
Top