1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2004 Novell, Inc.
23 // John BouAntoun jba-mono@optusnet.com.au
26 // - get the date_cell_size and title_size to be pixel perfect match of SWF
27 // - show the year spin control
28 // - remove comments around the "if (this.Capture) {" in the TimerHandler method
31 using System.Collections;
32 using System.ComponentModel;
33 using System.ComponentModel.Design;
35 using System.Windows.Forms;
37 namespace System.Windows.Forms {
38 [DefaultProperty("SelectionRange")]
39 [DefaultEvent("DateChanged")]
40 [Designer ("System.Windows.Forms.Design.MonthCalendarDesigner, " + Consts.AssemblySystem_Design, (string)null)]
41 public class MonthCalendar : Control {
42 #region Local variables
43 DateTime [] annually_bolded_dates;
45 DateTime [] bolded_dates;
46 Size calendar_dimensions;
47 Day first_day_of_week;
50 int max_selection_count;
52 DateTime [] monthly_bolded_dates;
54 SelectionRange selection_range;
56 bool show_today_circle;
57 bool show_week_numbers;
58 Color title_back_color;
59 Color title_fore_color;
62 Color trailing_fore_color;
66 // internal variables used
67 internal DateTime current_month; // the month that is being displayed in top left corner of the grid
68 internal DateTimePicker owner; // used if this control is popped up
69 internal int button_x_offset;
70 internal Size button_size;
71 internal Size title_size;
72 internal Size date_cell_size;
73 internal Size calendar_spacing;
74 internal int divider_line_offset;
75 internal DateTime clicked_date;
76 internal bool is_date_clicked;
77 internal bool is_previous_clicked;
78 internal bool is_next_clicked;
79 internal bool is_shift_pressed;
80 internal DateTime first_select_start_date;
81 private Point month_title_click_location;
82 // this is used to see which item was actually clicked on in the beginning
83 // so that we know which item to fire on timer
85 // 1: previous clicked
87 private bool[] click_state;
89 // arraylists used to store new dates
90 ArrayList added_bolded_dates;
91 ArrayList removed_bolded_dates;
92 ArrayList added_annually_bolded_dates;
93 ArrayList removed_annually_bolded_dates;
94 ArrayList added_monthly_bolded_dates;
95 ArrayList removed_monthly_bolded_dates;
98 #endregion // Local variables
100 #region Public Constructors
102 public MonthCalendar () {
103 // set up the control painting
104 SetStyle (ControlStyles.UserPaint, true);
105 SetStyle (ControlStyles.AllPaintingInWmPaint, true);
108 timer = new Timer ();
109 timer.Interval = 500;
110 timer.Enabled = false;
112 // initialise default values
113 DateTime now = DateTime.Now.Date;
114 selection_range = new SelectionRange (now, now);
116 current_month = new DateTime (now.Year , now.Month, 1);
118 // iniatialise local members
119 annually_bolded_dates = null;
120 back_color = ThemeEngine.Current.ColorWindow;
122 calendar_dimensions = new Size (1,1);
123 first_day_of_week = Day.Default;
124 fore_color = SystemColors.ControlText;
125 max_date = new DateTime (9998, 12, 31);
126 max_selection_count = 7;
127 min_date = new DateTime (1953, 1, 1);
128 monthly_bolded_dates = null;
131 show_today_circle = true;
132 show_week_numbers = false;
133 title_back_color = ThemeEngine.Current.ColorActiveTitle;
134 title_fore_color = ThemeEngine.Current.ColorTitleText;
135 today_date_set = false;
136 trailing_fore_color = Color.Gray;
138 // initialise the arraylest for bolded dates
139 added_bolded_dates = new ArrayList ();
140 removed_bolded_dates = new ArrayList ();
141 added_annually_bolded_dates = new ArrayList ();
142 removed_annually_bolded_dates = new ArrayList ();
143 added_monthly_bolded_dates = new ArrayList ();
144 removed_monthly_bolded_dates = new ArrayList ();
146 // intiailise internal variables used
148 button_size = new Size (22, 17);
149 // default settings based on 8.25 pt San Serif Font
150 // Not sure of algorithm used to establish this
151 date_cell_size = new Size (24, 16); // default size at san-serif 8.25
152 divider_line_offset = 4;
153 calendar_spacing = new Size (4, 5); // horiz and vert spacing between months in a calendar grid
155 // set some state info
157 is_date_clicked = false;
158 is_previous_clicked = false;
159 is_next_clicked = false;
160 is_shift_pressed = false;
161 click_state = new bool [] {false, false, false};
162 first_select_start_date = now;
163 month_title_click_location = Point.Empty;
168 LostFocus += new EventHandler (LostFocusHandler);
169 timer.Tick += new EventHandler (TimerHandler);
170 MouseMove += new MouseEventHandler (MouseMoveHandler);
171 MouseDown += new MouseEventHandler (MouseDownHandler);
172 KeyDown += new KeyEventHandler (KeyDownHandler);
173 MouseUp += new MouseEventHandler (MouseUpHandler);
174 KeyUp += new KeyEventHandler (KeyUpHandler);
175 // this replaces paint so call the control version
176 ((Control)this).Paint += new PaintEventHandler (PaintHandler);
179 // called when this control is added to date time picker
180 internal MonthCalendar (DateTimePicker owner) : this () {
182 this.is_visible = false;
183 this.Size = this.DefaultSize;
186 #endregion // Public Constructors
188 #region Public Instance Properties
190 // dates to make bold on calendar annually (recurring)
192 public DateTime[] AnnuallyBoldedDates {
194 if (annually_bolded_dates == null || annually_bolded_dates != value) {
195 annually_bolded_dates = value;
196 this.UpdateBoldedDates ();
201 return annually_bolded_dates;
205 // the back color for the main part of the calendar
206 public Color BackColor {
208 if (back_color != value) {
210 this.OnBackColorChanged (EventArgs.Empty);
219 // specific dates to make bold on calendar (non-recurring)
221 public DateTime[] BoldedDates {
223 if (bolded_dates == null || bolded_dates != value) {
224 bolded_dates = value;
225 this.UpdateBoldedDates ();
234 // the configuration of the monthly grid display - only allowed to display at most,
235 // 1 calendar year at a time, will be trimmed to fit it properly
237 public Size CalendarDimensions {
239 if (value.Width < 0 || value.Height < 0) {
240 throw new ArgumentException ();
242 if (calendar_dimensions != value) {
243 // squeeze the grid into 1 calendar year
244 if (value.Width * value.Height > 12) {
245 // iteratively reduce the largest dimension till our
246 // product is less than 12
247 if (value.Width > 12 && value.Height > 12) {
248 calendar_dimensions = new Size (4, 3);
249 } else if (value.Width > 12) {
250 for (int i = 12; i > 0; i--) {
251 if (i * value.Height <= 12) {
252 calendar_dimensions = new Size (i, value.Height);
256 } else if (value.Height > 12) {
257 for (int i = 12; i > 0; i--) {
258 if (i * value.Width <= 12) {
259 calendar_dimensions = new Size (value.Width, i);
265 calendar_dimensions = value;
271 return calendar_dimensions;
275 // the first day of the week to display
277 [DefaultValue (Day.Default)]
278 public Day FirstDayOfWeek {
280 if (first_day_of_week != value) {
281 first_day_of_week = value;
286 return first_day_of_week;
290 // the fore color for the main part of the calendar
291 public Color ForeColor {
293 if (fore_color != value) {
295 this.OnForeColorChanged (EventArgs.Empty);
304 // the maximum date allowed to be selected on this month calendar
305 public DateTime MaxDate {
307 if (value < MinDate) {
308 throw new ArgumentException();
311 if (max_date != value) {
320 // the maximum number of selectable days
322 public int MaxSelectionCount {
325 throw new ArgumentException();
328 // can't set selectioncount less than already selected dates
329 if ((SelectionEnd - SelectionStart).Days > value) {
330 throw new ArgumentException();
333 if (max_selection_count != value) {
334 max_selection_count = value;
338 return max_selection_count;
342 // the minimum date allowed to be selected on this month calendar
343 public DateTime MinDate {
345 if (value < new DateTime (1953, 1, 1)) {
346 throw new ArgumentException();
349 if (value > MaxDate) {
350 throw new ArgumentException();
353 if (max_date != value) {
362 // dates to make bold on calendar monthly (recurring)
364 public DateTime[] MonthlyBoldedDates {
366 if (monthly_bolded_dates == null || monthly_bolded_dates != value) {
367 monthly_bolded_dates = value;
368 this.UpdateBoldedDates ();
373 return monthly_bolded_dates;
377 // the ammount by which to scroll this calendar by
379 public int ScrollChange {
381 if (value < 0 || value > 20000) {
382 throw new ArgumentException();
385 if (scroll_change != value) {
386 scroll_change = value;
390 // if zero it to the default -> the total number of months currently visible
391 if (scroll_change == 0) {
392 return CalendarDimensions.Width * CalendarDimensions.Height;
394 return scroll_change;
399 // the last selected date
401 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
402 public DateTime SelectionEnd {
404 if (value < MinDate || value > MaxDate) {
405 throw new ArgumentException();
408 if (SelectionRange.End != value) {
409 DateTime old_end = SelectionRange.End;
410 // make sure the end obeys the max selection range count
411 if (value < SelectionRange.Start) {
412 SelectionRange.Start = value;
414 if (value.AddDays((MaxSelectionCount-1)*-1) > SelectionRange.Start) {
415 SelectionRange.Start = value.AddDays((MaxSelectionCount-1)*-1);
417 SelectionRange.End = value;
418 this.InvalidateDateRange (new SelectionRange (old_end, SelectionRange.End));
419 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
423 return SelectionRange.End;
427 // the range of selected dates
428 public SelectionRange SelectionRange {
430 if (selection_range != value) {
431 SelectionRange old_range = selection_range;
433 // make sure the end obeys the max selection range count
434 if (value.End.AddDays((MaxSelectionCount-1)*-1) > value.Start) {
435 selection_range = new SelectionRange (value.End.AddDays((MaxSelectionCount-1)*-1), value.End);
437 selection_range = value;
439 SelectionRange visible_range = this.GetDisplayRange(true);
440 if(visible_range.Start > selection_range.End) {
441 this.current_month = new DateTime (selection_range.Start.Year, selection_range.Start.Month, 1);
443 } else if (visible_range.End < selection_range.Start) {
444 int year_diff = selection_range.End.Year - visible_range.End.Year;
445 int month_diff = selection_range.End.Month - visible_range.End.Month;
446 this.current_month = current_month.AddMonths(year_diff * 12 + month_diff);
449 // invalidate the selected range changes
450 DateTime diff_start = old_range.Start;
451 DateTime diff_end = old_range.End;
452 // now decide which region is greated
453 if (old_range.Start > SelectionRange.Start) {
454 diff_start = SelectionRange.Start;
455 } else if (old_range.Start == SelectionRange.Start) {
456 if (old_range.End < SelectionRange.End) {
457 diff_start = old_range.End;
459 diff_start = SelectionRange.End;
462 if (old_range.End < SelectionRange.End) {
463 diff_end = SelectionRange.End;
464 } else if (old_range.End == SelectionRange.End) {
465 if (old_range.Start < SelectionRange.Start) {
466 diff_end = SelectionRange.Start;
468 diff_end = old_range.Start;
472 // invalidate the region required
473 this.InvalidateDateRange (new SelectionRange (diff_start, diff_end));
474 // raise date changed event
475 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
479 return selection_range;
483 // the first selected date
485 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
486 public DateTime SelectionStart {
488 if (value < MinDate || value > MaxDate) {
489 throw new ArgumentException();
492 if (SelectionRange.Start != value) {
493 DateTime old_start = SelectionRange.Start;
494 // make sure the end obeys the max selection range count
495 if (value > SelectionRange.End) {
496 SelectionRange.End = value;
497 } else if (value.AddDays(MaxSelectionCount-1) < SelectionRange.End) {
498 SelectionRange.End = value.AddDays(MaxSelectionCount-1);
500 SelectionRange.Start = value;
501 DateTime new_month = new DateTime(value.Year, value.Month, 1);
502 if (current_month != new_month) {
503 current_month = new_month;
506 this.InvalidateDateRange (new SelectionRange (old_start, SelectionRange.Start));
508 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
512 return selection_range.Start;
516 // whether or not to show todays date
517 [DefaultValue (true)]
518 public bool ShowToday {
520 if (show_today != value) {
530 // whether or not to show a circle around todays date
531 [DefaultValue (true)]
532 public bool ShowTodayCircle {
534 if (show_today_circle != value) {
535 show_today_circle = value;
540 return show_today_circle;
544 // whether or not to show numbers beside each row of weeks
546 [DefaultValue (false)]
547 public bool ShowWeekNumbers {
549 if (show_week_numbers != value) {
550 show_week_numbers = value;
555 return show_week_numbers;
559 // the rectangle size required to render one month based on current font
561 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
562 public Size SingleMonthSize {
564 if (this.Font == null) {
565 throw new InvalidOperationException();
568 // multiplier is sucked out from the font size
569 int multiplier = this.Font.Height;
571 // establis how many columns and rows we have
572 int column_count = (ShowWeekNumbers) ? 8 : 7;
573 int row_count = 7; // not including the today date
575 // set the date_cell_size and the title_size
576 date_cell_size = new Size ((int) Math.Ceiling (2.5 * multiplier), (int) Math.Ceiling (1.5 * multiplier));
577 title_size = new Size ((date_cell_size.Width * column_count), 3 * multiplier);
579 return new Size (column_count * date_cell_size.Width, row_count * date_cell_size.Height + title_size.Height);
583 // the back color for the title of the calendar and the
584 // forecolor for the day of the week text
585 public Color TitleBackColor {
587 if (title_back_color != value) {
588 title_back_color = value;
593 return title_back_color;
597 // the fore color for the title of the calendar
598 public Color TitleForeColor {
600 if (title_fore_color != value) {
601 title_fore_color = value;
606 return title_fore_color;
610 // the date this calendar is using to refer to today's date
611 public DateTime TodayDate {
613 today_date_set = true;
614 if (today_date != value) {
624 // tells if user specifically set today_date for this control
626 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
627 public bool TodayDateSet {
629 return today_date_set;
633 // the color used for trailing dates in the calendar
634 public Color TrailingForeColor {
636 if (trailing_fore_color != value) {
637 trailing_fore_color = value;
638 SelectionRange bounds = this.GetDisplayRange (false);
639 SelectionRange visible_bounds = this.GetDisplayRange (true);
640 this.InvalidateDateRange (new SelectionRange (bounds.Start, visible_bounds.Start));
641 this.InvalidateDateRange (new SelectionRange (bounds.End, visible_bounds.End));
645 return trailing_fore_color;
649 #endregion // Public Instance Properties
651 #region Protected Instance Properties
653 // overloaded to allow controll to be windowed for drop down
654 protected override CreateParams CreateParams {
656 if (this.owner == null) {
657 return base.CreateParams;
659 CreateParams cp = base.CreateParams;
660 cp.Style = unchecked ((int)(WindowStyles.WS_POPUP | WindowStyles.WS_VISIBLE | WindowStyles.WS_CLIPSIBLINGS | WindowStyles.WS_CLIPCHILDREN));
661 cp.ExStyle |= (int)(WindowStyles.WS_EX_TOOLWINDOW | WindowStyles.WS_EX_TOPMOST);
667 // not sure what to put in here - just doing a base() call - jba
668 protected override ImeMode DefaultImeMode {
670 return base.DefaultImeMode;
674 protected override Size DefaultSize {
676 Size single_month = SingleMonthSize;
678 int width = calendar_dimensions.Width * single_month.Width;
679 if (calendar_dimensions.Width > 1) {
680 width += (calendar_dimensions.Width - 1) * calendar_spacing.Width;
684 int height = calendar_dimensions.Height * single_month.Height;
685 if (this.ShowToday) {
686 height += date_cell_size.Height + 2; // add the height of the "Today: " ...
688 if (calendar_dimensions.Height > 1) {
689 height += (calendar_dimensions.Height - 1) * calendar_spacing.Height;
692 // add the 1 pixel boundary
700 return new Size (width, height);
704 #endregion // Protected Instance Properties
706 #region Public Instance Methods
708 // add a date to the anually bolded date arraylist
709 public void AddAnnuallyBoldedDate (DateTime date) {
710 added_annually_bolded_dates.Add (date.Date);
713 // add a date to the normal bolded date arraylist
714 public void AddBoldedDate (DateTime date) {
715 added_bolded_dates.Add (date.Date);
718 // add a date to the anually monthly date arraylist
719 public void AddMonthlyBoldedDate (DateTime date) {
720 added_monthly_bolded_dates.Add (date.Date);
723 // if visible = true, return only the dates of full months, else return all dates visible
724 public SelectionRange GetDisplayRange (bool visible) {
727 start = new DateTime (current_month.Year, current_month.Month, 1);
728 end = start.AddMonths (calendar_dimensions.Width * calendar_dimensions.Height);
729 end = end.AddDays(-1);
731 // process all visible dates if needed (including the grayed out dates
733 start = GetFirstDateInMonthGrid (start);
734 end = GetLastDateInMonthGrid (end);
737 return new SelectionRange (start, end);
740 // HitTest overload that recieve's x and y co-ordinates as separate ints
741 public HitTestInfo HitTest (int x, int y) {
742 return HitTest (new Point (x, y));
745 // returns a HitTestInfo for MonthCalendar element's under the specified point
746 public HitTestInfo HitTest (Point point) {
747 // before doing all the hard work, see if the today's date wasn't clicked
748 Rectangle today_rect = new Rectangle (
750 ClientRectangle.Bottom - date_cell_size.Height,
751 7 * date_cell_size.Width,
752 date_cell_size.Height);
753 if (today_rect.Contains (point) && this.ShowToday) {
754 return new HitTestInfo(HitArea.TodayLink, point, DateTime.Now);
757 Size month_size = SingleMonthSize;
758 // define calendar rect's that this thing can land in
759 Rectangle[] calendars = new Rectangle [CalendarDimensions.Width * CalendarDimensions.Height];
760 for (int i=0; i < CalendarDimensions.Width * CalendarDimensions.Height; i ++) {
762 calendars[i] = new Rectangle (
763 new Point (ClientRectangle.X + 1, ClientRectangle.Y + 1),
766 // calendar on the next row
767 if (i % CalendarDimensions.Width == 0) {
768 calendars[i] = new Rectangle (
769 new Point (calendars[i-CalendarDimensions.Width].X, calendars[i-CalendarDimensions.Width].Bottom + calendar_spacing.Height),
772 // calendar on the next column
773 calendars[i] = new Rectangle (
774 new Point (calendars[i-1].Right + calendar_spacing.Width, calendars[i-1].Y),
780 // through each trying to find a match
781 for (int i = 0; i < calendars.Length ; i++) {
782 if (calendars[i].Contains (point)) {
783 // check the title section
784 Rectangle title_rect = new Rectangle (
785 calendars[i].Location,
787 if (title_rect.Contains (point) ) {
788 // make sure it's not a previous button
790 Rectangle button_rect = new Rectangle(
791 new Point (calendars[i].X + button_x_offset, (title_size.Height - button_size.Height)/2),
793 if (button_rect.Contains (point))
\r
795 return new HitTestInfo(HitArea.PrevMonthButton, point, DateTime.Now);
798 // make sure it's not the next button
799 if (i % CalendarDimensions.Height == 0 && i % CalendarDimensions.Width == calendar_dimensions.Width - 1) {
800 Rectangle button_rect = new Rectangle(
801 new Point (calendars[i].Right - button_x_offset - button_size.Width, (title_size.Height - button_size.Height)/2),
803 if (button_rect.Contains (point))
\r
805 return new HitTestInfo(HitArea.NextMonthButton, point, DateTime.Now);
809 // make sure it's not the month or the year of the calendar
810 if (GetMonthNameRectangle (title_rect, i).Contains (point)) {
811 return new HitTestInfo(HitArea.TitleMonth, point, DateTime.Now);
813 if (GetYearNameRectangle (title_rect, i).Contains (point)) {
814 return new HitTestInfo(HitArea.TitleYear, point, DateTime.Now);
817 // return the hit test in the title background
818 return new HitTestInfo(HitArea.TitleBackground, point, DateTime.Now);
821 Point date_grid_location = new Point (calendars[i].X, title_rect.Bottom);
823 // see if it's in the Week numbers
824 if (ShowWeekNumbers) {
825 Rectangle weeks_rect = new Rectangle (
827 new Size (date_cell_size.Width,Math.Max (calendars[i].Height - title_rect.Height, 0)));
828 if (weeks_rect.Contains (point)) {
829 return new HitTestInfo(HitArea.WeekNumbers, point, DateTime.Now);
832 // move the location of the grid over
833 date_grid_location.X += date_cell_size.Width;
836 // see if it's in the week names
837 Rectangle day_rect = new Rectangle (
839 new Size (Math.Max (calendars[i].Right - date_grid_location.X, 0), date_cell_size.Height));
840 if (day_rect.Contains (point)) {
841 return new HitTestInfo(HitArea.DayOfWeek, point, DateTime.Now);
844 // finally see if it was a date that was clicked
845 Rectangle date_grid = new Rectangle (
846 new Point (day_rect.X, day_rect.Bottom),
847 new Size (day_rect.Width, Math.Max(calendars[i].Bottom - day_rect.Bottom, 0)));
848 if (date_grid.Contains (point)) {
849 // okay so it's inside the grid, get the offset
850 Point offset = new Point (point.X - date_grid.X, point.Y - date_grid.Y);
851 int row = offset.Y / date_cell_size.Height;
852 int col = offset.X / date_cell_size.Width;
853 // establish our first day of the month
854 DateTime calendar_month = this.CurrentMonth.AddMonths(i);
855 DateTime first_day = GetFirstDateInMonthGrid (calendar_month);
856 DateTime time = first_day.AddDays ((row * 7) + col);
857 // establish which date was clicked
858 if (time.Year != calendar_month.Year || time.Month != calendar_month.Month) {
859 if (time < calendar_month && i == 0) {
860 return new HitTestInfo(HitArea.PrevMonthDate, point, time);
861 } else if (time > calendar_month && i == CalendarDimensions.Width*CalendarDimensions.Height - 1) {
862 return new HitTestInfo(HitArea.NextMonthDate, point, time);
864 return new HitTestInfo(HitArea.Nowhere, point, time);
866 return new HitTestInfo(HitArea.Date, point, time);
871 return new HitTestInfo ();
874 // clears all the annually bolded dates
875 public void RemoveAllAnnuallyBoldedDates () {
876 annually_bolded_dates = null;
877 added_annually_bolded_dates.Clear ();
878 removed_annually_bolded_dates.Clear ();
881 // clears all the normal bolded dates
882 public void RemoveAllBoldedDates () {
884 added_bolded_dates.Clear ();
885 removed_bolded_dates.Clear ();
888 // clears all the monthly bolded dates
889 public void RemoveAllMonthlyBoldedDates () {
890 monthly_bolded_dates = null;
891 added_monthly_bolded_dates.Clear ();
892 removed_monthly_bolded_dates.Clear ();
895 // clears the specified annually bolded date (only compares day and month)
896 // only removes the first instance of the match
897 public void RemoveAnnuallyBoldedDate (DateTime date) {
898 if (!removed_annually_bolded_dates.Contains (date.Date)) {
899 removed_annually_bolded_dates.Add (date.Date);
903 // clears all the normal bolded date
904 // only removes the first instance of the match
905 public void RemoveBoldedDate (DateTime date) {
906 if (!removed_bolded_dates.Contains (date.Date)) {
907 removed_bolded_dates.Add (date.Date);
911 // clears the specified monthly bolded date (only compares day and month)
912 // only removes the first instance of the match
913 public void RemoveMonthlyBoldedDate (DateTime date) {
914 if (!removed_monthly_bolded_dates.Contains (date.Date)) {
915 removed_monthly_bolded_dates.Add (date.Date);
919 // sets the calendar_dimensions. If product is > 12, the larger dimension is reduced to make product < 12
920 public void SetCalendarDimensions(int x, int y) {
921 this.CalendarDimensions = new Size(x, y);
924 // sets the currently selected date as date
925 public void SetDate (DateTime date) {
926 this.SetSelectionRange (date.Date, date.Date);
929 // utility method set the SelectionRange property using individual dates
930 public void SetSelectionRange (DateTime date1, DateTime date2) {
931 this.SelectionRange = new SelectionRange(date1, date2);
934 public override string ToString () {
935 return this.GetType().Name + ", " + this.SelectionRange.ToString ();
938 // usually called after an AddBoldedDate method is called
939 // formats monthly and daily bolded dates according to the current calendar year
940 public void UpdateBoldedDates () {
941 UpdateDateArray (ref bolded_dates, added_bolded_dates, removed_bolded_dates);
942 UpdateDateArray (ref monthly_bolded_dates, added_monthly_bolded_dates, removed_monthly_bolded_dates);
943 UpdateDateArray (ref annually_bolded_dates, added_annually_bolded_dates, removed_annually_bolded_dates);
946 #endregion // Public Instance Methods
948 #region Protected Instance Methods
950 // not sure why this needs to be overriden
951 protected override void CreateHandle () {
952 base.CreateHandle ();
955 // not sure why this needs to be overriden
956 protected override void Dispose (bool disposing) {
957 base.Dispose (disposing);
960 // not sure why this needs to be overriden
961 protected override bool IsInputKey (Keys keyData) {
962 return base.IsInputKey (keyData);
965 // not sure why this needs to be overriden
966 protected override void OnBackColorChanged (EventArgs e) {
967 base.OnBackColorChanged (e);
971 // raises the date changed event
972 protected virtual void OnDateChanged (DateRangeEventArgs drevent) {
973 if (this.DateChanged != null) {
974 this.DateChanged (this, drevent);
978 // raises the DateSelected event
979 protected virtual void OnDateSelected (DateRangeEventArgs drevent) {
980 if (this.DateSelected != null) {
981 this.DateSelected (this, drevent);
985 protected override void OnFontChanged (EventArgs e) {
986 base.OnFontChanged (e);
989 protected override void OnForeColorChanged (EventArgs e) {
990 base.OnForeColorChanged (e);
993 protected override void OnHandleCreated (EventArgs e) {
994 base.OnHandleCreated (e);
997 // i think this is overriden to not allow the control to be changed to an arbitrary size
998 protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified) {
999 if ((specified & BoundsSpecified.Height) == BoundsSpecified.Height ||
1000 (specified & BoundsSpecified.Width) == BoundsSpecified.Width ||
1001 (specified & BoundsSpecified.Size) == BoundsSpecified.Size) {
1002 // only allow sizes = default size to be set
1003 Size min_size = DefaultSize;
1004 Size max_size = new Size (
1005 DefaultSize.Width + SingleMonthSize.Width + calendar_spacing.Width,
1006 DefaultSize.Height + SingleMonthSize.Height + calendar_spacing.Height);
1007 int x_mid_point = (max_size.Width + min_size.Width)/2;
1008 int y_mid_point = (max_size.Height + min_size.Height)/2;
1009 if (width < x_mid_point) {
1010 width = min_size.Width;
1012 width = max_size.Width;
1014 if (height < y_mid_point) {
1015 height = min_size.Height;
1017 height = max_size.Height;
1019 base.SetBoundsCore (x, y, width, height, specified);
1021 base.SetBoundsCore (x, y, width, height, specified);
1025 protected override void WndProc (ref Message m) {
1026 base.WndProc (ref m);
1029 #endregion // Protected Instance Methods
1031 #region public events
1033 // fired when the date is changed (either explicitely or implicitely)
1034 // when navigating the month selector
1035 public event DateRangeEventHandler DateChanged;
1037 // fired when the user explicitely clicks on date to select it
1038 public event DateRangeEventHandler DateSelected;
1040 [MonoTODO("Fire BackgroundImageChanged event")]
1042 [EditorBrowsable (EditorBrowsableState.Never)]
1043 public event EventHandler BackgroundImageChanged;
1045 [MonoTODO("Fire Click event")]
1047 [EditorBrowsable (EditorBrowsableState.Never)]
1048 public event EventHandler Click;
1050 [MonoTODO("Fire DoubleClick event")]
1052 [EditorBrowsable (EditorBrowsableState.Never)]
1053 public event EventHandler DoubleClick;
1055 [MonoTODO("Fire ImeModeChanged event")]
1057 [EditorBrowsable (EditorBrowsableState.Never)]
1058 public event EventHandler ImeModeChanged;
1061 [EditorBrowsable (EditorBrowsableState.Never)]
1062 public new event PaintEventHandler Paint;
1064 [MonoTODO("Fire TextChanged event")]
1066 [EditorBrowsable (EditorBrowsableState.Never)]
1067 public event EventHandler TextChanged;
1068 #endregion // public events
1070 #region internal properties
1072 internal DateTime CurrentMonth {
1074 // only interested in if the month (not actual date) has changed
1075 if (value.Month != current_month.Month ||
1076 value.Year != current_month.Year) {
1077 this.SelectionRange = new SelectionRange(
1078 this.SelectionStart.Add(value.Subtract(current_month)),
1079 this.SelectionEnd.Add(value.Subtract(current_month)));
1080 current_month = value;
1081 UpdateBoldedDates();
1086 return current_month;
1090 #endregion // internal properties
1092 #region internal/private methods
1094 // returns the date of the first cell of the specified month grid
1095 internal DateTime GetFirstDateInMonthGrid (DateTime month) {
1096 // convert the first_day_of_week into a DayOfWeekEnum
1097 DayOfWeek first_day = GetDayOfWeek (first_day_of_week);
1098 // find the first day of the month
1099 DateTime first_date_of_month = new DateTime (month.Year, month.Month, 1);
1100 DayOfWeek first_day_of_month = first_date_of_month.DayOfWeek;
1101 // adjust for the starting day of the week
1102 int offset = first_day_of_month - first_day;
1106 return first_date_of_month.AddDays (-1*offset);
1109 // returns the date of the last cell of the specified month grid
1110 internal DateTime GetLastDateInMonthGrid (DateTime month)
\r
1112 DateTime start = GetFirstDateInMonthGrid(month);
1113 return start.AddDays ((7 * 6)-1);
1116 internal bool IsBoldedDate (DateTime date) {
1117 // check bolded dates
1118 if (bolded_dates != null && bolded_dates.Length > 0) {
1119 foreach (DateTime bolded_date in bolded_dates) {
1120 if (bolded_date.Date == date.Date) {
1125 // check monthly dates
1126 if (monthly_bolded_dates != null && monthly_bolded_dates.Length > 0) {
1127 foreach (DateTime bolded_date in monthly_bolded_dates) {
1128 if (bolded_date.Day == date.Day) {
1133 // check yearly dates
1134 if (annually_bolded_dates != null && annually_bolded_dates.Length > 0) {
1135 foreach (DateTime bolded_date in annually_bolded_dates) {
1136 if (bolded_date.Month == date.Month && bolded_date.Day == date.Day) {
1142 return false; // no match
1145 // updates the specified bolded dates array with ones to add and ones to remove
1146 private void UpdateDateArray (ref DateTime [] dates, ArrayList to_add, ArrayList to_remove) {
1147 ArrayList list = new ArrayList ();
1149 // update normal bolded dates
1150 if (dates != null) {
1151 foreach (DateTime date in dates) {
1152 list.Add (date.Date);
1157 foreach (DateTime date in to_add) {
1158 if (!list.Contains (date.Date)) {
1159 list.Add (date.Date);
1164 // remove ones to remove
1165 foreach (DateTime date in to_remove) {
1166 if (list.Contains (date.Date)) {
1167 list.Remove (date.Date);
1171 // set up the array now
1172 if (list.Count > 0) {
1173 dates = (DateTime []) list.ToArray (typeof (DateTime));
1181 // initialise the context menu
1182 private void SetUpContextMenu () {
1183 menu = new ContextMenu ();
1184 for (int i=0; i < 12; i++) {
1185 MenuItem menu_item = new MenuItem ( new DateTime (2000, i+1, 1).ToString ("MMMM"));
1186 menu_item.Click += new EventHandler (MenuItemClickHandler);
1187 menu.MenuItems.Add (menu_item);
1191 // returns the first date of the month
1192 private DateTime GetFirstDateInMonth (DateTime date) {
1193 return new DateTime (date.Year, date.Month, 1);
1196 // returns the last date of the month
1197 private DateTime GetLastDateInMonth (DateTime date) {
1198 return new DateTime (date.Year, date.Month, 1).AddMonths(1).AddDays(-1);
1201 // called in response to users seletion with shift key
1202 private void AddTimeToSelection (int delta, bool isDays)
1204 DateTime cursor_point;
1206 // okay we add the period to the date that is not the same as the
1207 // start date when shift was first clicked.
1208 if (SelectionStart != first_select_start_date) {
1209 cursor_point = SelectionStart;
1211 cursor_point = SelectionEnd;
1215 end_point = cursor_point.AddDays (delta);
1217 // delta must be months
1218 end_point = cursor_point.AddMonths (delta);
1220 // set the new selection range
1221 SelectionRange range = new SelectionRange (first_select_start_date, end_point);
1222 if (range.Start.AddDays (MaxSelectionCount-1) < range.End) {
1223 // okay the date is beyond what is allowed, lets set the maximum we can
1224 if (range.Start != first_select_start_date) {
1225 range.Start = range.End.AddDays ((MaxSelectionCount-1)*-1);
1227 range.End = range.Start.AddDays (MaxSelectionCount-1);
1230 this.SelectionRange = range;
1233 // attempts to add the date to the selection without throwing exception
1234 private void SelectDate (DateTime date) {
1235 // try and add the new date to the selction range
1236 if (is_shift_pressed || (click_state [0])) {
1237 SelectionRange range = new SelectionRange (first_select_start_date, date);
1238 if (range.Start.AddDays (MaxSelectionCount-1) < range.End) {
1239 // okay the date is beyond what is allowed, lets set the maximum we can
1240 if (range.Start != first_select_start_date) {
1241 range.Start = range.End.AddDays ((MaxSelectionCount-1)*-1);
1243 range.End = range.Start.AddDays (MaxSelectionCount-1);
1246 SelectionRange = range;
1248 SelectionRange = new SelectionRange (date, date);
1249 first_select_start_date = date;
1253 // gets the week of the year
1254 internal int GetWeekOfYear (DateTime date) {
1255 // convert the first_day_of_week into a DayOfWeekEnum
1256 DayOfWeek first_day = GetDayOfWeek (first_day_of_week);
1257 // find the first day of the year
1258 DayOfWeek first_day_of_year = new DateTime (date.Year, 1, 1).DayOfWeek;
1259 // adjust for the starting day of the week
1260 int offset = first_day_of_year - first_day;
1261 int week = ((date.DayOfYear + offset) / 7) + 1;
1265 // convert a Day enum into a DayOfWeek enum
1266 internal DayOfWeek GetDayOfWeek (Day day) {
1267 if (day == Day.Default) {
1268 return DayOfWeek.Sunday;
1270 return (DayOfWeek) DayOfWeek.Parse (typeof (DayOfWeek), day.ToString ());
1274 // returns the rectangle for themonth name
1275 internal Rectangle GetMonthNameRectangle (Rectangle title_rect, int calendar_index) {
1276 Graphics g = this.DeviceContext;
1277 DateTime this_month = this.current_month.AddMonths (calendar_index);
1278 Size title_text_size = g.MeasureString (this_month.ToString ("MMMM yyyy"), this.Font).ToSize ();
1279 Size month_size = g.MeasureString (this_month.ToString ("MMMM"), this.Font).ToSize ();
1280 // return only the month name part of that
1281 return new Rectangle (
1283 title_rect.X + ((title_rect.Width - title_text_size.Width)/2),
1284 title_rect.Y + ((title_rect.Height - title_text_size.Height)/2)),
1288 // returns the rectangle for the year in the title
1289 internal Rectangle GetYearNameRectangle (Rectangle title_rect, int calendar_index) {
1291 Graphics g = this.DeviceContext;
1292 DateTime this_month = this.current_month.AddMonths (calendar_index);
1293 Size title_text_size = g.MeasureString (this_month.ToString ("MMMM yyyy"), this.Font).ToSize ();
1294 Size year_size = g.MeasureString (this_month.ToString ("yyyy"), this.Font).ToSize ();
1295 // find out how much space the title took
1296 Rectangle text_rect = new Rectangle (
1298 title_rect.X + ((title_rect.Width - title_text_size.Width)/2),
1299 title_rect.Y + ((title_rect.Height - title_text_size.Height)/2)),
1301 // return only the rect of the year
1302 return new Rectangle (
1304 text_rect.Right - year_size.Width,
1309 // determine if date is allowed to be drawn in month
1310 internal bool IsValidWeekToDraw (DateTime month, DateTime date, int row, int col) {
1311 DateTime tocheck = month.AddMonths (-1);
1312 if ((month.Year == date.Year && month.Month == date.Month) ||
1313 (tocheck.Year == date.Year && tocheck.Month == date.Month)) {
1317 // check the railing dates (days in the month after the last month in grid)
1318 if (row == CalendarDimensions.Height - 1 && col == CalendarDimensions.Width - 1) {
1319 tocheck = month.AddMonths (1);
1320 return (tocheck.Year == date.Year && tocheck.Month == date.Month) ;
1326 // set one item clicked and all others off
1327 private void SetItemClick(HitTestInfo hti)
\r
1329 switch(hti.HitArea) {
1330 case HitArea.NextMonthButton:
1331 this.is_previous_clicked = false;
1332 this.is_next_clicked = true;
1333 this.is_date_clicked = false;
1335 case HitArea.PrevMonthButton:
1336 this.is_previous_clicked = true;
1337 this.is_next_clicked = false;
1338 this.is_date_clicked = false;
1340 case HitArea.PrevMonthDate:
1341 case HitArea.NextMonthDate:
1343 this.clicked_date = hti.Time;
1344 this.is_previous_clicked = false;
1345 this.is_next_clicked = false;
1346 this.is_date_clicked = true;
1349 this.is_previous_clicked = false;
1350 this.is_next_clicked = false;
1351 this.is_date_clicked = false;
1356 // called when context menu is clicked
1357 private void MenuItemClickHandler (object sender, EventArgs e) {
1358 MenuItem item = sender as MenuItem;
1359 if (item != null && month_title_click_location != Point.Empty) {
1360 // establish which month we want to move to
1361 if (item.Parent == null) {
1364 int new_month = item.Parent.MenuItems.IndexOf (item) + 1;
1365 if (new_month == 0) {
1368 // okay let's establish which calendar was hit
1369 Size month_size = this.SingleMonthSize;
1370 for (int i=0; i < CalendarDimensions.Height; i++) {
1371 for (int j=0; j < CalendarDimensions.Width; j++) {
1372 int month_index = (i * CalendarDimensions.Width) + j;
1373 Rectangle month_rect = new Rectangle ( new Point (0, 0), month_size);
1375 month_rect.X = this.ClientRectangle.X + 1;
1377 month_rect.X = this.ClientRectangle.X + 1 + ((j)*(month_size.Width+calendar_spacing.Width));
1380 month_rect.Y = this.ClientRectangle.Y + 1;
1382 month_rect.Y = this.ClientRectangle.Y + 1 + ((i)*(month_size.Height+calendar_spacing.Height));
1384 // see if the point is inside
1385 if (month_rect.Contains (month_title_click_location)) {
1386 DateTime clicked_month = CurrentMonth.AddMonths (month_index);
1387 // get the month that we want to move to
1388 int month_offset = new_month - clicked_month.Month;
1390 // move forward however more months we need to
1391 this.CurrentMonth = this.CurrentMonth.AddMonths (month_offset);
1398 month_title_click_location = Point.Empty;
1402 // raised on the timer, for mouse hold clicks
1403 private void TimerHandler (object sender, EventArgs e) {
1404 // NOTE: i have diabled the if this.Capture because it doesn't work
1405 // when this.Capture works then need to renable the if in this section
1406 // // now find out which area was click
1407 // if (this.Capture) {
1408 HitTestInfo hti = this.HitTest (this.PointToClient (MousePosition));
1409 // see if it was clicked on the prev or next mouse
1410 if (click_state [1] || click_state [2]) {
1411 // invalidate the area where the mouse was last held
1413 Application.DoEvents ();
1414 // register the click
1415 if (hti.HitArea == HitArea.PrevMonthButton ||
1416 hti.HitArea == HitArea.NextMonthButton) {
1417 DoButtonMouseDown (hti);
1418 click_state [1] = (hti.HitArea == HitArea.PrevMonthButton);
1419 click_state [2] = !click_state [1];
1421 if (timer.Interval != 100) {
1422 timer.Interval = 100;
1426 // timer.Enabled = false;
1430 // selects one of the buttons
1431 private void DoButtonMouseDown (HitTestInfo hti) {
1432 // show the click then move on
1434 if (hti.HitArea == HitArea.PrevMonthButton) {
1435 // invalidate the prev monthbutton
1438 this.ClientRectangle.X + 1 + button_x_offset,
1439 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1441 button_size.Height));
1442 this.CurrentMonth = this.CurrentMonth.AddMonths (ScrollChange*-1);
1444 // invalidate the next monthbutton
1447 this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width,
1448 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1450 button_size.Height));
1451 this.CurrentMonth = this.CurrentMonth.AddMonths (ScrollChange);
1455 // selects the clicked date
1456 private void DoDateMouseDown (HitTestInfo hti) {
1458 this.SelectDate (clicked_date);
1459 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1462 // event run on the mouse up event
1463 private void DoMouseUp () {
1464 // invalidate the next monthbutton
1465 if (this.is_next_clicked) {
1468 this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width,
1469 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1471 button_size.Height));
1473 // invalidate the prev monthbutton
1474 if (this.is_previous_clicked) {
1477 this.ClientRectangle.X + 1 + button_x_offset,
1478 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1480 button_size.Height));
1482 if (this.is_date_clicked) {
1483 // invalidate the area under the cursor, to remove focus rect
1484 this.InvalidateDateRange (new SelectionRange (clicked_date, clicked_date));
1486 this.is_previous_clicked = false;
1487 this.is_next_clicked = false;
1488 this.is_date_clicked = false;
1491 // need when in windowed mode
1492 private void LostFocusHandler (object sender, EventArgs e)
1494 if (this.owner != null) {
1496 this.owner.HideMonthCalendar ();
1501 // occurs when mouse moves around control, used for selection
1502 private void MouseMoveHandler (object sender, MouseEventArgs e) {
1503 HitTestInfo hti = this.HitTest (e.X, e.Y);
1504 // clear the last clicked item
1505 if (click_state [0]) {
1506 // register the click
1507 if (hti.HitArea == HitArea.PrevMonthDate ||
1508 hti.HitArea == HitArea.NextMonthDate ||
1509 hti.HitArea == HitArea.Date)
1511 DoDateMouseDown (hti);
1512 if (owner == null) {
1513 click_state [0] = true;
1515 click_state [0] = false;
1516 click_state [1] = false;
1517 click_state [2] = false;
1524 // to check if the mouse has come down on this control
1525 private void MouseDownHandler (object sender, MouseEventArgs e)
1527 // clear the click_state variables
1528 click_state [0] = false;
1529 click_state [1] = false;
1530 click_state [2] = false;
1532 // disable the timer if it was enabled
1533 if (timer.Enabled) {
1535 timer.Enabled = false;
1537 //establish where was hit
1538 HitTestInfo hti = this.HitTest(e.X, e.Y);
1539 switch (hti.HitArea) {
\r
1540 case HitArea.PrevMonthButton:
1541 case HitArea.NextMonthButton:
1542 DoButtonMouseDown (hti);
1543 click_state [1] = (hti.HitArea == HitArea.PrevMonthDate);
1544 click_state [2] = !click_state [1];
1545 timer.Interval = 500;
1549 case HitArea.PrevMonthDate:
1550 case HitArea.NextMonthDate:
1551 DoDateMouseDown (hti);
1552 // leave clicked state blank if drop down window
1553 if (owner == null) {
1554 click_state [0] = true;
1556 click_state [0] = false;
1557 click_state [1] = false;
1558 click_state [2] = false;
1561 case HitArea.TitleMonth:
1562 month_title_click_location = hti.Point;
1563 menu.Show (this, hti.Point);
1565 case HitArea.TitleYear:
1566 //TODO: show the year spin control
1567 System.Console.WriteLine ("//TODO: show the year spin control");
1569 case HitArea.TodayLink:
1570 this.SetSelectionRange (DateTime.Now.Date, DateTime.Now.Date);
1571 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1574 this.is_previous_clicked = false;
1575 this.is_next_clicked = false;
1576 this.is_date_clicked = false;
1581 // raised by any key down events
1582 private void KeyDownHandler (object sender, KeyEventArgs e) {
1583 if (!is_shift_pressed && e.Shift) {
1584 first_select_start_date = SelectionStart;
1585 is_shift_pressed = e.Shift;
1587 switch (e.KeyCode) {
1589 // set the date to the start of the month
1590 if (is_shift_pressed) {
1591 DateTime date = GetFirstDateInMonth (first_select_start_date);
1592 if (date < first_select_start_date.AddDays ((MaxSelectionCount-1)*-1)) {
1593 date = first_select_start_date.AddDays ((MaxSelectionCount-1)*-1);
1595 this.SetSelectionRange (date, first_select_start_date);
1597 DateTime date = GetFirstDateInMonth (this.SelectionStart);
1598 this.SetSelectionRange (date, date);
1600 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1603 // set the date to the last of the month
1604 if (is_shift_pressed) {
1605 DateTime date = GetLastDateInMonth (first_select_start_date);
1606 if (date > first_select_start_date.AddDays (MaxSelectionCount-1)) {
1607 date = first_select_start_date.AddDays (MaxSelectionCount-1);
1609 this.SetSelectionRange (date, first_select_start_date);
1611 DateTime date = GetLastDateInMonth (this.SelectionStart);
1612 this.SetSelectionRange (date, date);
1614 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1617 // set the date to the last of the month
1618 if (is_shift_pressed) {
1619 this.AddTimeToSelection (-1, false);
1621 DateTime date = this.SelectionStart.AddMonths (-1);
1622 this.SetSelectionRange (date, date);
1624 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1627 // set the date to the last of the month
1628 if (is_shift_pressed) {
1629 this.AddTimeToSelection (1, false);
1631 DateTime date = this.SelectionStart.AddMonths (1);
1632 this.SetSelectionRange (date, date);
1634 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1637 // set the back 1 week
1638 if (is_shift_pressed) {
1639 this.AddTimeToSelection (-7, true);
1641 DateTime date = this.SelectionStart.AddDays (-7);
1642 this.SetSelectionRange (date, date);
1644 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1647 // set the date forward 1 week
1648 if (is_shift_pressed) {
1649 this.AddTimeToSelection (7, true);
1651 DateTime date = this.SelectionStart.AddDays (7);
1652 this.SetSelectionRange (date, date);
1654 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1658 if (is_shift_pressed) {
1659 this.AddTimeToSelection (-1, true);
1661 DateTime date = this.SelectionStart.AddDays (-1);
1662 this.SetSelectionRange (date, date);
1664 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1668 if (is_shift_pressed) {
1669 this.AddTimeToSelection (1, true);
1671 DateTime date = this.SelectionStart.AddDays (1);
1672 this.SetSelectionRange (date, date);
1674 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1682 // to check if the mouse has come up on this control
1683 private void MouseUpHandler (object sender, MouseEventArgs e)
1685 if (timer.Enabled) {
1688 // clear the click state array
1689 click_state [0] = false;
1690 click_state [1] = false;
1691 click_state [2] = false;
1692 // do the regulare mouseup stuff
1696 // raised by any key up events
1697 private void KeyUpHandler (object sender, KeyEventArgs e) {
1698 is_shift_pressed = e.Shift ;
1702 // paint this control now
1703 private void PaintHandler (object sender, PaintEventArgs pe) {
1704 if (Width <= 0 || Height <= 0 || Visible == false)
1707 Draw (pe.ClipRectangle);
1708 pe.Graphics.DrawImage (ImageBuffer, 0, 0);
1710 // fire the new paint handler
1711 if (this.Paint != null)
1713 this.Paint (sender, pe);
1717 // returns the region of the control that needs to be redrawn
1718 private void InvalidateDateRange (SelectionRange range) {
1719 SelectionRange bounds = this.GetDisplayRange (false);
1720 if (range.End < bounds.Start || range.Start > bounds.End) {
1721 // don't invalidate anything, as the modified date range
1722 // is outside the visible bounds of this control
1725 // adjust the start and end to be inside the visible range
1726 if (range.Start < bounds.Start) {
1727 range = new SelectionRange (bounds.Start, range.End);
1729 if (range.End > bounds.End) {
1730 range = new SelectionRange (range.Start, bounds.End);
1732 // now invalidate the date rectangles as series of rows
1733 DateTime last_month = this.current_month.AddMonths ((CalendarDimensions.Width * CalendarDimensions.Height)).AddDays (-1);
1734 DateTime current = range.Start;
1735 while (current <= range.End) {
1736 DateTime month_end = new DateTime (current.Year, current.Month, 1).AddMonths (1).AddDays (-1);;
1737 Rectangle start_rect;
1739 // see if entire selection is in this current month
1740 if (range.End <= month_end && current < last_month) {
1741 // the end is the last date
1742 if (current < this.current_month) {
1743 start_rect = GetDateRowRect (current_month, current_month);
1745 start_rect = GetDateRowRect (current, current);
1747 end_rect = GetDateRowRect (current, range.End);
1748 } else if (current < last_month) {
1749 // otherwise it simply means we have a selection spaning
1750 // multiple months simply set rectangle inside the current month
1751 start_rect = GetDateRowRect (current, current);
1752 end_rect = GetDateRowRect (last_month, month_end);
1754 // it's outside the visible range
1755 start_rect = GetDateRowRect (last_month, last_month.AddDays (1));
1756 end_rect = GetDateRowRect (last_month, range.End);
1758 // push to the next month
1759 current = month_end.AddDays (1);
1760 // invalidate from the start row to the end row for this month
1766 Math.Max (end_rect.Bottom - start_rect.Y, 0)));
1770 // gets the rect of the row where the specified date appears on the specified month
1771 private Rectangle GetDateRowRect (DateTime month, DateTime date) {
1772 // first get the general rect of the supplied month
1773 Size month_size = SingleMonthSize;
1774 Rectangle month_rect = Rectangle.Empty;
1775 for (int i=0; i < CalendarDimensions.Width*CalendarDimensions.Height; i++) {
1776 DateTime this_month = this.current_month.AddMonths (i);
1777 if (month.Year == this_month.Year && month.Month == this_month.Month) {
1778 month_rect = new Rectangle (
1779 this.ClientRectangle.X + 1 + (month_size.Width * (i%CalendarDimensions.Width)) + (this.calendar_spacing.Width * (i%CalendarDimensions.Width)),
1780 this.ClientRectangle.Y + 1 + (month_size.Height * (i/CalendarDimensions.Width)) + (this.calendar_spacing.Height * (i/CalendarDimensions.Width)),
1786 // now find out where in the month the supplied date is
1787 if (month_rect == Rectangle.Empty) {
1788 return Rectangle.Empty;
1790 // find out which row this date is in
1792 DateTime first_date = GetFirstDateInMonthGrid (month);
1793 DateTime end_date = first_date.AddDays (7);
1794 for (int i=0; i < 6; i++) {
1795 if (date >= first_date && date < end_date) {
1799 first_date = end_date;
1800 end_date = end_date.AddDays (7);
1802 // ensure it's a valid row
1804 return Rectangle.Empty;
1806 int x_offset = (this.ShowWeekNumbers) ? date_cell_size.Width : 0;
1807 int y_offset = title_size.Height + (date_cell_size.Height * (row + 1));
1808 return new Rectangle (
1809 month_rect.X + x_offset,
1810 month_rect.Y + y_offset,
1811 date_cell_size.Width * 7,
1812 date_cell_size.Height);
1815 internal void Draw (Rectangle clip_rect)
1817 ThemeEngine.Current.DrawMonthCalendar(DeviceContext, clip_rect, this);
1820 #endregion //internal methods
1822 #region internal drawing methods
1825 #endregion // internal drawing methods
1827 #region inner classes and enumerations
1829 // enumeration about what type of area on the calendar was hit
1830 public enum HitArea {
1846 // info regarding to a hit test on this calendar
1847 public sealed class HitTestInfo {
1849 private HitArea hit_area;
1850 private Point point;
1851 private DateTime time;
1853 // default constructor
1854 internal HitTestInfo () {
1855 hit_area = HitArea.Nowhere;
1856 point = new Point (0, 0);
1857 time = DateTime.Now;
1860 // overload receives all properties
1861 internal HitTestInfo (HitArea hit_area, Point point, DateTime time) {
1862 this.hit_area = hit_area;
1867 // the type of area that was hit
1868 public HitArea HitArea {
1874 // the point that is being test
1875 public Point Point {
1881 // the date under the hit test point, only valid if HitArea is Date
1882 public DateTime Time {
1889 #endregion // inner classes