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 Graphics g = this.DeviceContext;
1637 DateTime this_month = this.current_month.AddMonths (calendar_index);
1638 Size title_text_size = g.MeasureString (this_month.ToString ("MMMM yyyy"), this.Font).ToSize ();
1639 Size month_size = g.MeasureString (this_month.ToString ("MMMM"), this.Font).ToSize ();
1640 // return only the month name part of that
1641 return new Rectangle (
1643 title_rect.X + ((title_rect.Width - title_text_size.Width)/2),
1644 title_rect.Y + ((title_rect.Height - title_text_size.Height)/2)),
1648 internal void GetYearNameRectangles (Rectangle title_rect, int calendar_index, out Rectangle year_rect, out Rectangle up_rect, out Rectangle down_rect)
1650 Graphics g = this.DeviceContext;
1651 DateTime this_month = this.current_month.AddMonths (calendar_index);
1652 SizeF title_text_size = g.MeasureString (this_month.ToString ("MMMM yyyy"), this.bold_font, int.MaxValue, centered_format);
1653 SizeF year_size = g.MeasureString (this_month.ToString ("yyyy"), this.bold_font, int.MaxValue, centered_format);
1654 // find out how much space the title took
1655 RectangleF text_rect = new RectangleF (
1657 title_rect.X + ((title_rect.Width - title_text_size.Width) / 2),
1658 title_rect.Y + ((title_rect.Height - title_text_size.Height) / 2)),
1660 // return only the rect of the year
1661 year_rect = new Rectangle (
1663 ((int)(text_rect.Right - year_size.Width + 1)),
1665 new Size ((int)(year_size.Width + 1), (int)(year_size.Height + 1)));
1667 year_rect.Inflate (0, 1);
1668 up_rect = new Rectangle ();
1669 up_rect.Location = new Point (year_rect.X + year_rect.Width + 2, year_rect.Y);
1670 up_rect.Size = new Size (16, year_rect.Height / 2);
1671 down_rect = new Rectangle ();
1672 down_rect.Location = new Point (up_rect.X, up_rect.Y + up_rect.Height + 1);
1673 down_rect.Size = up_rect.Size;
1676 // returns the rectangle for the year in the title
1677 internal Rectangle GetYearNameRectangle (Rectangle title_rect, int calendar_index) {
1678 Rectangle result, discard;
1679 GetYearNameRectangles (title_rect, calendar_index, out result, out discard, out discard);
1683 // determine if date is allowed to be drawn in month
1684 internal bool IsValidWeekToDraw (DateTime month, DateTime date, int row, int col) {
1685 DateTime tocheck = month.AddMonths (-1);
1686 if ((month.Year == date.Year && month.Month == date.Month) ||
1687 (tocheck.Year == date.Year && tocheck.Month == date.Month)) {
1691 // check the railing dates (days in the month after the last month in grid)
1692 if (row == CalendarDimensions.Height - 1 && col == CalendarDimensions.Width - 1) {
1693 tocheck = month.AddMonths (1);
1694 return (tocheck.Year == date.Year && tocheck.Month == date.Month) ;
1700 // set one item clicked and all others off
1701 private void SetItemClick(HitTestInfo hti)
1703 switch(hti.HitArea) {
1704 case HitArea.NextMonthButton:
1705 this.is_previous_clicked = false;
1706 this.is_next_clicked = true;
1707 this.is_date_clicked = false;
1709 case HitArea.PrevMonthButton:
1710 this.is_previous_clicked = true;
1711 this.is_next_clicked = false;
1712 this.is_date_clicked = false;
1714 case HitArea.PrevMonthDate:
1715 case HitArea.NextMonthDate:
1717 this.clicked_date = hti.hit_time;
1718 this.is_previous_clicked = false;
1719 this.is_next_clicked = false;
1720 this.is_date_clicked = true;
1723 this.is_previous_clicked = false;
1724 this.is_next_clicked = false;
1725 this.is_date_clicked = false;
1731 // called when context menu is clicked
1732 private void MenuItemClickHandler (object sender, EventArgs e) {
1733 MenuItem item = sender as MenuItem;
1734 if (item != null && month_title_click_location != Point.Empty) {
1735 // establish which month we want to move to
1736 if (item.Parent == null) {
1739 int new_month = item.Parent.MenuItems.IndexOf (item) + 1;
1740 if (new_month == 0) {
1743 // okay let's establish which calendar was hit
1744 Size month_size = this.SingleMonthSize;
1745 for (int i=0; i < CalendarDimensions.Height; i++) {
1746 for (int j=0; j < CalendarDimensions.Width; j++) {
1747 int month_index = (i * CalendarDimensions.Width) + j;
1748 Rectangle month_rect = new Rectangle ( new Point (0, 0), month_size);
1750 month_rect.X = this.ClientRectangle.X + 1;
1752 month_rect.X = this.ClientRectangle.X + 1 + ((j)*(month_size.Width+calendar_spacing.Width));
1755 month_rect.Y = this.ClientRectangle.Y + 1;
1757 month_rect.Y = this.ClientRectangle.Y + 1 + ((i)*(month_size.Height+calendar_spacing.Height));
1759 // see if the point is inside
1760 if (month_rect.Contains (month_title_click_location)) {
1761 DateTime clicked_month = CurrentMonth.AddMonths (month_index);
1762 // get the month that we want to move to
1763 int month_offset = new_month - clicked_month.Month;
1765 // move forward however more months we need to
1766 this.CurrentMonth = this.CurrentMonth.AddMonths (month_offset);
1773 month_title_click_location = Point.Empty;
1777 // raised on the timer, for mouse hold clicks
1778 private void TimerHandler (object sender, EventArgs e) {
1779 // now find out which area was click
1781 HitTestInfo hti = this.HitTest (this.PointToClient (MousePosition));
1782 // see if it was clicked on the prev or next mouse
1783 if (click_state [1] || click_state [2]) {
1784 // invalidate the area where the mouse was last held
1786 // register the click
1787 if (hti.HitArea == HitArea.PrevMonthButton ||
1788 hti.HitArea == HitArea.NextMonthButton) {
1789 DoButtonMouseDown (hti);
1790 click_state [1] = (hti.HitArea == HitArea.PrevMonthButton);
1791 click_state [2] = !click_state [1];
1793 if (timer.Interval != 300) {
1794 timer.Interval = 300;
1798 timer.Enabled = false;
1802 // selects one of the buttons
1803 private void DoButtonMouseDown (HitTestInfo hti) {
1804 // show the click then move on
1806 if (hti.HitArea == HitArea.PrevMonthButton) {
1807 // invalidate the prev monthbutton
1810 this.ClientRectangle.X + 1 + button_x_offset,
1811 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1813 button_size.Height));
1814 int scroll = (scroll_change == 0 ? CalendarDimensions.Width * CalendarDimensions.Height : scroll_change);
1815 this.CurrentMonth = this.CurrentMonth.AddMonths (-scroll);
1817 // invalidate the next monthbutton
1820 this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width,
1821 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1823 button_size.Height));
1824 int scroll = (scroll_change == 0 ? CalendarDimensions.Width * CalendarDimensions.Height : scroll_change);
1825 this.CurrentMonth = this.CurrentMonth.AddMonths (scroll);
1829 // selects the clicked date
1830 private void DoDateMouseDown (HitTestInfo hti) {
1834 // event run on the mouse up event
1835 private void DoMouseUp () {
1837 IsYearGoingDown = false;
1838 IsYearGoingUp = false;
1839 is_mouse_moving_year = false;
1841 HitTestInfo hti = this.HitTest (this.PointToClient (MousePosition));
1842 switch (hti.HitArea) {
1843 case HitArea.PrevMonthDate:
1844 case HitArea.NextMonthDate:
1846 this.SelectDate (clicked_date);
1847 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1851 // invalidate the next monthbutton
1852 if (this.is_next_clicked) {
1855 this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width,
1856 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1858 button_size.Height));
1860 // invalidate the prev monthbutton
1861 if (this.is_previous_clicked) {
1864 this.ClientRectangle.X + 1 + button_x_offset,
1865 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1867 button_size.Height));
1869 if (this.is_date_clicked) {
1870 // invalidate the area under the cursor, to remove focus rect
1871 this.InvalidateDateRange (new SelectionRange (clicked_date, clicked_date));
1873 this.is_previous_clicked = false;
1874 this.is_next_clicked = false;
1875 this.is_date_clicked = false;
1878 // needed when in windowed mode to close the calendar if no
1879 // part of it has focus.
1880 private void UpDownTimerTick(object sender, EventArgs e)
1882 if (IsYearGoingUp) {
1883 IsYearGoingUp = true;
1885 if (IsYearGoingDown) {
1886 IsYearGoingDown = true;
1889 if (!IsYearGoingDown && !IsYearGoingUp) {
1890 updown_timer.Enabled = false;
1891 } else if (IsYearGoingDown || IsYearGoingUp) {
1892 updown_timer.Interval = subsequent_delay;
1896 // Needed when in windowed mode.
1897 private void StartHideTimer ()
1899 if (updown_timer == null) {
1900 updown_timer = new Timer ();
1901 updown_timer.Tick += new EventHandler (UpDownTimerTick);
1903 updown_timer.Interval = initial_delay;
1904 updown_timer.Enabled = true;
1908 // occurs when mouse moves around control, used for selection
1909 private void MouseMoveHandler (object sender, MouseEventArgs e) {
1910 HitTestInfo hti = this.HitTest (e.X, e.Y);
1911 // clear the last clicked item
1912 if (click_state [0]) {
1913 // register the click
1914 if (hti.HitArea == HitArea.PrevMonthDate ||
1915 hti.HitArea == HitArea.NextMonthDate ||
1916 hti.HitArea == HitArea.Date)
1918 Rectangle prev_rect = clicked_rect;
1919 DateTime prev_clicked = clicked_date;
1920 DoDateMouseDown (hti);
1921 if (owner == null) {
1922 click_state [0] = true;
1924 click_state [0] = false;
1925 click_state [1] = false;
1926 click_state [2] = false;
1929 if (prev_clicked != clicked_date) {
1930 Rectangle invalid = Rectangle.Union (prev_rect, clicked_rect);
1931 Invalidate (invalid);
1938 // to check if the mouse has come down on this control
1939 private void MouseDownHandler (object sender, MouseEventArgs e)
1941 // clear the click_state variables
1942 click_state [0] = false;
1943 click_state [1] = false;
1944 click_state [2] = false;
1946 // disable the timer if it was enabled
1947 if (timer.Enabled) {
1949 timer.Enabled = false;
1952 Point point = new Point (e.X, e.Y);
1953 // figure out if we are in drop down mode and a click happened outside us
1954 if (this.owner != null) {
1955 if (!this.ClientRectangle.Contains (point)) {
1956 this.owner.HideMonthCalendar ();
1961 //establish where was hit
1962 HitTestInfo hti = this.HitTest(point);
1963 // hide the year numeric up down if it was clicked
1964 if (ShowYearUpDown && hti.HitArea != HitArea.TitleYear) {
1965 ShowYearUpDown = false;
1967 switch (hti.HitArea) {
1968 case HitArea.PrevMonthButton:
1969 case HitArea.NextMonthButton:
1970 DoButtonMouseDown (hti);
1971 click_state [1] = (hti.HitArea == HitArea.PrevMonthDate);
1972 click_state [2] = !click_state [1];
1973 timer.Interval = 750;
1977 case HitArea.PrevMonthDate:
1978 case HitArea.NextMonthDate:
1979 DoDateMouseDown (hti);
1980 // leave clicked state blank if drop down window
1981 if (owner == null) {
1982 click_state [0] = true;
1984 click_state [0] = false;
1985 click_state [1] = false;
1986 click_state [2] = false;
1989 case HitArea.TitleMonth:
1990 month_title_click_location = hti.Point;
1991 menu.Show (this, hti.Point);
1992 if (this.Capture && owner != null) {
1997 case HitArea.TitleYear:
1998 // place the numeric up down
1999 if (ShowYearUpDown) {
2000 if (hti.hit_area_extra == HitAreaExtra.UpButton) {
2001 is_mouse_moving_year = true;
2002 IsYearGoingUp = true;
2003 } else if (hti.hit_area_extra == HitAreaExtra.DownButton) {
2004 is_mouse_moving_year = true;
2005 IsYearGoingDown = true;
2009 ShowYearUpDown = true;
2012 case HitArea.TodayLink:
2013 this.SetSelectionRange (DateTime.Now.Date, DateTime.Now.Date);
2014 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
2017 this.is_previous_clicked = false;
2018 this.is_next_clicked = false;
2019 this.is_date_clicked = false;
2024 // raised by any key down events
2025 private void KeyDownHandler (object sender, KeyEventArgs e) {
2026 // send keys to the year_updown control, let it handle it
2027 if(ShowYearUpDown) {
2028 switch (e.KeyCode) {
2030 ShowYearUpDown = false;
2031 IsYearGoingDown = false;
2032 IsYearGoingUp = false;
2035 IsYearGoingUp = true;
2039 IsYearGoingDown = true;
2044 if (!is_shift_pressed && e.Shift) {
2045 first_select_start_date = SelectionStart;
2046 is_shift_pressed = e.Shift;
2049 switch (e.KeyCode) {
2051 // set the date to the start of the month
2052 if (is_shift_pressed) {
2053 DateTime date = GetFirstDateInMonth (first_select_start_date);
2054 if (date < first_select_start_date.AddDays ((MaxSelectionCount-1)*-1)) {
2055 date = first_select_start_date.AddDays ((MaxSelectionCount-1)*-1);
2057 this.SetSelectionRange (date, first_select_start_date);
2059 DateTime date = GetFirstDateInMonth (this.SelectionStart);
2060 this.SetSelectionRange (date, date);
2062 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
2066 // set the date to the last of the month
2067 if (is_shift_pressed) {
2068 DateTime date = GetLastDateInMonth (first_select_start_date);
2069 if (date > first_select_start_date.AddDays (MaxSelectionCount-1)) {
2070 date = first_select_start_date.AddDays (MaxSelectionCount-1);
2072 this.SetSelectionRange (date, first_select_start_date);
2074 DateTime date = GetLastDateInMonth (this.SelectionStart);
2075 this.SetSelectionRange (date, date);
2077 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
2081 // set the date to the last of the month
2082 if (is_shift_pressed) {
2083 this.AddTimeToSelection (-1, false);
2085 DateTime date = this.SelectionStart.AddMonths (-1);
2086 this.SetSelectionRange (date, date);
2088 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
2092 // set the date to the last of the month
2093 if (is_shift_pressed) {
2094 this.AddTimeToSelection (1, false);
2096 DateTime date = this.SelectionStart.AddMonths (1);
2097 this.SetSelectionRange (date, date);
2099 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
2103 // set the back 1 week
2104 if (is_shift_pressed) {
2105 this.AddTimeToSelection (-7, true);
2107 DateTime date = this.SelectionStart.AddDays (-7);
2108 this.SetSelectionRange (date, date);
2110 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
2114 // set the date forward 1 week
2115 if (is_shift_pressed) {
2116 this.AddTimeToSelection (7, true);
2118 DateTime date = this.SelectionStart.AddDays (7);
2119 this.SetSelectionRange (date, date);
2121 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
2126 if (is_shift_pressed) {
2127 this.AddTimeToSelection (-1, true);
2129 DateTime date = this.SelectionStart.AddDays (-1);
2130 this.SetSelectionRange (date, date);
2132 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
2137 if (is_shift_pressed) {
2138 this.AddTimeToSelection (1, true);
2140 DateTime date = this.SelectionStart.AddDays (1);
2141 this.SetSelectionRange (date, date);
2143 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
2152 // to check if the mouse has come up on this control
2153 private void MouseUpHandler (object sender, MouseEventArgs e)
2155 if (timer.Enabled) {
2158 // clear the click state array
2159 click_state [0] = false;
2160 click_state [1] = false;
2161 click_state [2] = false;
2162 // do the regulare mouseup stuff
2166 // raised by any key up events
2167 private void KeyUpHandler (object sender, KeyEventArgs e) {
2168 is_shift_pressed = e.Shift ;
2170 IsYearGoingUp = false;
2171 IsYearGoingDown = false;
2174 // paint this control now
2175 private void PaintHandler (object sender, PaintEventArgs pe) {
2176 if (Width <= 0 || Height <= 0 || Visible == false)
2179 Draw (pe.ClipRectangle, pe.Graphics);
2181 // fire the new paint handler
2182 if (this.Paint != null)
2184 this.Paint (sender, pe);
2188 // returns the region of the control that needs to be redrawn
2189 private void InvalidateDateRange (SelectionRange range) {
2190 SelectionRange bounds = this.GetDisplayRange (false);
2192 if (range.End < bounds.Start || range.Start > bounds.End) {
2193 // don't invalidate anything, as the modified date range
2194 // is outside the visible bounds of this control
2197 // adjust the start and end to be inside the visible range
2198 if (range.Start < bounds.Start) {
2199 range = new SelectionRange (bounds.Start, range.End);
2201 if (range.End > bounds.End) {
2202 range = new SelectionRange (range.Start, bounds.End);
2204 // now invalidate the date rectangles as series of rows
2205 DateTime last_month = this.current_month.AddMonths ((CalendarDimensions.Width * CalendarDimensions.Height)).AddDays (-1);
2206 DateTime current = range.Start;
2207 while (current <= range.End) {
2208 DateTime month_end = new DateTime (current.Year, current.Month, 1).AddMonths (1).AddDays (-1);;
2209 Rectangle start_rect;
2211 // see if entire selection is in this current month
2212 if (range.End <= month_end && current < last_month) {
2213 // the end is the last date
2214 if (current < this.current_month) {
2215 start_rect = GetDateRowRect (current_month, current_month);
2217 start_rect = GetDateRowRect (current, current);
2219 end_rect = GetDateRowRect (current, range.End);
2220 } else if (current < last_month) {
2221 // otherwise it simply means we have a selection spaning
2222 // multiple months simply set rectangle inside the current month
2223 start_rect = GetDateRowRect (current, current);
2224 end_rect = GetDateRowRect (month_end, month_end);
2226 // it's outside the visible range
2227 start_rect = GetDateRowRect (last_month, last_month.AddDays (1));
2228 end_rect = GetDateRowRect (last_month, range.End);
2230 // push to the next month
2231 current = month_end.AddDays (1);
2232 // invalidate from the start row to the end row for this month
2238 Math.Max (end_rect.Bottom - start_rect.Y, 0)));
2242 // gets the rect of the row where the specified date appears on the specified month
2243 private Rectangle GetDateRowRect (DateTime month, DateTime date) {
2244 // first get the general rect of the supplied month
2245 Size month_size = SingleMonthSize;
2246 Rectangle month_rect = Rectangle.Empty;
2247 for (int i=0; i < CalendarDimensions.Width*CalendarDimensions.Height; i++) {
2248 DateTime this_month = this.current_month.AddMonths (i);
2249 if (month.Year == this_month.Year && month.Month == this_month.Month) {
2250 month_rect = new Rectangle (
2251 this.ClientRectangle.X + 1 + (month_size.Width * (i%CalendarDimensions.Width)) + (this.calendar_spacing.Width * (i%CalendarDimensions.Width)),
2252 this.ClientRectangle.Y + 1 + (month_size.Height * (i/CalendarDimensions.Width)) + (this.calendar_spacing.Height * (i/CalendarDimensions.Width)),
2258 // now find out where in the month the supplied date is
2259 if (month_rect == Rectangle.Empty) {
2260 return Rectangle.Empty;
2262 // find out which row this date is in
2264 DateTime first_date = GetFirstDateInMonthGrid (month);
2265 DateTime end_date = first_date.AddDays (7);
2266 for (int i=0; i < 6; i++) {
2267 if (date >= first_date && date < end_date) {
2271 first_date = end_date;
2272 end_date = end_date.AddDays (7);
2274 // ensure it's a valid row
2276 return Rectangle.Empty;
2278 int x_offset = (this.ShowWeekNumbers) ? date_cell_size.Width : 0;
2279 int y_offset = title_size.Height + (date_cell_size.Height * (row + 1));
2280 return new Rectangle (
2281 month_rect.X + x_offset,
2282 month_rect.Y + y_offset,
2283 date_cell_size.Width * 7,
2284 date_cell_size.Height);
2287 internal void Draw (Rectangle clip_rect, Graphics dc)
2289 ThemeEngine.Current.DrawMonthCalendar (dc, clip_rect, this);
2292 internal override bool InternalCapture {
2294 return base.InternalCapture;
2297 // Don't allow internal capture when DateTimePicker is using us
2298 // Control sets this on MouseDown
2300 base.InternalCapture = value;
2304 #endregion //internal methods
2306 #region internal drawing methods
2309 #endregion // internal drawing methods
2311 #region inner classes and enumerations
2313 // enumeration about what type of area on the calendar was hit
2314 public enum HitArea {
2330 internal enum HitAreaExtra {
2336 // info regarding to a hit test on this calendar
2337 public sealed class HitTestInfo {
2339 private HitArea hit_area;
2340 private Point point;
2341 private DateTime time;
2343 internal HitAreaExtra hit_area_extra;
2344 internal DateTime hit_time;
2346 // default constructor
2347 internal HitTestInfo () {
2348 hit_area = HitArea.Nowhere;
2349 point = new Point (0, 0);
2350 time = DateTime.Now;
2353 // overload receives all properties
2354 internal HitTestInfo (HitArea hit_area, Point point, DateTime time) {
2355 this.hit_area = hit_area;
2358 this.hit_time = time;
2361 // overload receives all properties
2362 internal HitTestInfo (HitArea hit_area, Point point, DateTime time, DateTime hit_time)
2364 this.hit_area = hit_area;
2367 this.hit_time = hit_time;
2370 internal HitTestInfo (HitArea hit_area, Point point, DateTime time, HitAreaExtra hit_area_extra)
2372 this.hit_area = hit_area;
2373 this.hit_area_extra = hit_area_extra;
2378 // the type of area that was hit
2379 public HitArea HitArea {
2385 // the point that is being test
2386 public Point Point {
2392 // the date under the hit test point, only valid if HitArea is Date
2393 public DateTime Time {
2400 #endregion // inner classes