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-2006 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.Globalization;
34 using System.Windows.Forms;
35 using System.Runtime.InteropServices;
37 namespace System.Windows.Forms {
39 [DefaultBindingProperty("SelectionRange")]
40 [ClassInterface(ClassInterfaceType.AutoDispatch)]
43 [DefaultProperty("SelectionRange")]
44 [DefaultEvent("DateChanged")]
45 [Designer ("System.Windows.Forms.Design.MonthCalendarDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")]
46 public class MonthCalendar : Control {
47 #region Local variables
48 ArrayList annually_bolded_dates;
49 ArrayList monthly_bolded_dates;
50 ArrayList bolded_dates;
51 Size calendar_dimensions;
52 Day first_day_of_week;
54 int max_selection_count;
57 SelectionRange selection_range;
59 bool show_today_circle;
60 bool show_week_numbers;
61 Color title_back_color;
62 Color title_fore_color;
65 Color trailing_fore_color;
69 const int initial_delay = 500;
70 const int subsequent_delay = 100;
71 private bool is_year_going_up;
72 private bool is_year_going_down;
73 private bool is_mouse_moving_year;
74 private int year_moving_count;
76 bool right_to_left_layout;
79 // internal variables used
80 internal bool show_year_updown;
81 internal DateTime current_month; // the month that is being displayed in top left corner of the grid
82 internal DateTimePicker owner; // used if this control is popped up
83 internal int button_x_offset;
84 internal Size button_size;
85 internal Size title_size;
86 internal Size date_cell_size;
87 internal Size calendar_spacing;
88 internal int divider_line_offset;
89 internal DateTime clicked_date;
90 internal Rectangle clicked_rect;
91 internal bool is_date_clicked;
92 internal bool is_previous_clicked;
93 internal bool is_next_clicked;
94 internal bool is_shift_pressed;
95 internal DateTime first_select_start_date;
96 internal int last_clicked_calendar_index;
97 internal Rectangle last_clicked_calendar_rect;
98 internal Font bold_font; // Cache the font in FontStyle.Bold
99 internal StringFormat centered_format; // Cache centered string format
100 private Point month_title_click_location;
101 // this is used to see which item was actually clicked on in the beginning
102 // so that we know which item to fire on timer
104 // 1: previous clicked
106 private bool[] click_state;
110 #endregion // Local variables
112 #region Public Constructors
114 public MonthCalendar () {
115 // set up the control painting
116 SetStyle (ControlStyles.UserPaint | ControlStyles.StandardClick, false);
119 timer = new Timer ();
120 timer.Interval = 500;
121 timer.Enabled = false;
123 // initialise default values
124 DateTime now = DateTime.Now.Date;
125 selection_range = new SelectionRange (now, now);
127 current_month = new DateTime (now.Year , now.Month, 1);
129 // iniatialise local members
130 annually_bolded_dates = null;
132 calendar_dimensions = new Size (1,1);
133 first_day_of_week = Day.Default;
134 max_date = new DateTime (9998, 12, 31);
135 max_selection_count = 7;
136 min_date = new DateTime (1753, 1, 1);
137 monthly_bolded_dates = null;
140 show_today_circle = true;
141 show_week_numbers = false;
142 title_back_color = ThemeEngine.Current.ColorActiveCaption;
143 title_fore_color = ThemeEngine.Current.ColorActiveCaptionText;
144 today_date_set = false;
145 trailing_fore_color = SystemColors.GrayText;
146 bold_font = new Font (Font, Font.Style | FontStyle.Bold);
147 centered_format = new StringFormat (StringFormat.GenericTypographic);
148 centered_format.FormatFlags = centered_format.FormatFlags | StringFormatFlags.MeasureTrailingSpaces | StringFormatFlags.NoWrap | StringFormatFlags.FitBlackBox;
149 centered_format.FormatFlags &= ~StringFormatFlags.NoClip;
150 centered_format.LineAlignment = StringAlignment.Center;
151 centered_format.Alignment = StringAlignment.Center;
153 // Set default values
154 ForeColor = SystemColors.WindowText;
155 BackColor = ThemeEngine.Current.ColorWindow;
157 // intiailise internal variables used
159 button_size = new Size (22, 17);
160 // default settings based on 8.25 pt San Serif Font
161 // Not sure of algorithm used to establish this
162 date_cell_size = new Size (24, 16); // default size at san-serif 8.25
163 divider_line_offset = 4;
164 calendar_spacing = new Size (4, 5); // horiz and vert spacing between months in a calendar grid
166 // set some state info
168 is_date_clicked = false;
169 is_previous_clicked = false;
170 is_next_clicked = false;
171 is_shift_pressed = false;
172 click_state = new bool [] {false, false, false};
173 first_select_start_date = now;
174 month_title_click_location = Point.Empty;
176 // set up context menu
181 timer.Tick += new EventHandler (TimerHandler);
182 MouseMove += new MouseEventHandler (MouseMoveHandler);
183 MouseDown += new MouseEventHandler (MouseDownHandler);
184 KeyDown += new KeyEventHandler (KeyDownHandler);
185 MouseUp += new MouseEventHandler (MouseUpHandler);
186 KeyUp += new KeyEventHandler (KeyUpHandler);
188 // this replaces paint so call the control version
189 base.Paint += new PaintEventHandler (PaintHandler);
194 // called when this control is added to date time picker
195 internal MonthCalendar (DateTimePicker owner) : this () {
197 this.is_visible = false;
198 this.Size = this.DefaultSize;
201 #endregion // Public Constructors
203 #region Public Instance Properties
205 // dates to make bold on calendar annually (recurring)
207 public DateTime [] AnnuallyBoldedDates {
209 if (annually_bolded_dates == null)
210 annually_bolded_dates = new ArrayList (value);
212 annually_bolded_dates.Clear ();
213 annually_bolded_dates.AddRange (value);
217 if (annually_bolded_dates == null || annually_bolded_dates.Count == 0) {
218 return new DateTime [0];
220 DateTime [] result = new DateTime [annually_bolded_dates.Count];
221 annually_bolded_dates.CopyTo (result);
227 [EditorBrowsable(EditorBrowsableState.Never)]
228 public override Image BackgroundImage {
230 return base.BackgroundImage;
233 base.BackgroundImage = value;
238 [EditorBrowsable (EditorBrowsableState.Never)]
240 public override ImageLayout BackgroundImageLayout {
242 return base.BackgroundImageLayout;
245 base.BackgroundImageLayout = value;
250 // the back color for the main part of the calendar
251 public override Color BackColor {
253 base.BackColor = value;
256 return base.BackColor;
260 // specific dates to make bold on calendar (non-recurring)
262 public DateTime[] BoldedDates {
264 if (bolded_dates == null) {
265 bolded_dates = new ArrayList (value);
267 bolded_dates.Clear ();
268 bolded_dates.AddRange (value);
272 if (bolded_dates == null || bolded_dates.Count == 0)
273 return new DateTime [0];
275 DateTime [] result = new DateTime [bolded_dates.Count];
276 bolded_dates.CopyTo (result);
281 // the configuration of the monthly grid display - only allowed to display at most,
282 // 1 calendar year at a time, will be trimmed to fit it properly
284 public Size CalendarDimensions {
286 if (value.Width < 0 || value.Height < 0) {
287 throw new ArgumentException ();
289 if (calendar_dimensions != value) {
290 // squeeze the grid into 1 calendar year
291 if (value.Width * value.Height > 12) {
292 // iteratively reduce the largest dimension till our
293 // product is less than 12
294 if (value.Width > 12 && value.Height > 12) {
295 calendar_dimensions = new Size (4, 3);
296 } else if (value.Width > 12) {
297 for (int i = 12; i > 0; i--) {
298 if (i * value.Height <= 12) {
299 calendar_dimensions = new Size (i, value.Height);
303 } else if (value.Height > 12) {
304 for (int i = 12; i > 0; i--) {
305 if (i * value.Width <= 12) {
306 calendar_dimensions = new Size (value.Width, i);
312 calendar_dimensions = value;
318 return calendar_dimensions;
323 [EditorBrowsable (EditorBrowsableState.Never)]
324 protected override bool DoubleBuffered {
326 return base.DoubleBuffered;
329 base.DoubleBuffered = value;
334 // the first day of the week to display
336 [DefaultValue (Day.Default)]
337 public Day FirstDayOfWeek {
339 if (first_day_of_week != value) {
340 first_day_of_week = value;
345 return first_day_of_week;
349 // the fore color for the main part of the calendar
350 public override Color ForeColor {
352 base.ForeColor = value;
355 return base.ForeColor;
360 [EditorBrowsable(EditorBrowsableState.Never)]
361 public new ImeMode ImeMode {
362 get { return base.ImeMode; }
363 set { base.ImeMode = value; }
366 // the maximum date allowed to be selected on this month calendar
367 public DateTime MaxDate {
369 if (value < MinDate) {
370 string msg = string.Format (CultureInfo.CurrentCulture,
371 "Value of '{0}' is not valid for 'MaxDate'. 'MaxDate' " +
372 "must be greater than or equal to MinDate.",
373 value.ToString ("d", CultureInfo.CurrentCulture));
375 throw new ArgumentOutOfRangeException ("MaxDate",
378 throw new ArgumentException (msg);
382 if (max_date != value) {
391 // the maximum number of selectable days
393 public int MaxSelectionCount {
396 string msg = string.Format (CultureInfo.CurrentCulture,
397 "Value of '{0}' is not valid for 'MaxSelectionCount'. " +
398 "'MaxSelectionCount' must be greater than or equal to {1}.",
401 throw new ArgumentOutOfRangeException ("MaxSelectionCount",
404 throw new ArgumentException (msg);
408 // can't set selectioncount less than already selected dates
409 if ((SelectionEnd - SelectionStart).Days > value) {
410 throw new ArgumentException();
413 if (max_selection_count != value) {
414 max_selection_count = value;
418 return max_selection_count;
422 // the minimum date allowed to be selected on this month calendar
423 public DateTime MinDate {
425 DateTime absoluteMinDate = new DateTime (1753, 1, 1);
427 if (value < absoluteMinDate) {
428 string msg = string.Format (CultureInfo.CurrentCulture,
429 "Value of '{0}' is not valid for 'MinDate'. 'MinDate' " +
430 "must be greater than or equal to {1}.",
431 value.ToString ("d", CultureInfo.CurrentCulture),
432 absoluteMinDate.ToString ("d", CultureInfo.CurrentCulture));
434 throw new ArgumentOutOfRangeException ("MinDate",
437 throw new ArgumentException (msg);
441 if (value > MaxDate) {
442 string msg = string.Format (CultureInfo.CurrentCulture,
443 "Value of '{0}' is not valid for 'MinDate'. 'MinDate' " +
444 "must be less than MaxDate.",
445 value.ToString ("d", CultureInfo.CurrentCulture));
447 throw new ArgumentOutOfRangeException ("MinDate",
450 throw new ArgumentException (msg);
454 if (max_date != value) {
463 // dates to make bold on calendar monthly (recurring)
465 public DateTime[] MonthlyBoldedDates {
467 if (monthly_bolded_dates == null) {
468 monthly_bolded_dates = new ArrayList (value);
470 monthly_bolded_dates.Clear ();
471 monthly_bolded_dates.AddRange (value);
475 if (monthly_bolded_dates == null || monthly_bolded_dates.Count == 0)
476 return new DateTime [0];
478 DateTime [] result = new DateTime [monthly_bolded_dates.Count];
479 monthly_bolded_dates.CopyTo (result);
485 [EditorBrowsable (EditorBrowsableState.Never)]
486 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
488 // Padding should not have any effect on the appearance of MonthCalendar.
489 public new Padding Padding {
494 base.Padding = value;
498 [DefaultValue (false)]
500 public virtual bool RightToLeftLayout {
502 return right_to_left_layout;
505 right_to_left_layout = value;
510 // the ammount by which to scroll this calendar by
512 public int ScrollChange {
514 if (value < 0 || value > 20000) {
515 throw new ArgumentException();
518 if (scroll_change != value) {
519 scroll_change = value;
523 return scroll_change;
528 // the last selected date
530 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
531 public DateTime SelectionEnd {
533 if (value < MinDate || value > MaxDate) {
534 throw new ArgumentException();
537 if (SelectionRange.End != value) {
538 DateTime old_end = SelectionRange.End;
539 // make sure the end obeys the max selection range count
540 if (value < SelectionRange.Start) {
541 SelectionRange.Start = value;
543 if (value.AddDays((MaxSelectionCount-1)*-1) > SelectionRange.Start) {
544 SelectionRange.Start = value.AddDays((MaxSelectionCount-1)*-1);
546 SelectionRange.End = value;
547 this.InvalidateDateRange (new SelectionRange (old_end, SelectionRange.End));
548 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
552 return SelectionRange.End;
558 // the range of selected dates
559 public SelectionRange SelectionRange {
561 if (selection_range != value) {
562 if (value.Start < MinDate)
563 throw new ArgumentException ("SelectionStart cannot be less than MinDate");
564 else if (value.End > MaxDate)
565 throw new ArgumentException ("SelectionEnd cannot be greated than MaxDate");
567 SelectionRange old_range = selection_range;
569 // make sure the end obeys the max selection range count
570 if (value.End.AddDays((MaxSelectionCount-1)*-1) > value.Start) {
571 selection_range = new SelectionRange (value.End.AddDays((MaxSelectionCount-1)*-1), value.End);
573 selection_range = value;
575 SelectionRange visible_range = this.GetDisplayRange(true);
576 if(visible_range.Start > selection_range.End) {
577 this.current_month = new DateTime (selection_range.Start.Year, selection_range.Start.Month, 1);
579 } else if (visible_range.End < selection_range.Start) {
580 int year_diff = selection_range.End.Year - visible_range.End.Year;
581 int month_diff = selection_range.End.Month - visible_range.End.Month;
582 this.current_month = current_month.AddMonths(year_diff * 12 + month_diff);
585 // invalidate the selected range changes
586 DateTime diff_start = old_range.Start;
587 DateTime diff_end = old_range.End;
588 // now decide which region is greated
589 if (old_range.Start > SelectionRange.Start) {
590 diff_start = SelectionRange.Start;
591 } else if (old_range.Start == SelectionRange.Start) {
592 if (old_range.End < SelectionRange.End) {
593 diff_start = old_range.End;
595 diff_start = SelectionRange.End;
598 if (old_range.End < SelectionRange.End) {
599 diff_end = SelectionRange.End;
600 } else if (old_range.End == SelectionRange.End) {
601 if (old_range.Start < SelectionRange.Start) {
602 diff_end = SelectionRange.Start;
604 diff_end = old_range.Start;
609 // invalidate the region required
610 SelectionRange new_range = new SelectionRange (diff_start, diff_end);
611 if (new_range.End != old_range.End || new_range.Start != old_range.Start)
612 this.InvalidateDateRange (new_range);
613 // raise date changed event
614 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
618 return selection_range;
622 // the first selected date
624 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
625 public DateTime SelectionStart {
627 if (value < MinDate || value > MaxDate) {
628 throw new ArgumentException();
631 if (SelectionRange.Start != value) {
632 DateTime old_start = SelectionRange.Start;
633 // make sure the end obeys the max selection range count
634 if (value > SelectionRange.End) {
635 SelectionRange.End = value;
636 } else if (value.AddDays(MaxSelectionCount-1) < SelectionRange.End) {
637 SelectionRange.End = value.AddDays(MaxSelectionCount-1);
639 SelectionRange.Start = value;
640 DateTime new_month = new DateTime(value.Year, value.Month, 1);
641 if (current_month != new_month) {
642 current_month = new_month;
645 this.InvalidateDateRange (new SelectionRange (old_start, SelectionRange.Start));
647 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
651 return selection_range.Start;
655 // whether or not to show todays date
656 [DefaultValue (true)]
657 public bool ShowToday {
659 if (show_today != value) {
669 // whether or not to show a circle around todays date
670 [DefaultValue (true)]
671 public bool ShowTodayCircle {
673 if (show_today_circle != value) {
674 show_today_circle = value;
679 return show_today_circle;
683 // whether or not to show numbers beside each row of weeks
685 [DefaultValue (false)]
686 public bool ShowWeekNumbers {
688 if (show_week_numbers != value) {
689 show_week_numbers = value;
694 return show_week_numbers;
698 // the rectangle size required to render one month based on current font
700 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
701 public Size SingleMonthSize {
703 if (this.Font == null) {
704 throw new InvalidOperationException();
707 // multiplier is sucked out from the font size
708 int multiplier = this.Font.Height;
710 // establis how many columns and rows we have
711 int column_count = (ShowWeekNumbers) ? 8 : 7;
712 int row_count = 7; // not including the today date
714 // set the date_cell_size and the title_size
715 date_cell_size = new Size ((int) Math.Ceiling (1.8 * multiplier), multiplier);
716 title_size = new Size ((date_cell_size.Width * column_count), 2 * multiplier);
718 return new Size (column_count * date_cell_size.Width, row_count * date_cell_size.Height + title_size.Height);
724 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
725 public new Size Size {
737 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
738 [EditorBrowsable(EditorBrowsableState.Never)]
739 public override string Text {
748 // the back color for the title of the calendar and the
749 // forecolor for the day of the week text
750 public Color TitleBackColor {
752 if (title_back_color != value) {
753 title_back_color = value;
758 return title_back_color;
762 // the fore color for the title of the calendar
763 public Color TitleForeColor {
765 if (title_fore_color != value) {
766 title_fore_color = value;
771 return title_fore_color;
775 // the date this calendar is using to refer to today's date
776 public DateTime TodayDate {
778 today_date_set = true;
779 if (today_date != value) {
789 // tells if user specifically set today_date for this control
791 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
792 public bool TodayDateSet {
794 return today_date_set;
798 // the color used for trailing dates in the calendar
799 public Color TrailingForeColor {
801 if (trailing_fore_color != value) {
802 trailing_fore_color = value;
803 SelectionRange bounds = this.GetDisplayRange (false);
804 SelectionRange visible_bounds = this.GetDisplayRange (true);
805 this.InvalidateDateRange (new SelectionRange (bounds.Start, visible_bounds.Start));
806 this.InvalidateDateRange (new SelectionRange (bounds.End, visible_bounds.End));
810 return trailing_fore_color;
814 #endregion // Public Instance Properties
816 #region Protected Instance Properties
818 // overloaded to allow controll to be windowed for drop down
819 protected override CreateParams CreateParams {
821 if (this.owner == null) {
822 return base.CreateParams;
824 CreateParams cp = base.CreateParams;
825 cp.Style ^= (int) WindowStyles.WS_CHILD;
826 cp.Style |= (int) WindowStyles.WS_POPUP;
827 cp.ExStyle |= (int)(WindowExStyles.WS_EX_TOOLWINDOW | WindowExStyles.WS_EX_TOPMOST);
834 // not sure what to put in here - just doing a base() call - jba
835 protected override ImeMode DefaultImeMode {
837 return base.DefaultImeMode;
842 protected override Padding DefaultMargin {
844 return new Padding (9);
849 protected override Size DefaultSize {
851 Size single_month = SingleMonthSize;
853 int width = calendar_dimensions.Width * single_month.Width;
854 if (calendar_dimensions.Width > 1) {
855 width += (calendar_dimensions.Width - 1) * calendar_spacing.Width;
859 int height = calendar_dimensions.Height * single_month.Height;
860 if (this.ShowToday) {
861 height += date_cell_size.Height + 2; // add the height of the "Today: " ...
863 if (calendar_dimensions.Height > 1) {
864 height += (calendar_dimensions.Height - 1) * calendar_spacing.Height;
867 // add the 1 pixel boundary
875 return new Size (width, height);
879 #endregion // Protected Instance Properties
881 #region Public Instance Methods
883 // add a date to the anually bolded date arraylist
884 public void AddAnnuallyBoldedDate (DateTime date) {
885 if (annually_bolded_dates == null)
886 annually_bolded_dates = new ArrayList ();
887 if (!annually_bolded_dates.Contains (date))
888 annually_bolded_dates.Add (date);
891 // add a date to the normal bolded date arraylist
892 public void AddBoldedDate (DateTime date) {
893 if (bolded_dates == null)
894 bolded_dates = new ArrayList ();
895 if (!bolded_dates.Contains (date))
896 bolded_dates.Add (date);
899 // add a date to the anually monthly date arraylist
900 public void AddMonthlyBoldedDate (DateTime date) {
901 if (monthly_bolded_dates == null)
902 monthly_bolded_dates = new ArrayList ();
903 if (!monthly_bolded_dates.Contains (date))
904 monthly_bolded_dates.Add (date);
907 // if visible = true, return only the dates of full months, else return all dates visible
908 public SelectionRange GetDisplayRange (bool visible) {
911 start = new DateTime (current_month.Year, current_month.Month, 1);
912 end = start.AddMonths (calendar_dimensions.Width * calendar_dimensions.Height);
913 end = end.AddDays(-1);
915 // process all visible dates if needed (including the grayed out dates
917 start = GetFirstDateInMonthGrid (start);
918 end = GetLastDateInMonthGrid (end);
921 return new SelectionRange (start, end);
924 // HitTest overload that recieve's x and y co-ordinates as separate ints
925 public HitTestInfo HitTest (int x, int y) {
926 return HitTest (new Point (x, y));
929 // returns a HitTestInfo for MonthCalendar element's under the specified point
930 public HitTestInfo HitTest (Point point) {
931 return HitTest (point, out last_clicked_calendar_index, out last_clicked_calendar_rect);
934 // clears all the annually bolded dates
935 public void RemoveAllAnnuallyBoldedDates () {
936 if (annually_bolded_dates != null)
937 annually_bolded_dates.Clear ();
940 // clears all the normal bolded dates
941 public void RemoveAllBoldedDates () {
942 if (bolded_dates != null)
943 bolded_dates.Clear ();
946 // clears all the monthly bolded dates
947 public void RemoveAllMonthlyBoldedDates () {
948 if (monthly_bolded_dates != null)
949 monthly_bolded_dates.Clear ();
952 // clears the specified annually bolded date (only compares day and month)
953 // only removes the first instance of the match
954 public void RemoveAnnuallyBoldedDate (DateTime date) {
955 if (annually_bolded_dates == null)
958 for (int i = 0; i < annually_bolded_dates.Count; i++) {
959 DateTime dt = (DateTime) annually_bolded_dates [i];
960 if (dt.Day == date.Day && dt.Month == date.Month) {
961 annually_bolded_dates.RemoveAt (i);
967 // clears all the normal bolded date
968 // only removes the first instance of the match
969 public void RemoveBoldedDate (DateTime date) {
970 if (bolded_dates == null)
973 for (int i = 0; i < bolded_dates.Count; i++) {
974 DateTime dt = (DateTime) bolded_dates [i];
975 if (dt.Year == date.Year && dt.Month == date.Month && dt.Day == date.Day) {
976 bolded_dates.RemoveAt (i);
982 // clears the specified monthly bolded date (only compares day and month)
983 // only removes the first instance of the match
984 public void RemoveMonthlyBoldedDate (DateTime date) {
985 if (monthly_bolded_dates == null)
988 for (int i = 0; i < monthly_bolded_dates.Count; i++) {
989 DateTime dt = (DateTime) monthly_bolded_dates [i];
990 if (dt.Day == date.Day && dt.Month == date.Month) {
991 monthly_bolded_dates.RemoveAt (i);
997 // sets the calendar_dimensions. If product is > 12, the larger dimension is reduced to make product < 12
998 public void SetCalendarDimensions(int x, int y) {
999 this.CalendarDimensions = new Size(x, y);
1002 // sets the currently selected date as date
1003 public void SetDate (DateTime date) {
1004 this.SetSelectionRange (date.Date, date.Date);
1007 // utility method set the SelectionRange property using individual dates
1008 public void SetSelectionRange (DateTime date1, DateTime date2) {
1009 this.SelectionRange = new SelectionRange(date1, date2);
1012 public override string ToString () {
1013 return this.GetType().Name + ", " + this.SelectionRange.ToString ();
1016 // usually called after an AddBoldedDate method is called
1017 // formats monthly and daily bolded dates according to the current calendar year
1018 public void UpdateBoldedDates () {
1022 #endregion // Public Instance Methods
1024 #region Protected Instance Methods
1026 // not sure why this needs to be overriden
1027 protected override void CreateHandle () {
1028 base.CreateHandle ();
1031 // not sure why this needs to be overriden
1032 protected override void Dispose (bool disposing) {
1033 base.Dispose (disposing);
1036 // Handle arrow keys
1037 protected override bool IsInputKey (Keys keyData) {
1048 return base.IsInputKey (keyData);
1051 // not sure why this needs to be overriden
1052 protected override void OnBackColorChanged (EventArgs e) {
1053 base.OnBackColorChanged (e);
1057 // raises the date changed event
1058 protected virtual void OnDateChanged (DateRangeEventArgs drevent) {
1059 DateRangeEventHandler eh = (DateRangeEventHandler) (Events [DateChangedEvent]);
1064 // raises the DateSelected event
1065 protected virtual void OnDateSelected (DateRangeEventArgs drevent) {
1066 DateRangeEventHandler eh = (DateRangeEventHandler) (Events [DateSelectedEvent]);
1071 protected override void OnFontChanged (EventArgs e) {
1072 // Update size based on new font's space requirements
1073 Size = new Size (CalendarDimensions.Width * SingleMonthSize.Width,
1074 CalendarDimensions.Height * SingleMonthSize.Height);
1075 bold_font = new Font (Font, Font.Style | FontStyle.Bold);
1076 base.OnFontChanged (e);
1079 protected override void OnForeColorChanged (EventArgs e) {
1080 base.OnForeColorChanged (e);
1083 protected override void OnHandleCreated (EventArgs e) {
1084 base.OnHandleCreated (e);
1088 protected override void OnHandleDestroyed (EventArgs e)
1090 base.OnHandleDestroyed (e);
1093 [EditorBrowsable (EditorBrowsableState.Advanced)]
1094 protected virtual void OnRightToLeftLayoutChanged (EventArgs e) {
1095 EventHandler eh = (EventHandler) (Events [RightToLeftLayoutChangedEvent]);
1101 // i think this is overriden to not allow the control to be changed to an arbitrary size
1102 protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified) {
1103 if ((specified & BoundsSpecified.Height) == BoundsSpecified.Height ||
1104 (specified & BoundsSpecified.Width) == BoundsSpecified.Width ||
1105 (specified & BoundsSpecified.Size) == BoundsSpecified.Size) {
1106 // only allow sizes = default size to be set
1107 Size min_size = DefaultSize;
1108 Size max_size = new Size (
1109 DefaultSize.Width + SingleMonthSize.Width + calendar_spacing.Width,
1110 DefaultSize.Height + SingleMonthSize.Height + calendar_spacing.Height);
1111 int x_mid_point = (max_size.Width + min_size.Width)/2;
1112 int y_mid_point = (max_size.Height + min_size.Height)/2;
1113 if (width < x_mid_point) {
1114 width = min_size.Width;
1116 width = max_size.Width;
1118 if (height < y_mid_point) {
1119 height = min_size.Height;
1121 height = max_size.Height;
1123 base.SetBoundsCore (x, y, width, height, specified);
1125 base.SetBoundsCore (x, y, width, height, specified);
1129 protected override void WndProc (ref Message m) {
1130 base.WndProc (ref m);
1133 #endregion // Protected Instance Methods
1135 #region public events
1136 static object DateChangedEvent = new object ();
1137 static object DateSelectedEvent = new object ();
1139 static object RightToLeftLayoutChangedEvent = new object ();
1142 // fired when the date is changed (either explicitely or implicitely)
1143 // when navigating the month selector
1144 public event DateRangeEventHandler DateChanged {
1145 add { Events.AddHandler (DateChangedEvent, value); }
1146 remove { Events.RemoveHandler (DateChangedEvent, value); }
1149 // fired when the user explicitely clicks on date to select it
1150 public event DateRangeEventHandler DateSelected {
1151 add { Events.AddHandler (DateSelectedEvent, value); }
1152 remove { Events.RemoveHandler (DateSelectedEvent, value); }
1156 [EditorBrowsable (EditorBrowsableState.Never)]
1157 public new event EventHandler BackgroundImageChanged {
1158 add { base.BackgroundImageChanged += value; }
1159 remove { base.BackgroundImageChanged -= value; }
1164 [EditorBrowsable (EditorBrowsableState.Never)]
1165 public new event EventHandler BackgroundImageLayoutChanged
1167 add { base.BackgroundImageLayoutChanged += value;}
1168 remove { base.BackgroundImageLayoutChanged += value;}
1173 [EditorBrowsable (EditorBrowsableState.Never)]
1174 public new event EventHandler Click {
1175 add {base.Click += value; }
1176 remove {base.Click -= value;}
1180 [EditorBrowsable (EditorBrowsableState.Never)]
1181 public new event EventHandler DoubleClick {
1182 add {base.DoubleClick += value; }
1183 remove {base.DoubleClick -= value; }
1187 [EditorBrowsable (EditorBrowsableState.Never)]
1188 public new event EventHandler ImeModeChanged {
1189 add { base.ImeModeChanged += value; }
1190 remove { base.ImeModeChanged -= value; }
1195 [EditorBrowsable (EditorBrowsableState.Never)]
1196 public new event MouseEventHandler MouseClick {
1197 add { base.MouseClick += value;}
1198 remove { base.MouseClick -= value;}
1202 [EditorBrowsable (EditorBrowsableState.Never)]
1203 public new event MouseEventHandler MouseDoubleClick {
1204 add { base.MouseDoubleClick += value; }
1205 remove { base.MouseDoubleClick -= value; }
1209 [EditorBrowsable (EditorBrowsableState.Never)]
1210 public new event EventHandler PaddingChanged {
1211 add {base.PaddingChanged += value;}
1212 remove {base.PaddingChanged -= value;}
1215 // XXX check this out
1217 [EditorBrowsable (EditorBrowsableState.Never)]
1218 public new event PaintEventHandler Paint;
1221 public event EventHandler RightToLeftLayoutChanged {
1222 add {Events.AddHandler (RightToLeftLayoutChangedEvent, value);}
1223 remove {Events.RemoveHandler (RightToLeftLayoutChangedEvent, value);}
1227 [EditorBrowsable (EditorBrowsableState.Never)]
1228 public new event EventHandler TextChanged {
1229 add { base.TextChanged += value; }
1230 remove { base.TextChanged -= value; }
1232 #endregion // public events
1234 #region internal properties
1236 private void AddYears (int years, bool fast)
1240 if (!(CurrentMonth.Year + years * 5 > MaxDate.Year)) {
1241 newDate = CurrentMonth.AddYears (years * 5);
1242 if (MaxDate >= newDate && MinDate <= newDate) {
1243 CurrentMonth = newDate;
1248 if (!(CurrentMonth.Year + years > MaxDate.Year)) {
1249 newDate = CurrentMonth.AddYears (years);
1250 if (MaxDate >= newDate && MinDate <= newDate) {
1251 CurrentMonth = newDate;
1256 internal bool IsYearGoingUp {
1258 return is_year_going_up;
1262 is_year_going_down = false;
1263 year_moving_count = (is_year_going_up ? year_moving_count + 1 : 1);
1264 if (is_year_going_up)
1265 year_moving_count++;
1267 year_moving_count = 1;
1269 AddYears (1, year_moving_count > 10);
1270 if (is_mouse_moving_year)
1273 year_moving_count = 0;
1275 is_year_going_up = value;
1280 internal bool IsYearGoingDown {
1282 return is_year_going_down;
1287 is_year_going_up = false;
1288 year_moving_count = (is_year_going_down ? year_moving_count + 1 : 1);
1289 if (is_year_going_down)
1290 year_moving_count++;
1292 year_moving_count = 1;
1294 AddYears (-1, year_moving_count > 10);
1295 if (is_mouse_moving_year)
1298 year_moving_count = 0;
1300 is_year_going_down = value;
1305 internal bool ShowYearUpDown {
1307 return show_year_updown;
1310 if (show_year_updown != value) {
1311 show_year_updown = value;
1317 internal DateTime CurrentMonth {
1319 // only interested in if the month (not actual date) has change
1320 if (value < MinDate || value > MaxDate) {
1324 if (value.Month != current_month.Month ||
1325 value.Year != current_month.Year) {
1326 this.SelectionRange = new SelectionRange(
1327 this.SelectionStart.Add(value.Subtract(current_month)),
1328 this.SelectionEnd.Add(value.Subtract(current_month)));
1329 current_month = value;
1330 UpdateBoldedDates();
1335 return current_month;
1339 #endregion // internal properties
1341 #region internal/private methods
1342 internal HitTestInfo HitTest (
1344 out int calendar_index,
1345 out Rectangle calendar_rect) {
1346 // start by initialising the ref parameters
1347 calendar_index = -1;
1348 calendar_rect = Rectangle.Empty;
1350 // before doing all the hard work, see if the today's date wasn't clicked
1351 Rectangle today_rect = new Rectangle (
1353 ClientRectangle.Bottom - date_cell_size.Height,
1354 7 * date_cell_size.Width,
1355 date_cell_size.Height);
1356 if (today_rect.Contains (point) && this.ShowToday) {
1357 return new HitTestInfo(HitArea.TodayLink, point, DateTime.Now);
1360 Size month_size = SingleMonthSize;
1361 // define calendar rect's that this thing can land in
1362 Rectangle[] calendars = new Rectangle [CalendarDimensions.Width * CalendarDimensions.Height];
1363 for (int i=0; i < CalendarDimensions.Width * CalendarDimensions.Height; i ++) {
1365 calendars[i] = new Rectangle (
1366 new Point (ClientRectangle.X + 1, ClientRectangle.Y + 1),
1369 // calendar on the next row
1370 if (i % CalendarDimensions.Width == 0) {
1371 calendars[i] = new Rectangle (
1372 new Point (calendars[i-CalendarDimensions.Width].X, calendars[i-CalendarDimensions.Width].Bottom + calendar_spacing.Height),
1375 // calendar on the next column
1376 calendars[i] = new Rectangle (
1377 new Point (calendars[i-1].Right + calendar_spacing.Width, calendars[i-1].Y),
1383 // through each trying to find a match
1384 for (int i = 0; i < calendars.Length ; i++) {
1385 if (calendars[i].Contains (point)) {
1386 // check the title section
1387 Rectangle title_rect = new Rectangle (
1388 calendars[i].Location,
1390 if (title_rect.Contains (point) ) {
1391 // make sure it's not a previous button
1393 Rectangle button_rect = new Rectangle(
1394 new Point (calendars[i].X + button_x_offset, (title_size.Height - button_size.Height)/2),
1396 if (button_rect.Contains (point)) {
1397 return new HitTestInfo (HitArea.PrevMonthButton, point, new DateTime (1, 1, 1));
1400 // make sure it's not the next button
1401 if (i % CalendarDimensions.Height == 0 && i % CalendarDimensions.Width == calendar_dimensions.Width - 1) {
1402 Rectangle button_rect = new Rectangle(
1403 new Point (calendars[i].Right - button_x_offset - button_size.Width, (title_size.Height - button_size.Height)/2),
1405 if (button_rect.Contains (point)) {
1406 return new HitTestInfo (HitArea.NextMonthButton, point, new DateTime (1, 1, 1));
1410 // indicate which calendar and month it was
1412 calendar_rect = calendars[i];
1414 // make sure it's not the month or the year of the calendar
1415 if (GetMonthNameRectangle (title_rect, i).Contains (point)) {
1416 return new HitTestInfo (HitArea.TitleMonth, point, new DateTime (1, 1, 1));
1418 Rectangle year, up, down;
1419 GetYearNameRectangles (title_rect, i, out year, out up, out down);
1420 if (year.Contains (point)) {
1421 return new HitTestInfo (HitArea.TitleYear, point, new DateTime (1, 1, 1), HitAreaExtra.YearRectangle);
1422 } else if (up.Contains (point)) {
1423 return new HitTestInfo (HitArea.TitleYear, point, new DateTime (1, 1, 1), HitAreaExtra.UpButton);
1424 } else if (down.Contains (point)) {
1425 return new HitTestInfo (HitArea.TitleYear, point, new DateTime (1, 1, 1), HitAreaExtra.DownButton);
1428 // return the hit test in the title background
1429 return new HitTestInfo (HitArea.TitleBackground, point, new DateTime (1, 1, 1));
1432 Point date_grid_location = new Point (calendars[i].X, title_rect.Bottom);
1434 // see if it's in the Week numbers
1435 if (ShowWeekNumbers) {
1436 Rectangle weeks_rect = new Rectangle (
1438 new Size (date_cell_size.Width,Math.Max (calendars[i].Height - title_rect.Height, 0)));
1439 if (weeks_rect.Contains (point)) {
1440 return new HitTestInfo(HitArea.WeekNumbers, point, DateTime.Now);
1443 // move the location of the grid over
1444 date_grid_location.X += date_cell_size.Width;
1447 // see if it's in the week names
1448 Rectangle day_rect = new Rectangle (
1450 new Size (Math.Max (calendars[i].Right - date_grid_location.X, 0), date_cell_size.Height));
1451 if (day_rect.Contains (point)) {
1452 return new HitTestInfo (HitArea.DayOfWeek, point, new DateTime (1, 1, 1));
1455 // finally see if it was a date that was clicked
1456 Rectangle date_grid = new Rectangle (
1457 new Point (day_rect.X, day_rect.Bottom),
1458 new Size (day_rect.Width, Math.Max(calendars[i].Bottom - day_rect.Bottom, 0)));
1459 if (date_grid.Contains (point)) {
1460 clicked_rect = date_grid;
1461 // okay so it's inside the grid, get the offset
1462 Point offset = new Point (point.X - date_grid.X, point.Y - date_grid.Y);
1463 int row = offset.Y / date_cell_size.Height;
1464 int col = offset.X / date_cell_size.Width;
1465 // establish our first day of the month
1466 DateTime calendar_month = this.CurrentMonth.AddMonths(i);
1467 DateTime first_day = GetFirstDateInMonthGrid (calendar_month);
1468 DateTime time = first_day.AddDays ((row * 7) + col);
1469 // establish which date was clicked
1470 if (time.Year != calendar_month.Year || time.Month != calendar_month.Month) {
1471 if (time < calendar_month && i == 0) {
1472 return new HitTestInfo (HitArea.PrevMonthDate, point, new DateTime (1, 1, 1), time);
1473 } else if (time > calendar_month && i == CalendarDimensions.Width*CalendarDimensions.Height - 1) {
1474 return new HitTestInfo (HitArea.NextMonthDate, point, new DateTime (1, 1, 1), time);
1476 return new HitTestInfo (HitArea.Nowhere, point, new DateTime (1, 1, 1));
1478 return new HitTestInfo(HitArea.Date, point, time);
1483 return new HitTestInfo ();
1486 // returns the date of the first cell of the specified month grid
1487 internal DateTime GetFirstDateInMonthGrid (DateTime month) {
1488 // convert the first_day_of_week into a DayOfWeekEnum
1489 DayOfWeek first_day = GetDayOfWeek (first_day_of_week);
1490 // find the first day of the month
1491 DateTime first_date_of_month = new DateTime (month.Year, month.Month, 1);
1492 DayOfWeek first_day_of_month = first_date_of_month.DayOfWeek;
1493 // adjust for the starting day of the week
1494 int offset = first_day_of_month - first_day;
1498 return first_date_of_month.AddDays (-1*offset);
1501 // returns the date of the last cell of the specified month grid
1502 internal DateTime GetLastDateInMonthGrid (DateTime month)
1504 DateTime start = GetFirstDateInMonthGrid(month);
1505 return start.AddDays ((7 * 6)-1);
1508 internal bool IsBoldedDate (DateTime date) {
1509 // check bolded dates
1510 if (bolded_dates != null && bolded_dates.Count > 0) {
1511 foreach (DateTime bolded_date in bolded_dates) {
1512 if (bolded_date.Date == date.Date) {
1517 // check monthly dates
1518 if (monthly_bolded_dates != null && monthly_bolded_dates.Count > 0) {
1519 foreach (DateTime bolded_date in monthly_bolded_dates) {
1520 if (bolded_date.Day == date.Day) {
1525 // check yearly dates
1526 if (annually_bolded_dates != null && annually_bolded_dates.Count > 0) {
1527 foreach (DateTime bolded_date in annually_bolded_dates) {
1528 if (bolded_date.Month == date.Month && bolded_date.Day == date.Day) {
1534 return false; // no match
1538 // initialise the context menu
1539 private void SetUpContextMenu () {
1540 menu = new ContextMenu ();
1541 for (int i=0; i < 12; i++) {
1542 MenuItem menu_item = new MenuItem ( new DateTime (2000, i+1, 1).ToString ("MMMM"));
1543 menu_item.Click += new EventHandler (MenuItemClickHandler);
1544 menu.MenuItems.Add (menu_item);
1549 // returns the first date of the month
1550 private DateTime GetFirstDateInMonth (DateTime date) {
1551 return new DateTime (date.Year, date.Month, 1);
1554 // returns the last date of the month
1555 private DateTime GetLastDateInMonth (DateTime date) {
1556 return new DateTime (date.Year, date.Month, 1).AddMonths(1).AddDays(-1);
1559 // called in response to users seletion with shift key
1560 private void AddTimeToSelection (int delta, bool isDays)
1562 DateTime cursor_point;
1564 // okay we add the period to the date that is not the same as the
1565 // start date when shift was first clicked.
1566 if (SelectionStart != first_select_start_date) {
1567 cursor_point = SelectionStart;
1569 cursor_point = SelectionEnd;
1573 end_point = cursor_point.AddDays (delta);
1575 // delta must be months
1576 end_point = cursor_point.AddMonths (delta);
1578 // set the new selection range
1579 SelectionRange range = new SelectionRange (first_select_start_date, end_point);
1580 if (range.Start.AddDays (MaxSelectionCount-1) < range.End) {
1581 // okay the date is beyond what is allowed, lets set the maximum we can
1582 if (range.Start != first_select_start_date) {
1583 range.Start = range.End.AddDays ((MaxSelectionCount-1)*-1);
1585 range.End = range.Start.AddDays (MaxSelectionCount-1);
1588 this.SelectionRange = range;
1591 // attempts to add the date to the selection without throwing exception
1592 private void SelectDate (DateTime date) {
1593 // try and add the new date to the selction range
1594 if (is_shift_pressed || (click_state [0])) {
1595 SelectionRange range = new SelectionRange (first_select_start_date, date);
1596 if (range.Start.AddDays (MaxSelectionCount-1) < range.End) {
1597 // okay the date is beyond what is allowed, lets set the maximum we can
1598 if (range.Start != first_select_start_date) {
1599 range.Start = range.End.AddDays ((MaxSelectionCount-1)*-1);
1601 range.End = range.Start.AddDays (MaxSelectionCount-1);
1604 SelectionRange = range;
1606 if (date >= MinDate && date <= MaxDate) {
1607 SelectionRange = new SelectionRange (date, date);
1608 first_select_start_date = date;
1613 // gets the week of the year
1614 internal int GetWeekOfYear (DateTime date) {
1615 // convert the first_day_of_week into a DayOfWeekEnum
1616 DayOfWeek first_day = GetDayOfWeek (first_day_of_week);
1617 // find the first day of the year
1618 DayOfWeek first_day_of_year = new DateTime (date.Year, 1, 1).DayOfWeek;
1619 // adjust for the starting day of the week
1620 int offset = first_day_of_year - first_day;
1621 int week = ((date.DayOfYear + offset) / 7) + 1;
1625 // convert a Day enum into a DayOfWeek enum
1626 internal DayOfWeek GetDayOfWeek (Day day) {
1627 if (day == Day.Default) {
1628 return Threading.Thread.CurrentThread.CurrentCulture.DateTimeFormat.FirstDayOfWeek;
1630 return (DayOfWeek) DayOfWeek.Parse (typeof (DayOfWeek), day.ToString ());
1634 // returns the rectangle for themonth name
1635 internal Rectangle GetMonthNameRectangle (Rectangle title_rect, int calendar_index) {
1636 DateTime this_month = this.current_month.AddMonths (calendar_index);
1637 Size title_text_size = TextRenderer.MeasureString (this_month.ToString ("MMMM yyyy"), this.Font).ToSize ();
1638 Size month_size = TextRenderer.MeasureString (this_month.ToString ("MMMM"), this.Font).ToSize ();
1639 // return only the month name part of that
1640 return new Rectangle (
1642 title_rect.X + ((title_rect.Width - title_text_size.Width)/2),
1643 title_rect.Y + ((title_rect.Height - title_text_size.Height)/2)),
1647 internal void GetYearNameRectangles (Rectangle title_rect, int calendar_index, out Rectangle year_rect, out Rectangle up_rect, out Rectangle down_rect)
1649 DateTime this_month = this.current_month.AddMonths (calendar_index);
1650 SizeF title_text_size = TextRenderer.MeasureString (this_month.ToString ("MMMM yyyy"), this.bold_font, int.MaxValue, centered_format);
1651 SizeF year_size = TextRenderer.MeasureString (this_month.ToString ("yyyy"), this.bold_font, int.MaxValue, centered_format);
1652 // find out how much space the title took
1653 RectangleF text_rect = new RectangleF (
1655 title_rect.X + ((title_rect.Width - title_text_size.Width) / 2),
1656 title_rect.Y + ((title_rect.Height - title_text_size.Height) / 2)),
1658 // return only the rect of the year
1659 year_rect = new Rectangle (
1661 ((int)(text_rect.Right - year_size.Width + 1)),
1663 new Size ((int)(year_size.Width + 1), (int)(year_size.Height + 1)));
1665 year_rect.Inflate (0, 1);
1666 up_rect = new Rectangle ();
1667 up_rect.Location = new Point (year_rect.X + year_rect.Width + 2, year_rect.Y);
1668 up_rect.Size = new Size (16, year_rect.Height / 2);
1669 down_rect = new Rectangle ();
1670 down_rect.Location = new Point (up_rect.X, up_rect.Y + up_rect.Height + 1);
1671 down_rect.Size = up_rect.Size;
1674 // returns the rectangle for the year in the title
1675 internal Rectangle GetYearNameRectangle (Rectangle title_rect, int calendar_index) {
1676 Rectangle result, discard;
1677 GetYearNameRectangles (title_rect, calendar_index, out result, out discard, out discard);
1681 // determine if date is allowed to be drawn in month
1682 internal bool IsValidWeekToDraw (DateTime month, DateTime date, int row, int col) {
1683 DateTime tocheck = month.AddMonths (-1);
1684 if ((month.Year == date.Year && month.Month == date.Month) ||
1685 (tocheck.Year == date.Year && tocheck.Month == date.Month)) {
1689 // check the railing dates (days in the month after the last month in grid)
1690 if (row == CalendarDimensions.Height - 1 && col == CalendarDimensions.Width - 1) {
1691 tocheck = month.AddMonths (1);
1692 return (tocheck.Year == date.Year && tocheck.Month == date.Month) ;
1698 // set one item clicked and all others off
1699 private void SetItemClick(HitTestInfo hti)
1701 switch(hti.HitArea) {
1702 case HitArea.NextMonthButton:
1703 this.is_previous_clicked = false;
1704 this.is_next_clicked = true;
1705 this.is_date_clicked = false;
1707 case HitArea.PrevMonthButton:
1708 this.is_previous_clicked = true;
1709 this.is_next_clicked = false;
1710 this.is_date_clicked = false;
1712 case HitArea.PrevMonthDate:
1713 case HitArea.NextMonthDate:
1715 this.clicked_date = hti.hit_time;
1716 this.is_previous_clicked = false;
1717 this.is_next_clicked = false;
1718 this.is_date_clicked = true;
1721 this.is_previous_clicked = false;
1722 this.is_next_clicked = false;
1723 this.is_date_clicked = false;
1729 // called when context menu is clicked
1730 private void MenuItemClickHandler (object sender, EventArgs e) {
1731 MenuItem item = sender as MenuItem;
1732 if (item != null && month_title_click_location != Point.Empty) {
1733 // establish which month we want to move to
1734 if (item.Parent == null) {
1737 int new_month = item.Parent.MenuItems.IndexOf (item) + 1;
1738 if (new_month == 0) {
1741 // okay let's establish which calendar was hit
1742 Size month_size = this.SingleMonthSize;
1743 for (int i=0; i < CalendarDimensions.Height; i++) {
1744 for (int j=0; j < CalendarDimensions.Width; j++) {
1745 int month_index = (i * CalendarDimensions.Width) + j;
1746 Rectangle month_rect = new Rectangle ( new Point (0, 0), month_size);
1748 month_rect.X = this.ClientRectangle.X + 1;
1750 month_rect.X = this.ClientRectangle.X + 1 + ((j)*(month_size.Width+calendar_spacing.Width));
1753 month_rect.Y = this.ClientRectangle.Y + 1;
1755 month_rect.Y = this.ClientRectangle.Y + 1 + ((i)*(month_size.Height+calendar_spacing.Height));
1757 // see if the point is inside
1758 if (month_rect.Contains (month_title_click_location)) {
1759 DateTime clicked_month = CurrentMonth.AddMonths (month_index);
1760 // get the month that we want to move to
1761 int month_offset = new_month - clicked_month.Month;
1763 // move forward however more months we need to
1764 this.CurrentMonth = this.CurrentMonth.AddMonths (month_offset);
1771 month_title_click_location = Point.Empty;
1775 // raised on the timer, for mouse hold clicks
1776 private void TimerHandler (object sender, EventArgs e) {
1777 // now find out which area was click
1779 HitTestInfo hti = this.HitTest (this.PointToClient (MousePosition));
1780 // see if it was clicked on the prev or next mouse
1781 if (click_state [1] || click_state [2]) {
1782 // invalidate the area where the mouse was last held
1784 // register the click
1785 if (hti.HitArea == HitArea.PrevMonthButton ||
1786 hti.HitArea == HitArea.NextMonthButton) {
1787 DoButtonMouseDown (hti);
1788 click_state [1] = (hti.HitArea == HitArea.PrevMonthButton);
1789 click_state [2] = !click_state [1];
1791 if (timer.Interval != 300) {
1792 timer.Interval = 300;
1796 timer.Enabled = false;
1800 // selects one of the buttons
1801 private void DoButtonMouseDown (HitTestInfo hti) {
1802 // show the click then move on
1804 if (hti.HitArea == HitArea.PrevMonthButton) {
1805 // invalidate the prev monthbutton
1808 this.ClientRectangle.X + 1 + button_x_offset,
1809 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1811 button_size.Height));
1812 int scroll = (scroll_change == 0 ? CalendarDimensions.Width * CalendarDimensions.Height : scroll_change);
1813 this.CurrentMonth = this.CurrentMonth.AddMonths (-scroll);
1815 // invalidate the next monthbutton
1818 this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width,
1819 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1821 button_size.Height));
1822 int scroll = (scroll_change == 0 ? CalendarDimensions.Width * CalendarDimensions.Height : scroll_change);
1823 this.CurrentMonth = this.CurrentMonth.AddMonths (scroll);
1827 // selects the clicked date
1828 private void DoDateMouseDown (HitTestInfo hti) {
1832 // event run on the mouse up event
1833 private void DoMouseUp () {
1835 IsYearGoingDown = false;
1836 IsYearGoingUp = false;
1837 is_mouse_moving_year = false;
1839 HitTestInfo hti = this.HitTest (this.PointToClient (MousePosition));
1840 switch (hti.HitArea) {
1841 case HitArea.PrevMonthDate:
1842 case HitArea.NextMonthDate:
1844 this.SelectDate (clicked_date);
1845 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1849 // invalidate the next monthbutton
1850 if (this.is_next_clicked) {
1853 this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width,
1854 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1856 button_size.Height));
1858 // invalidate the prev monthbutton
1859 if (this.is_previous_clicked) {
1862 this.ClientRectangle.X + 1 + button_x_offset,
1863 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1865 button_size.Height));
1867 if (this.is_date_clicked) {
1868 // invalidate the area under the cursor, to remove focus rect
1869 this.InvalidateDateRange (new SelectionRange (clicked_date, clicked_date));
1871 this.is_previous_clicked = false;
1872 this.is_next_clicked = false;
1873 this.is_date_clicked = false;
1876 // needed when in windowed mode to close the calendar if no
1877 // part of it has focus.
1878 private void UpDownTimerTick(object sender, EventArgs e)
1880 if (IsYearGoingUp) {
1881 IsYearGoingUp = true;
1883 if (IsYearGoingDown) {
1884 IsYearGoingDown = true;
1887 if (!IsYearGoingDown && !IsYearGoingUp) {
1888 updown_timer.Enabled = false;
1889 } else if (IsYearGoingDown || IsYearGoingUp) {
1890 updown_timer.Interval = subsequent_delay;
1894 // Needed when in windowed mode.
1895 private void StartHideTimer ()
1897 if (updown_timer == null) {
1898 updown_timer = new Timer ();
1899 updown_timer.Tick += new EventHandler (UpDownTimerTick);
1901 updown_timer.Interval = initial_delay;
1902 updown_timer.Enabled = true;
1906 // occurs when mouse moves around control, used for selection
1907 private void MouseMoveHandler (object sender, MouseEventArgs e) {
1908 HitTestInfo hti = this.HitTest (e.X, e.Y);
1909 // clear the last clicked item
1910 if (click_state [0]) {
1911 // register the click
1912 if (hti.HitArea == HitArea.PrevMonthDate ||
1913 hti.HitArea == HitArea.NextMonthDate ||
1914 hti.HitArea == HitArea.Date)
1916 Rectangle prev_rect = clicked_rect;
1917 DateTime prev_clicked = clicked_date;
1918 DoDateMouseDown (hti);
1919 if (owner == null) {
1920 click_state [0] = true;
1922 click_state [0] = false;
1923 click_state [1] = false;
1924 click_state [2] = false;
1927 if (prev_clicked != clicked_date) {
1928 Rectangle invalid = Rectangle.Union (prev_rect, clicked_rect);
1929 Invalidate (invalid);
1936 // to check if the mouse has come down on this control
1937 private void MouseDownHandler (object sender, MouseEventArgs e)
1939 // clear the click_state variables
1940 click_state [0] = false;
1941 click_state [1] = false;
1942 click_state [2] = false;
1944 // disable the timer if it was enabled
1945 if (timer.Enabled) {
1947 timer.Enabled = false;
1950 Point point = new Point (e.X, e.Y);
1951 // figure out if we are in drop down mode and a click happened outside us
1952 if (this.owner != null) {
1953 if (!this.ClientRectangle.Contains (point)) {
1954 this.owner.HideMonthCalendar ();
1959 //establish where was hit
1960 HitTestInfo hti = this.HitTest(point);
1961 // hide the year numeric up down if it was clicked
1962 if (ShowYearUpDown && hti.HitArea != HitArea.TitleYear) {
1963 ShowYearUpDown = false;
1965 switch (hti.HitArea) {
1966 case HitArea.PrevMonthButton:
1967 case HitArea.NextMonthButton:
1968 DoButtonMouseDown (hti);
1969 click_state [1] = (hti.HitArea == HitArea.PrevMonthDate);
1970 click_state [2] = !click_state [1];
1971 timer.Interval = 750;
1975 case HitArea.PrevMonthDate:
1976 case HitArea.NextMonthDate:
1977 DoDateMouseDown (hti);
1978 // leave clicked state blank if drop down window
1979 if (owner == null) {
1980 click_state [0] = true;
1982 click_state [0] = false;
1983 click_state [1] = false;
1984 click_state [2] = false;
1987 case HitArea.TitleMonth:
1988 month_title_click_location = hti.Point;
1989 menu.Show (this, hti.Point);
1990 if (this.Capture && owner != null) {
1995 case HitArea.TitleYear:
1996 // place the numeric up down
1997 if (ShowYearUpDown) {
1998 if (hti.hit_area_extra == HitAreaExtra.UpButton) {
1999 is_mouse_moving_year = true;
2000 IsYearGoingUp = true;
2001 } else if (hti.hit_area_extra == HitAreaExtra.DownButton) {
2002 is_mouse_moving_year = true;
2003 IsYearGoingDown = true;
2007 ShowYearUpDown = true;
2010 case HitArea.TodayLink:
2011 this.SetSelectionRange (DateTime.Now.Date, DateTime.Now.Date);
2012 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
2015 this.is_previous_clicked = false;
2016 this.is_next_clicked = false;
2017 this.is_date_clicked = false;
2022 // raised by any key down events
2023 private void KeyDownHandler (object sender, KeyEventArgs e) {
2024 // send keys to the year_updown control, let it handle it
2025 if(ShowYearUpDown) {
2026 switch (e.KeyCode) {
2028 ShowYearUpDown = false;
2029 IsYearGoingDown = false;
2030 IsYearGoingUp = false;
2033 IsYearGoingUp = true;
2037 IsYearGoingDown = true;
2042 if (!is_shift_pressed && e.Shift) {
2043 first_select_start_date = SelectionStart;
2044 is_shift_pressed = e.Shift;
2047 switch (e.KeyCode) {
2049 // set the date to the start of the month
2050 if (is_shift_pressed) {
2051 DateTime date = GetFirstDateInMonth (first_select_start_date);
2052 if (date < first_select_start_date.AddDays ((MaxSelectionCount-1)*-1)) {
2053 date = first_select_start_date.AddDays ((MaxSelectionCount-1)*-1);
2055 this.SetSelectionRange (date, first_select_start_date);
2057 DateTime date = GetFirstDateInMonth (this.SelectionStart);
2058 this.SetSelectionRange (date, date);
2060 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
2064 // set the date to the last of the month
2065 if (is_shift_pressed) {
2066 DateTime date = GetLastDateInMonth (first_select_start_date);
2067 if (date > first_select_start_date.AddDays (MaxSelectionCount-1)) {
2068 date = first_select_start_date.AddDays (MaxSelectionCount-1);
2070 this.SetSelectionRange (date, first_select_start_date);
2072 DateTime date = GetLastDateInMonth (this.SelectionStart);
2073 this.SetSelectionRange (date, date);
2075 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
2079 // set the date to the last of the month
2080 if (is_shift_pressed) {
2081 this.AddTimeToSelection (-1, false);
2083 DateTime date = this.SelectionStart.AddMonths (-1);
2084 this.SetSelectionRange (date, date);
2086 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
2090 // set the date to the last of the month
2091 if (is_shift_pressed) {
2092 this.AddTimeToSelection (1, false);
2094 DateTime date = this.SelectionStart.AddMonths (1);
2095 this.SetSelectionRange (date, date);
2097 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
2101 // set the back 1 week
2102 if (is_shift_pressed) {
2103 this.AddTimeToSelection (-7, true);
2105 DateTime date = this.SelectionStart.AddDays (-7);
2106 this.SetSelectionRange (date, date);
2108 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
2112 // set the date forward 1 week
2113 if (is_shift_pressed) {
2114 this.AddTimeToSelection (7, true);
2116 DateTime date = this.SelectionStart.AddDays (7);
2117 this.SetSelectionRange (date, date);
2119 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
2124 if (is_shift_pressed) {
2125 this.AddTimeToSelection (-1, true);
2127 DateTime date = this.SelectionStart.AddDays (-1);
2128 this.SetSelectionRange (date, date);
2130 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
2135 if (is_shift_pressed) {
2136 this.AddTimeToSelection (1, true);
2138 DateTime date = this.SelectionStart.AddDays (1);
2139 this.SetSelectionRange (date, date);
2141 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
2150 // to check if the mouse has come up on this control
2151 private void MouseUpHandler (object sender, MouseEventArgs e)
2153 if (timer.Enabled) {
2156 // clear the click state array
2157 click_state [0] = false;
2158 click_state [1] = false;
2159 click_state [2] = false;
2160 // do the regulare mouseup stuff
2164 // raised by any key up events
2165 private void KeyUpHandler (object sender, KeyEventArgs e) {
2166 is_shift_pressed = e.Shift ;
2168 IsYearGoingUp = false;
2169 IsYearGoingDown = false;
2172 // paint this control now
2173 private void PaintHandler (object sender, PaintEventArgs pe) {
2174 if (Width <= 0 || Height <= 0 || Visible == false)
2177 Draw (pe.ClipRectangle, pe.Graphics);
2179 // fire the new paint handler
2180 if (this.Paint != null)
2182 this.Paint (sender, pe);
2186 // returns the region of the control that needs to be redrawn
2187 private void InvalidateDateRange (SelectionRange range) {
2188 SelectionRange bounds = this.GetDisplayRange (false);
2190 if (range.End < bounds.Start || range.Start > bounds.End) {
2191 // don't invalidate anything, as the modified date range
2192 // is outside the visible bounds of this control
2195 // adjust the start and end to be inside the visible range
2196 if (range.Start < bounds.Start) {
2197 range = new SelectionRange (bounds.Start, range.End);
2199 if (range.End > bounds.End) {
2200 range = new SelectionRange (range.Start, bounds.End);
2202 // now invalidate the date rectangles as series of rows
2203 DateTime last_month = this.current_month.AddMonths ((CalendarDimensions.Width * CalendarDimensions.Height)).AddDays (-1);
2204 DateTime current = range.Start;
2205 while (current <= range.End) {
2206 DateTime month_end = new DateTime (current.Year, current.Month, 1).AddMonths (1).AddDays (-1);;
2207 Rectangle start_rect;
2209 // see if entire selection is in this current month
2210 if (range.End <= month_end && current < last_month) {
2211 // the end is the last date
2212 if (current < this.current_month) {
2213 start_rect = GetDateRowRect (current_month, current_month);
2215 start_rect = GetDateRowRect (current, current);
2217 end_rect = GetDateRowRect (current, range.End);
2218 } else if (current < last_month) {
2219 // otherwise it simply means we have a selection spaning
2220 // multiple months simply set rectangle inside the current month
2221 start_rect = GetDateRowRect (current, current);
2222 end_rect = GetDateRowRect (month_end, month_end);
2224 // it's outside the visible range
2225 start_rect = GetDateRowRect (last_month, last_month.AddDays (1));
2226 end_rect = GetDateRowRect (last_month, range.End);
2228 // push to the next month
2229 current = month_end.AddDays (1);
2230 // invalidate from the start row to the end row for this month
2236 Math.Max (end_rect.Bottom - start_rect.Y, 0)));
2240 // gets the rect of the row where the specified date appears on the specified month
2241 private Rectangle GetDateRowRect (DateTime month, DateTime date) {
2242 // first get the general rect of the supplied month
2243 Size month_size = SingleMonthSize;
2244 Rectangle month_rect = Rectangle.Empty;
2245 for (int i=0; i < CalendarDimensions.Width*CalendarDimensions.Height; i++) {
2246 DateTime this_month = this.current_month.AddMonths (i);
2247 if (month.Year == this_month.Year && month.Month == this_month.Month) {
2248 month_rect = new Rectangle (
2249 this.ClientRectangle.X + 1 + (month_size.Width * (i%CalendarDimensions.Width)) + (this.calendar_spacing.Width * (i%CalendarDimensions.Width)),
2250 this.ClientRectangle.Y + 1 + (month_size.Height * (i/CalendarDimensions.Width)) + (this.calendar_spacing.Height * (i/CalendarDimensions.Width)),
2256 // now find out where in the month the supplied date is
2257 if (month_rect == Rectangle.Empty) {
2258 return Rectangle.Empty;
2260 // find out which row this date is in
2262 DateTime first_date = GetFirstDateInMonthGrid (month);
2263 DateTime end_date = first_date.AddDays (7);
2264 for (int i=0; i < 6; i++) {
2265 if (date >= first_date && date < end_date) {
2269 first_date = end_date;
2270 end_date = end_date.AddDays (7);
2272 // ensure it's a valid row
2274 return Rectangle.Empty;
2276 int x_offset = (this.ShowWeekNumbers) ? date_cell_size.Width : 0;
2277 int y_offset = title_size.Height + (date_cell_size.Height * (row + 1));
2278 return new Rectangle (
2279 month_rect.X + x_offset,
2280 month_rect.Y + y_offset,
2281 date_cell_size.Width * 7,
2282 date_cell_size.Height);
2285 internal void Draw (Rectangle clip_rect, Graphics dc)
2287 ThemeEngine.Current.DrawMonthCalendar (dc, clip_rect, this);
2290 internal override bool InternalCapture {
2292 return base.InternalCapture;
2295 // Don't allow internal capture when DateTimePicker is using us
2296 // Control sets this on MouseDown
2298 base.InternalCapture = value;
2302 #endregion //internal methods
2304 #region internal drawing methods
2307 #endregion // internal drawing methods
2309 #region inner classes and enumerations
2311 // enumeration about what type of area on the calendar was hit
2312 public enum HitArea {
2328 internal enum HitAreaExtra {
2334 // info regarding to a hit test on this calendar
2335 public sealed class HitTestInfo {
2337 private HitArea hit_area;
2338 private Point point;
2339 private DateTime time;
2341 internal HitAreaExtra hit_area_extra;
2342 internal DateTime hit_time;
2344 // default constructor
2345 internal HitTestInfo () {
2346 hit_area = HitArea.Nowhere;
2347 point = new Point (0, 0);
2348 time = DateTime.Now;
2351 // overload receives all properties
2352 internal HitTestInfo (HitArea hit_area, Point point, DateTime time) {
2353 this.hit_area = hit_area;
2356 this.hit_time = time;
2359 // overload receives all properties
2360 internal HitTestInfo (HitArea hit_area, Point point, DateTime time, DateTime hit_time)
2362 this.hit_area = hit_area;
2365 this.hit_time = hit_time;
2368 internal HitTestInfo (HitArea hit_area, Point point, DateTime time, HitAreaExtra hit_area_extra)
2370 this.hit_area = hit_area;
2371 this.hit_area_extra = hit_area_extra;
2376 // the type of area that was hit
2377 public HitArea HitArea {
2383 // the point that is being test
2384 public Point Point {
2390 // the date under the hit test point, only valid if HitArea is Date
2391 public DateTime Time {
2398 #endregion // inner classes