1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2004 Novell, Inc.
23 // John BouAntoun jba-mono@optusnet.com.au
26 // - get the date_cell_size and title_size to be pixel perfect match of SWF
29 using System.Collections;
30 using System.ComponentModel;
31 using System.ComponentModel.Design;
33 using System.Windows.Forms;
35 namespace System.Windows.Forms {
36 [DefaultProperty("SelectionRange")]
37 [DefaultEvent("DateChanged")]
38 [Designer ("System.Windows.Forms.Design.MonthCalendarDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")]
39 public class MonthCalendar : Control {
40 #region Local variables
41 DateTime [] annually_bolded_dates;
43 DateTime [] bolded_dates;
44 Size calendar_dimensions;
45 Day first_day_of_week;
48 int max_selection_count;
50 DateTime [] monthly_bolded_dates;
52 SelectionRange selection_range;
54 bool show_today_circle;
55 bool show_week_numbers;
56 Color title_back_color;
57 Color title_fore_color;
60 Color trailing_fore_color;
62 NumericUpDown year_updown;
65 // internal variables used
66 internal DateTime current_month; // the month that is being displayed in top left corner of the grid
67 internal DateTimePicker owner; // used if this control is popped up
68 internal int button_x_offset;
69 internal Size button_size;
70 internal Size title_size;
71 internal Size date_cell_size;
72 internal Size calendar_spacing;
73 internal int divider_line_offset;
74 internal DateTime clicked_date;
75 internal bool is_date_clicked;
76 internal bool is_previous_clicked;
77 internal bool is_next_clicked;
78 internal bool is_shift_pressed;
79 internal DateTime first_select_start_date;
80 internal int last_clicked_calendar_index;
81 internal Rectangle last_clicked_calendar_rect;
82 private Point month_title_click_location;
83 // this is used to see which item was actually clicked on in the beginning
84 // so that we know which item to fire on timer
86 // 1: previous clicked
88 private bool[] click_state;
90 // arraylists used to store new dates
91 ArrayList added_bolded_dates;
92 ArrayList removed_bolded_dates;
93 ArrayList added_annually_bolded_dates;
94 ArrayList removed_annually_bolded_dates;
95 ArrayList added_monthly_bolded_dates;
96 ArrayList removed_monthly_bolded_dates;
99 #endregion // Local variables
101 #region Public Constructors
103 public MonthCalendar () {
104 // set up the control painting
105 SetStyle (ControlStyles.UserPaint, true);
106 SetStyle (ControlStyles.AllPaintingInWmPaint, true);
109 timer = new Timer ();
110 timer.Interval = 500;
111 timer.Enabled = false;
113 // initialise default values
114 DateTime now = DateTime.Now.Date;
115 selection_range = new SelectionRange (now, now);
117 current_month = new DateTime (now.Year , now.Month, 1);
119 // iniatialise local members
120 annually_bolded_dates = null;
121 back_color = ThemeEngine.Current.ColorWindow;
123 calendar_dimensions = new Size (1,1);
124 first_day_of_week = Day.Default;
125 fore_color = SystemColors.ControlText;
126 max_date = new DateTime (9998, 12, 31);
127 max_selection_count = 7;
128 min_date = new DateTime (1953, 1, 1);
129 monthly_bolded_dates = null;
132 show_today_circle = true;
133 show_week_numbers = false;
134 title_back_color = ThemeEngine.Current.ColorActiveTitle;
135 title_fore_color = ThemeEngine.Current.ColorTitleText;
136 today_date_set = false;
137 trailing_fore_color = Color.Gray;
139 // initialise the arraylest for bolded dates
140 added_bolded_dates = new ArrayList ();
141 removed_bolded_dates = new ArrayList ();
142 added_annually_bolded_dates = new ArrayList ();
143 removed_annually_bolded_dates = new ArrayList ();
144 added_monthly_bolded_dates = new ArrayList ();
145 removed_monthly_bolded_dates = new ArrayList ();
147 // intiailise internal variables used
149 button_size = new Size (22, 17);
150 // default settings based on 8.25 pt San Serif Font
151 // Not sure of algorithm used to establish this
152 date_cell_size = new Size (24, 16); // default size at san-serif 8.25
153 divider_line_offset = 4;
154 calendar_spacing = new Size (4, 5); // horiz and vert spacing between months in a calendar grid
156 // set some state info
158 is_date_clicked = false;
159 is_previous_clicked = false;
160 is_next_clicked = false;
161 is_shift_pressed = false;
162 click_state = new bool [] {false, false, false};
163 first_select_start_date = now;
164 month_title_click_location = Point.Empty;
166 // set up context menu
169 // set up the year up down control
170 year_updown = new NumericUpDown();
171 year_updown.Font = this.Font;
172 year_updown.Minimum = MinDate.Year;
173 year_updown.Maximum = MaxDate.Year;
174 year_updown.ReadOnly = true;
175 year_updown.Visible = false;
176 this.Controls.Add(year_updown);
179 // LostFocus += new EventHandler (LostFocusHandler);
180 timer.Tick += new EventHandler (TimerHandler);
181 MouseMove += new MouseEventHandler (MouseMoveHandler);
182 MouseDown += new MouseEventHandler (MouseDownHandler);
183 KeyDown += new KeyEventHandler (KeyDownHandler);
184 MouseUp += new MouseEventHandler (MouseUpHandler);
185 KeyUp += new KeyEventHandler (KeyUpHandler);
186 year_updown.ValueChanged += new EventHandler(UpDownYearChangedHandler);
188 // this replaces paint so call the control version
189 ((Control)this).Paint += new PaintEventHandler (PaintHandler);
192 // called when this control is added to date time picker
193 internal MonthCalendar (DateTimePicker owner) : this () {
195 this.is_visible = false;
196 this.Size = this.DefaultSize;
199 #endregion // Public Constructors
201 #region Public Instance Properties
203 // dates to make bold on calendar annually (recurring)
205 public DateTime[] AnnuallyBoldedDates {
207 if (annually_bolded_dates == null || annually_bolded_dates != value) {
208 annually_bolded_dates = value;
209 this.UpdateBoldedDates ();
214 return annually_bolded_dates;
219 [EditorBrowsable(EditorBrowsableState.Never)]
220 public override Image BackgroundImage {
222 return base.BackgroundImage;
225 base.BackgroundImage = value;
230 // the back color for the main part of the calendar
231 public Color BackColor {
233 if (back_color != value) {
235 this.OnBackColorChanged (EventArgs.Empty);
244 // specific dates to make bold on calendar (non-recurring)
246 public DateTime[] BoldedDates {
248 if (bolded_dates == null || bolded_dates != value) {
249 bolded_dates = value;
250 this.UpdateBoldedDates ();
259 // the configuration of the monthly grid display - only allowed to display at most,
260 // 1 calendar year at a time, will be trimmed to fit it properly
262 public Size CalendarDimensions {
264 if (value.Width < 0 || value.Height < 0) {
265 throw new ArgumentException ();
267 if (calendar_dimensions != value) {
268 // squeeze the grid into 1 calendar year
269 if (value.Width * value.Height > 12) {
270 // iteratively reduce the largest dimension till our
271 // product is less than 12
272 if (value.Width > 12 && value.Height > 12) {
273 calendar_dimensions = new Size (4, 3);
274 } else if (value.Width > 12) {
275 for (int i = 12; i > 0; i--) {
276 if (i * value.Height <= 12) {
277 calendar_dimensions = new Size (i, value.Height);
281 } else if (value.Height > 12) {
282 for (int i = 12; i > 0; i--) {
283 if (i * value.Width <= 12) {
284 calendar_dimensions = new Size (value.Width, i);
290 calendar_dimensions = value;
296 return calendar_dimensions;
300 // the first day of the week to display
302 [DefaultValue (Day.Default)]
303 public Day FirstDayOfWeek {
305 if (first_day_of_week != value) {
306 first_day_of_week = value;
311 return first_day_of_week;
315 // the fore color for the main part of the calendar
316 public Color ForeColor {
318 if (fore_color != value) {
320 this.OnForeColorChanged (EventArgs.Empty);
330 [EditorBrowsable(EditorBrowsableState.Never)]
331 public ImeMode ImeMode {
337 if (ime_mode != value) {
340 if (ImeModeChanged != null) {
341 ImeModeChanged(this, EventArgs.Empty);
347 // the maximum date allowed to be selected on this month calendar
348 public DateTime MaxDate {
350 if (value < MinDate) {
351 throw new ArgumentException();
354 if (max_date != value) {
363 // the maximum number of selectable days
365 public int MaxSelectionCount {
368 throw new ArgumentException();
371 // can't set selectioncount less than already selected dates
372 if ((SelectionEnd - SelectionStart).Days > value) {
373 throw new ArgumentException();
376 if (max_selection_count != value) {
377 max_selection_count = value;
381 return max_selection_count;
385 // the minimum date allowed to be selected on this month calendar
386 public DateTime MinDate {
388 if (value < new DateTime (1953, 1, 1)) {
389 throw new ArgumentException();
392 if (value > MaxDate) {
393 throw new ArgumentException();
396 if (max_date != value) {
405 // dates to make bold on calendar monthly (recurring)
407 public DateTime[] MonthlyBoldedDates {
409 if (monthly_bolded_dates == null || monthly_bolded_dates != value) {
410 monthly_bolded_dates = value;
411 this.UpdateBoldedDates ();
416 return monthly_bolded_dates;
420 // the ammount by which to scroll this calendar by
422 public int ScrollChange {
424 if (value < 0 || value > 20000) {
425 throw new ArgumentException();
428 if (scroll_change != value) {
429 scroll_change = value;
433 // if zero it to the default -> the total number of months currently visible
434 if (scroll_change == 0) {
435 return CalendarDimensions.Width * CalendarDimensions.Height;
437 return scroll_change;
442 // the last selected date
444 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
445 public DateTime SelectionEnd {
447 if (value < MinDate || value > MaxDate) {
448 throw new ArgumentException();
451 if (SelectionRange.End != value) {
452 DateTime old_end = SelectionRange.End;
453 // make sure the end obeys the max selection range count
454 if (value < SelectionRange.Start) {
455 SelectionRange.Start = value;
457 if (value.AddDays((MaxSelectionCount-1)*-1) > SelectionRange.Start) {
458 SelectionRange.Start = value.AddDays((MaxSelectionCount-1)*-1);
460 SelectionRange.End = value;
461 this.InvalidateDateRange (new SelectionRange (old_end, SelectionRange.End));
462 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
466 return SelectionRange.End;
470 // the range of selected dates
471 public SelectionRange SelectionRange {
473 if (selection_range != value) {
474 SelectionRange old_range = selection_range;
476 // make sure the end obeys the max selection range count
477 if (value.End.AddDays((MaxSelectionCount-1)*-1) > value.Start) {
478 selection_range = new SelectionRange (value.End.AddDays((MaxSelectionCount-1)*-1), value.End);
480 selection_range = value;
482 SelectionRange visible_range = this.GetDisplayRange(true);
483 if(visible_range.Start > selection_range.End) {
484 this.current_month = new DateTime (selection_range.Start.Year, selection_range.Start.Month, 1);
486 } else if (visible_range.End < selection_range.Start) {
487 int year_diff = selection_range.End.Year - visible_range.End.Year;
488 int month_diff = selection_range.End.Month - visible_range.End.Month;
489 this.current_month = current_month.AddMonths(year_diff * 12 + month_diff);
492 // invalidate the selected range changes
493 DateTime diff_start = old_range.Start;
494 DateTime diff_end = old_range.End;
495 // now decide which region is greated
496 if (old_range.Start > SelectionRange.Start) {
497 diff_start = SelectionRange.Start;
498 } else if (old_range.Start == SelectionRange.Start) {
499 if (old_range.End < SelectionRange.End) {
500 diff_start = old_range.End;
502 diff_start = SelectionRange.End;
505 if (old_range.End < SelectionRange.End) {
506 diff_end = SelectionRange.End;
507 } else if (old_range.End == SelectionRange.End) {
508 if (old_range.Start < SelectionRange.Start) {
509 diff_end = SelectionRange.Start;
511 diff_end = old_range.Start;
515 // invalidate the region required
516 this.InvalidateDateRange (new SelectionRange (diff_start, diff_end));
517 // raise date changed event
518 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
522 return selection_range;
526 // the first selected date
528 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
529 public DateTime SelectionStart {
531 if (value < MinDate || value > MaxDate) {
532 throw new ArgumentException();
535 if (SelectionRange.Start != value) {
536 DateTime old_start = SelectionRange.Start;
537 // make sure the end obeys the max selection range count
538 if (value > SelectionRange.End) {
539 SelectionRange.End = value;
540 } else if (value.AddDays(MaxSelectionCount-1) < SelectionRange.End) {
541 SelectionRange.End = value.AddDays(MaxSelectionCount-1);
543 SelectionRange.Start = value;
544 DateTime new_month = new DateTime(value.Year, value.Month, 1);
545 if (current_month != new_month) {
546 current_month = new_month;
549 this.InvalidateDateRange (new SelectionRange (old_start, SelectionRange.Start));
551 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
555 return selection_range.Start;
559 // whether or not to show todays date
560 [DefaultValue (true)]
561 public bool ShowToday {
563 if (show_today != value) {
573 // whether or not to show a circle around todays date
574 [DefaultValue (true)]
575 public bool ShowTodayCircle {
577 if (show_today_circle != value) {
578 show_today_circle = value;
583 return show_today_circle;
587 // whether or not to show numbers beside each row of weeks
589 [DefaultValue (false)]
590 public bool ShowWeekNumbers {
592 if (show_week_numbers != value) {
593 show_week_numbers = value;
598 return show_week_numbers;
602 // the rectangle size required to render one month based on current font
604 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
605 public Size SingleMonthSize {
607 if (this.Font == null) {
608 throw new InvalidOperationException();
611 // multiplier is sucked out from the font size
612 int multiplier = this.Font.Height;
614 // establis how many columns and rows we have
615 int column_count = (ShowWeekNumbers) ? 8 : 7;
616 int row_count = 7; // not including the today date
618 // set the date_cell_size and the title_size
619 date_cell_size = new Size ((int) Math.Ceiling (2.5 * multiplier), (int) Math.Ceiling (1.5 * multiplier));
620 title_size = new Size ((date_cell_size.Width * column_count), 3 * multiplier);
622 return new Size (column_count * date_cell_size.Width, row_count * date_cell_size.Height + title_size.Height);
628 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
629 [EditorBrowsable(EditorBrowsableState.Never)]
630 public override string Text {
639 // the back color for the title of the calendar and the
640 // forecolor for the day of the week text
641 public Color TitleBackColor {
643 if (title_back_color != value) {
644 title_back_color = value;
649 return title_back_color;
653 // the fore color for the title of the calendar
654 public Color TitleForeColor {
656 if (title_fore_color != value) {
657 title_fore_color = value;
662 return title_fore_color;
666 // the date this calendar is using to refer to today's date
667 public DateTime TodayDate {
669 today_date_set = true;
670 if (today_date != value) {
680 // tells if user specifically set today_date for this control
682 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
683 public bool TodayDateSet {
685 return today_date_set;
689 // the color used for trailing dates in the calendar
690 public Color TrailingForeColor {
692 if (trailing_fore_color != value) {
693 trailing_fore_color = value;
694 SelectionRange bounds = this.GetDisplayRange (false);
695 SelectionRange visible_bounds = this.GetDisplayRange (true);
696 this.InvalidateDateRange (new SelectionRange (bounds.Start, visible_bounds.Start));
697 this.InvalidateDateRange (new SelectionRange (bounds.End, visible_bounds.End));
701 return trailing_fore_color;
705 #endregion // Public Instance Properties
707 #region Protected Instance Properties
709 // overloaded to allow controll to be windowed for drop down
710 protected override CreateParams CreateParams {
712 if (this.owner == null) {
713 return base.CreateParams;
715 CreateParams cp = base.CreateParams;
716 cp.Style = unchecked ((int)(WindowStyles.WS_POPUP | WindowStyles.WS_VISIBLE | WindowStyles.WS_CLIPSIBLINGS | WindowStyles.WS_CLIPCHILDREN));
717 cp.ExStyle |= (int)(WindowStyles.WS_EX_TOOLWINDOW | WindowStyles.WS_EX_TOPMOST);
723 // not sure what to put in here - just doing a base() call - jba
724 protected override ImeMode DefaultImeMode {
726 return base.DefaultImeMode;
730 protected override Size DefaultSize {
732 Size single_month = SingleMonthSize;
734 int width = calendar_dimensions.Width * single_month.Width;
735 if (calendar_dimensions.Width > 1) {
736 width += (calendar_dimensions.Width - 1) * calendar_spacing.Width;
740 int height = calendar_dimensions.Height * single_month.Height;
741 if (this.ShowToday) {
742 height += date_cell_size.Height + 2; // add the height of the "Today: " ...
744 if (calendar_dimensions.Height > 1) {
745 height += (calendar_dimensions.Height - 1) * calendar_spacing.Height;
748 // add the 1 pixel boundary
756 return new Size (width, height);
760 #endregion // Protected Instance Properties
762 #region Public Instance Methods
764 // add a date to the anually bolded date arraylist
765 public void AddAnnuallyBoldedDate (DateTime date) {
766 added_annually_bolded_dates.Add (date.Date);
769 // add a date to the normal bolded date arraylist
770 public void AddBoldedDate (DateTime date) {
771 added_bolded_dates.Add (date.Date);
774 // add a date to the anually monthly date arraylist
775 public void AddMonthlyBoldedDate (DateTime date) {
776 added_monthly_bolded_dates.Add (date.Date);
779 // if visible = true, return only the dates of full months, else return all dates visible
780 public SelectionRange GetDisplayRange (bool visible) {
783 start = new DateTime (current_month.Year, current_month.Month, 1);
784 end = start.AddMonths (calendar_dimensions.Width * calendar_dimensions.Height);
785 end = end.AddDays(-1);
787 // process all visible dates if needed (including the grayed out dates
789 start = GetFirstDateInMonthGrid (start);
790 end = GetLastDateInMonthGrid (end);
793 return new SelectionRange (start, end);
796 // HitTest overload that recieve's x and y co-ordinates as separate ints
797 public HitTestInfo HitTest (int x, int y) {
798 return HitTest (new Point (x, y));
801 // returns a HitTestInfo for MonthCalendar element's under the specified point
802 public HitTestInfo HitTest (Point point) {
803 return HitTest (point, out last_clicked_calendar_index, out last_clicked_calendar_rect);
806 // clears all the annually bolded dates
807 public void RemoveAllAnnuallyBoldedDates () {
808 annually_bolded_dates = null;
809 added_annually_bolded_dates.Clear ();
810 removed_annually_bolded_dates.Clear ();
813 // clears all the normal bolded dates
814 public void RemoveAllBoldedDates () {
816 added_bolded_dates.Clear ();
817 removed_bolded_dates.Clear ();
820 // clears all the monthly bolded dates
821 public void RemoveAllMonthlyBoldedDates () {
822 monthly_bolded_dates = null;
823 added_monthly_bolded_dates.Clear ();
824 removed_monthly_bolded_dates.Clear ();
827 // clears the specified annually bolded date (only compares day and month)
828 // only removes the first instance of the match
829 public void RemoveAnnuallyBoldedDate (DateTime date) {
830 if (!removed_annually_bolded_dates.Contains (date.Date)) {
831 removed_annually_bolded_dates.Add (date.Date);
835 // clears all the normal bolded date
836 // only removes the first instance of the match
837 public void RemoveBoldedDate (DateTime date) {
838 if (!removed_bolded_dates.Contains (date.Date)) {
839 removed_bolded_dates.Add (date.Date);
843 // clears the specified monthly bolded date (only compares day and month)
844 // only removes the first instance of the match
845 public void RemoveMonthlyBoldedDate (DateTime date) {
846 if (!removed_monthly_bolded_dates.Contains (date.Date)) {
847 removed_monthly_bolded_dates.Add (date.Date);
851 // sets the calendar_dimensions. If product is > 12, the larger dimension is reduced to make product < 12
852 public void SetCalendarDimensions(int x, int y) {
853 this.CalendarDimensions = new Size(x, y);
856 // sets the currently selected date as date
857 public void SetDate (DateTime date) {
858 this.SetSelectionRange (date.Date, date.Date);
861 // utility method set the SelectionRange property using individual dates
862 public void SetSelectionRange (DateTime date1, DateTime date2) {
863 this.SelectionRange = new SelectionRange(date1, date2);
866 public override string ToString () {
867 return this.GetType().Name + ", " + this.SelectionRange.ToString ();
870 // usually called after an AddBoldedDate method is called
871 // formats monthly and daily bolded dates according to the current calendar year
872 public void UpdateBoldedDates () {
873 UpdateDateArray (ref bolded_dates, added_bolded_dates, removed_bolded_dates);
874 UpdateDateArray (ref monthly_bolded_dates, added_monthly_bolded_dates, removed_monthly_bolded_dates);
875 UpdateDateArray (ref annually_bolded_dates, added_annually_bolded_dates, removed_annually_bolded_dates);
878 #endregion // Public Instance Methods
880 #region Protected Instance Methods
882 // not sure why this needs to be overriden
883 protected override void CreateHandle () {
884 base.CreateHandle ();
887 // not sure why this needs to be overriden
888 protected override void Dispose (bool disposing) {
889 base.Dispose (disposing);
892 // not sure why this needs to be overriden
893 protected override bool IsInputKey (Keys keyData) {
894 return base.IsInputKey (keyData);
897 // not sure why this needs to be overriden
898 protected override void OnBackColorChanged (EventArgs e) {
899 base.OnBackColorChanged (e);
903 // raises the date changed event
904 protected virtual void OnDateChanged (DateRangeEventArgs drevent) {
905 if (this.DateChanged != null) {
906 this.DateChanged (this, drevent);
910 // raises the DateSelected event
911 protected virtual void OnDateSelected (DateRangeEventArgs drevent) {
912 if (this.DateSelected != null) {
913 this.DateSelected (this, drevent);
917 protected override void OnFontChanged (EventArgs e) {
918 base.OnFontChanged (e);
921 protected override void OnForeColorChanged (EventArgs e) {
922 base.OnForeColorChanged (e);
925 protected override void OnHandleCreated (EventArgs e) {
926 base.OnHandleCreated (e);
929 // i think this is overriden to not allow the control to be changed to an arbitrary size
930 protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified) {
931 if ((specified & BoundsSpecified.Height) == BoundsSpecified.Height ||
932 (specified & BoundsSpecified.Width) == BoundsSpecified.Width ||
933 (specified & BoundsSpecified.Size) == BoundsSpecified.Size) {
934 // only allow sizes = default size to be set
935 Size min_size = DefaultSize;
936 Size max_size = new Size (
937 DefaultSize.Width + SingleMonthSize.Width + calendar_spacing.Width,
938 DefaultSize.Height + SingleMonthSize.Height + calendar_spacing.Height);
939 int x_mid_point = (max_size.Width + min_size.Width)/2;
940 int y_mid_point = (max_size.Height + min_size.Height)/2;
941 if (width < x_mid_point) {
942 width = min_size.Width;
944 width = max_size.Width;
946 if (height < y_mid_point) {
947 height = min_size.Height;
949 height = max_size.Height;
951 base.SetBoundsCore (x, y, width, height, specified);
953 base.SetBoundsCore (x, y, width, height, specified);
957 protected override void WndProc (ref Message m) {
958 base.WndProc (ref m);
961 #endregion // Protected Instance Methods
963 #region public events
965 // fired when the date is changed (either explicitely or implicitely)
966 // when navigating the month selector
967 public event DateRangeEventHandler DateChanged;
969 // fired when the user explicitely clicks on date to select it
970 public event DateRangeEventHandler DateSelected;
973 [EditorBrowsable (EditorBrowsableState.Never)]
974 public event EventHandler BackgroundImageChanged;
976 // this event is overridden to supress it from being fired
978 [EditorBrowsable (EditorBrowsableState.Never)]
979 public event EventHandler Click;
981 // this event is overridden to supress it from being fired
983 [EditorBrowsable (EditorBrowsableState.Never)]
984 public event EventHandler DoubleClick;
987 [EditorBrowsable (EditorBrowsableState.Never)]
988 public event EventHandler ImeModeChanged;
991 [EditorBrowsable (EditorBrowsableState.Never)]
992 public new event PaintEventHandler Paint;
995 [EditorBrowsable (EditorBrowsableState.Never)]
996 public event EventHandler TextChanged;
997 #endregion // public events
999 #region internal properties
1001 internal DateTime CurrentMonth {
1003 // only interested in if the month (not actual date) has changed
1004 if (value.Month != current_month.Month ||
1005 value.Year != current_month.Year) {
1006 this.SelectionRange = new SelectionRange(
1007 this.SelectionStart.Add(value.Subtract(current_month)),
1008 this.SelectionEnd.Add(value.Subtract(current_month)));
1009 current_month = value;
1010 UpdateBoldedDates();
1015 return current_month;
1019 #endregion // internal properties
1021 #region internal/private methods
1022 internal HitTestInfo HitTest (
1024 out int calendar_index,
1025 out Rectangle calendar_rect) {
1026 // start by initialising the ref parameters
1027 calendar_index = -1;
1028 calendar_rect = Rectangle.Empty;
1030 // before doing all the hard work, see if the today's date wasn't clicked
1031 Rectangle today_rect = new Rectangle (
1033 ClientRectangle.Bottom - date_cell_size.Height,
1034 7 * date_cell_size.Width,
1035 date_cell_size.Height);
1036 if (today_rect.Contains (point) && this.ShowToday) {
1037 return new HitTestInfo(HitArea.TodayLink, point, DateTime.Now);
1040 Size month_size = SingleMonthSize;
1041 // define calendar rect's that this thing can land in
1042 Rectangle[] calendars = new Rectangle [CalendarDimensions.Width * CalendarDimensions.Height];
1043 for (int i=0; i < CalendarDimensions.Width * CalendarDimensions.Height; i ++) {
1045 calendars[i] = new Rectangle (
1046 new Point (ClientRectangle.X + 1, ClientRectangle.Y + 1),
1049 // calendar on the next row
1050 if (i % CalendarDimensions.Width == 0) {
1051 calendars[i] = new Rectangle (
1052 new Point (calendars[i-CalendarDimensions.Width].X, calendars[i-CalendarDimensions.Width].Bottom + calendar_spacing.Height),
1055 // calendar on the next column
1056 calendars[i] = new Rectangle (
1057 new Point (calendars[i-1].Right + calendar_spacing.Width, calendars[i-1].Y),
1063 // through each trying to find a match
1064 for (int i = 0; i < calendars.Length ; i++) {
1065 if (calendars[i].Contains (point)) {
1066 // check the title section
1067 Rectangle title_rect = new Rectangle (
1068 calendars[i].Location,
1070 if (title_rect.Contains (point) ) {
1071 // make sure it's not a previous button
1073 Rectangle button_rect = new Rectangle(
1074 new Point (calendars[i].X + button_x_offset, (title_size.Height - button_size.Height)/2),
1076 if (button_rect.Contains (point)) {
1077 return new HitTestInfo(HitArea.PrevMonthButton, point, DateTime.Now);
1080 // make sure it's not the next button
1081 if (i % CalendarDimensions.Height == 0 && i % CalendarDimensions.Width == calendar_dimensions.Width - 1) {
1082 Rectangle button_rect = new Rectangle(
1083 new Point (calendars[i].Right - button_x_offset - button_size.Width, (title_size.Height - button_size.Height)/2),
1085 if (button_rect.Contains (point)) {
1086 return new HitTestInfo(HitArea.NextMonthButton, point, DateTime.Now);
1090 // indicate which calendar and month it was
1092 calendar_rect = calendars[i];
1094 // make sure it's not the month or the year of the calendar
1095 if (GetMonthNameRectangle (title_rect, i).Contains (point)) {
1096 return new HitTestInfo(HitArea.TitleMonth, point, DateTime.Now);
1098 if (GetYearNameRectangle (title_rect, i).Contains (point)) {
1099 return new HitTestInfo(HitArea.TitleYear, point, DateTime.Now);
1102 // return the hit test in the title background
1103 return new HitTestInfo(HitArea.TitleBackground, point, DateTime.Now);
1106 Point date_grid_location = new Point (calendars[i].X, title_rect.Bottom);
1108 // see if it's in the Week numbers
1109 if (ShowWeekNumbers) {
1110 Rectangle weeks_rect = new Rectangle (
1112 new Size (date_cell_size.Width,Math.Max (calendars[i].Height - title_rect.Height, 0)));
1113 if (weeks_rect.Contains (point)) {
1114 return new HitTestInfo(HitArea.WeekNumbers, point, DateTime.Now);
1117 // move the location of the grid over
1118 date_grid_location.X += date_cell_size.Width;
1121 // see if it's in the week names
1122 Rectangle day_rect = new Rectangle (
1124 new Size (Math.Max (calendars[i].Right - date_grid_location.X, 0), date_cell_size.Height));
1125 if (day_rect.Contains (point)) {
1126 return new HitTestInfo(HitArea.DayOfWeek, point, DateTime.Now);
1129 // finally see if it was a date that was clicked
1130 Rectangle date_grid = new Rectangle (
1131 new Point (day_rect.X, day_rect.Bottom),
1132 new Size (day_rect.Width, Math.Max(calendars[i].Bottom - day_rect.Bottom, 0)));
1133 if (date_grid.Contains (point)) {
1134 // okay so it's inside the grid, get the offset
1135 Point offset = new Point (point.X - date_grid.X, point.Y - date_grid.Y);
1136 int row = offset.Y / date_cell_size.Height;
1137 int col = offset.X / date_cell_size.Width;
1138 // establish our first day of the month
1139 DateTime calendar_month = this.CurrentMonth.AddMonths(i);
1140 DateTime first_day = GetFirstDateInMonthGrid (calendar_month);
1141 DateTime time = first_day.AddDays ((row * 7) + col);
1142 // establish which date was clicked
1143 if (time.Year != calendar_month.Year || time.Month != calendar_month.Month) {
1144 if (time < calendar_month && i == 0) {
1145 return new HitTestInfo(HitArea.PrevMonthDate, point, time);
1146 } else if (time > calendar_month && i == CalendarDimensions.Width*CalendarDimensions.Height - 1) {
1147 return new HitTestInfo(HitArea.NextMonthDate, point, time);
1149 return new HitTestInfo(HitArea.Nowhere, point, time);
1151 return new HitTestInfo(HitArea.Date, point, time);
1156 return new HitTestInfo ();
1159 // returns the date of the first cell of the specified month grid
1160 internal DateTime GetFirstDateInMonthGrid (DateTime month) {
1161 // convert the first_day_of_week into a DayOfWeekEnum
1162 DayOfWeek first_day = GetDayOfWeek (first_day_of_week);
1163 // find the first day of the month
1164 DateTime first_date_of_month = new DateTime (month.Year, month.Month, 1);
1165 DayOfWeek first_day_of_month = first_date_of_month.DayOfWeek;
1166 // adjust for the starting day of the week
1167 int offset = first_day_of_month - first_day;
1171 return first_date_of_month.AddDays (-1*offset);
1174 // returns the date of the last cell of the specified month grid
1175 internal DateTime GetLastDateInMonthGrid (DateTime month)
1177 DateTime start = GetFirstDateInMonthGrid(month);
1178 return start.AddDays ((7 * 6)-1);
1181 internal bool IsBoldedDate (DateTime date) {
1182 // check bolded dates
1183 if (bolded_dates != null && bolded_dates.Length > 0) {
1184 foreach (DateTime bolded_date in bolded_dates) {
1185 if (bolded_date.Date == date.Date) {
1190 // check monthly dates
1191 if (monthly_bolded_dates != null && monthly_bolded_dates.Length > 0) {
1192 foreach (DateTime bolded_date in monthly_bolded_dates) {
1193 if (bolded_date.Day == date.Day) {
1198 // check yearly dates
1199 if (annually_bolded_dates != null && annually_bolded_dates.Length > 0) {
1200 foreach (DateTime bolded_date in annually_bolded_dates) {
1201 if (bolded_date.Month == date.Month && bolded_date.Day == date.Day) {
1207 return false; // no match
1210 // updates the specified bolded dates array with ones to add and ones to remove
1211 private void UpdateDateArray (ref DateTime [] dates, ArrayList to_add, ArrayList to_remove) {
1212 ArrayList list = new ArrayList ();
1214 // update normal bolded dates
1215 if (dates != null) {
1216 foreach (DateTime date in dates) {
1217 list.Add (date.Date);
1222 foreach (DateTime date in to_add) {
1223 if (!list.Contains (date.Date)) {
1224 list.Add (date.Date);
1229 // remove ones to remove
1230 foreach (DateTime date in to_remove) {
1231 if (list.Contains (date.Date)) {
1232 list.Remove (date.Date);
1236 // set up the array now
1237 if (list.Count > 0) {
1238 dates = (DateTime []) list.ToArray (typeof (DateTime));
1246 // initialise the context menu
1247 private void SetUpContextMenu () {
1248 menu = new ContextMenu ();
1249 for (int i=0; i < 12; i++) {
1250 MenuItem menu_item = new MenuItem ( new DateTime (2000, i+1, 1).ToString ("MMMM"));
1251 menu_item.Click += new EventHandler (MenuItemClickHandler);
1252 menu.MenuItems.Add (menu_item);
1256 // initialises text value and show's year up down in correct position
1257 private void PrepareYearUpDown (Point p) {
1258 Rectangle old_location = year_updown.Bounds;
1261 Rectangle title_rect = new Rectangle(
1262 last_clicked_calendar_rect.Location,
1265 year_updown.Bounds = GetYearNameRectangle(
1267 last_clicked_calendar_index);
1268 year_updown.Top -= 4;
1269 year_updown.Width += (int) (this.Font.Size * 4);
1270 // set year - only do this if this isn't being called because of a year up down click
1271 if(year_updown.Bounds != old_location) {
1272 year_updown.Value = current_month.AddMonths(last_clicked_calendar_index).Year;
1275 if(!year_updown.Visible) {
1276 year_updown.Visible = true;
1280 // returns the first date of the month
1281 private DateTime GetFirstDateInMonth (DateTime date) {
1282 return new DateTime (date.Year, date.Month, 1);
1285 // returns the last date of the month
1286 private DateTime GetLastDateInMonth (DateTime date) {
1287 return new DateTime (date.Year, date.Month, 1).AddMonths(1).AddDays(-1);
1290 // called in response to users seletion with shift key
1291 private void AddTimeToSelection (int delta, bool isDays)
1293 DateTime cursor_point;
1295 // okay we add the period to the date that is not the same as the
1296 // start date when shift was first clicked.
1297 if (SelectionStart != first_select_start_date) {
1298 cursor_point = SelectionStart;
1300 cursor_point = SelectionEnd;
1304 end_point = cursor_point.AddDays (delta);
1306 // delta must be months
1307 end_point = cursor_point.AddMonths (delta);
1309 // set the new selection range
1310 SelectionRange range = new SelectionRange (first_select_start_date, end_point);
1311 if (range.Start.AddDays (MaxSelectionCount-1) < range.End) {
1312 // okay the date is beyond what is allowed, lets set the maximum we can
1313 if (range.Start != first_select_start_date) {
1314 range.Start = range.End.AddDays ((MaxSelectionCount-1)*-1);
1316 range.End = range.Start.AddDays (MaxSelectionCount-1);
1319 this.SelectionRange = range;
1322 // attempts to add the date to the selection without throwing exception
1323 private void SelectDate (DateTime date) {
1324 // try and add the new date to the selction range
1325 if (is_shift_pressed || (click_state [0])) {
1326 SelectionRange range = new SelectionRange (first_select_start_date, date);
1327 if (range.Start.AddDays (MaxSelectionCount-1) < range.End) {
1328 // okay the date is beyond what is allowed, lets set the maximum we can
1329 if (range.Start != first_select_start_date) {
1330 range.Start = range.End.AddDays ((MaxSelectionCount-1)*-1);
1332 range.End = range.Start.AddDays (MaxSelectionCount-1);
1335 SelectionRange = range;
1337 SelectionRange = new SelectionRange (date, date);
1338 first_select_start_date = date;
1342 // gets the week of the year
1343 internal int GetWeekOfYear (DateTime date) {
1344 // convert the first_day_of_week into a DayOfWeekEnum
1345 DayOfWeek first_day = GetDayOfWeek (first_day_of_week);
1346 // find the first day of the year
1347 DayOfWeek first_day_of_year = new DateTime (date.Year, 1, 1).DayOfWeek;
1348 // adjust for the starting day of the week
1349 int offset = first_day_of_year - first_day;
1350 int week = ((date.DayOfYear + offset) / 7) + 1;
1354 // convert a Day enum into a DayOfWeek enum
1355 internal DayOfWeek GetDayOfWeek (Day day) {
1356 if (day == Day.Default) {
1357 return DayOfWeek.Sunday;
1359 return (DayOfWeek) DayOfWeek.Parse (typeof (DayOfWeek), day.ToString ());
1363 // returns the rectangle for themonth name
1364 internal Rectangle GetMonthNameRectangle (Rectangle title_rect, int calendar_index) {
1365 Graphics g = this.DeviceContext;
1366 DateTime this_month = this.current_month.AddMonths (calendar_index);
1367 Size title_text_size = g.MeasureString (this_month.ToString ("MMMM yyyy"), this.Font).ToSize ();
1368 Size month_size = g.MeasureString (this_month.ToString ("MMMM"), this.Font).ToSize ();
1369 // return only the month name part of that
1370 return new Rectangle (
1372 title_rect.X + ((title_rect.Width - title_text_size.Width)/2),
1373 title_rect.Y + ((title_rect.Height - title_text_size.Height)/2)),
1377 // returns the rectangle for the year in the title
1378 internal Rectangle GetYearNameRectangle (Rectangle title_rect, int calendar_index) {
1379 Graphics g = this.DeviceContext;
1380 DateTime this_month = this.current_month.AddMonths (calendar_index);
1381 Size title_text_size = g.MeasureString (this_month.ToString ("MMMM yyyy"), this.Font).ToSize ();
1382 Size year_size = g.MeasureString (this_month.ToString ("yyyy"), this.Font).ToSize ();
1383 // find out how much space the title took
1384 Rectangle text_rect = new Rectangle (
1386 title_rect.X + ((title_rect.Width - title_text_size.Width)/2),
1387 title_rect.Y + ((title_rect.Height - title_text_size.Height)/2)),
1389 // return only the rect of the year
1390 return new Rectangle (
1392 text_rect.Right - year_size.Width,
1397 // determine if date is allowed to be drawn in month
1398 internal bool IsValidWeekToDraw (DateTime month, DateTime date, int row, int col) {
1399 DateTime tocheck = month.AddMonths (-1);
1400 if ((month.Year == date.Year && month.Month == date.Month) ||
1401 (tocheck.Year == date.Year && tocheck.Month == date.Month)) {
1405 // check the railing dates (days in the month after the last month in grid)
1406 if (row == CalendarDimensions.Height - 1 && col == CalendarDimensions.Width - 1) {
1407 tocheck = month.AddMonths (1);
1408 return (tocheck.Year == date.Year && tocheck.Month == date.Month) ;
1414 // set one item clicked and all others off
1415 private void SetItemClick(HitTestInfo hti)
1417 switch(hti.HitArea) {
1418 case HitArea.NextMonthButton:
1419 this.is_previous_clicked = false;
1420 this.is_next_clicked = true;
1421 this.is_date_clicked = false;
1423 case HitArea.PrevMonthButton:
1424 this.is_previous_clicked = true;
1425 this.is_next_clicked = false;
1426 this.is_date_clicked = false;
1428 case HitArea.PrevMonthDate:
1429 case HitArea.NextMonthDate:
1431 this.clicked_date = hti.Time;
1432 this.is_previous_clicked = false;
1433 this.is_next_clicked = false;
1434 this.is_date_clicked = true;
1437 this.is_previous_clicked = false;
1438 this.is_next_clicked = false;
1439 this.is_date_clicked = false;
1444 // called when the year is changed
1445 private void UpDownYearChangedHandler (object sender, EventArgs e) {
1446 int initial_year_value = this.CurrentMonth.AddMonths(last_clicked_calendar_index).Year;
1447 int diff = (int) year_updown.Value - initial_year_value;
1448 this.CurrentMonth = this.CurrentMonth.AddYears(diff);
1451 // called when context menu is clicked
1452 private void MenuItemClickHandler (object sender, EventArgs e) {
1453 MenuItem item = sender as MenuItem;
1454 if (item != null && month_title_click_location != Point.Empty) {
1455 // establish which month we want to move to
1456 if (item.Parent == null) {
1459 int new_month = item.Parent.MenuItems.IndexOf (item) + 1;
1460 if (new_month == 0) {
1463 // okay let's establish which calendar was hit
1464 Size month_size = this.SingleMonthSize;
1465 for (int i=0; i < CalendarDimensions.Height; i++) {
1466 for (int j=0; j < CalendarDimensions.Width; j++) {
1467 int month_index = (i * CalendarDimensions.Width) + j;
1468 Rectangle month_rect = new Rectangle ( new Point (0, 0), month_size);
1470 month_rect.X = this.ClientRectangle.X + 1;
1472 month_rect.X = this.ClientRectangle.X + 1 + ((j)*(month_size.Width+calendar_spacing.Width));
1475 month_rect.Y = this.ClientRectangle.Y + 1;
1477 month_rect.Y = this.ClientRectangle.Y + 1 + ((i)*(month_size.Height+calendar_spacing.Height));
1479 // see if the point is inside
1480 if (month_rect.Contains (month_title_click_location)) {
1481 DateTime clicked_month = CurrentMonth.AddMonths (month_index);
1482 // get the month that we want to move to
1483 int month_offset = new_month - clicked_month.Month;
1485 // move forward however more months we need to
1486 this.CurrentMonth = this.CurrentMonth.AddMonths (month_offset);
1493 month_title_click_location = Point.Empty;
1497 // raised on the timer, for mouse hold clicks
1498 private void TimerHandler (object sender, EventArgs e) {
1499 // now find out which area was click
1501 HitTestInfo hti = this.HitTest (this.PointToClient (MousePosition));
1502 // see if it was clicked on the prev or next mouse
1503 if (click_state [1] || click_state [2]) {
1504 // invalidate the area where the mouse was last held
1506 Application.DoEvents ();
1507 // register the click
1508 if (hti.HitArea == HitArea.PrevMonthButton ||
1509 hti.HitArea == HitArea.NextMonthButton) {
1510 DoButtonMouseDown (hti);
1511 click_state [1] = (hti.HitArea == HitArea.PrevMonthButton);
1512 click_state [2] = !click_state [1];
1514 if (timer.Interval != 100) {
1515 timer.Interval = 100;
1519 timer.Enabled = false;
1523 // selects one of the buttons
1524 private void DoButtonMouseDown (HitTestInfo hti) {
1525 // show the click then move on
1527 if (hti.HitArea == HitArea.PrevMonthButton) {
1528 // invalidate the prev monthbutton
1531 this.ClientRectangle.X + 1 + button_x_offset,
1532 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1534 button_size.Height));
1535 this.CurrentMonth = this.CurrentMonth.AddMonths (ScrollChange*-1);
1537 // invalidate the next monthbutton
1540 this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width,
1541 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1543 button_size.Height));
1544 this.CurrentMonth = this.CurrentMonth.AddMonths (ScrollChange);
1548 // selects the clicked date
1549 private void DoDateMouseDown (HitTestInfo hti) {
1551 this.SelectDate (clicked_date);
1552 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1555 // event run on the mouse up event
1556 private void DoMouseUp () {
1557 // invalidate the next monthbutton
1558 if (this.is_next_clicked) {
1561 this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width,
1562 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1564 button_size.Height));
1566 // invalidate the prev monthbutton
1567 if (this.is_previous_clicked) {
1570 this.ClientRectangle.X + 1 + button_x_offset,
1571 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1573 button_size.Height));
1575 if (this.is_date_clicked) {
1576 // invalidate the area under the cursor, to remove focus rect
1577 this.InvalidateDateRange (new SelectionRange (clicked_date, clicked_date));
1579 this.is_previous_clicked = false;
1580 this.is_next_clicked = false;
1581 this.is_date_clicked = false;
1584 // // need when in windowed mode
1585 // private void LostFocusHandler (object sender, EventArgs e)
1587 // if (this.owner != null) {
1588 // if (this.Visible) {
1589 // this.owner.HideMonthCalendar ();
1594 // occurs when mouse moves around control, used for selection
1595 private void MouseMoveHandler (object sender, MouseEventArgs e) {
1596 HitTestInfo hti = this.HitTest (e.X, e.Y);
1597 // clear the last clicked item
1598 if (click_state [0]) {
1599 // register the click
1600 if (hti.HitArea == HitArea.PrevMonthDate ||
1601 hti.HitArea == HitArea.NextMonthDate ||
1602 hti.HitArea == HitArea.Date)
1604 DoDateMouseDown (hti);
1605 if (owner == null) {
1606 click_state [0] = true;
1608 click_state [0] = false;
1609 click_state [1] = false;
1610 click_state [2] = false;
1617 // to check if the mouse has come down on this control
1618 private void MouseDownHandler (object sender, MouseEventArgs e)
1620 // clear the click_state variables
1621 click_state [0] = false;
1622 click_state [1] = false;
1623 click_state [2] = false;
1625 // disable the timer if it was enabled
1626 if (timer.Enabled) {
1628 timer.Enabled = false;
1631 Point point = new Point (e.X, e.Y);
1632 // figure out if we are in drop down mode and a click happened outside us
1633 if (this.owner != null) {
1634 if (!this.ClientRectangle.Contains (point)) {
1635 this.owner.HideMonthCalendar ();
1640 //establish where was hit
1641 HitTestInfo hti = this.HitTest(point);
1642 // hide the year numeric up down if it was clicked
1643 if (year_updown != null && year_updown.Visible && hti.HitArea != HitArea.TitleYear)
1645 year_updown.Visible = false;
1647 switch (hti.HitArea) {
1648 case HitArea.PrevMonthButton:
1649 case HitArea.NextMonthButton:
1650 DoButtonMouseDown (hti);
1651 click_state [1] = (hti.HitArea == HitArea.PrevMonthDate);
1652 click_state [2] = !click_state [1];
1653 timer.Interval = 500;
1657 case HitArea.PrevMonthDate:
1658 case HitArea.NextMonthDate:
1659 DoDateMouseDown (hti);
1660 // leave clicked state blank if drop down window
1661 if (owner == null) {
1662 click_state [0] = true;
1664 click_state [0] = false;
1665 click_state [1] = false;
1666 click_state [2] = false;
1669 case HitArea.TitleMonth:
1670 month_title_click_location = hti.Point;
1671 menu.Show (this, hti.Point);
1673 case HitArea.TitleYear:
1674 // place the numeric up down
1675 PrepareYearUpDown(hti.Point);
1677 case HitArea.TodayLink:
1678 this.SetSelectionRange (DateTime.Now.Date, DateTime.Now.Date);
1679 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1682 this.is_previous_clicked = false;
1683 this.is_next_clicked = false;
1684 this.is_date_clicked = false;
1689 // raised by any key down events
1690 private void KeyDownHandler (object sender, KeyEventArgs e) {
1691 // send keys to the year_updown control, let it handle it
1692 if(year_updown.Visible) {
1693 switch (e.KeyCode) {
1695 year_updown.Visible = false;
1698 year_updown.Value = year_updown.Value + 1;
1701 year_updown.Value = year_updown.Value - 1;
1705 if (!is_shift_pressed && e.Shift) {
1706 first_select_start_date = SelectionStart;
1707 is_shift_pressed = e.Shift;
1709 switch (e.KeyCode) {
1711 // set the date to the start of the month
1712 if (is_shift_pressed) {
1713 DateTime date = GetFirstDateInMonth (first_select_start_date);
1714 if (date < first_select_start_date.AddDays ((MaxSelectionCount-1)*-1)) {
1715 date = first_select_start_date.AddDays ((MaxSelectionCount-1)*-1);
1717 this.SetSelectionRange (date, first_select_start_date);
1719 DateTime date = GetFirstDateInMonth (this.SelectionStart);
1720 this.SetSelectionRange (date, date);
1722 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1725 // set the date to the last of the month
1726 if (is_shift_pressed) {
1727 DateTime date = GetLastDateInMonth (first_select_start_date);
1728 if (date > first_select_start_date.AddDays (MaxSelectionCount-1)) {
1729 date = first_select_start_date.AddDays (MaxSelectionCount-1);
1731 this.SetSelectionRange (date, first_select_start_date);
1733 DateTime date = GetLastDateInMonth (this.SelectionStart);
1734 this.SetSelectionRange (date, date);
1736 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1739 // set the date to the last of the month
1740 if (is_shift_pressed) {
1741 this.AddTimeToSelection (-1, false);
1743 DateTime date = this.SelectionStart.AddMonths (-1);
1744 this.SetSelectionRange (date, date);
1746 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1749 // set the date to the last of the month
1750 if (is_shift_pressed) {
1751 this.AddTimeToSelection (1, false);
1753 DateTime date = this.SelectionStart.AddMonths (1);
1754 this.SetSelectionRange (date, date);
1756 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1759 // set the back 1 week
1760 if (is_shift_pressed) {
1761 this.AddTimeToSelection (-7, true);
1763 DateTime date = this.SelectionStart.AddDays (-7);
1764 this.SetSelectionRange (date, date);
1766 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1769 // set the date forward 1 week
1770 if (is_shift_pressed) {
1771 this.AddTimeToSelection (7, true);
1773 DateTime date = this.SelectionStart.AddDays (7);
1774 this.SetSelectionRange (date, date);
1776 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1780 if (is_shift_pressed) {
1781 this.AddTimeToSelection (-1, true);
1783 DateTime date = this.SelectionStart.AddDays (-1);
1784 this.SetSelectionRange (date, date);
1786 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1790 if (is_shift_pressed) {
1791 this.AddTimeToSelection (1, true);
1793 DateTime date = this.SelectionStart.AddDays (1);
1794 this.SetSelectionRange (date, date);
1796 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1805 // to check if the mouse has come up on this control
1806 private void MouseUpHandler (object sender, MouseEventArgs e)
1808 if (timer.Enabled) {
1811 // clear the click state array
1812 click_state [0] = false;
1813 click_state [1] = false;
1814 click_state [2] = false;
1815 // do the regulare mouseup stuff
1819 // raised by any key up events
1820 private void KeyUpHandler (object sender, KeyEventArgs e) {
1821 is_shift_pressed = e.Shift ;
1825 // paint this control now
1826 private void PaintHandler (object sender, PaintEventArgs pe) {
1827 if (Width <= 0 || Height <= 0 || Visible == false)
1830 Draw (pe.ClipRectangle, pe.Graphics);
1832 // fire the new paint handler
1833 if (this.Paint != null)
1835 this.Paint (sender, pe);
1839 // returns the region of the control that needs to be redrawn
1840 private void InvalidateDateRange (SelectionRange range) {
1841 SelectionRange bounds = this.GetDisplayRange (false);
1842 if (range.End < bounds.Start || range.Start > bounds.End) {
1843 // don't invalidate anything, as the modified date range
1844 // is outside the visible bounds of this control
1847 // adjust the start and end to be inside the visible range
1848 if (range.Start < bounds.Start) {
1849 range = new SelectionRange (bounds.Start, range.End);
1851 if (range.End > bounds.End) {
1852 range = new SelectionRange (range.Start, bounds.End);
1854 // now invalidate the date rectangles as series of rows
1855 DateTime last_month = this.current_month.AddMonths ((CalendarDimensions.Width * CalendarDimensions.Height)).AddDays (-1);
1856 DateTime current = range.Start;
1857 while (current <= range.End) {
1858 DateTime month_end = new DateTime (current.Year, current.Month, 1).AddMonths (1).AddDays (-1);;
1859 Rectangle start_rect;
1861 // see if entire selection is in this current month
1862 if (range.End <= month_end && current < last_month) {
1863 // the end is the last date
1864 if (current < this.current_month) {
1865 start_rect = GetDateRowRect (current_month, current_month);
1867 start_rect = GetDateRowRect (current, current);
1869 end_rect = GetDateRowRect (current, range.End);
1870 } else if (current < last_month) {
1871 // otherwise it simply means we have a selection spaning
1872 // multiple months simply set rectangle inside the current month
1873 start_rect = GetDateRowRect (current, current);
1874 end_rect = GetDateRowRect (last_month, month_end);
1876 // it's outside the visible range
1877 start_rect = GetDateRowRect (last_month, last_month.AddDays (1));
1878 end_rect = GetDateRowRect (last_month, range.End);
1880 // push to the next month
1881 current = month_end.AddDays (1);
1882 // invalidate from the start row to the end row for this month
1888 Math.Max (end_rect.Bottom - start_rect.Y, 0)));
1892 // gets the rect of the row where the specified date appears on the specified month
1893 private Rectangle GetDateRowRect (DateTime month, DateTime date) {
1894 // first get the general rect of the supplied month
1895 Size month_size = SingleMonthSize;
1896 Rectangle month_rect = Rectangle.Empty;
1897 for (int i=0; i < CalendarDimensions.Width*CalendarDimensions.Height; i++) {
1898 DateTime this_month = this.current_month.AddMonths (i);
1899 if (month.Year == this_month.Year && month.Month == this_month.Month) {
1900 month_rect = new Rectangle (
1901 this.ClientRectangle.X + 1 + (month_size.Width * (i%CalendarDimensions.Width)) + (this.calendar_spacing.Width * (i%CalendarDimensions.Width)),
1902 this.ClientRectangle.Y + 1 + (month_size.Height * (i/CalendarDimensions.Width)) + (this.calendar_spacing.Height * (i/CalendarDimensions.Width)),
1908 // now find out where in the month the supplied date is
1909 if (month_rect == Rectangle.Empty) {
1910 return Rectangle.Empty;
1912 // find out which row this date is in
1914 DateTime first_date = GetFirstDateInMonthGrid (month);
1915 DateTime end_date = first_date.AddDays (7);
1916 for (int i=0; i < 6; i++) {
1917 if (date >= first_date && date < end_date) {
1921 first_date = end_date;
1922 end_date = end_date.AddDays (7);
1924 // ensure it's a valid row
1926 return Rectangle.Empty;
1928 int x_offset = (this.ShowWeekNumbers) ? date_cell_size.Width : 0;
1929 int y_offset = title_size.Height + (date_cell_size.Height * (row + 1));
1930 return new Rectangle (
1931 month_rect.X + x_offset,
1932 month_rect.Y + y_offset,
1933 date_cell_size.Width * 7,
1934 date_cell_size.Height);
1937 internal void Draw (Rectangle clip_rect, Graphics dc)
1939 ThemeEngine.Current.DrawMonthCalendar (dc, clip_rect, this);
1942 #endregion //internal methods
1944 #region internal drawing methods
1947 #endregion // internal drawing methods
1949 #region inner classes and enumerations
1951 // enumeration about what type of area on the calendar was hit
1952 public enum HitArea {
1968 // info regarding to a hit test on this calendar
1969 public sealed class HitTestInfo {
1971 private HitArea hit_area;
1972 private Point point;
1973 private DateTime time;
1975 // default constructor
1976 internal HitTestInfo () {
1977 hit_area = HitArea.Nowhere;
1978 point = new Point (0, 0);
1979 time = DateTime.Now;
1982 // overload receives all properties
1983 internal HitTestInfo (HitArea hit_area, Point point, DateTime time) {
1984 this.hit_area = hit_area;
1989 // the type of area that was hit
1990 public HitArea HitArea {
1996 // the point that is being test
1997 public Point Point {
2003 // the date under the hit test point, only valid if HitArea is Date
2004 public DateTime Time {
2011 #endregion // inner classes