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
.Windows
.Forms
;
35 namespace System
.Windows
.Forms
{
36 [DefaultProperty("SelectionRange")]
37 [DefaultEvent("DateChanged")]
38 [Designer ("System.Windows.Forms.Design.MonthCalendarDesigner, " + Consts
.AssemblySystem_Design
, "System.ComponentModel.Design.IDesigner")]
39 public class MonthCalendar
: Control
{
40 #region Local variables
41 DateTime
[] annually_bolded_dates
;
43 DateTime
[] bolded_dates
;
44 Size calendar_dimensions
;
45 Day first_day_of_week
;
48 int max_selection_count
;
50 DateTime
[] monthly_bolded_dates
;
52 SelectionRange selection_range
;
54 bool show_today_circle
;
55 bool show_week_numbers
;
56 Color title_back_color
;
57 Color title_fore_color
;
60 Color trailing_fore_color
;
62 NumericUpDown year_updown
;
65 bool showing_context_menu
;
66 bool updown_has_focus
;
68 // internal variables used
69 internal DateTime current_month
; // the month that is being displayed in top left corner of the grid
70 internal DateTimePicker owner
; // used if this control is popped up
71 internal int button_x_offset
;
72 internal Size button_size
;
73 internal Size title_size
;
74 internal Size date_cell_size
;
75 internal Size calendar_spacing
;
76 internal int divider_line_offset
;
77 internal DateTime clicked_date
;
78 internal Rectangle clicked_rect
;
79 internal bool is_date_clicked
;
80 internal bool is_previous_clicked
;
81 internal bool is_next_clicked
;
82 internal bool is_shift_pressed
;
83 internal DateTime first_select_start_date
;
84 internal int last_clicked_calendar_index
;
85 internal Rectangle last_clicked_calendar_rect
;
86 internal Font bold_font
; // Cache the font in FontStyle.Bold
87 internal StringFormat centered_format
; // Cache centered string format
88 private Point month_title_click_location
;
89 // this is used to see which item was actually clicked on in the beginning
90 // so that we know which item to fire on timer
92 // 1: previous clicked
94 private bool[] click_state
;
96 // arraylists used to store new dates
97 ArrayList added_bolded_dates
;
98 ArrayList removed_bolded_dates
;
99 ArrayList added_annually_bolded_dates
;
100 ArrayList removed_annually_bolded_dates
;
101 ArrayList added_monthly_bolded_dates
;
102 ArrayList removed_monthly_bolded_dates
;
105 #endregion // Local variables
107 #region Public Constructors
109 public MonthCalendar () {
110 // set up the control painting
111 SetStyle (ControlStyles
.UserPaint
| ControlStyles
.StandardClick
, false);
114 timer
= new Timer ();
115 timer
.Interval
= 500;
116 timer
.Enabled
= false;
118 // initialise default values
119 DateTime now
= DateTime
.Now
.Date
;
120 selection_range
= new SelectionRange (now
, now
);
122 current_month
= new DateTime (now
.Year
, now
.Month
, 1);
124 // iniatialise local members
125 annually_bolded_dates
= null;
126 back_color
= ThemeEngine
.Current
.ColorWindow
;
128 calendar_dimensions
= new Size (1,1);
129 first_day_of_week
= Day
.Default
;
130 fore_color
= SystemColors
.ControlText
;
131 max_date
= new DateTime (9998, 12, 31);
132 max_selection_count
= 7;
133 min_date
= new DateTime (1953, 1, 1);
134 monthly_bolded_dates
= null;
137 show_today_circle
= true;
138 show_week_numbers
= false;
139 title_back_color
= ThemeEngine
.Current
.ColorActiveCaption
;
140 title_fore_color
= ThemeEngine
.Current
.ColorActiveCaptionText
;
141 today_date_set
= false;
142 trailing_fore_color
= Color
.Gray
;
143 bold_font
= new Font (Font
, Font
.Style
| FontStyle
.Bold
);
144 centered_format
= new StringFormat ();
145 centered_format
.LineAlignment
= StringAlignment
.Center
;
146 centered_format
.Alignment
= StringAlignment
.Center
;
148 // initialise the arraylest for bolded dates
149 added_bolded_dates
= new ArrayList ();
150 removed_bolded_dates
= new ArrayList ();
151 added_annually_bolded_dates
= new ArrayList ();
152 removed_annually_bolded_dates
= new ArrayList ();
153 added_monthly_bolded_dates
= new ArrayList ();
154 removed_monthly_bolded_dates
= new ArrayList ();
156 // intiailise internal variables used
158 button_size
= new Size (22, 17);
159 // default settings based on 8.25 pt San Serif Font
160 // Not sure of algorithm used to establish this
161 date_cell_size
= new Size (24, 16); // default size at san-serif 8.25
162 divider_line_offset
= 4;
163 calendar_spacing
= new Size (4, 5); // horiz and vert spacing between months in a calendar grid
165 // set some state info
167 is_date_clicked
= false;
168 is_previous_clicked
= false;
169 is_next_clicked
= false;
170 is_shift_pressed
= false;
171 click_state
= new bool [] {false, false, false}
;
172 first_select_start_date
= now
;
173 month_title_click_location
= Point
.Empty
;
175 // set up context menu
180 LostFocus
+= new EventHandler (LostFocusHandler
);
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
);
192 // called when this control is added to date time picker
193 internal MonthCalendar (DateTimePicker owner
) : this () {
195 this.is_visible
= false;
196 this.Size
= this.DefaultSize
;
199 #endregion // Public Constructors
201 #region Public Instance Properties
203 // dates to make bold on calendar annually (recurring)
205 public DateTime
[] AnnuallyBoldedDates
{
207 if (annually_bolded_dates
== null || annually_bolded_dates
!= value) {
208 annually_bolded_dates
= value;
209 this.UpdateBoldedDates ();
214 return annually_bolded_dates
;
219 [EditorBrowsable(EditorBrowsableState
.Never
)]
220 public override Image BackgroundImage
{
222 return base.BackgroundImage
;
225 base.BackgroundImage
= value;
230 // the back color for the main part of the calendar
231 public override Color BackColor
{
233 if (back_color
!= value) {
235 this.OnBackColorChanged (EventArgs
.Empty
);
244 // specific dates to make bold on calendar (non-recurring)
246 public DateTime
[] BoldedDates
{
248 if (bolded_dates
== null || bolded_dates
!= value) {
249 bolded_dates
= value;
250 this.UpdateBoldedDates ();
259 // the configuration of the monthly grid display - only allowed to display at most,
260 // 1 calendar year at a time, will be trimmed to fit it properly
262 public Size CalendarDimensions
{
264 if (value.Width
< 0 || value.Height
< 0) {
265 throw new ArgumentException ();
267 if (calendar_dimensions
!= value) {
268 // squeeze the grid into 1 calendar year
269 if (value.Width
* value.Height
> 12) {
270 // iteratively reduce the largest dimension till our
271 // product is less than 12
272 if (value.Width
> 12 && value.Height
> 12) {
273 calendar_dimensions
= new Size (4, 3);
274 } else if (value.Width
> 12) {
275 for (int i
= 12; i
> 0; i
--) {
276 if (i
* value.Height
<= 12) {
277 calendar_dimensions
= new Size (i
, value.Height
);
281 } else if (value.Height
> 12) {
282 for (int i
= 12; i
> 0; i
--) {
283 if (i
* value.Width
<= 12) {
284 calendar_dimensions
= new Size (value.Width
, i
);
290 calendar_dimensions
= value;
296 return calendar_dimensions
;
300 // the first day of the week to display
302 [DefaultValue (Day
.Default
)]
303 public Day FirstDayOfWeek
{
305 if (first_day_of_week
!= value) {
306 first_day_of_week
= value;
311 return first_day_of_week
;
315 // the fore color for the main part of the calendar
316 public override Color ForeColor
{
318 if (fore_color
!= value) {
320 this.OnForeColorChanged (EventArgs
.Empty
);
330 [EditorBrowsable(EditorBrowsableState
.Never
)]
331 public new ImeMode ImeMode
{
332 get { return base.ImeMode; }
333 set { base.ImeMode = value; }
336 // the maximum date allowed to be selected on this month calendar
337 public DateTime MaxDate
{
339 if (value < MinDate
) {
340 throw new ArgumentException();
343 if (max_date
!= value) {
352 // the maximum number of selectable days
354 public int MaxSelectionCount
{
357 throw new ArgumentException();
360 // can't set selectioncount less than already selected dates
361 if ((SelectionEnd
- SelectionStart
).Days
> value) {
362 throw new ArgumentException();
365 if (max_selection_count
!= value) {
366 max_selection_count
= value;
370 return max_selection_count
;
374 // the minimum date allowed to be selected on this month calendar
375 public DateTime MinDate
{
377 if (value < new DateTime (1953, 1, 1)) {
378 throw new ArgumentException();
381 if (value > MaxDate
) {
382 throw new ArgumentException();
385 if (max_date
!= value) {
394 // dates to make bold on calendar monthly (recurring)
396 public DateTime
[] MonthlyBoldedDates
{
398 if (monthly_bolded_dates
== null || monthly_bolded_dates
!= value) {
399 monthly_bolded_dates
= value;
400 this.UpdateBoldedDates ();
405 return monthly_bolded_dates
;
409 // the ammount by which to scroll this calendar by
411 public int ScrollChange
{
413 if (value < 0 || value > 20000) {
414 throw new ArgumentException();
417 if (scroll_change
!= value) {
418 scroll_change
= value;
422 // if zero it to the default -> the total number of months currently visible
423 if (scroll_change
== 0) {
424 return CalendarDimensions
.Width
* CalendarDimensions
.Height
;
426 return scroll_change
;
431 // the last selected date
433 [DesignerSerializationVisibility (DesignerSerializationVisibility
.Hidden
)]
434 public DateTime SelectionEnd
{
436 if (value < MinDate
|| value > MaxDate
) {
437 throw new ArgumentException();
440 if (SelectionRange
.End
!= value) {
441 DateTime old_end
= SelectionRange
.End
;
442 // make sure the end obeys the max selection range count
443 if (value < SelectionRange
.Start
) {
444 SelectionRange
.Start
= value;
446 if (value.AddDays((MaxSelectionCount
-1)*-1) > SelectionRange
.Start
) {
447 SelectionRange
.Start
= value.AddDays((MaxSelectionCount
-1)*-1);
449 SelectionRange
.End
= value;
450 this.InvalidateDateRange (new SelectionRange (old_end
, SelectionRange
.End
));
451 this.OnDateChanged (new DateRangeEventArgs (SelectionStart
, SelectionEnd
));
455 return SelectionRange
.End
;
459 // the range of selected dates
460 public SelectionRange SelectionRange
{
462 if (selection_range
!= value) {
463 SelectionRange old_range
= selection_range
;
465 // make sure the end obeys the max selection range count
466 if (value.End
.AddDays((MaxSelectionCount
-1)*-1) > value.Start
) {
467 selection_range
= new SelectionRange (value.End
.AddDays((MaxSelectionCount
-1)*-1), value.End
);
469 selection_range
= value;
471 SelectionRange visible_range
= this.GetDisplayRange(true);
472 if(visible_range
.Start
> selection_range
.End
) {
473 this.current_month
= new DateTime (selection_range
.Start
.Year
, selection_range
.Start
.Month
, 1);
475 } else if (visible_range
.End
< selection_range
.Start
) {
476 int year_diff
= selection_range
.End
.Year
- visible_range
.End
.Year
;
477 int month_diff
= selection_range
.End
.Month
- visible_range
.End
.Month
;
478 this.current_month
= current_month
.AddMonths(year_diff
* 12 + month_diff
);
481 // invalidate the selected range changes
482 DateTime diff_start
= old_range
.Start
;
483 DateTime diff_end
= old_range
.End
;
484 // now decide which region is greated
485 if (old_range
.Start
> SelectionRange
.Start
) {
486 diff_start
= SelectionRange
.Start
;
487 } else if (old_range
.Start
== SelectionRange
.Start
) {
488 if (old_range
.End
< SelectionRange
.End
) {
489 diff_start
= old_range
.End
;
491 diff_start
= SelectionRange
.End
;
494 if (old_range
.End
< SelectionRange
.End
) {
495 diff_end
= SelectionRange
.End
;
496 } else if (old_range
.End
== SelectionRange
.End
) {
497 if (old_range
.Start
< SelectionRange
.Start
) {
498 diff_end
= SelectionRange
.Start
;
500 diff_end
= old_range
.Start
;
505 // invalidate the region required
506 SelectionRange new_range
= new SelectionRange (diff_start
, diff_end
);
507 if (new_range
.End
!= old_range
.End
|| new_range
.Start
!= old_range
.Start
)
508 this.InvalidateDateRange (new_range
);
509 // raise date changed event
510 this.OnDateChanged (new DateRangeEventArgs (SelectionStart
, SelectionEnd
));
514 return selection_range
;
518 // the first selected date
520 [DesignerSerializationVisibility (DesignerSerializationVisibility
.Hidden
)]
521 public DateTime SelectionStart
{
523 if (value < MinDate
|| value > MaxDate
) {
524 throw new ArgumentException();
527 if (SelectionRange
.Start
!= value) {
528 DateTime old_start
= SelectionRange
.Start
;
529 // make sure the end obeys the max selection range count
530 if (value > SelectionRange
.End
) {
531 SelectionRange
.End
= value;
532 } else if (value.AddDays(MaxSelectionCount
-1) < SelectionRange
.End
) {
533 SelectionRange
.End
= value.AddDays(MaxSelectionCount
-1);
535 SelectionRange
.Start
= value;
536 DateTime new_month
= new DateTime(value.Year
, value.Month
, 1);
537 if (current_month
!= new_month
) {
538 current_month
= new_month
;
541 this.InvalidateDateRange (new SelectionRange (old_start
, SelectionRange
.Start
));
543 this.OnDateChanged (new DateRangeEventArgs (SelectionStart
, SelectionEnd
));
547 return selection_range
.Start
;
551 // whether or not to show todays date
552 [DefaultValue (true)]
553 public bool ShowToday
{
555 if (show_today
!= value) {
565 // whether or not to show a circle around todays date
566 [DefaultValue (true)]
567 public bool ShowTodayCircle
{
569 if (show_today_circle
!= value) {
570 show_today_circle
= value;
575 return show_today_circle
;
579 // whether or not to show numbers beside each row of weeks
581 [DefaultValue (false)]
582 public bool ShowWeekNumbers
{
584 if (show_week_numbers
!= value) {
585 show_week_numbers
= value;
590 return show_week_numbers
;
594 // the rectangle size required to render one month based on current font
596 [DesignerSerializationVisibility (DesignerSerializationVisibility
.Hidden
)]
597 public Size SingleMonthSize
{
599 if (this.Font
== null) {
600 throw new InvalidOperationException();
603 // multiplier is sucked out from the font size
604 int multiplier
= this.Font
.Height
;
606 // establis how many columns and rows we have
607 int column_count
= (ShowWeekNumbers
) ? 8 : 7;
608 int row_count
= 7; // not including the today date
610 // set the date_cell_size and the title_size
611 date_cell_size
= new Size ((int) Math
.Ceiling (1.8 * multiplier
), multiplier
);
612 title_size
= new Size ((date_cell_size
.Width
* column_count
), 2 * multiplier
);
614 return new Size (column_count
* date_cell_size
.Width
, row_count
* date_cell_size
.Height
+ title_size
.Height
);
620 [DesignerSerializationVisibility(DesignerSerializationVisibility
.Hidden
)]
621 [EditorBrowsable(EditorBrowsableState
.Never
)]
622 public override string Text
{
631 // the back color for the title of the calendar and the
632 // forecolor for the day of the week text
633 public Color TitleBackColor
{
635 if (title_back_color
!= value) {
636 title_back_color
= value;
641 return title_back_color
;
645 // the fore color for the title of the calendar
646 public Color TitleForeColor
{
648 if (title_fore_color
!= value) {
649 title_fore_color
= value;
654 return title_fore_color
;
658 // the date this calendar is using to refer to today's date
659 public DateTime TodayDate
{
661 today_date_set
= true;
662 if (today_date
!= value) {
672 // tells if user specifically set today_date for this control
674 [DesignerSerializationVisibility (DesignerSerializationVisibility
.Hidden
)]
675 public bool TodayDateSet
{
677 return today_date_set
;
681 // the color used for trailing dates in the calendar
682 public Color TrailingForeColor
{
684 if (trailing_fore_color
!= value) {
685 trailing_fore_color
= value;
686 SelectionRange bounds
= this.GetDisplayRange (false);
687 SelectionRange visible_bounds
= this.GetDisplayRange (true);
688 this.InvalidateDateRange (new SelectionRange (bounds
.Start
, visible_bounds
.Start
));
689 this.InvalidateDateRange (new SelectionRange (bounds
.End
, visible_bounds
.End
));
693 return trailing_fore_color
;
697 #endregion // Public Instance Properties
699 #region Protected Instance Properties
701 // overloaded to allow controll to be windowed for drop down
702 protected override CreateParams CreateParams
{
704 if (this.owner
== null) {
705 return base.CreateParams
;
707 CreateParams cp
= base.CreateParams
;
708 cp
.Style ^
= (int) WindowStyles
.WS_CHILD
;
709 cp
.Style
|= (int) WindowStyles
.WS_POPUP
;
710 cp
.ExStyle
|= (int)(WindowExStyles
.WS_EX_TOOLWINDOW
| WindowExStyles
.WS_EX_TOPMOST
);
717 // not sure what to put in here - just doing a base() call - jba
718 protected override ImeMode DefaultImeMode
{
720 return base.DefaultImeMode
;
724 protected override Size DefaultSize
{
726 Size single_month
= SingleMonthSize
;
728 int width
= calendar_dimensions
.Width
* single_month
.Width
;
729 if (calendar_dimensions
.Width
> 1) {
730 width
+= (calendar_dimensions
.Width
- 1) * calendar_spacing
.Width
;
734 int height
= calendar_dimensions
.Height
* single_month
.Height
;
735 if (this.ShowToday
) {
736 height
+= date_cell_size
.Height
+ 2; // add the height of the "Today: " ...
738 if (calendar_dimensions
.Height
> 1) {
739 height
+= (calendar_dimensions
.Height
- 1) * calendar_spacing
.Height
;
742 // add the 1 pixel boundary
750 return new Size (width
, height
);
754 #endregion // Protected Instance Properties
756 #region Public Instance Methods
758 // add a date to the anually bolded date arraylist
759 public void AddAnnuallyBoldedDate (DateTime date
) {
760 added_annually_bolded_dates
.Add (date
.Date
);
763 // add a date to the normal bolded date arraylist
764 public void AddBoldedDate (DateTime date
) {
765 added_bolded_dates
.Add (date
.Date
);
768 // add a date to the anually monthly date arraylist
769 public void AddMonthlyBoldedDate (DateTime date
) {
770 added_monthly_bolded_dates
.Add (date
.Date
);
773 // if visible = true, return only the dates of full months, else return all dates visible
774 public SelectionRange
GetDisplayRange (bool visible
) {
777 start
= new DateTime (current_month
.Year
, current_month
.Month
, 1);
778 end
= start
.AddMonths (calendar_dimensions
.Width
* calendar_dimensions
.Height
);
779 end
= end
.AddDays(-1);
781 // process all visible dates if needed (including the grayed out dates
783 start
= GetFirstDateInMonthGrid (start
);
784 end
= GetLastDateInMonthGrid (end
);
787 return new SelectionRange (start
, end
);
790 // HitTest overload that recieve's x and y co-ordinates as separate ints
791 public HitTestInfo
HitTest (int x
, int y
) {
792 return HitTest (new Point (x
, y
));
795 // returns a HitTestInfo for MonthCalendar element's under the specified point
796 public HitTestInfo
HitTest (Point point
) {
797 return HitTest (point
, out last_clicked_calendar_index
, out last_clicked_calendar_rect
);
800 // clears all the annually bolded dates
801 public void RemoveAllAnnuallyBoldedDates () {
802 annually_bolded_dates
= null;
803 added_annually_bolded_dates
.Clear ();
804 removed_annually_bolded_dates
.Clear ();
807 // clears all the normal bolded dates
808 public void RemoveAllBoldedDates () {
810 added_bolded_dates
.Clear ();
811 removed_bolded_dates
.Clear ();
814 // clears all the monthly bolded dates
815 public void RemoveAllMonthlyBoldedDates () {
816 monthly_bolded_dates
= null;
817 added_monthly_bolded_dates
.Clear ();
818 removed_monthly_bolded_dates
.Clear ();
821 // clears the specified annually bolded date (only compares day and month)
822 // only removes the first instance of the match
823 public void RemoveAnnuallyBoldedDate (DateTime date
) {
824 if (!removed_annually_bolded_dates
.Contains (date
.Date
)) {
825 removed_annually_bolded_dates
.Add (date
.Date
);
829 // clears all the normal bolded date
830 // only removes the first instance of the match
831 public void RemoveBoldedDate (DateTime date
) {
832 if (!removed_bolded_dates
.Contains (date
.Date
)) {
833 removed_bolded_dates
.Add (date
.Date
);
837 // clears the specified monthly bolded date (only compares day and month)
838 // only removes the first instance of the match
839 public void RemoveMonthlyBoldedDate (DateTime date
) {
840 if (!removed_monthly_bolded_dates
.Contains (date
.Date
)) {
841 removed_monthly_bolded_dates
.Add (date
.Date
);
845 // sets the calendar_dimensions. If product is > 12, the larger dimension is reduced to make product < 12
846 public void SetCalendarDimensions(int x
, int y
) {
847 this.CalendarDimensions
= new Size(x
, y
);
850 // sets the currently selected date as date
851 public void SetDate (DateTime date
) {
852 this.SetSelectionRange (date
.Date
, date
.Date
);
855 // utility method set the SelectionRange property using individual dates
856 public void SetSelectionRange (DateTime date1
, DateTime date2
) {
857 this.SelectionRange
= new SelectionRange(date1
, date2
);
860 public override string ToString () {
861 return this.GetType().Name
+ ", " + this.SelectionRange
.ToString ();
864 // usually called after an AddBoldedDate method is called
865 // formats monthly and daily bolded dates according to the current calendar year
866 public void UpdateBoldedDates () {
867 UpdateDateArray (ref bolded_dates
, added_bolded_dates
, removed_bolded_dates
);
868 UpdateDateArray (ref monthly_bolded_dates
, added_monthly_bolded_dates
, removed_monthly_bolded_dates
);
869 UpdateDateArray (ref annually_bolded_dates
, added_annually_bolded_dates
, removed_annually_bolded_dates
);
872 #endregion // Public Instance Methods
874 #region Protected Instance Methods
876 // not sure why this needs to be overriden
877 protected override void CreateHandle () {
878 base.CreateHandle ();
881 private void CreateYearUpDown ()
883 year_updown
= new NumericUpDown ();
884 year_updown
.Font
= this.Font
;
885 year_updown
.Minimum
= MinDate
.Year
;
886 year_updown
.Maximum
= MaxDate
.Year
;
887 year_updown
.ReadOnly
= true;
888 year_updown
.Visible
= false;
889 this.Controls
.AddImplicit (year_updown
);
890 year_updown
.ValueChanged
+= new EventHandler(UpDownYearChangedHandler
);
891 year_updown
.GotFocus
+= new EventHandler(UpDownYearGotFocusHandler
);
892 year_updown
.LostFocus
+= new EventHandler(UpDownYearLostFocusHandler
);
895 // not sure why this needs to be overriden
896 protected override void Dispose (bool disposing
) {
897 base.Dispose (disposing
);
901 protected override bool IsInputKey (Keys keyData
) {
912 return base.IsInputKey (keyData
);
915 // not sure why this needs to be overriden
916 protected override void OnBackColorChanged (EventArgs e
) {
917 base.OnBackColorChanged (e
);
921 // raises the date changed event
922 protected virtual void OnDateChanged (DateRangeEventArgs drevent
) {
923 EventHandler eh
= (EventHandler
)(Events
[DateChangedEvent
]);
928 // raises the DateSelected event
929 protected virtual void OnDateSelected (DateRangeEventArgs drevent
) {
930 EventHandler eh
= (EventHandler
)(Events
[DateSelectedEvent
]);
935 protected override void OnFontChanged (EventArgs e
) {
936 // Update size based on new font's space requirements
937 Size
= new Size (CalendarDimensions
.Width
* SingleMonthSize
.Width
,
938 CalendarDimensions
.Height
* SingleMonthSize
.Height
);
939 bold_font
= new Font (Font
, Font
.Style
| FontStyle
.Bold
);
940 base.OnFontChanged (e
);
943 protected override void OnForeColorChanged (EventArgs e
) {
944 base.OnForeColorChanged (e
);
947 protected override void OnHandleCreated (EventArgs e
) {
948 base.OnHandleCreated (e
);
952 // i think this is overriden to not allow the control to be changed to an arbitrary size
953 protected override void SetBoundsCore (int x
, int y
, int width
, int height
, BoundsSpecified specified
) {
954 if ((specified
& BoundsSpecified
.Height
) == BoundsSpecified
.Height
||
955 (specified
& BoundsSpecified
.Width
) == BoundsSpecified
.Width
||
956 (specified
& BoundsSpecified
.Size
) == BoundsSpecified
.Size
) {
957 // only allow sizes = default size to be set
958 Size min_size
= DefaultSize
;
959 Size max_size
= new Size (
960 DefaultSize
.Width
+ SingleMonthSize
.Width
+ calendar_spacing
.Width
,
961 DefaultSize
.Height
+ SingleMonthSize
.Height
+ calendar_spacing
.Height
);
962 int x_mid_point
= (max_size
.Width
+ min_size
.Width
)/2;
963 int y_mid_point
= (max_size
.Height
+ min_size
.Height
)/2;
964 if (width
< x_mid_point
) {
965 width
= min_size
.Width
;
967 width
= max_size
.Width
;
969 if (height
< y_mid_point
) {
970 height
= min_size
.Height
;
972 height
= max_size
.Height
;
974 base.SetBoundsCore (x
, y
, width
, height
, specified
);
976 base.SetBoundsCore (x
, y
, width
, height
, specified
);
980 protected override void WndProc (ref Message m
) {
981 base.WndProc (ref m
);
984 #endregion // Protected Instance Methods
986 #region public events
987 static object DateChangedEvent
= new object ();
988 static object DateSelectedEvent
= new object ();
990 // fired when the date is changed (either explicitely or implicitely)
991 // when navigating the month selector
992 public event DateRangeEventHandler DateChanged
{
993 add { Events.AddHandler (DateChangedEvent, value); }
994 remove { Events.RemoveHandler (DateChangedEvent, value); }
997 // fired when the user explicitely clicks on date to select it
998 public event DateRangeEventHandler DateSelected
{
999 add { Events.AddHandler (DateSelectedEvent, value); }
1000 remove { Events.RemoveHandler (DateSelectedEvent, value); }
1004 [EditorBrowsable (EditorBrowsableState
.Never
)]
1005 public new event EventHandler BackgroundImageChanged
{
1006 add { base.BackgroundImageChanged += value; }
1007 remove { base.BackgroundImageChanged -= value; }
1010 // this event is overridden to supress it from being fired
1011 // XXX check this out
1013 [EditorBrowsable (EditorBrowsableState
.Never
)]
1014 public new event EventHandler Click
;
1016 // this event is overridden to supress it from being fired
1017 // XXX check this out
1019 [EditorBrowsable (EditorBrowsableState
.Never
)]
1020 public new event EventHandler DoubleClick
;
1023 [EditorBrowsable (EditorBrowsableState
.Never
)]
1024 public new event EventHandler ImeModeChanged
{
1025 add { base.ImeModeChanged += value; }
1026 remove { base.ImeModeChanged -= value; }
1029 // XXX check this out
1031 [EditorBrowsable (EditorBrowsableState
.Never
)]
1032 public new event PaintEventHandler Paint
;
1035 [EditorBrowsable (EditorBrowsableState
.Never
)]
1036 public new event EventHandler TextChanged
{
1037 add { base.TextChanged += value; }
1038 remove { base.TextChanged -= value; }
1040 #endregion // public events
1042 #region internal properties
1044 internal DateTime CurrentMonth
{
1046 // only interested in if the month (not actual date) has changed
1047 if (value.Month
!= current_month
.Month
||
1048 value.Year
!= current_month
.Year
) {
1049 this.SelectionRange
= new SelectionRange(
1050 this.SelectionStart
.Add(value.Subtract(current_month
)),
1051 this.SelectionEnd
.Add(value.Subtract(current_month
)));
1052 current_month
= value;
1053 UpdateBoldedDates();
1058 return current_month
;
1062 #endregion // internal properties
1064 #region internal/private methods
1065 internal HitTestInfo
HitTest (
1067 out int calendar_index
,
1068 out Rectangle calendar_rect
) {
1069 // start by initialising the ref parameters
1070 calendar_index
= -1;
1071 calendar_rect
= Rectangle
.Empty
;
1073 // before doing all the hard work, see if the today's date wasn't clicked
1074 Rectangle today_rect
= new Rectangle (
1076 ClientRectangle
.Bottom
- date_cell_size
.Height
,
1077 7 * date_cell_size
.Width
,
1078 date_cell_size
.Height
);
1079 if (today_rect
.Contains (point
) && this.ShowToday
) {
1080 return new HitTestInfo(HitArea
.TodayLink
, point
, DateTime
.Now
);
1083 Size month_size
= SingleMonthSize
;
1084 // define calendar rect's that this thing can land in
1085 Rectangle
[] calendars
= new Rectangle
[CalendarDimensions
.Width
* CalendarDimensions
.Height
];
1086 for (int i
=0; i
< CalendarDimensions
.Width
* CalendarDimensions
.Height
; i
++) {
1088 calendars
[i
] = new Rectangle (
1089 new Point (ClientRectangle
.X
+ 1, ClientRectangle
.Y
+ 1),
1092 // calendar on the next row
1093 if (i
% CalendarDimensions
.Width
== 0) {
1094 calendars
[i
] = new Rectangle (
1095 new Point (calendars
[i
-CalendarDimensions
.Width
].X
, calendars
[i
-CalendarDimensions
.Width
].Bottom
+ calendar_spacing
.Height
),
1098 // calendar on the next column
1099 calendars
[i
] = new Rectangle (
1100 new Point (calendars
[i
-1].Right
+ calendar_spacing
.Width
, calendars
[i
-1].Y
),
1106 // through each trying to find a match
1107 for (int i
= 0; i
< calendars
.Length
; i
++) {
1108 if (calendars
[i
].Contains (point
)) {
1109 // check the title section
1110 Rectangle title_rect
= new Rectangle (
1111 calendars
[i
].Location
,
1113 if (title_rect
.Contains (point
) ) {
1114 // make sure it's not a previous button
1116 Rectangle button_rect
= new Rectangle(
1117 new Point (calendars
[i
].X
+ button_x_offset
, (title_size
.Height
- button_size
.Height
)/2),
1119 if (button_rect
.Contains (point
)) {
1120 return new HitTestInfo(HitArea
.PrevMonthButton
, point
, DateTime
.Now
);
1123 // make sure it's not the next button
1124 if (i
% CalendarDimensions
.Height
== 0 && i
% CalendarDimensions
.Width
== calendar_dimensions
.Width
- 1) {
1125 Rectangle button_rect
= new Rectangle(
1126 new Point (calendars
[i
].Right
- button_x_offset
- button_size
.Width
, (title_size
.Height
- button_size
.Height
)/2),
1128 if (button_rect
.Contains (point
)) {
1129 return new HitTestInfo(HitArea
.NextMonthButton
, point
, DateTime
.Now
);
1133 // indicate which calendar and month it was
1135 calendar_rect
= calendars
[i
];
1137 // make sure it's not the month or the year of the calendar
1138 if (GetMonthNameRectangle (title_rect
, i
).Contains (point
)) {
1139 return new HitTestInfo(HitArea
.TitleMonth
, point
, DateTime
.Now
);
1141 if (GetYearNameRectangle (title_rect
, i
).Contains (point
)) {
1142 return new HitTestInfo(HitArea
.TitleYear
, point
, DateTime
.Now
);
1145 // return the hit test in the title background
1146 return new HitTestInfo(HitArea
.TitleBackground
, point
, DateTime
.Now
);
1149 Point date_grid_location
= new Point (calendars
[i
].X
, title_rect
.Bottom
);
1151 // see if it's in the Week numbers
1152 if (ShowWeekNumbers
) {
1153 Rectangle weeks_rect
= new Rectangle (
1155 new Size (date_cell_size
.Width
,Math
.Max (calendars
[i
].Height
- title_rect
.Height
, 0)));
1156 if (weeks_rect
.Contains (point
)) {
1157 return new HitTestInfo(HitArea
.WeekNumbers
, point
, DateTime
.Now
);
1160 // move the location of the grid over
1161 date_grid_location
.X
+= date_cell_size
.Width
;
1164 // see if it's in the week names
1165 Rectangle day_rect
= new Rectangle (
1167 new Size (Math
.Max (calendars
[i
].Right
- date_grid_location
.X
, 0), date_cell_size
.Height
));
1168 if (day_rect
.Contains (point
)) {
1169 return new HitTestInfo(HitArea
.DayOfWeek
, point
, DateTime
.Now
);
1172 // finally see if it was a date that was clicked
1173 Rectangle date_grid
= new Rectangle (
1174 new Point (day_rect
.X
, day_rect
.Bottom
),
1175 new Size (day_rect
.Width
, Math
.Max(calendars
[i
].Bottom
- day_rect
.Bottom
, 0)));
1176 if (date_grid
.Contains (point
)) {
1177 clicked_rect
= date_grid
;
1178 // okay so it's inside the grid, get the offset
1179 Point offset
= new Point (point
.X
- date_grid
.X
, point
.Y
- date_grid
.Y
);
1180 int row
= offset
.Y
/ date_cell_size
.Height
;
1181 int col
= offset
.X
/ date_cell_size
.Width
;
1182 // establish our first day of the month
1183 DateTime calendar_month
= this.CurrentMonth
.AddMonths(i
);
1184 DateTime first_day
= GetFirstDateInMonthGrid (calendar_month
);
1185 DateTime time
= first_day
.AddDays ((row
* 7) + col
);
1186 // establish which date was clicked
1187 if (time
.Year
!= calendar_month
.Year
|| time
.Month
!= calendar_month
.Month
) {
1188 if (time
< calendar_month
&& i
== 0) {
1189 return new HitTestInfo(HitArea
.PrevMonthDate
, point
, time
);
1190 } else if (time
> calendar_month
&& i
== CalendarDimensions
.Width
*CalendarDimensions
.Height
- 1) {
1191 return new HitTestInfo(HitArea
.NextMonthDate
, point
, time
);
1193 return new HitTestInfo(HitArea
.Nowhere
, point
, time
);
1195 return new HitTestInfo(HitArea
.Date
, point
, time
);
1200 return new HitTestInfo ();
1203 // returns the date of the first cell of the specified month grid
1204 internal DateTime
GetFirstDateInMonthGrid (DateTime month
) {
1205 // convert the first_day_of_week into a DayOfWeekEnum
1206 DayOfWeek first_day
= GetDayOfWeek (first_day_of_week
);
1207 // find the first day of the month
1208 DateTime first_date_of_month
= new DateTime (month
.Year
, month
.Month
, 1);
1209 DayOfWeek first_day_of_month
= first_date_of_month
.DayOfWeek
;
1210 // adjust for the starting day of the week
1211 int offset
= first_day_of_month
- first_day
;
1215 return first_date_of_month
.AddDays (-1*offset
);
1218 // returns the date of the last cell of the specified month grid
1219 internal DateTime
GetLastDateInMonthGrid (DateTime month
)
1221 DateTime start
= GetFirstDateInMonthGrid(month
);
1222 return start
.AddDays ((7 * 6)-1);
1225 internal bool IsBoldedDate (DateTime date
) {
1226 // check bolded dates
1227 if (bolded_dates
!= null && bolded_dates
.Length
> 0) {
1228 foreach (DateTime bolded_date
in bolded_dates
) {
1229 if (bolded_date
.Date
== date
.Date
) {
1234 // check monthly dates
1235 if (monthly_bolded_dates
!= null && monthly_bolded_dates
.Length
> 0) {
1236 foreach (DateTime bolded_date
in monthly_bolded_dates
) {
1237 if (bolded_date
.Day
== date
.Day
) {
1242 // check yearly dates
1243 if (annually_bolded_dates
!= null && annually_bolded_dates
.Length
> 0) {
1244 foreach (DateTime bolded_date
in annually_bolded_dates
) {
1245 if (bolded_date
.Month
== date
.Month
&& bolded_date
.Day
== date
.Day
) {
1251 return false; // no match
1254 // updates the specified bolded dates array with ones to add and ones to remove
1255 private void UpdateDateArray (ref DateTime
[] dates
, ArrayList to_add
, ArrayList to_remove
) {
1256 ArrayList list
= new ArrayList ();
1258 // update normal bolded dates
1259 if (dates
!= null) {
1260 foreach (DateTime date
in dates
) {
1261 list
.Add (date
.Date
);
1266 foreach (DateTime date
in to_add
) {
1267 if (!list
.Contains (date
.Date
)) {
1268 list
.Add (date
.Date
);
1273 // remove ones to remove
1274 foreach (DateTime date
in to_remove
) {
1275 if (list
.Contains (date
.Date
)) {
1276 list
.Remove (date
.Date
);
1280 // set up the array now
1281 if (list
.Count
> 0) {
1282 dates
= (DateTime
[]) list
.ToArray (typeof (DateTime
));
1290 // initialise the context menu
1291 private void SetUpContextMenu () {
1292 menu
= new ContextMenu ();
1293 for (int i
=0; i
< 12; i
++) {
1294 MenuItem menu_item
= new MenuItem ( new DateTime (2000, i
+1, 1).ToString ("MMMM"));
1295 menu_item
.Click
+= new EventHandler (MenuItemClickHandler
);
1296 menu
.MenuItems
.Add (menu_item
);
1300 // initialises text value and show's year up down in correct position
1301 private void PrepareYearUpDown (Point p
) {
1302 Rectangle old_location
= year_updown
.Bounds
;
1305 Rectangle title_rect
= new Rectangle(
1306 last_clicked_calendar_rect
.Location
,
1309 year_updown
.Bounds
= GetYearNameRectangle(
1311 last_clicked_calendar_index
);
1312 year_updown
.Top
-= 4;
1313 year_updown
.Width
+= (int) (this.Font
.Size
* 4);
1314 // set year - only do this if this isn't being called because of a year up down click
1315 if(year_updown
.Bounds
!= old_location
) {
1316 year_updown
.Value
= current_month
.AddMonths(last_clicked_calendar_index
).Year
;
1319 if(!year_updown
.Visible
) {
1320 year_updown
.Visible
= true;
1324 // returns the first date of the month
1325 private DateTime
GetFirstDateInMonth (DateTime date
) {
1326 return new DateTime (date
.Year
, date
.Month
, 1);
1329 // returns the last date of the month
1330 private DateTime
GetLastDateInMonth (DateTime date
) {
1331 return new DateTime (date
.Year
, date
.Month
, 1).AddMonths(1).AddDays(-1);
1334 // called in response to users seletion with shift key
1335 private void AddTimeToSelection (int delta
, bool isDays
)
1337 DateTime cursor_point
;
1339 // okay we add the period to the date that is not the same as the
1340 // start date when shift was first clicked.
1341 if (SelectionStart
!= first_select_start_date
) {
1342 cursor_point
= SelectionStart
;
1344 cursor_point
= SelectionEnd
;
1348 end_point
= cursor_point
.AddDays (delta
);
1350 // delta must be months
1351 end_point
= cursor_point
.AddMonths (delta
);
1353 // set the new selection range
1354 SelectionRange range
= new SelectionRange (first_select_start_date
, end_point
);
1355 if (range
.Start
.AddDays (MaxSelectionCount
-1) < range
.End
) {
1356 // okay the date is beyond what is allowed, lets set the maximum we can
1357 if (range
.Start
!= first_select_start_date
) {
1358 range
.Start
= range
.End
.AddDays ((MaxSelectionCount
-1)*-1);
1360 range
.End
= range
.Start
.AddDays (MaxSelectionCount
-1);
1363 this.SelectionRange
= range
;
1366 // attempts to add the date to the selection without throwing exception
1367 private void SelectDate (DateTime date
) {
1368 // try and add the new date to the selction range
1369 if (is_shift_pressed
|| (click_state
[0])) {
1370 SelectionRange range
= new SelectionRange (first_select_start_date
, date
);
1371 if (range
.Start
.AddDays (MaxSelectionCount
-1) < range
.End
) {
1372 // okay the date is beyond what is allowed, lets set the maximum we can
1373 if (range
.Start
!= first_select_start_date
) {
1374 range
.Start
= range
.End
.AddDays ((MaxSelectionCount
-1)*-1);
1376 range
.End
= range
.Start
.AddDays (MaxSelectionCount
-1);
1379 SelectionRange
= range
;
1381 SelectionRange
= new SelectionRange (date
, date
);
1382 first_select_start_date
= date
;
1386 // gets the week of the year
1387 internal int GetWeekOfYear (DateTime date
) {
1388 // convert the first_day_of_week into a DayOfWeekEnum
1389 DayOfWeek first_day
= GetDayOfWeek (first_day_of_week
);
1390 // find the first day of the year
1391 DayOfWeek first_day_of_year
= new DateTime (date
.Year
, 1, 1).DayOfWeek
;
1392 // adjust for the starting day of the week
1393 int offset
= first_day_of_year
- first_day
;
1394 int week
= ((date
.DayOfYear
+ offset
) / 7) + 1;
1398 // convert a Day enum into a DayOfWeek enum
1399 internal DayOfWeek
GetDayOfWeek (Day day
) {
1400 if (day
== Day
.Default
) {
1401 return Threading
.Thread
.CurrentThread
.CurrentUICulture
.DateTimeFormat
.FirstDayOfWeek
;
1403 return (DayOfWeek
) DayOfWeek
.Parse (typeof (DayOfWeek
), day
.ToString ());
1407 // returns the rectangle for themonth name
1408 internal Rectangle
GetMonthNameRectangle (Rectangle title_rect
, int calendar_index
) {
1409 Graphics g
= this.DeviceContext
;
1410 DateTime this_month
= this.current_month
.AddMonths (calendar_index
);
1411 Size title_text_size
= g
.MeasureString (this_month
.ToString ("MMMM yyyy"), this.Font
).ToSize ();
1412 Size month_size
= g
.MeasureString (this_month
.ToString ("MMMM"), this.Font
).ToSize ();
1413 // return only the month name part of that
1414 return new Rectangle (
1416 title_rect
.X
+ ((title_rect
.Width
- title_text_size
.Width
)/2),
1417 title_rect
.Y
+ ((title_rect
.Height
- title_text_size
.Height
)/2)),
1421 // returns the rectangle for the year in the title
1422 internal Rectangle
GetYearNameRectangle (Rectangle title_rect
, int calendar_index
) {
1423 Graphics g
= this.DeviceContext
;
1424 DateTime this_month
= this.current_month
.AddMonths (calendar_index
);
1425 Size title_text_size
= g
.MeasureString (this_month
.ToString ("MMMM yyyy"), this.Font
).ToSize ();
1426 Size year_size
= g
.MeasureString (this_month
.ToString ("yyyy"), this.Font
).ToSize ();
1427 // find out how much space the title took
1428 Rectangle text_rect
= new Rectangle (
1430 title_rect
.X
+ ((title_rect
.Width
- title_text_size
.Width
)/2),
1431 title_rect
.Y
+ ((title_rect
.Height
- title_text_size
.Height
)/2)),
1433 // return only the rect of the year
1434 return new Rectangle (
1436 text_rect
.Right
- year_size
.Width
,
1441 // determine if date is allowed to be drawn in month
1442 internal bool IsValidWeekToDraw (DateTime month
, DateTime date
, int row
, int col
) {
1443 DateTime tocheck
= month
.AddMonths (-1);
1444 if ((month
.Year
== date
.Year
&& month
.Month
== date
.Month
) ||
1445 (tocheck
.Year
== date
.Year
&& tocheck
.Month
== date
.Month
)) {
1449 // check the railing dates (days in the month after the last month in grid)
1450 if (row
== CalendarDimensions
.Height
- 1 && col
== CalendarDimensions
.Width
- 1) {
1451 tocheck
= month
.AddMonths (1);
1452 return (tocheck
.Year
== date
.Year
&& tocheck
.Month
== date
.Month
) ;
1458 // set one item clicked and all others off
1459 private void SetItemClick(HitTestInfo hti
)
1461 switch(hti
.HitArea
) {
1462 case HitArea
.NextMonthButton
:
1463 this.is_previous_clicked
= false;
1464 this.is_next_clicked
= true;
1465 this.is_date_clicked
= false;
1467 case HitArea
.PrevMonthButton
:
1468 this.is_previous_clicked
= true;
1469 this.is_next_clicked
= false;
1470 this.is_date_clicked
= false;
1472 case HitArea
.PrevMonthDate
:
1473 case HitArea
.NextMonthDate
:
1475 this.clicked_date
= hti
.Time
;
1476 this.is_previous_clicked
= false;
1477 this.is_next_clicked
= false;
1478 this.is_date_clicked
= true;
1481 this.is_previous_clicked
= false;
1482 this.is_next_clicked
= false;
1483 this.is_date_clicked
= false;
1488 // called when the year is changed
1489 private void UpDownYearChangedHandler (object sender
, EventArgs e
) {
1490 int initial_year_value
= this.CurrentMonth
.AddMonths(last_clicked_calendar_index
).Year
;
1491 int diff
= (int) year_updown
.Value
- initial_year_value
;
1492 this.CurrentMonth
= this.CurrentMonth
.AddYears(diff
);
1495 // called when context menu is clicked
1496 private void MenuItemClickHandler (object sender
, EventArgs e
) {
1497 MenuItem item
= sender
as MenuItem
;
1498 if (item
!= null && month_title_click_location
!= Point
.Empty
) {
1499 // establish which month we want to move to
1500 if (item
.Parent
== null) {
1503 int new_month
= item
.Parent
.MenuItems
.IndexOf (item
) + 1;
1504 if (new_month
== 0) {
1507 // okay let's establish which calendar was hit
1508 Size month_size
= this.SingleMonthSize
;
1509 for (int i
=0; i
< CalendarDimensions
.Height
; i
++) {
1510 for (int j
=0; j
< CalendarDimensions
.Width
; j
++) {
1511 int month_index
= (i
* CalendarDimensions
.Width
) + j
;
1512 Rectangle month_rect
= new Rectangle ( new Point (0, 0), month_size
);
1514 month_rect
.X
= this.ClientRectangle
.X
+ 1;
1516 month_rect
.X
= this.ClientRectangle
.X
+ 1 + ((j
)*(month_size
.Width
+calendar_spacing
.Width
));
1519 month_rect
.Y
= this.ClientRectangle
.Y
+ 1;
1521 month_rect
.Y
= this.ClientRectangle
.Y
+ 1 + ((i
)*(month_size
.Height
+calendar_spacing
.Height
));
1523 // see if the point is inside
1524 if (month_rect
.Contains (month_title_click_location
)) {
1525 DateTime clicked_month
= CurrentMonth
.AddMonths (month_index
);
1526 // get the month that we want to move to
1527 int month_offset
= new_month
- clicked_month
.Month
;
1529 // move forward however more months we need to
1530 this.CurrentMonth
= this.CurrentMonth
.AddMonths (month_offset
);
1537 month_title_click_location
= Point
.Empty
;
1541 // raised on the timer, for mouse hold clicks
1542 private void TimerHandler (object sender
, EventArgs e
) {
1543 // now find out which area was click
1545 HitTestInfo hti
= this.HitTest (this.PointToClient (MousePosition
));
1546 // see if it was clicked on the prev or next mouse
1547 if (click_state
[1] || click_state
[2]) {
1548 // invalidate the area where the mouse was last held
1550 // register the click
1551 if (hti
.HitArea
== HitArea
.PrevMonthButton
||
1552 hti
.HitArea
== HitArea
.NextMonthButton
) {
1553 DoButtonMouseDown (hti
);
1554 click_state
[1] = (hti
.HitArea
== HitArea
.PrevMonthButton
);
1555 click_state
[2] = !click_state
[1];
1557 if (timer
.Interval
!= 300) {
1558 timer
.Interval
= 300;
1562 timer
.Enabled
= false;
1566 // selects one of the buttons
1567 private void DoButtonMouseDown (HitTestInfo hti
) {
1568 // show the click then move on
1570 if (hti
.HitArea
== HitArea
.PrevMonthButton
) {
1571 // invalidate the prev monthbutton
1574 this.ClientRectangle
.X
+ 1 + button_x_offset
,
1575 this.ClientRectangle
.Y
+ 1 + (title_size
.Height
- button_size
.Height
)/2,
1577 button_size
.Height
));
1578 this.CurrentMonth
= this.CurrentMonth
.AddMonths (ScrollChange
*-1);
1580 // invalidate the next monthbutton
1583 this.ClientRectangle
.Right
- 1 - button_x_offset
- button_size
.Width
,
1584 this.ClientRectangle
.Y
+ 1 + (title_size
.Height
- button_size
.Height
)/2,
1586 button_size
.Height
));
1587 this.CurrentMonth
= this.CurrentMonth
.AddMonths (ScrollChange
);
1591 // selects the clicked date
1592 private void DoDateMouseDown (HitTestInfo hti
) {
1596 // event run on the mouse up event
1597 private void DoMouseUp () {
1599 HitTestInfo hti
= this.HitTest (this.PointToClient (MousePosition
));
1600 switch (hti
.HitArea
) {
1601 case HitArea
.PrevMonthDate
:
1602 case HitArea
.NextMonthDate
:
1604 this.SelectDate (clicked_date
);
1605 this.OnDateSelected (new DateRangeEventArgs (SelectionStart
, SelectionEnd
));
1609 // invalidate the next monthbutton
1610 if (this.is_next_clicked
) {
1613 this.ClientRectangle
.Right
- 1 - button_x_offset
- button_size
.Width
,
1614 this.ClientRectangle
.Y
+ 1 + (title_size
.Height
- button_size
.Height
)/2,
1616 button_size
.Height
));
1618 // invalidate the prev monthbutton
1619 if (this.is_previous_clicked
) {
1622 this.ClientRectangle
.X
+ 1 + button_x_offset
,
1623 this.ClientRectangle
.Y
+ 1 + (title_size
.Height
- button_size
.Height
)/2,
1625 button_size
.Height
));
1627 if (this.is_date_clicked
) {
1628 // invalidate the area under the cursor, to remove focus rect
1629 this.InvalidateDateRange (new SelectionRange (clicked_date
, clicked_date
));
1631 this.is_previous_clicked
= false;
1632 this.is_next_clicked
= false;
1633 this.is_date_clicked
= false;
1636 // need when in windowed mode
1637 private void LostFocusHandler(object sender
, EventArgs e
)
1642 // needed when in windowed mode to close the calendar if no
1643 // part of it has focus.
1644 private void UpDownTimerTick(object sender
, EventArgs e
)
1646 HideMonthCalendarIfWindowed ();
1647 if (updown_timer
!= null)
1649 updown_timer
.Dispose();
1650 updown_timer
= null;
1654 // Needed when in windowed mode.
1655 private void UpDownYearLostFocusHandler(object sender
, EventArgs e
)
1657 updown_has_focus
= false;
1661 // Needed when in windowed mode.
1662 private void UpDownYearGotFocusHandler(object sender
, EventArgs e
)
1664 updown_has_focus
= true;
1667 // Needed when in windowed mode.
1668 private void StartHideTimer()
1670 if (updown_timer
== null) {
1671 updown_timer
= new Timer ();
1672 updown_timer
.Interval
= 50;
1673 updown_timer
.Tick
+= new EventHandler (UpDownTimerTick
);
1674 updown_timer
.Enabled
= true;
1678 // needed when in windowed mode.
1679 private void HideMonthCalendarIfWindowed()
1681 if (this.owner
!= null && this.Visible
) {
1682 if (updown_has_focus
)
1684 if (showing_context_menu
)
1687 this.owner
.HideMonthCalendar ();
1691 // occurs when mouse moves around control, used for selection
1692 private void MouseMoveHandler (object sender
, MouseEventArgs e
) {
1693 HitTestInfo hti
= this.HitTest (e
.X
, e
.Y
);
1694 // clear the last clicked item
1695 if (click_state
[0]) {
1696 // register the click
1697 if (hti
.HitArea
== HitArea
.PrevMonthDate
||
1698 hti
.HitArea
== HitArea
.NextMonthDate
||
1699 hti
.HitArea
== HitArea
.Date
)
1701 Rectangle prev_rect
= clicked_rect
;
1702 DateTime prev_clicked
= clicked_date
;
1703 DoDateMouseDown (hti
);
1704 if (owner
== null) {
1705 click_state
[0] = true;
1707 click_state
[0] = false;
1708 click_state
[1] = false;
1709 click_state
[2] = false;
1712 if (prev_clicked
!= clicked_date
) {
1713 Rectangle invalid
= Rectangle
.Union (prev_rect
, clicked_rect
);
1714 Invalidate (invalid
);
1721 // to check if the mouse has come down on this control
1722 private void MouseDownHandler (object sender
, MouseEventArgs e
)
1724 // clear the click_state variables
1725 click_state
[0] = false;
1726 click_state
[1] = false;
1727 click_state
[2] = false;
1729 // disable the timer if it was enabled
1730 if (timer
.Enabled
) {
1732 timer
.Enabled
= false;
1735 Point point
= new Point (e
.X
, e
.Y
);
1736 // figure out if we are in drop down mode and a click happened outside us
1737 if (this.owner
!= null) {
1738 if (!this.ClientRectangle
.Contains (point
)) {
1739 this.owner
.HideMonthCalendar ();
1744 //establish where was hit
1745 HitTestInfo hti
= this.HitTest(point
);
1746 // hide the year numeric up down if it was clicked
1747 if (year_updown
!= null && year_updown
.Visible
&& hti
.HitArea
!= HitArea
.TitleYear
)
1749 year_updown
.Visible
= false;
1751 switch (hti
.HitArea
) {
1752 case HitArea
.PrevMonthButton
:
1753 case HitArea
.NextMonthButton
:
1754 DoButtonMouseDown (hti
);
1755 click_state
[1] = (hti
.HitArea
== HitArea
.PrevMonthDate
);
1756 click_state
[2] = !click_state
[1];
1757 timer
.Interval
= 750;
1761 case HitArea
.PrevMonthDate
:
1762 case HitArea
.NextMonthDate
:
1763 DoDateMouseDown (hti
);
1764 // leave clicked state blank if drop down window
1765 if (owner
== null) {
1766 click_state
[0] = true;
1768 click_state
[0] = false;
1769 click_state
[1] = false;
1770 click_state
[2] = false;
1773 case HitArea
.TitleMonth
:
1774 month_title_click_location
= hti
.Point
;
1775 showing_context_menu
= true;
1776 menu
.Show (this, hti
.Point
);
1777 showing_context_menu
= false;
1779 case HitArea
.TitleYear
:
1780 // place the numeric up down
1781 PrepareYearUpDown(hti
.Point
);
1783 case HitArea
.TodayLink
:
1784 this.SetSelectionRange (DateTime
.Now
.Date
, DateTime
.Now
.Date
);
1785 this.OnDateSelected (new DateRangeEventArgs (SelectionStart
, SelectionEnd
));
1788 this.is_previous_clicked
= false;
1789 this.is_next_clicked
= false;
1790 this.is_date_clicked
= false;
1795 // raised by any key down events
1796 private void KeyDownHandler (object sender
, KeyEventArgs e
) {
1797 // send keys to the year_updown control, let it handle it
1798 if(year_updown
.Visible
) {
1799 switch (e
.KeyCode
) {
1801 year_updown
.Visible
= false;
1804 year_updown
.Value
= year_updown
.Value
+ 1;
1807 year_updown
.Value
= year_updown
.Value
- 1;
1811 if (!is_shift_pressed
&& e
.Shift
) {
1812 first_select_start_date
= SelectionStart
;
1813 is_shift_pressed
= e
.Shift
;
1815 switch (e
.KeyCode
) {
1817 // set the date to the start of the month
1818 if (is_shift_pressed
) {
1819 DateTime date
= GetFirstDateInMonth (first_select_start_date
);
1820 if (date
< first_select_start_date
.AddDays ((MaxSelectionCount
-1)*-1)) {
1821 date
= first_select_start_date
.AddDays ((MaxSelectionCount
-1)*-1);
1823 this.SetSelectionRange (date
, first_select_start_date
);
1825 DateTime date
= GetFirstDateInMonth (this.SelectionStart
);
1826 this.SetSelectionRange (date
, date
);
1828 this.OnDateChanged (new DateRangeEventArgs (SelectionStart
, SelectionEnd
));
1831 // set the date to the last of the month
1832 if (is_shift_pressed
) {
1833 DateTime date
= GetLastDateInMonth (first_select_start_date
);
1834 if (date
> first_select_start_date
.AddDays (MaxSelectionCount
-1)) {
1835 date
= first_select_start_date
.AddDays (MaxSelectionCount
-1);
1837 this.SetSelectionRange (date
, first_select_start_date
);
1839 DateTime date
= GetLastDateInMonth (this.SelectionStart
);
1840 this.SetSelectionRange (date
, date
);
1842 this.OnDateChanged (new DateRangeEventArgs (SelectionStart
, SelectionEnd
));
1845 // set the date to the last of the month
1846 if (is_shift_pressed
) {
1847 this.AddTimeToSelection (-1, false);
1849 DateTime date
= this.SelectionStart
.AddMonths (-1);
1850 this.SetSelectionRange (date
, date
);
1852 this.OnDateChanged (new DateRangeEventArgs (SelectionStart
, SelectionEnd
));
1855 // set the date to the last of the month
1856 if (is_shift_pressed
) {
1857 this.AddTimeToSelection (1, false);
1859 DateTime date
= this.SelectionStart
.AddMonths (1);
1860 this.SetSelectionRange (date
, date
);
1862 this.OnDateChanged (new DateRangeEventArgs (SelectionStart
, SelectionEnd
));
1865 // set the back 1 week
1866 if (is_shift_pressed
) {
1867 this.AddTimeToSelection (-7, true);
1869 DateTime date
= this.SelectionStart
.AddDays (-7);
1870 this.SetSelectionRange (date
, date
);
1872 this.OnDateChanged (new DateRangeEventArgs (SelectionStart
, SelectionEnd
));
1875 // set the date forward 1 week
1876 if (is_shift_pressed
) {
1877 this.AddTimeToSelection (7, true);
1879 DateTime date
= this.SelectionStart
.AddDays (7);
1880 this.SetSelectionRange (date
, date
);
1882 this.OnDateChanged (new DateRangeEventArgs (SelectionStart
, SelectionEnd
));
1886 if (is_shift_pressed
) {
1887 this.AddTimeToSelection (-1, true);
1889 DateTime date
= this.SelectionStart
.AddDays (-1);
1890 this.SetSelectionRange (date
, date
);
1892 this.OnDateChanged (new DateRangeEventArgs (SelectionStart
, SelectionEnd
));
1896 if (is_shift_pressed
) {
1897 this.AddTimeToSelection (1, true);
1899 DateTime date
= this.SelectionStart
.AddDays (1);
1900 this.SetSelectionRange (date
, date
);
1902 this.OnDateChanged (new DateRangeEventArgs (SelectionStart
, SelectionEnd
));
1911 // to check if the mouse has come up on this control
1912 private void MouseUpHandler (object sender
, MouseEventArgs e
)
1914 if (timer
.Enabled
) {
1917 // clear the click state array
1918 click_state
[0] = false;
1919 click_state
[1] = false;
1920 click_state
[2] = false;
1921 // do the regulare mouseup stuff
1925 // raised by any key up events
1926 private void KeyUpHandler (object sender
, KeyEventArgs e
) {
1927 is_shift_pressed
= e
.Shift
;
1931 // paint this control now
1932 private void PaintHandler (object sender
, PaintEventArgs pe
) {
1933 if (Width
<= 0 || Height
<= 0 || Visible
== false)
1936 Draw (pe
.ClipRectangle
, pe
.Graphics
);
1938 // fire the new paint handler
1939 if (this.Paint
!= null)
1941 this.Paint (sender
, pe
);
1945 // returns the region of the control that needs to be redrawn
1946 private void InvalidateDateRange (SelectionRange range
) {
1947 SelectionRange bounds
= this.GetDisplayRange (false);
1949 if (range
.End
< bounds
.Start
|| range
.Start
> bounds
.End
) {
1950 // don't invalidate anything, as the modified date range
1951 // is outside the visible bounds of this control
1954 // adjust the start and end to be inside the visible range
1955 if (range
.Start
< bounds
.Start
) {
1956 range
= new SelectionRange (bounds
.Start
, range
.End
);
1958 if (range
.End
> bounds
.End
) {
1959 range
= new SelectionRange (range
.Start
, bounds
.End
);
1961 // now invalidate the date rectangles as series of rows
1962 DateTime last_month
= this.current_month
.AddMonths ((CalendarDimensions
.Width
* CalendarDimensions
.Height
)).AddDays (-1);
1963 DateTime current
= range
.Start
;
1964 while (current
<= range
.End
) {
1965 DateTime month_end
= new DateTime (current
.Year
, current
.Month
, 1).AddMonths (1).AddDays (-1);;
1966 Rectangle start_rect
;
1968 // see if entire selection is in this current month
1969 if (range
.End
<= month_end
&& current
< last_month
) {
1970 // the end is the last date
1971 if (current
< this.current_month
) {
1972 start_rect
= GetDateRowRect (current_month
, current_month
);
1974 start_rect
= GetDateRowRect (current
, current
);
1976 end_rect
= GetDateRowRect (current
, range
.End
);
1977 } else if (current
< last_month
) {
1978 // otherwise it simply means we have a selection spaning
1979 // multiple months simply set rectangle inside the current month
1980 start_rect
= GetDateRowRect (current
, current
);
1981 end_rect
= GetDateRowRect (month_end
, month_end
);
1983 // it's outside the visible range
1984 start_rect
= GetDateRowRect (last_month
, last_month
.AddDays (1));
1985 end_rect
= GetDateRowRect (last_month
, range
.End
);
1987 // push to the next month
1988 current
= month_end
.AddDays (1);
1989 // invalidate from the start row to the end row for this month
1995 Math
.Max (end_rect
.Bottom
- start_rect
.Y
, 0)));
1999 // gets the rect of the row where the specified date appears on the specified month
2000 private Rectangle
GetDateRowRect (DateTime month
, DateTime date
) {
2001 // first get the general rect of the supplied month
2002 Size month_size
= SingleMonthSize
;
2003 Rectangle month_rect
= Rectangle
.Empty
;
2004 for (int i
=0; i
< CalendarDimensions
.Width
*CalendarDimensions
.Height
; i
++) {
2005 DateTime this_month
= this.current_month
.AddMonths (i
);
2006 if (month
.Year
== this_month
.Year
&& month
.Month
== this_month
.Month
) {
2007 month_rect
= new Rectangle (
2008 this.ClientRectangle
.X
+ 1 + (month_size
.Width
* (i
%CalendarDimensions
.Width
)) + (this.calendar_spacing
.Width
* (i
%CalendarDimensions
.Width
)),
2009 this.ClientRectangle
.Y
+ 1 + (month_size
.Height
* (i
/CalendarDimensions
.Width
)) + (this.calendar_spacing
.Height
* (i
/CalendarDimensions
.Width
)),
2015 // now find out where in the month the supplied date is
2016 if (month_rect
== Rectangle
.Empty
) {
2017 return Rectangle
.Empty
;
2019 // find out which row this date is in
2021 DateTime first_date
= GetFirstDateInMonthGrid (month
);
2022 DateTime end_date
= first_date
.AddDays (7);
2023 for (int i
=0; i
< 6; i
++) {
2024 if (date
>= first_date
&& date
< end_date
) {
2028 first_date
= end_date
;
2029 end_date
= end_date
.AddDays (7);
2031 // ensure it's a valid row
2033 return Rectangle
.Empty
;
2035 int x_offset
= (this.ShowWeekNumbers
) ? date_cell_size
.Width
: 0;
2036 int y_offset
= title_size
.Height
+ (date_cell_size
.Height
* (row
+ 1));
2037 return new Rectangle (
2038 month_rect
.X
+ x_offset
,
2039 month_rect
.Y
+ y_offset
,
2040 date_cell_size
.Width
* 7,
2041 date_cell_size
.Height
);
2044 internal void Draw (Rectangle clip_rect
, Graphics dc
)
2046 ThemeEngine
.Current
.DrawMonthCalendar (dc
, clip_rect
, this);
2049 #endregion //internal methods
2051 #region internal drawing methods
2054 #endregion // internal drawing methods
2056 #region inner classes and enumerations
2058 // enumeration about what type of area on the calendar was hit
2059 public enum HitArea
{
2075 // info regarding to a hit test on this calendar
2076 public sealed class HitTestInfo
{
2078 private HitArea hit_area
;
2079 private Point point
;
2080 private DateTime time
;
2082 // default constructor
2083 internal HitTestInfo () {
2084 hit_area
= HitArea
.Nowhere
;
2085 point
= new Point (0, 0);
2086 time
= DateTime
.Now
;
2089 // overload receives all properties
2090 internal HitTestInfo (HitArea hit_area
, Point point
, DateTime time
) {
2091 this.hit_area
= hit_area
;
2096 // the type of area that was hit
2097 public HitArea HitArea
{
2103 // the point that is being test
2104 public Point Point
{
2110 // the date under the hit test point, only valid if HitArea is Date
2111 public DateTime Time
{
2118 #endregion // inner classes