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 | ControlStyles.StandardClick, false);
108 timer = new Timer ();
109 timer.Interval = 500;
110 timer.Enabled = false;
112 // initialise default values
113 DateTime now = DateTime.Now.Date;
114 selection_range = new SelectionRange (now, now);
116 current_month = new DateTime (now.Year , now.Month, 1);
118 // iniatialise local members
119 annually_bolded_dates = null;
120 back_color = ThemeEngine.Current.ColorWindow;
122 calendar_dimensions = new Size (1,1);
123 first_day_of_week = Day.Default;
124 fore_color = SystemColors.ControlText;
125 max_date = new DateTime (9998, 12, 31);
126 max_selection_count = 7;
127 min_date = new DateTime (1953, 1, 1);
128 monthly_bolded_dates = null;
131 show_today_circle = true;
132 show_week_numbers = false;
133 title_back_color = ThemeEngine.Current.ColorActiveCaption;
134 title_fore_color = ThemeEngine.Current.ColorActiveCaptionText;
135 today_date_set = false;
136 trailing_fore_color = Color.Gray;
138 // initialise the arraylest for bolded dates
139 added_bolded_dates = new ArrayList ();
140 removed_bolded_dates = new ArrayList ();
141 added_annually_bolded_dates = new ArrayList ();
142 removed_annually_bolded_dates = new ArrayList ();
143 added_monthly_bolded_dates = new ArrayList ();
144 removed_monthly_bolded_dates = new ArrayList ();
146 // intiailise internal variables used
148 button_size = new Size (22, 17);
149 // default settings based on 8.25 pt San Serif Font
150 // Not sure of algorithm used to establish this
151 date_cell_size = new Size (24, 16); // default size at san-serif 8.25
152 divider_line_offset = 4;
153 calendar_spacing = new Size (4, 5); // horiz and vert spacing between months in a calendar grid
155 // set some state info
157 is_date_clicked = false;
158 is_previous_clicked = false;
159 is_next_clicked = false;
160 is_shift_pressed = false;
161 click_state = new bool [] {false, false, false};
162 first_select_start_date = now;
163 month_title_click_location = Point.Empty;
165 // set up context menu
170 // LostFocus += new EventHandler (LostFocusHandler);
171 timer.Tick += new EventHandler (TimerHandler);
172 MouseMove += new MouseEventHandler (MouseMoveHandler);
173 MouseDown += new MouseEventHandler (MouseDownHandler);
174 KeyDown += new KeyEventHandler (KeyDownHandler);
175 MouseUp += new MouseEventHandler (MouseUpHandler);
176 KeyUp += new KeyEventHandler (KeyUpHandler);
178 // this replaces paint so call the control version
179 base.Paint += new PaintEventHandler (PaintHandler);
182 // called when this control is added to date time picker
183 internal MonthCalendar (DateTimePicker owner) : this () {
185 this.is_visible = false;
186 this.Size = this.DefaultSize;
189 #endregion // Public Constructors
191 #region Public Instance Properties
193 // dates to make bold on calendar annually (recurring)
195 public DateTime[] AnnuallyBoldedDates {
197 if (annually_bolded_dates == null || annually_bolded_dates != value) {
198 annually_bolded_dates = value;
199 this.UpdateBoldedDates ();
204 return annually_bolded_dates;
209 [EditorBrowsable(EditorBrowsableState.Never)]
210 public override Image BackgroundImage {
212 return base.BackgroundImage;
215 base.BackgroundImage = value;
220 // the back color for the main part of the calendar
221 public override Color BackColor {
223 if (back_color != value) {
225 this.OnBackColorChanged (EventArgs.Empty);
234 // specific dates to make bold on calendar (non-recurring)
236 public DateTime[] BoldedDates {
238 if (bolded_dates == null || bolded_dates != value) {
239 bolded_dates = value;
240 this.UpdateBoldedDates ();
249 // the configuration of the monthly grid display - only allowed to display at most,
250 // 1 calendar year at a time, will be trimmed to fit it properly
252 public Size CalendarDimensions {
254 if (value.Width < 0 || value.Height < 0) {
255 throw new ArgumentException ();
257 if (calendar_dimensions != value) {
258 // squeeze the grid into 1 calendar year
259 if (value.Width * value.Height > 12) {
260 // iteratively reduce the largest dimension till our
261 // product is less than 12
262 if (value.Width > 12 && value.Height > 12) {
263 calendar_dimensions = new Size (4, 3);
264 } else if (value.Width > 12) {
265 for (int i = 12; i > 0; i--) {
266 if (i * value.Height <= 12) {
267 calendar_dimensions = new Size (i, value.Height);
271 } else if (value.Height > 12) {
272 for (int i = 12; i > 0; i--) {
273 if (i * value.Width <= 12) {
274 calendar_dimensions = new Size (value.Width, i);
280 calendar_dimensions = value;
286 return calendar_dimensions;
290 // the first day of the week to display
292 [DefaultValue (Day.Default)]
293 public Day FirstDayOfWeek {
295 if (first_day_of_week != value) {
296 first_day_of_week = value;
301 return first_day_of_week;
305 // the fore color for the main part of the calendar
306 public override Color ForeColor {
308 if (fore_color != value) {
310 this.OnForeColorChanged (EventArgs.Empty);
320 [EditorBrowsable(EditorBrowsableState.Never)]
321 public ImeMode ImeMode {
327 if (ime_mode != value) {
330 if (ImeModeChanged != null) {
331 ImeModeChanged(this, EventArgs.Empty);
337 // the maximum date allowed to be selected on this month calendar
338 public DateTime MaxDate {
340 if (value < MinDate) {
341 throw new ArgumentException();
344 if (max_date != value) {
353 // the maximum number of selectable days
355 public int MaxSelectionCount {
358 throw new ArgumentException();
361 // can't set selectioncount less than already selected dates
362 if ((SelectionEnd - SelectionStart).Days > value) {
363 throw new ArgumentException();
366 if (max_selection_count != value) {
367 max_selection_count = value;
371 return max_selection_count;
375 // the minimum date allowed to be selected on this month calendar
376 public DateTime MinDate {
378 if (value < new DateTime (1953, 1, 1)) {
379 throw new ArgumentException();
382 if (value > MaxDate) {
383 throw new ArgumentException();
386 if (max_date != value) {
395 // dates to make bold on calendar monthly (recurring)
397 public DateTime[] MonthlyBoldedDates {
399 if (monthly_bolded_dates == null || monthly_bolded_dates != value) {
400 monthly_bolded_dates = value;
401 this.UpdateBoldedDates ();
406 return monthly_bolded_dates;
410 // the ammount by which to scroll this calendar by
412 public int ScrollChange {
414 if (value < 0 || value > 20000) {
415 throw new ArgumentException();
418 if (scroll_change != value) {
419 scroll_change = value;
423 // if zero it to the default -> the total number of months currently visible
424 if (scroll_change == 0) {
425 return CalendarDimensions.Width * CalendarDimensions.Height;
427 return scroll_change;
432 // the last selected date
434 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
435 public DateTime SelectionEnd {
437 if (value < MinDate || value > MaxDate) {
438 throw new ArgumentException();
441 if (SelectionRange.End != value) {
442 DateTime old_end = SelectionRange.End;
443 // make sure the end obeys the max selection range count
444 if (value < SelectionRange.Start) {
445 SelectionRange.Start = value;
447 if (value.AddDays((MaxSelectionCount-1)*-1) > SelectionRange.Start) {
448 SelectionRange.Start = value.AddDays((MaxSelectionCount-1)*-1);
450 SelectionRange.End = value;
451 this.InvalidateDateRange (new SelectionRange (old_end, SelectionRange.End));
452 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
456 return SelectionRange.End;
460 // the range of selected dates
461 public SelectionRange SelectionRange {
463 if (selection_range != value) {
464 SelectionRange old_range = selection_range;
466 // make sure the end obeys the max selection range count
467 if (value.End.AddDays((MaxSelectionCount-1)*-1) > value.Start) {
468 selection_range = new SelectionRange (value.End.AddDays((MaxSelectionCount-1)*-1), value.End);
470 selection_range = value;
472 SelectionRange visible_range = this.GetDisplayRange(true);
473 if(visible_range.Start > selection_range.End) {
474 this.current_month = new DateTime (selection_range.Start.Year, selection_range.Start.Month, 1);
476 } else if (visible_range.End < selection_range.Start) {
477 int year_diff = selection_range.End.Year - visible_range.End.Year;
478 int month_diff = selection_range.End.Month - visible_range.End.Month;
479 this.current_month = current_month.AddMonths(year_diff * 12 + month_diff);
482 // invalidate the selected range changes
483 DateTime diff_start = old_range.Start;
484 DateTime diff_end = old_range.End;
485 // now decide which region is greated
486 if (old_range.Start > SelectionRange.Start) {
487 diff_start = SelectionRange.Start;
488 } else if (old_range.Start == SelectionRange.Start) {
489 if (old_range.End < SelectionRange.End) {
490 diff_start = old_range.End;
492 diff_start = SelectionRange.End;
495 if (old_range.End < SelectionRange.End) {
496 diff_end = SelectionRange.End;
497 } else if (old_range.End == SelectionRange.End) {
498 if (old_range.Start < SelectionRange.Start) {
499 diff_end = SelectionRange.Start;
501 diff_end = old_range.Start;
505 // invalidate the region required
506 this.InvalidateDateRange (new SelectionRange (diff_start, diff_end));
507 // raise date changed event
508 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
512 return selection_range;
516 // the first selected date
518 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
519 public DateTime SelectionStart {
521 if (value < MinDate || value > MaxDate) {
522 throw new ArgumentException();
525 if (SelectionRange.Start != value) {
526 DateTime old_start = SelectionRange.Start;
527 // make sure the end obeys the max selection range count
528 if (value > SelectionRange.End) {
529 SelectionRange.End = value;
530 } else if (value.AddDays(MaxSelectionCount-1) < SelectionRange.End) {
531 SelectionRange.End = value.AddDays(MaxSelectionCount-1);
533 SelectionRange.Start = value;
534 DateTime new_month = new DateTime(value.Year, value.Month, 1);
535 if (current_month != new_month) {
536 current_month = new_month;
539 this.InvalidateDateRange (new SelectionRange (old_start, SelectionRange.Start));
541 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
545 return selection_range.Start;
549 // whether or not to show todays date
550 [DefaultValue (true)]
551 public bool ShowToday {
553 if (show_today != value) {
563 // whether or not to show a circle around todays date
564 [DefaultValue (true)]
565 public bool ShowTodayCircle {
567 if (show_today_circle != value) {
568 show_today_circle = value;
573 return show_today_circle;
577 // whether or not to show numbers beside each row of weeks
579 [DefaultValue (false)]
580 public bool ShowWeekNumbers {
582 if (show_week_numbers != value) {
583 show_week_numbers = value;
588 return show_week_numbers;
592 // the rectangle size required to render one month based on current font
594 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
595 public Size SingleMonthSize {
597 if (this.Font == null) {
598 throw new InvalidOperationException();
601 // multiplier is sucked out from the font size
602 int multiplier = this.Font.Height;
604 // establis how many columns and rows we have
605 int column_count = (ShowWeekNumbers) ? 8 : 7;
606 int row_count = 7; // not including the today date
608 // set the date_cell_size and the title_size
609 date_cell_size = new Size ((int) Math.Ceiling (2.5 * multiplier), (int) Math.Ceiling (1.5 * multiplier));
610 title_size = new Size ((date_cell_size.Width * column_count), 3 * multiplier);
612 return new Size (column_count * date_cell_size.Width, row_count * date_cell_size.Height + title_size.Height);
618 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
619 [EditorBrowsable(EditorBrowsableState.Never)]
620 public override string Text {
629 // the back color for the title of the calendar and the
630 // forecolor for the day of the week text
631 public Color TitleBackColor {
633 if (title_back_color != value) {
634 title_back_color = value;
639 return title_back_color;
643 // the fore color for the title of the calendar
644 public Color TitleForeColor {
646 if (title_fore_color != value) {
647 title_fore_color = value;
652 return title_fore_color;
656 // the date this calendar is using to refer to today's date
657 public DateTime TodayDate {
659 today_date_set = true;
660 if (today_date != value) {
670 // tells if user specifically set today_date for this control
672 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
673 public bool TodayDateSet {
675 return today_date_set;
679 // the color used for trailing dates in the calendar
680 public Color TrailingForeColor {
682 if (trailing_fore_color != value) {
683 trailing_fore_color = value;
684 SelectionRange bounds = this.GetDisplayRange (false);
685 SelectionRange visible_bounds = this.GetDisplayRange (true);
686 this.InvalidateDateRange (new SelectionRange (bounds.Start, visible_bounds.Start));
687 this.InvalidateDateRange (new SelectionRange (bounds.End, visible_bounds.End));
691 return trailing_fore_color;
695 #endregion // Public Instance Properties
697 #region Protected Instance Properties
699 // overloaded to allow controll to be windowed for drop down
700 protected override CreateParams CreateParams {
702 if (this.owner == null) {
703 return base.CreateParams;
705 CreateParams cp = base.CreateParams;
706 cp.Style = unchecked ((int)(WindowStyles.WS_POPUP | WindowStyles.WS_VISIBLE | WindowStyles.WS_CLIPSIBLINGS | WindowStyles.WS_CLIPCHILDREN));
707 cp.ExStyle |= (int)(WindowStyles.WS_EX_TOOLWINDOW | WindowStyles.WS_EX_TOPMOST);
713 // not sure what to put in here - just doing a base() call - jba
714 protected override ImeMode DefaultImeMode {
716 return base.DefaultImeMode;
720 protected override Size DefaultSize {
722 Size single_month = SingleMonthSize;
724 int width = calendar_dimensions.Width * single_month.Width;
725 if (calendar_dimensions.Width > 1) {
726 width += (calendar_dimensions.Width - 1) * calendar_spacing.Width;
730 int height = calendar_dimensions.Height * single_month.Height;
731 if (this.ShowToday) {
732 height += date_cell_size.Height + 2; // add the height of the "Today: " ...
734 if (calendar_dimensions.Height > 1) {
735 height += (calendar_dimensions.Height - 1) * calendar_spacing.Height;
738 // add the 1 pixel boundary
746 return new Size (width, height);
750 #endregion // Protected Instance Properties
752 #region Public Instance Methods
754 // add a date to the anually bolded date arraylist
755 public void AddAnnuallyBoldedDate (DateTime date) {
756 added_annually_bolded_dates.Add (date.Date);
759 // add a date to the normal bolded date arraylist
760 public void AddBoldedDate (DateTime date) {
761 added_bolded_dates.Add (date.Date);
764 // add a date to the anually monthly date arraylist
765 public void AddMonthlyBoldedDate (DateTime date) {
766 added_monthly_bolded_dates.Add (date.Date);
769 // if visible = true, return only the dates of full months, else return all dates visible
770 public SelectionRange GetDisplayRange (bool visible) {
773 start = new DateTime (current_month.Year, current_month.Month, 1);
774 end = start.AddMonths (calendar_dimensions.Width * calendar_dimensions.Height);
775 end = end.AddDays(-1);
777 // process all visible dates if needed (including the grayed out dates
779 start = GetFirstDateInMonthGrid (start);
780 end = GetLastDateInMonthGrid (end);
783 return new SelectionRange (start, end);
786 // HitTest overload that recieve's x and y co-ordinates as separate ints
787 public HitTestInfo HitTest (int x, int y) {
788 return HitTest (new Point (x, y));
791 // returns a HitTestInfo for MonthCalendar element's under the specified point
792 public HitTestInfo HitTest (Point point) {
793 return HitTest (point, out last_clicked_calendar_index, out last_clicked_calendar_rect);
796 // clears all the annually bolded dates
797 public void RemoveAllAnnuallyBoldedDates () {
798 annually_bolded_dates = null;
799 added_annually_bolded_dates.Clear ();
800 removed_annually_bolded_dates.Clear ();
803 // clears all the normal bolded dates
804 public void RemoveAllBoldedDates () {
806 added_bolded_dates.Clear ();
807 removed_bolded_dates.Clear ();
810 // clears all the monthly bolded dates
811 public void RemoveAllMonthlyBoldedDates () {
812 monthly_bolded_dates = null;
813 added_monthly_bolded_dates.Clear ();
814 removed_monthly_bolded_dates.Clear ();
817 // clears the specified annually bolded date (only compares day and month)
818 // only removes the first instance of the match
819 public void RemoveAnnuallyBoldedDate (DateTime date) {
820 if (!removed_annually_bolded_dates.Contains (date.Date)) {
821 removed_annually_bolded_dates.Add (date.Date);
825 // clears all the normal bolded date
826 // only removes the first instance of the match
827 public void RemoveBoldedDate (DateTime date) {
828 if (!removed_bolded_dates.Contains (date.Date)) {
829 removed_bolded_dates.Add (date.Date);
833 // clears the specified monthly bolded date (only compares day and month)
834 // only removes the first instance of the match
835 public void RemoveMonthlyBoldedDate (DateTime date) {
836 if (!removed_monthly_bolded_dates.Contains (date.Date)) {
837 removed_monthly_bolded_dates.Add (date.Date);
841 // sets the calendar_dimensions. If product is > 12, the larger dimension is reduced to make product < 12
842 public void SetCalendarDimensions(int x, int y) {
843 this.CalendarDimensions = new Size(x, y);
846 // sets the currently selected date as date
847 public void SetDate (DateTime date) {
848 this.SetSelectionRange (date.Date, date.Date);
851 // utility method set the SelectionRange property using individual dates
852 public void SetSelectionRange (DateTime date1, DateTime date2) {
853 this.SelectionRange = new SelectionRange(date1, date2);
856 public override string ToString () {
857 return this.GetType().Name + ", " + this.SelectionRange.ToString ();
860 // usually called after an AddBoldedDate method is called
861 // formats monthly and daily bolded dates according to the current calendar year
862 public void UpdateBoldedDates () {
863 UpdateDateArray (ref bolded_dates, added_bolded_dates, removed_bolded_dates);
864 UpdateDateArray (ref monthly_bolded_dates, added_monthly_bolded_dates, removed_monthly_bolded_dates);
865 UpdateDateArray (ref annually_bolded_dates, added_annually_bolded_dates, removed_annually_bolded_dates);
868 #endregion // Public Instance Methods
870 #region Protected Instance Methods
872 // not sure why this needs to be overriden
873 protected override void CreateHandle () {
874 base.CreateHandle ();
877 private void CreateYearUpDown ()
879 year_updown = new NumericUpDown ();
880 year_updown.Font = this.Font;
881 year_updown.Minimum = MinDate.Year;
882 year_updown.Maximum = MaxDate.Year;
883 year_updown.ReadOnly = true;
884 year_updown.Visible = false;
885 this.Controls.AddImplicit (year_updown);
886 year_updown.ValueChanged += new EventHandler(UpDownYearChangedHandler);
889 // not sure why this needs to be overriden
890 protected override void Dispose (bool disposing) {
891 base.Dispose (disposing);
894 // not sure why this needs to be overriden
895 protected override bool IsInputKey (Keys keyData) {
896 return base.IsInputKey (keyData);
899 // not sure why this needs to be overriden
900 protected override void OnBackColorChanged (EventArgs e) {
901 base.OnBackColorChanged (e);
905 // raises the date changed event
906 protected virtual void OnDateChanged (DateRangeEventArgs drevent) {
907 if (this.DateChanged != null) {
908 this.DateChanged (this, drevent);
912 // raises the DateSelected event
913 protected virtual void OnDateSelected (DateRangeEventArgs drevent) {
914 if (this.DateSelected != null) {
915 this.DateSelected (this, drevent);
919 protected override void OnFontChanged (EventArgs e) {
920 base.OnFontChanged (e);
923 protected override void OnForeColorChanged (EventArgs e) {
924 base.OnForeColorChanged (e);
927 protected override void OnHandleCreated (EventArgs e) {
928 base.OnHandleCreated (e);
932 // i think this is overriden to not allow the control to be changed to an arbitrary size
933 protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified) {
934 if ((specified & BoundsSpecified.Height) == BoundsSpecified.Height ||
935 (specified & BoundsSpecified.Width) == BoundsSpecified.Width ||
936 (specified & BoundsSpecified.Size) == BoundsSpecified.Size) {
937 // only allow sizes = default size to be set
938 Size min_size = DefaultSize;
939 Size max_size = new Size (
940 DefaultSize.Width + SingleMonthSize.Width + calendar_spacing.Width,
941 DefaultSize.Height + SingleMonthSize.Height + calendar_spacing.Height);
942 int x_mid_point = (max_size.Width + min_size.Width)/2;
943 int y_mid_point = (max_size.Height + min_size.Height)/2;
944 if (width < x_mid_point) {
945 width = min_size.Width;
947 width = max_size.Width;
949 if (height < y_mid_point) {
950 height = min_size.Height;
952 height = max_size.Height;
954 base.SetBoundsCore (x, y, width, height, specified);
956 base.SetBoundsCore (x, y, width, height, specified);
960 protected override void WndProc (ref Message m) {
961 base.WndProc (ref m);
964 #endregion // Protected Instance Methods
966 #region public events
968 // fired when the date is changed (either explicitely or implicitely)
969 // when navigating the month selector
970 public event DateRangeEventHandler DateChanged;
972 // fired when the user explicitely clicks on date to select it
973 public event DateRangeEventHandler DateSelected;
976 [EditorBrowsable (EditorBrowsableState.Never)]
977 public event EventHandler BackgroundImageChanged;
979 // this event is overridden to supress it from being fired
981 [EditorBrowsable (EditorBrowsableState.Never)]
982 public event EventHandler Click;
984 // this event is overridden to supress it from being fired
986 [EditorBrowsable (EditorBrowsableState.Never)]
987 public event EventHandler DoubleClick;
990 [EditorBrowsable (EditorBrowsableState.Never)]
991 public event EventHandler ImeModeChanged;
994 [EditorBrowsable (EditorBrowsableState.Never)]
995 public new event PaintEventHandler Paint;
998 [EditorBrowsable (EditorBrowsableState.Never)]
999 public event EventHandler TextChanged;
1000 #endregion // public events
1002 #region internal properties
1004 internal DateTime CurrentMonth {
1006 // only interested in if the month (not actual date) has changed
1007 if (value.Month != current_month.Month ||
1008 value.Year != current_month.Year) {
1009 this.SelectionRange = new SelectionRange(
1010 this.SelectionStart.Add(value.Subtract(current_month)),
1011 this.SelectionEnd.Add(value.Subtract(current_month)));
1012 current_month = value;
1013 UpdateBoldedDates();
1018 return current_month;
1022 #endregion // internal properties
1024 #region internal/private methods
1025 internal HitTestInfo HitTest (
1027 out int calendar_index,
1028 out Rectangle calendar_rect) {
1029 // start by initialising the ref parameters
1030 calendar_index = -1;
1031 calendar_rect = Rectangle.Empty;
1033 // before doing all the hard work, see if the today's date wasn't clicked
1034 Rectangle today_rect = new Rectangle (
1036 ClientRectangle.Bottom - date_cell_size.Height,
1037 7 * date_cell_size.Width,
1038 date_cell_size.Height);
1039 if (today_rect.Contains (point) && this.ShowToday) {
1040 return new HitTestInfo(HitArea.TodayLink, point, DateTime.Now);
1043 Size month_size = SingleMonthSize;
1044 // define calendar rect's that this thing can land in
1045 Rectangle[] calendars = new Rectangle [CalendarDimensions.Width * CalendarDimensions.Height];
1046 for (int i=0; i < CalendarDimensions.Width * CalendarDimensions.Height; i ++) {
1048 calendars[i] = new Rectangle (
1049 new Point (ClientRectangle.X + 1, ClientRectangle.Y + 1),
1052 // calendar on the next row
1053 if (i % CalendarDimensions.Width == 0) {
1054 calendars[i] = new Rectangle (
1055 new Point (calendars[i-CalendarDimensions.Width].X, calendars[i-CalendarDimensions.Width].Bottom + calendar_spacing.Height),
1058 // calendar on the next column
1059 calendars[i] = new Rectangle (
1060 new Point (calendars[i-1].Right + calendar_spacing.Width, calendars[i-1].Y),
1066 // through each trying to find a match
1067 for (int i = 0; i < calendars.Length ; i++) {
1068 if (calendars[i].Contains (point)) {
1069 // check the title section
1070 Rectangle title_rect = new Rectangle (
1071 calendars[i].Location,
1073 if (title_rect.Contains (point) ) {
1074 // make sure it's not a previous button
1076 Rectangle button_rect = new Rectangle(
1077 new Point (calendars[i].X + button_x_offset, (title_size.Height - button_size.Height)/2),
1079 if (button_rect.Contains (point)) {
1080 return new HitTestInfo(HitArea.PrevMonthButton, point, DateTime.Now);
1083 // make sure it's not the next button
1084 if (i % CalendarDimensions.Height == 0 && i % CalendarDimensions.Width == calendar_dimensions.Width - 1) {
1085 Rectangle button_rect = new Rectangle(
1086 new Point (calendars[i].Right - button_x_offset - button_size.Width, (title_size.Height - button_size.Height)/2),
1088 if (button_rect.Contains (point)) {
1089 return new HitTestInfo(HitArea.NextMonthButton, point, DateTime.Now);
1093 // indicate which calendar and month it was
1095 calendar_rect = calendars[i];
1097 // make sure it's not the month or the year of the calendar
1098 if (GetMonthNameRectangle (title_rect, i).Contains (point)) {
1099 return new HitTestInfo(HitArea.TitleMonth, point, DateTime.Now);
1101 if (GetYearNameRectangle (title_rect, i).Contains (point)) {
1102 return new HitTestInfo(HitArea.TitleYear, point, DateTime.Now);
1105 // return the hit test in the title background
1106 return new HitTestInfo(HitArea.TitleBackground, point, DateTime.Now);
1109 Point date_grid_location = new Point (calendars[i].X, title_rect.Bottom);
1111 // see if it's in the Week numbers
1112 if (ShowWeekNumbers) {
1113 Rectangle weeks_rect = new Rectangle (
1115 new Size (date_cell_size.Width,Math.Max (calendars[i].Height - title_rect.Height, 0)));
1116 if (weeks_rect.Contains (point)) {
1117 return new HitTestInfo(HitArea.WeekNumbers, point, DateTime.Now);
1120 // move the location of the grid over
1121 date_grid_location.X += date_cell_size.Width;
1124 // see if it's in the week names
1125 Rectangle day_rect = new Rectangle (
1127 new Size (Math.Max (calendars[i].Right - date_grid_location.X, 0), date_cell_size.Height));
1128 if (day_rect.Contains (point)) {
1129 return new HitTestInfo(HitArea.DayOfWeek, point, DateTime.Now);
1132 // finally see if it was a date that was clicked
1133 Rectangle date_grid = new Rectangle (
1134 new Point (day_rect.X, day_rect.Bottom),
1135 new Size (day_rect.Width, Math.Max(calendars[i].Bottom - day_rect.Bottom, 0)));
1136 if (date_grid.Contains (point)) {
1137 // okay so it's inside the grid, get the offset
1138 Point offset = new Point (point.X - date_grid.X, point.Y - date_grid.Y);
1139 int row = offset.Y / date_cell_size.Height;
1140 int col = offset.X / date_cell_size.Width;
1141 // establish our first day of the month
1142 DateTime calendar_month = this.CurrentMonth.AddMonths(i);
1143 DateTime first_day = GetFirstDateInMonthGrid (calendar_month);
1144 DateTime time = first_day.AddDays ((row * 7) + col);
1145 // establish which date was clicked
1146 if (time.Year != calendar_month.Year || time.Month != calendar_month.Month) {
1147 if (time < calendar_month && i == 0) {
1148 return new HitTestInfo(HitArea.PrevMonthDate, point, time);
1149 } else if (time > calendar_month && i == CalendarDimensions.Width*CalendarDimensions.Height - 1) {
1150 return new HitTestInfo(HitArea.NextMonthDate, point, time);
1152 return new HitTestInfo(HitArea.Nowhere, point, time);
1154 return new HitTestInfo(HitArea.Date, point, time);
1159 return new HitTestInfo ();
1162 // returns the date of the first cell of the specified month grid
1163 internal DateTime GetFirstDateInMonthGrid (DateTime month) {
1164 // convert the first_day_of_week into a DayOfWeekEnum
1165 DayOfWeek first_day = GetDayOfWeek (first_day_of_week);
1166 // find the first day of the month
1167 DateTime first_date_of_month = new DateTime (month.Year, month.Month, 1);
1168 DayOfWeek first_day_of_month = first_date_of_month.DayOfWeek;
1169 // adjust for the starting day of the week
1170 int offset = first_day_of_month - first_day;
1174 return first_date_of_month.AddDays (-1*offset);
1177 // returns the date of the last cell of the specified month grid
1178 internal DateTime GetLastDateInMonthGrid (DateTime month)
1180 DateTime start = GetFirstDateInMonthGrid(month);
1181 return start.AddDays ((7 * 6)-1);
1184 internal bool IsBoldedDate (DateTime date) {
1185 // check bolded dates
1186 if (bolded_dates != null && bolded_dates.Length > 0) {
1187 foreach (DateTime bolded_date in bolded_dates) {
1188 if (bolded_date.Date == date.Date) {
1193 // check monthly dates
1194 if (monthly_bolded_dates != null && monthly_bolded_dates.Length > 0) {
1195 foreach (DateTime bolded_date in monthly_bolded_dates) {
1196 if (bolded_date.Day == date.Day) {
1201 // check yearly dates
1202 if (annually_bolded_dates != null && annually_bolded_dates.Length > 0) {
1203 foreach (DateTime bolded_date in annually_bolded_dates) {
1204 if (bolded_date.Month == date.Month && bolded_date.Day == date.Day) {
1210 return false; // no match
1213 // updates the specified bolded dates array with ones to add and ones to remove
1214 private void UpdateDateArray (ref DateTime [] dates, ArrayList to_add, ArrayList to_remove) {
1215 ArrayList list = new ArrayList ();
1217 // update normal bolded dates
1218 if (dates != null) {
1219 foreach (DateTime date in dates) {
1220 list.Add (date.Date);
1225 foreach (DateTime date in to_add) {
1226 if (!list.Contains (date.Date)) {
1227 list.Add (date.Date);
1232 // remove ones to remove
1233 foreach (DateTime date in to_remove) {
1234 if (list.Contains (date.Date)) {
1235 list.Remove (date.Date);
1239 // set up the array now
1240 if (list.Count > 0) {
1241 dates = (DateTime []) list.ToArray (typeof (DateTime));
1249 // initialise the context menu
1250 private void SetUpContextMenu () {
1251 menu = new ContextMenu ();
1252 for (int i=0; i < 12; i++) {
1253 MenuItem menu_item = new MenuItem ( new DateTime (2000, i+1, 1).ToString ("MMMM"));
1254 menu_item.Click += new EventHandler (MenuItemClickHandler);
1255 menu.MenuItems.Add (menu_item);
1259 // initialises text value and show's year up down in correct position
1260 private void PrepareYearUpDown (Point p) {
1261 Rectangle old_location = year_updown.Bounds;
1264 Rectangle title_rect = new Rectangle(
1265 last_clicked_calendar_rect.Location,
1268 year_updown.Bounds = GetYearNameRectangle(
1270 last_clicked_calendar_index);
1271 year_updown.Top -= 4;
1272 year_updown.Width += (int) (this.Font.Size * 4);
1273 // set year - only do this if this isn't being called because of a year up down click
1274 if(year_updown.Bounds != old_location) {
1275 year_updown.Value = current_month.AddMonths(last_clicked_calendar_index).Year;
1278 if(!year_updown.Visible) {
1279 year_updown.Visible = true;
1283 // returns the first date of the month
1284 private DateTime GetFirstDateInMonth (DateTime date) {
1285 return new DateTime (date.Year, date.Month, 1);
1288 // returns the last date of the month
1289 private DateTime GetLastDateInMonth (DateTime date) {
1290 return new DateTime (date.Year, date.Month, 1).AddMonths(1).AddDays(-1);
1293 // called in response to users seletion with shift key
1294 private void AddTimeToSelection (int delta, bool isDays)
1296 DateTime cursor_point;
1298 // okay we add the period to the date that is not the same as the
1299 // start date when shift was first clicked.
1300 if (SelectionStart != first_select_start_date) {
1301 cursor_point = SelectionStart;
1303 cursor_point = SelectionEnd;
1307 end_point = cursor_point.AddDays (delta);
1309 // delta must be months
1310 end_point = cursor_point.AddMonths (delta);
1312 // set the new selection range
1313 SelectionRange range = new SelectionRange (first_select_start_date, end_point);
1314 if (range.Start.AddDays (MaxSelectionCount-1) < range.End) {
1315 // okay the date is beyond what is allowed, lets set the maximum we can
1316 if (range.Start != first_select_start_date) {
1317 range.Start = range.End.AddDays ((MaxSelectionCount-1)*-1);
1319 range.End = range.Start.AddDays (MaxSelectionCount-1);
1322 this.SelectionRange = range;
1325 // attempts to add the date to the selection without throwing exception
1326 private void SelectDate (DateTime date) {
1327 // try and add the new date to the selction range
1328 if (is_shift_pressed || (click_state [0])) {
1329 SelectionRange range = new SelectionRange (first_select_start_date, date);
1330 if (range.Start.AddDays (MaxSelectionCount-1) < range.End) {
1331 // okay the date is beyond what is allowed, lets set the maximum we can
1332 if (range.Start != first_select_start_date) {
1333 range.Start = range.End.AddDays ((MaxSelectionCount-1)*-1);
1335 range.End = range.Start.AddDays (MaxSelectionCount-1);
1338 SelectionRange = range;
1340 SelectionRange = new SelectionRange (date, date);
1341 first_select_start_date = date;
1345 // gets the week of the year
1346 internal int GetWeekOfYear (DateTime date) {
1347 // convert the first_day_of_week into a DayOfWeekEnum
1348 DayOfWeek first_day = GetDayOfWeek (first_day_of_week);
1349 // find the first day of the year
1350 DayOfWeek first_day_of_year = new DateTime (date.Year, 1, 1).DayOfWeek;
1351 // adjust for the starting day of the week
1352 int offset = first_day_of_year - first_day;
1353 int week = ((date.DayOfYear + offset) / 7) + 1;
1357 // convert a Day enum into a DayOfWeek enum
1358 internal DayOfWeek GetDayOfWeek (Day day) {
1359 if (day == Day.Default) {
1360 return DayOfWeek.Sunday;
1362 return (DayOfWeek) DayOfWeek.Parse (typeof (DayOfWeek), day.ToString ());
1366 // returns the rectangle for themonth name
1367 internal Rectangle GetMonthNameRectangle (Rectangle title_rect, int calendar_index) {
1368 Graphics g = this.DeviceContext;
1369 DateTime this_month = this.current_month.AddMonths (calendar_index);
1370 Size title_text_size = g.MeasureString (this_month.ToString ("MMMM yyyy"), this.Font).ToSize ();
1371 Size month_size = g.MeasureString (this_month.ToString ("MMMM"), this.Font).ToSize ();
1372 // return only the month name part of that
1373 return new Rectangle (
1375 title_rect.X + ((title_rect.Width - title_text_size.Width)/2),
1376 title_rect.Y + ((title_rect.Height - title_text_size.Height)/2)),
1380 // returns the rectangle for the year in the title
1381 internal Rectangle GetYearNameRectangle (Rectangle title_rect, int calendar_index) {
1382 Graphics g = this.DeviceContext;
1383 DateTime this_month = this.current_month.AddMonths (calendar_index);
1384 Size title_text_size = g.MeasureString (this_month.ToString ("MMMM yyyy"), this.Font).ToSize ();
1385 Size year_size = g.MeasureString (this_month.ToString ("yyyy"), this.Font).ToSize ();
1386 // find out how much space the title took
1387 Rectangle text_rect = new Rectangle (
1389 title_rect.X + ((title_rect.Width - title_text_size.Width)/2),
1390 title_rect.Y + ((title_rect.Height - title_text_size.Height)/2)),
1392 // return only the rect of the year
1393 return new Rectangle (
1395 text_rect.Right - year_size.Width,
1400 // determine if date is allowed to be drawn in month
1401 internal bool IsValidWeekToDraw (DateTime month, DateTime date, int row, int col) {
1402 DateTime tocheck = month.AddMonths (-1);
1403 if ((month.Year == date.Year && month.Month == date.Month) ||
1404 (tocheck.Year == date.Year && tocheck.Month == date.Month)) {
1408 // check the railing dates (days in the month after the last month in grid)
1409 if (row == CalendarDimensions.Height - 1 && col == CalendarDimensions.Width - 1) {
1410 tocheck = month.AddMonths (1);
1411 return (tocheck.Year == date.Year && tocheck.Month == date.Month) ;
1417 // set one item clicked and all others off
1418 private void SetItemClick(HitTestInfo hti)
1420 switch(hti.HitArea) {
1421 case HitArea.NextMonthButton:
1422 this.is_previous_clicked = false;
1423 this.is_next_clicked = true;
1424 this.is_date_clicked = false;
1426 case HitArea.PrevMonthButton:
1427 this.is_previous_clicked = true;
1428 this.is_next_clicked = false;
1429 this.is_date_clicked = false;
1431 case HitArea.PrevMonthDate:
1432 case HitArea.NextMonthDate:
1434 this.clicked_date = hti.Time;
1435 this.is_previous_clicked = false;
1436 this.is_next_clicked = false;
1437 this.is_date_clicked = true;
1440 this.is_previous_clicked = false;
1441 this.is_next_clicked = false;
1442 this.is_date_clicked = false;
1447 // called when the year is changed
1448 private void UpDownYearChangedHandler (object sender, EventArgs e) {
1449 int initial_year_value = this.CurrentMonth.AddMonths(last_clicked_calendar_index).Year;
1450 int diff = (int) year_updown.Value - initial_year_value;
1451 this.CurrentMonth = this.CurrentMonth.AddYears(diff);
1454 // called when context menu is clicked
1455 private void MenuItemClickHandler (object sender, EventArgs e) {
1456 MenuItem item = sender as MenuItem;
1457 if (item != null && month_title_click_location != Point.Empty) {
1458 // establish which month we want to move to
1459 if (item.Parent == null) {
1462 int new_month = item.Parent.MenuItems.IndexOf (item) + 1;
1463 if (new_month == 0) {
1466 // okay let's establish which calendar was hit
1467 Size month_size = this.SingleMonthSize;
1468 for (int i=0; i < CalendarDimensions.Height; i++) {
1469 for (int j=0; j < CalendarDimensions.Width; j++) {
1470 int month_index = (i * CalendarDimensions.Width) + j;
1471 Rectangle month_rect = new Rectangle ( new Point (0, 0), month_size);
1473 month_rect.X = this.ClientRectangle.X + 1;
1475 month_rect.X = this.ClientRectangle.X + 1 + ((j)*(month_size.Width+calendar_spacing.Width));
1478 month_rect.Y = this.ClientRectangle.Y + 1;
1480 month_rect.Y = this.ClientRectangle.Y + 1 + ((i)*(month_size.Height+calendar_spacing.Height));
1482 // see if the point is inside
1483 if (month_rect.Contains (month_title_click_location)) {
1484 DateTime clicked_month = CurrentMonth.AddMonths (month_index);
1485 // get the month that we want to move to
1486 int month_offset = new_month - clicked_month.Month;
1488 // move forward however more months we need to
1489 this.CurrentMonth = this.CurrentMonth.AddMonths (month_offset);
1496 month_title_click_location = Point.Empty;
1500 // raised on the timer, for mouse hold clicks
1501 private void TimerHandler (object sender, EventArgs e) {
1502 // now find out which area was click
1504 HitTestInfo hti = this.HitTest (this.PointToClient (MousePosition));
1505 // see if it was clicked on the prev or next mouse
1506 if (click_state [1] || click_state [2]) {
1507 // invalidate the area where the mouse was last held
1509 Application.DoEvents ();
1510 // register the click
1511 if (hti.HitArea == HitArea.PrevMonthButton ||
1512 hti.HitArea == HitArea.NextMonthButton) {
1513 DoButtonMouseDown (hti);
1514 click_state [1] = (hti.HitArea == HitArea.PrevMonthButton);
1515 click_state [2] = !click_state [1];
1517 if (timer.Interval != 100) {
1518 timer.Interval = 100;
1522 timer.Enabled = false;
1526 // selects one of the buttons
1527 private void DoButtonMouseDown (HitTestInfo hti) {
1528 // show the click then move on
1530 if (hti.HitArea == HitArea.PrevMonthButton) {
1531 // invalidate the prev monthbutton
1534 this.ClientRectangle.X + 1 + button_x_offset,
1535 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1537 button_size.Height));
1538 this.CurrentMonth = this.CurrentMonth.AddMonths (ScrollChange*-1);
1540 // invalidate the next monthbutton
1543 this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width,
1544 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1546 button_size.Height));
1547 this.CurrentMonth = this.CurrentMonth.AddMonths (ScrollChange);
1551 // selects the clicked date
1552 private void DoDateMouseDown (HitTestInfo hti) {
1554 this.SelectDate (clicked_date);
1555 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1558 // event run on the mouse up event
1559 private void DoMouseUp () {
1560 // invalidate the next monthbutton
1561 if (this.is_next_clicked) {
1564 this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width,
1565 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1567 button_size.Height));
1569 // invalidate the prev monthbutton
1570 if (this.is_previous_clicked) {
1573 this.ClientRectangle.X + 1 + button_x_offset,
1574 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1576 button_size.Height));
1578 if (this.is_date_clicked) {
1579 // invalidate the area under the cursor, to remove focus rect
1580 this.InvalidateDateRange (new SelectionRange (clicked_date, clicked_date));
1582 this.is_previous_clicked = false;
1583 this.is_next_clicked = false;
1584 this.is_date_clicked = false;
1587 // // need when in windowed mode
1588 // private void LostFocusHandler (object sender, EventArgs e)
1590 // if (this.owner != null) {
1591 // if (this.Visible) {
1592 // this.owner.HideMonthCalendar ();
1597 // occurs when mouse moves around control, used for selection
1598 private void MouseMoveHandler (object sender, MouseEventArgs e) {
1599 HitTestInfo hti = this.HitTest (e.X, e.Y);
1600 // clear the last clicked item
1601 if (click_state [0]) {
1602 // register the click
1603 if (hti.HitArea == HitArea.PrevMonthDate ||
1604 hti.HitArea == HitArea.NextMonthDate ||
1605 hti.HitArea == HitArea.Date)
1607 DoDateMouseDown (hti);
1608 if (owner == null) {
1609 click_state [0] = true;
1611 click_state [0] = false;
1612 click_state [1] = false;
1613 click_state [2] = false;
1620 // to check if the mouse has come down on this control
1621 private void MouseDownHandler (object sender, MouseEventArgs e)
1623 // clear the click_state variables
1624 click_state [0] = false;
1625 click_state [1] = false;
1626 click_state [2] = false;
1628 // disable the timer if it was enabled
1629 if (timer.Enabled) {
1631 timer.Enabled = false;
1634 Point point = new Point (e.X, e.Y);
1635 // figure out if we are in drop down mode and a click happened outside us
1636 if (this.owner != null) {
1637 if (!this.ClientRectangle.Contains (point)) {
1638 this.owner.HideMonthCalendar ();
1643 //establish where was hit
1644 HitTestInfo hti = this.HitTest(point);
1645 // hide the year numeric up down if it was clicked
1646 if (year_updown != null && year_updown.Visible && hti.HitArea != HitArea.TitleYear)
1648 year_updown.Visible = false;
1650 switch (hti.HitArea) {
1651 case HitArea.PrevMonthButton:
1652 case HitArea.NextMonthButton:
1653 DoButtonMouseDown (hti);
1654 click_state [1] = (hti.HitArea == HitArea.PrevMonthDate);
1655 click_state [2] = !click_state [1];
1656 timer.Interval = 500;
1660 case HitArea.PrevMonthDate:
1661 case HitArea.NextMonthDate:
1662 DoDateMouseDown (hti);
1663 // leave clicked state blank if drop down window
1664 if (owner == null) {
1665 click_state [0] = true;
1667 click_state [0] = false;
1668 click_state [1] = false;
1669 click_state [2] = false;
1672 case HitArea.TitleMonth:
1673 month_title_click_location = hti.Point;
1674 menu.Show (this, hti.Point);
1676 case HitArea.TitleYear:
1677 // place the numeric up down
1678 PrepareYearUpDown(hti.Point);
1680 case HitArea.TodayLink:
1681 this.SetSelectionRange (DateTime.Now.Date, DateTime.Now.Date);
1682 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1685 this.is_previous_clicked = false;
1686 this.is_next_clicked = false;
1687 this.is_date_clicked = false;
1692 // raised by any key down events
1693 private void KeyDownHandler (object sender, KeyEventArgs e) {
1694 // send keys to the year_updown control, let it handle it
1695 if(year_updown.Visible) {
1696 switch (e.KeyCode) {
1698 year_updown.Visible = false;
1701 year_updown.Value = year_updown.Value + 1;
1704 year_updown.Value = year_updown.Value - 1;
1708 if (!is_shift_pressed && e.Shift) {
1709 first_select_start_date = SelectionStart;
1710 is_shift_pressed = e.Shift;
1712 switch (e.KeyCode) {
1714 // set the date to the start of the month
1715 if (is_shift_pressed) {
1716 DateTime date = GetFirstDateInMonth (first_select_start_date);
1717 if (date < first_select_start_date.AddDays ((MaxSelectionCount-1)*-1)) {
1718 date = first_select_start_date.AddDays ((MaxSelectionCount-1)*-1);
1720 this.SetSelectionRange (date, first_select_start_date);
1722 DateTime date = GetFirstDateInMonth (this.SelectionStart);
1723 this.SetSelectionRange (date, date);
1725 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1728 // set the date to the last of the month
1729 if (is_shift_pressed) {
1730 DateTime date = GetLastDateInMonth (first_select_start_date);
1731 if (date > first_select_start_date.AddDays (MaxSelectionCount-1)) {
1732 date = first_select_start_date.AddDays (MaxSelectionCount-1);
1734 this.SetSelectionRange (date, first_select_start_date);
1736 DateTime date = GetLastDateInMonth (this.SelectionStart);
1737 this.SetSelectionRange (date, date);
1739 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1742 // set the date to the last of the month
1743 if (is_shift_pressed) {
1744 this.AddTimeToSelection (-1, false);
1746 DateTime date = this.SelectionStart.AddMonths (-1);
1747 this.SetSelectionRange (date, date);
1749 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1752 // set the date to the last of the month
1753 if (is_shift_pressed) {
1754 this.AddTimeToSelection (1, false);
1756 DateTime date = this.SelectionStart.AddMonths (1);
1757 this.SetSelectionRange (date, date);
1759 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1762 // set the back 1 week
1763 if (is_shift_pressed) {
1764 this.AddTimeToSelection (-7, true);
1766 DateTime date = this.SelectionStart.AddDays (-7);
1767 this.SetSelectionRange (date, date);
1769 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1772 // set the date forward 1 week
1773 if (is_shift_pressed) {
1774 this.AddTimeToSelection (7, true);
1776 DateTime date = this.SelectionStart.AddDays (7);
1777 this.SetSelectionRange (date, date);
1779 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1783 if (is_shift_pressed) {
1784 this.AddTimeToSelection (-1, true);
1786 DateTime date = this.SelectionStart.AddDays (-1);
1787 this.SetSelectionRange (date, date);
1789 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1793 if (is_shift_pressed) {
1794 this.AddTimeToSelection (1, true);
1796 DateTime date = this.SelectionStart.AddDays (1);
1797 this.SetSelectionRange (date, date);
1799 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1808 // to check if the mouse has come up on this control
1809 private void MouseUpHandler (object sender, MouseEventArgs e)
1811 if (timer.Enabled) {
1814 // clear the click state array
1815 click_state [0] = false;
1816 click_state [1] = false;
1817 click_state [2] = false;
1818 // do the regulare mouseup stuff
1822 // raised by any key up events
1823 private void KeyUpHandler (object sender, KeyEventArgs e) {
1824 is_shift_pressed = e.Shift ;
1828 // paint this control now
1829 private void PaintHandler (object sender, PaintEventArgs pe) {
1830 if (Width <= 0 || Height <= 0 || Visible == false)
1833 Draw (pe.ClipRectangle, pe.Graphics);
1835 // fire the new paint handler
1836 if (this.Paint != null)
1838 this.Paint (sender, pe);
1842 // returns the region of the control that needs to be redrawn
1843 private void InvalidateDateRange (SelectionRange range) {
1844 SelectionRange bounds = this.GetDisplayRange (false);
1845 if (range.End < bounds.Start || range.Start > bounds.End) {
1846 // don't invalidate anything, as the modified date range
1847 // is outside the visible bounds of this control
1850 // adjust the start and end to be inside the visible range
1851 if (range.Start < bounds.Start) {
1852 range = new SelectionRange (bounds.Start, range.End);
1854 if (range.End > bounds.End) {
1855 range = new SelectionRange (range.Start, bounds.End);
1857 // now invalidate the date rectangles as series of rows
1858 DateTime last_month = this.current_month.AddMonths ((CalendarDimensions.Width * CalendarDimensions.Height)).AddDays (-1);
1859 DateTime current = range.Start;
1860 while (current <= range.End) {
1861 DateTime month_end = new DateTime (current.Year, current.Month, 1).AddMonths (1).AddDays (-1);;
1862 Rectangle start_rect;
1864 // see if entire selection is in this current month
1865 if (range.End <= month_end && current < last_month) {
1866 // the end is the last date
1867 if (current < this.current_month) {
1868 start_rect = GetDateRowRect (current_month, current_month);
1870 start_rect = GetDateRowRect (current, current);
1872 end_rect = GetDateRowRect (current, range.End);
1873 } else if (current < last_month) {
1874 // otherwise it simply means we have a selection spaning
1875 // multiple months simply set rectangle inside the current month
1876 start_rect = GetDateRowRect (current, current);
1877 end_rect = GetDateRowRect (last_month, month_end);
1879 // it's outside the visible range
1880 start_rect = GetDateRowRect (last_month, last_month.AddDays (1));
1881 end_rect = GetDateRowRect (last_month, range.End);
1883 // push to the next month
1884 current = month_end.AddDays (1);
1885 // invalidate from the start row to the end row for this month
1891 Math.Max (end_rect.Bottom - start_rect.Y, 0)));
1895 // gets the rect of the row where the specified date appears on the specified month
1896 private Rectangle GetDateRowRect (DateTime month, DateTime date) {
1897 // first get the general rect of the supplied month
1898 Size month_size = SingleMonthSize;
1899 Rectangle month_rect = Rectangle.Empty;
1900 for (int i=0; i < CalendarDimensions.Width*CalendarDimensions.Height; i++) {
1901 DateTime this_month = this.current_month.AddMonths (i);
1902 if (month.Year == this_month.Year && month.Month == this_month.Month) {
1903 month_rect = new Rectangle (
1904 this.ClientRectangle.X + 1 + (month_size.Width * (i%CalendarDimensions.Width)) + (this.calendar_spacing.Width * (i%CalendarDimensions.Width)),
1905 this.ClientRectangle.Y + 1 + (month_size.Height * (i/CalendarDimensions.Width)) + (this.calendar_spacing.Height * (i/CalendarDimensions.Width)),
1911 // now find out where in the month the supplied date is
1912 if (month_rect == Rectangle.Empty) {
1913 return Rectangle.Empty;
1915 // find out which row this date is in
1917 DateTime first_date = GetFirstDateInMonthGrid (month);
1918 DateTime end_date = first_date.AddDays (7);
1919 for (int i=0; i < 6; i++) {
1920 if (date >= first_date && date < end_date) {
1924 first_date = end_date;
1925 end_date = end_date.AddDays (7);
1927 // ensure it's a valid row
1929 return Rectangle.Empty;
1931 int x_offset = (this.ShowWeekNumbers) ? date_cell_size.Width : 0;
1932 int y_offset = title_size.Height + (date_cell_size.Height * (row + 1));
1933 return new Rectangle (
1934 month_rect.X + x_offset,
1935 month_rect.Y + y_offset,
1936 date_cell_size.Width * 7,
1937 date_cell_size.Height);
1940 internal void Draw (Rectangle clip_rect, Graphics dc)
1942 ThemeEngine.Current.DrawMonthCalendar (dc, clip_rect, this);
1945 #endregion //internal methods
1947 #region internal drawing methods
1950 #endregion // internal drawing methods
1952 #region inner classes and enumerations
1954 // enumeration about what type of area on the calendar was hit
1955 public enum HitArea {
1971 // info regarding to a hit test on this calendar
1972 public sealed class HitTestInfo {
1974 private HitArea hit_area;
1975 private Point point;
1976 private DateTime time;
1978 // default constructor
1979 internal HitTestInfo () {
1980 hit_area = HitArea.Nowhere;
1981 point = new Point (0, 0);
1982 time = DateTime.Now;
1985 // overload receives all properties
1986 internal HitTestInfo (HitArea hit_area, Point point, DateTime time) {
1987 this.hit_area = hit_area;
1992 // the type of area that was hit
1993 public HitArea HitArea {
1999 // the point that is being test
2000 public Point Point {
2006 // the date under the hit test point, only valid if HitArea is Date
2007 public DateTime Time {
2014 #endregion // inner classes