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-2005 Novell, Inc.
23 // John BouAntoun jba-mono@optusnet.com.au
24 // Rolf Bjarne Kvinge rolfkvinge@ya.com
27 // - wire in all events from monthcalendar
32 using System.Collections;
33 using System.ComponentModel;
34 using System.Globalization;
35 using System.Runtime.InteropServices;
36 using System.Windows.Forms;
38 namespace System.Windows.Forms {
40 [ClassInterface (ClassInterfaceType.AutoDispatch)]
41 [DefaultBindingProperty ("Value")]
44 [DefaultEvent("ValueChanged")]
45 [DefaultProperty("Value")]
46 [Designer("System.Windows.Forms.Design.DateTimePickerDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")]
47 public class DateTimePicker : Control {
49 #region Public variables
51 // this class has to have the specified hour, minute and second, as it says in msdn
53 [EditorBrowsable (EditorBrowsableState.Never)]
56 public static readonly DateTime MaxDateTime = new DateTime (9998, 12, 31, 0, 0, 0);
58 [EditorBrowsable (EditorBrowsableState.Never)]
61 public static readonly DateTime MinDateTime = new DateTime (1753, 1, 1);
63 internal const int check_box_size = 13;
64 internal const int check_box_space = 4;
66 #endregion // Public variables
68 #region Local variables
70 protected static readonly Color DefaultMonthBackColor = ThemeEngine.Current.ColorWindow;
71 protected static readonly Color DefaultTitleBackColor = ThemeEngine.Current.ColorActiveCaption;
72 protected static readonly Color DefaultTitleForeColor = ThemeEngine.Current.ColorActiveCaptionText;
73 protected static readonly Color DefaultTrailingForeColor = SystemColors.GrayText;
75 internal MonthCalendar month_calendar;
78 LeftRightAlignment drop_down_align;
79 DateTimePickerFormat format;
86 bool right_to_left_layout;
88 // variables used for drawing and such
89 internal const int up_down_width = check_box_size;
90 internal bool is_drop_down_visible;
91 internal bool is_up_pressed;
92 internal bool is_down_pressed;
93 internal Timer updown_timer;
94 internal const int initial_timer_delay = 500;
95 internal const int subsequent_timer_delay = 100;
96 internal bool is_checkbox_selected;
98 // variables for determining how to format the string
99 internal PartData[] part_data;
101 #endregion // Local variables
103 #region DateTimePickerAccessibleObject Subclass
105 public class DateTimePickerAccessibleObject : ControlAccessibleObject {
106 #region DateTimePickerAccessibleObject Local Variables
107 private new DateTimePicker owner;
108 #endregion // DateTimePickerAccessibleObject Local Variables
110 #region DateTimePickerAccessibleObject Constructors
111 public DateTimePickerAccessibleObject(DateTimePicker owner) : base(owner) {
114 #endregion // DateTimePickerAccessibleObject Constructors
116 #region DateTimePickerAccessibleObject Properties
118 public override string KeyboardShortcut {
120 return base.KeyboardShortcut;
124 public override AccessibleRole Role {
130 public override AccessibleStates State {
132 AccessibleStates retval;
134 retval = AccessibleStates.Default;
137 retval |= AccessibleStates.Checked;
144 public override string Value {
149 #endregion // DateTimePickerAccessibleObject Properties
151 #endregion // DateTimePickerAccessibleObject Sub-class
153 #region public constructors
155 // only public constructor
156 public DateTimePicker () {
158 // initialise the month calendar
159 month_calendar = new MonthCalendar (this);
160 month_calendar.CalendarDimensions = new Size (1, 1);
161 month_calendar.MaxSelectionCount = 1;
162 month_calendar.ForeColor = Control.DefaultForeColor;
163 month_calendar.BackColor = DefaultMonthBackColor;
164 month_calendar.TitleBackColor = DefaultTitleBackColor;
165 month_calendar.TitleForeColor = DefaultTitleForeColor;
166 month_calendar.TrailingForeColor = DefaultTrailingForeColor;
167 month_calendar.Visible = false;
168 // initialize the timer
169 updown_timer = new Timer();
170 updown_timer.Interval = initial_timer_delay;
173 // initialise other variables
175 custom_format = null;
176 drop_down_align = LeftRightAlignment.Left;
177 format = DateTimePickerFormat.Long;
178 max_date = MaxDateTime;
179 min_date = MinDateTime;
180 show_check_box = false;
181 show_up_down = false;
182 date_value = DateTime.Now;
184 is_drop_down_visible = false;
185 BackColor = SystemColors.Window;
186 ForeColor = SystemColors.WindowText;
188 month_calendar.DateChanged += new DateRangeEventHandler (MonthCalendarDateChangedHandler);
189 month_calendar.DateSelected += new DateRangeEventHandler (MonthCalendarDateSelectedHandler);
190 month_calendar.LostFocus += new EventHandler (MonthCalendarLostFocusHandler);
191 updown_timer.Tick += new EventHandler (UpDownTimerTick);
192 KeyPress += new KeyPressEventHandler (KeyPressHandler);
193 KeyDown += new KeyEventHandler (KeyDownHandler);
194 LostFocus += new EventHandler (LostFocusHandler);
195 MouseDown += new MouseEventHandler (MouseDownHandler);
196 MouseUp += new MouseEventHandler (MouseUpHandler);
197 Paint += new PaintEventHandler (PaintHandler);
198 Resize += new EventHandler (ResizeHandler);
199 SetStyle (ControlStyles.UserPaint | ControlStyles.StandardClick, false);
200 SetStyle (ControlStyles.FixedHeight, true);
201 SetStyle (ControlStyles.Selectable, true);
208 #region public properties
211 [EditorBrowsable(EditorBrowsableState.Never)]
212 public override Color BackColor {
214 base.BackColor = value;
217 return base.BackColor;
222 [EditorBrowsable(EditorBrowsableState.Never)]
223 public override Image BackgroundImage {
225 base.BackgroundImage = value;
228 return base.BackgroundImage;
234 [EditorBrowsable (EditorBrowsableState.Never)]
235 public override ImageLayout BackgroundImageLayout {
237 return base.BackgroundImageLayout;
240 base.BackgroundImageLayout = value;
247 public Font CalendarFont {
249 month_calendar.Font = value;
252 return month_calendar.Font;
256 public Color CalendarForeColor {
258 month_calendar.ForeColor = value;
261 return month_calendar.ForeColor;
265 public Color CalendarMonthBackground {
267 month_calendar.BackColor = value;
270 return month_calendar.BackColor;
274 public Color CalendarTitleBackColor {
276 month_calendar.TitleBackColor = value;
279 return month_calendar.TitleBackColor;
283 public Color CalendarTitleForeColor {
285 month_calendar.TitleForeColor = value;
288 return month_calendar.TitleForeColor;
292 public Color CalendarTrailingForeColor {
294 month_calendar.TrailingForeColor = value;
297 return month_calendar.TrailingForeColor;
301 // when checked the value is grayed out
304 public bool Checked {
306 if (is_checked != value) {
308 // invalidate the value inside this control
310 for (int i = 0; i < part_data.Length; i++)
311 part_data [i].is_selected = false;
312 Invalidate (date_area_rect);
321 // the custom format string to format this control with
326 [RefreshProperties(RefreshProperties.Repaint)]
327 public string CustomFormat {
329 if (custom_format != value) {
330 custom_format = value;
331 if (this.Format == DateTimePickerFormat.Custom) {
337 return custom_format;
342 [EditorBrowsable (EditorBrowsableState.Never)]
343 protected override bool DoubleBuffered {
345 return base.DoubleBuffered;
348 base.DoubleBuffered = value;
353 // which side the drop down is to be aligned on
354 [DefaultValue(LeftRightAlignment.Left)]
356 public LeftRightAlignment DropDownAlign {
358 if (drop_down_align != value) {
359 drop_down_align = value;
363 return drop_down_align;
368 [EditorBrowsable(EditorBrowsableState.Never)]
369 public override Color ForeColor {
371 base.ForeColor = value;
374 return base.ForeColor;
378 // the format of the date time picker text, default is long
379 [RefreshProperties(RefreshProperties.Repaint)]
380 public DateTimePickerFormat Format {
382 if (format != value) {
384 RecreateHandle (); // MS recreates the handle on every format change.
386 this.OnFormatChanged (EventArgs.Empty);
387 // invalidate the value inside this control
388 this.Invalidate (date_area_rect);
396 public DateTime MaxDate {
398 if (value < min_date) {
399 string msg = string.Format (CultureInfo.CurrentCulture,
400 "'{0}' is not a valid value for 'MaxDate'. 'MaxDate' "
401 + "must be greater than or equal to MinDate.",
402 value.ToString ("G"));
404 throw new ArgumentOutOfRangeException ("MaxDate", msg);
406 throw new ArgumentException (msg);
409 if (value > MaxDateTime) {
410 string msg = string.Format (CultureInfo.CurrentCulture,
411 "DateTimePicker does not support dates after {0}.",
412 MaxDateTime.ToString ("G", CultureInfo.CurrentCulture));
414 throw new ArgumentOutOfRangeException ("MaxDate", msg);
416 throw new ArgumentException (msg, "value");
419 if (max_date != value) {
421 if (Value > max_date) {
423 // invalidate the value inside this control
424 this.Invalidate (date_area_rect);
434 public static DateTime MaximumDateTime {
441 public DateTime MinDate {
443 // If the user tries to set DateTime.MinValue, fix it to
444 // DateTimePicker's minimum.
445 if (value == DateTime.MinValue)
449 if (value > MaxDate) {
451 if (value >= MaxDate) {
453 string msg = string.Format (CultureInfo.CurrentCulture,
454 "'{0}' is not a valid value for 'MinDate'. 'MinDate' "
455 + "must be less than MaxDate.",
456 value.ToString ("G"));
458 throw new ArgumentOutOfRangeException ("MinDate", msg);
460 throw new ArgumentException (msg);
463 if (value < MinDateTime) {
464 string msg = string.Format (CultureInfo.CurrentCulture,
465 "DateTimePicker does not support dates before {0}.",
466 MinDateTime.ToString ("G", CultureInfo.CurrentCulture));
468 throw new ArgumentOutOfRangeException ("MinDate", msg);
470 throw new ArgumentException (msg, "value");
474 if (min_date != value) {
476 if (Value < min_date) {
478 // invalidate the value inside this control
479 this.Invalidate (date_area_rect);
488 public static DateTime MinimumDateTime {
495 [EditorBrowsable (EditorBrowsableState.Never)]
496 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
498 public new Padding Padding {
499 get { return base.Padding; }
500 set { base.Padding = value; }
504 // the prefered height to draw this control using current font
506 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
507 public int PreferredHeight {
509 // Make it proportional
510 return (int) Math.Ceiling (Font.Height * 1.5);
515 [DefaultValue (false)]
517 public virtual bool RightToLeftLayout {
519 return right_to_left_layout;
522 if (right_to_left_layout != value) {
523 right_to_left_layout = value;
524 OnRightToLeftLayoutChanged (EventArgs.Empty);
530 // whether or not the check box is shown
531 [DefaultValue(false)]
532 public bool ShowCheckBox {
534 if (show_check_box != value) {
535 show_check_box = value;
536 // invalidate the value inside this control
537 this.Invalidate (date_area_rect);
541 return show_check_box;
545 // if true show the updown control, else popup the monthcalendar
546 [DefaultValue(false)]
547 public bool ShowUpDown {
549 if (show_up_down != value) {
550 show_up_down = value;
551 // need to invalidate the whole control
561 [EditorBrowsable(EditorBrowsableState.Advanced)]
562 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
563 public override string Text {
565 DateTime parsed_value;
567 if (value == null || value == string.Empty) {
568 date_value = DateTime.Now;
569 OnValueChanged (EventArgs.Empty);
570 OnTextChanged (EventArgs.Empty);
574 if (format == DateTimePickerFormat.Custom) {
575 // TODO: if the format is a custom format we need to do a custom parse here
576 // This implementation will fail if the custom format is set to something that can
577 // be a standard datetime format string
578 // http://msdn2.microsoft.com/en-us/library/az4se3k1.aspx
579 parsed_value = DateTime.ParseExact (value, GetExactFormat (), null);
581 parsed_value = DateTime.ParseExact (value, GetExactFormat (), null);
584 if (date_value != parsed_value) {
585 Value = parsed_value;
589 if (!IsHandleCreated)
592 if (format == DateTimePickerFormat.Custom) {
593 System.Text.StringBuilder result = new System.Text.StringBuilder ();
594 for (int i = 0; i < part_data.Length; i++) {
595 result.Append(part_data[i].GetText(date_value));
597 return result.ToString ();
599 return Value.ToString (GetExactFormat ());
605 [RefreshProperties(RefreshProperties.All)]
606 public DateTime Value {
608 if (date_value != value) {
609 if (value < MinDate || value > MaxDate)
611 throw new ArgumentOutOfRangeException ("value", "value must be between MinDate and MaxDate");
613 throw new ArgumentException ("value", "value must be between MinDate and MaxDate");
617 this.OnValueChanged (EventArgs.Empty);
618 this.Invalidate (date_area_rect);
626 #endregion // public properties
628 #region public methods
630 // just return the text value
631 public override string ToString () {
635 #endregion // public methods
637 #region public events
638 static object CloseUpEvent = new object ();
639 static object DropDownEvent = new object ();
640 static object FormatChangedEvent = new object ();
641 static object ValueChangedEvent = new object ();
643 static object RightToLeftLayoutChangedEvent = new object ();
646 // raised when the monthcalendar is closed
647 public event EventHandler CloseUp {
648 add { Events.AddHandler (CloseUpEvent, value); }
649 remove { Events.RemoveHandler (CloseUpEvent, value); }
652 // raised when the monthcalendar is opened
653 public event EventHandler DropDown {
654 add { Events.AddHandler (DropDownEvent, value); }
655 remove { Events.RemoveHandler (DropDownEvent, value); }
658 // raised when the format of the value is changed
659 public event EventHandler FormatChanged {
660 add { Events.AddHandler (FormatChangedEvent, value); }
661 remove { Events.RemoveHandler (FormatChangedEvent, value); }
664 // raised when the date Value is changed
665 public event EventHandler ValueChanged {
666 add { Events.AddHandler (ValueChangedEvent, value); }
667 remove { Events.RemoveHandler (ValueChangedEvent, value); }
671 [EditorBrowsable(EditorBrowsableState.Never)]
672 public new event EventHandler BackColorChanged {
674 base.BackColorChanged += value;
678 base.BackColorChanged -= value;
683 [EditorBrowsable(EditorBrowsableState.Never)]
684 public new event EventHandler BackgroundImageChanged {
686 base.BackgroundImageChanged += value;
690 base.BackgroundImageChanged -= value;
695 [EditorBrowsable (EditorBrowsableState.Never)]
696 public new event EventHandler BackgroundImageLayoutChanged {
699 base.BackgroundImageLayoutChanged += value;
704 base.BackgroundImageLayoutChanged -= value;
709 [EditorBrowsable (EditorBrowsableState.Never)]
710 public new event EventHandler Click {
720 [EditorBrowsable (EditorBrowsableState.Never)]
721 public new event EventHandler DoubleClick {
723 base.DoubleClick += value;
726 base.DoubleClick -= value;
732 [EditorBrowsable(EditorBrowsableState.Never)]
733 public new event EventHandler ForeColorChanged {
735 base.ForeColorChanged += value;
739 base.ForeColorChanged -= value;
744 [EditorBrowsable (EditorBrowsableState.Never)]
745 public new event MouseEventHandler MouseClick {
747 base.MouseClick += value;
750 base.MouseClick -= value;
755 [EditorBrowsable (EditorBrowsableState.Never)]
756 public new event MouseEventHandler MouseDoubleClick {
758 base.MouseDoubleClick += value;
761 base.MouseDoubleClick -= value;
766 [EditorBrowsable (EditorBrowsableState.Never)]
767 public new event EventHandler PaddingChanged {
770 base.PaddingChanged += value;
774 base.PaddingChanged -= value;
780 [EditorBrowsable(EditorBrowsableState.Never)]
781 public new event PaintEventHandler Paint {
791 public event EventHandler RightToLeftLayoutChanged {
793 Events.AddHandler (RightToLeftLayoutChangedEvent, value);
796 Events.RemoveHandler (RightToLeftLayoutChangedEvent, value);
802 [EditorBrowsable(EditorBrowsableState.Advanced)]
803 public new event EventHandler TextChanged {
805 base.TextChanged += value;
809 base.TextChanged -= value;
812 #endregion // public events
814 #region protected properties
816 // not sure why we're overriding this one
817 protected override CreateParams CreateParams {
819 return base.CreateParams;
823 // specify the default size for this control
824 protected override Size DefaultSize {
826 // todo actually measure this properly
827 return new Size (200, PreferredHeight);
831 #endregion // protected properties
833 #region protected methods
835 // not sure why we're overriding this one
836 protected override AccessibleObject CreateAccessibilityInstance () {
837 return base.CreateAccessibilityInstance ();
840 // not sure why we're overriding this one
841 protected override void CreateHandle () {
842 base.CreateHandle ();
845 // not sure why we're overriding this one
846 protected override void DestroyHandle () {
847 base.DestroyHandle ();
851 // not sure why we're overriding this one
852 protected override void Dispose (bool disposing) {
853 updown_timer.Dispose ();
854 base.Dispose (disposing);
858 // find out if this key is an input key for us, depends on which date part is focused
859 protected override bool IsInputKey (Keys keyData) {
871 // raises the CloseUp event
872 protected virtual void OnCloseUp (EventArgs eventargs) {
873 EventHandler eh = (EventHandler)(Events [CloseUpEvent]);
875 eh (this, eventargs);
878 // raise the drop down event
879 protected virtual void OnDropDown (EventArgs eventargs) {
880 EventHandler eh = (EventHandler)(Events [DropDownEvent]);
882 eh (this, eventargs);
885 protected override void OnFontChanged(EventArgs e) {
886 // FIXME - do we need to update/invalidate/recalc our stuff?
887 month_calendar.Font = Font;
888 Size = new Size (Size.Width, PreferredHeight);
890 base.OnFontChanged (e);
893 // raises the format changed event
894 protected virtual void OnFormatChanged (EventArgs e) {
895 EventHandler eh = (EventHandler)(Events [FormatChangedEvent]);
900 protected override void OnHandleCreated (EventArgs e) {
901 base.OnHandleCreated(e);
903 protected override void OnHandleDestroyed (EventArgs e) {
904 base.OnHandleDestroyed(e);
907 [EditorBrowsable (EditorBrowsableState.Advanced)]
908 protected virtual void OnRightToLeftLayoutChanged (EventArgs e) {
909 EventHandler eh = (EventHandler) Events [RightToLeftLayoutChangedEvent];
914 // not sure why we're overriding this one
915 protected override void OnSystemColorsChanged (EventArgs e) {
916 base.OnSystemColorsChanged (e);
919 // raise the ValueChanged event
920 protected virtual void OnValueChanged (EventArgs eventargs) {
921 EventHandler eh = (EventHandler)(Events [ValueChangedEvent]);
923 eh (this, eventargs);
926 // overridden to set the bounds of this control properly
927 protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified) {
928 // TODO: ensure I implemented the bounds core setting properly.
929 if ((specified & BoundsSpecified.Height) == BoundsSpecified.Height ||
930 (specified & BoundsSpecified.Size) == BoundsSpecified.Size) {
931 base.SetBoundsCore (x, y, width, DefaultSize.Height, specified);
933 base.SetBoundsCore (x, y, width, height, specified);
936 // need to set the rectangles for all the support internal rects
937 // this is done here as a optimisation since this is an array of rects
938 if ((specified & BoundsSpecified.X) == BoundsSpecified.X ||
939 (specified & BoundsSpecified.Y) == BoundsSpecified.Y) {
940 // TODO set up all the datepart rects
944 // not sure why we're overriding this
945 protected override void WndProc (ref Message m) {
946 base.WndProc (ref m);
949 #endregion // protected methods
951 #region internal / private properties
953 // this is the region that the date and the check box is drawn on
954 internal Rectangle date_area_rect {
956 Rectangle rect = this.ClientRectangle;
958 // set the space to the left of the up/down button
959 if (rect.Width > (up_down_width + 4)) {
960 rect.Width -= (up_down_width + 4);
965 // set the space to the left of the up/down button
966 // TODO make this use up down button
967 if (rect.Width > (SystemInformation.VerticalScrollBarWidth + 4)) {
968 rect.Width -= SystemInformation.VerticalScrollBarWidth;
974 rect.Inflate (-2, -2);
979 internal Rectangle CheckBoxRect {
981 Rectangle retval = new Rectangle (check_box_space, ClientSize.Height / 2 - check_box_size / 2,
982 check_box_size, check_box_size);
987 // the rectangle for the drop down arrow
988 internal Rectangle drop_down_arrow_rect {
990 Rectangle rect = this.ClientRectangle;
991 rect.X = rect.Right - SystemInformation.VerticalScrollBarWidth - 2;
992 if (rect.Width > (SystemInformation.VerticalScrollBarWidth + 2)) {
993 rect.Width = SystemInformation.VerticalScrollBarWidth;
995 rect.Width = Math.Max (rect.Width - 2, 0);
998 rect.Inflate (0, -2);
1003 // the part of the date that is currently hilighted
1004 internal Rectangle hilight_date_area {
1006 // TODO: put hilighted part calculation in here
1007 return Rectangle.Empty;
1013 #region internal / private methods
1015 private void ResizeHandler (object sender, EventArgs e)
1020 private void UpDownTimerTick (object sender, EventArgs e)
1022 if (updown_timer.Interval == initial_timer_delay)
1023 updown_timer.Interval = subsequent_timer_delay;
1025 if (is_down_pressed)
1026 IncrementSelectedPart (-1);
1027 else if (is_up_pressed)
1028 IncrementSelectedPart (1);
1030 updown_timer.Enabled = false;
1033 // calculates the maximum width
1034 internal Single CalculateMaxWidth(string format, Graphics gr, StringFormat string_format)
1039 Font font = this.Font;
1047 for (int i = 1; i <= 12; i++) {
1048 text = PartData.GetText (Value.AddMonths (i), format);
1049 size = gr.MeasureString (text, font, int.MaxValue, string_format);
1050 result = Math.Max (result, size.Width);
1057 for (int i = 1; i <= 12; i++) {
1058 text = PartData.GetText (Value.AddDays (i), format);
1059 size = gr.MeasureString (text, font, int.MaxValue, string_format);
1060 result = Math.Max (result, size.Width);
1065 for (int i = 1; i <= 12; i++) {
1066 text = PartData.GetText (Value.AddHours (i), format);
1067 size = gr.MeasureString (text, font, int.MaxValue, string_format);
1068 result = Math.Max (result, size.Width);
1073 for (int i = 1; i <= 24; i++) {
1074 text = PartData.GetText (Value.AddDays (i), format);
1075 size = gr.MeasureString (text, font, int.MaxValue, string_format);
1076 result = Math.Max (result, size.Width);
1081 for (int i = 1; i <= 60; i++) {
1082 text = PartData.GetText (Value.AddMinutes (i), format);
1083 size = gr.MeasureString (text, font, int.MaxValue, string_format);
1084 result = Math.Max (result, size.Width);
1089 for (int i = 1; i <= 60; i++) {
1090 text = PartData.GetText (Value.AddSeconds (i), format);
1091 size = gr.MeasureString (text, font, int.MaxValue, string_format);
1092 result = Math.Max (result, size.Width);
1097 for (int i = 1; i <= 2; i++) {
1098 text = PartData.GetText (Value.AddHours (i * 12), format);
1099 size = gr.MeasureString (text, font, int.MaxValue, string_format);
1100 result = Math.Max (result, size.Width);
1106 for (int i = 1; i <= 10; i++) {
1107 text = PartData.GetText (Value.AddYears (i), format);
1108 size = gr.MeasureString (text, font, int.MaxValue, string_format);
1109 result = Math.Max (result, size.Width);
1113 return gr.MeasureString (format, font, int.MaxValue, string_format).Width;
1117 // returns the format of the date as a string
1118 // (i.e. resolves the Format enum values to it's corresponding string format)
1119 // Why CurrentCulture and not CurrentUICulture is explained here:
1120 // http://blogs.msdn.com/michkap/archive/2007/01/11/1449754.aspx
1121 private string GetExactFormat()
1123 switch (this.format) {
1124 case DateTimePickerFormat.Long:
1125 return Threading.Thread.CurrentThread.CurrentCulture.DateTimeFormat.LongDatePattern;
1126 case DateTimePickerFormat.Short:
1127 return Threading.Thread.CurrentThread.CurrentCulture.DateTimeFormat.ShortDatePattern;
1128 case DateTimePickerFormat.Time:
1129 return Threading.Thread.CurrentThread.CurrentCulture.DateTimeFormat.LongTimePattern;
1130 case DateTimePickerFormat.Custom:
1131 return this.custom_format == null ? String.Empty : this.custom_format;
1133 return Threading.Thread.CurrentThread.CurrentCulture.DateTimeFormat.LongDatePattern;
1137 private void CalculateFormats()
1140 System.Text.StringBuilder literal = new System.Text.StringBuilder ();
1141 System.Collections.ArrayList formats = new ArrayList ();
1142 bool is_literal = false;
1143 char lastch = (char) 0;
1146 real_format = GetExactFormat ();
1148 // parse the format string
1149 for (int i = 0; i < real_format.Length; i++)
1151 ch = real_format [i];
1153 if (is_literal && ch != '\'')
1155 literal.Append (ch);
1169 case 'g': // Spec says nothing about g, but it seems to be treated like spaces.
1170 if (!(lastch == ch || lastch == 0) && literal.Length != 0)
1172 formats.Add (new PartData(literal.ToString (), false));
1175 literal.Append (ch);
1178 if (is_literal && i < real_format.Length - 1 && real_format [i + 1] == '\'') {
1179 literal.Append (ch);
1183 if (literal.Length == 0) {
1184 is_literal = !is_literal;
1187 formats.Add (new PartData (literal.ToString (), is_literal));
1189 is_literal = !is_literal;
1192 if (literal.Length != 0)
1194 formats.Add (new PartData(literal.ToString (), false));
1197 formats.Add (new PartData (ch.ToString(), true));
1203 if (literal.Length >= 0)
1204 formats.Add (new PartData (literal.ToString (), is_literal));
1206 part_data = new PartData [formats.Count];
1207 formats.CopyTo (part_data);
1210 private Point CalculateDropDownLocation (Rectangle parent_control_rect, Size child_size, bool align_left)
1212 // default bottom left
1213 Point location = new Point(parent_control_rect.Left + 5, parent_control_rect.Bottom);
1214 // now adjust the alignment
1216 location.X = parent_control_rect.Right - child_size.Width;
1219 Point screen_location = PointToScreen (location);
1220 Rectangle working_area = Screen.FromControl(this).WorkingArea;
1221 // now adjust if off the right side of the screen
1222 if (screen_location.X < working_area.X) {
1223 screen_location.X = working_area.X;
1225 // now adjust if it should be displayed above control
1226 if (screen_location.Y + child_size.Height > working_area.Bottom) {
1227 screen_location.Y -= (parent_control_rect.Height + child_size.Height);
1230 // since the parent of the month calendar is the form, adjust accordingly.
1231 if (month_calendar.Parent != null) {
1232 screen_location = month_calendar.Parent.PointToClient(screen_location);
1235 return screen_location;
1238 // actually draw this control
1239 internal void Draw (Rectangle clip_rect, Graphics dc)
1241 ThemeEngine.Current.DrawDateTimePicker (dc, clip_rect, this);
1244 // drop the calendar down
1245 internal void DropDownMonthCalendar ()
1247 // ensure the right date is set for the month_calendar
1248 month_calendar.SetDate (this.date_value);
1249 // get a rectangle that has the dimensions of the text area,
1250 // but the height of the dtp control.
1251 Rectangle align_area = this.date_area_rect;
1252 align_area.Y = this.ClientRectangle.Y;
1253 align_area.Height = this.ClientRectangle.Height;
1255 // establish the month calendar's location
1256 month_calendar.Location = CalculateDropDownLocation (
1258 month_calendar.Size,
1259 (this.DropDownAlign == LeftRightAlignment.Left));
1260 month_calendar.Show ();
1261 month_calendar.Focus ();
1262 month_calendar.Capture = true;
1264 // fire any registered events
1265 // XXX should this just call OnDropDown?
1266 EventHandler eh = (EventHandler)(Events [DropDownEvent]);
1268 eh (this, EventArgs.Empty);
1271 // hide the month calendar
1272 internal void HideMonthCalendar ()
1274 this.is_drop_down_visible = false;
1275 Invalidate (drop_down_arrow_rect);
1276 month_calendar.Capture = false;
1277 if (month_calendar.Visible) {
1278 month_calendar.Hide ();
1282 private int GetSelectedPartIndex()
1284 for (int i = 0; i < part_data.Length; i++)
1286 if (part_data[i].is_selected && !part_data[i].is_literal)
1292 private void IncrementSelectedPart(int delta)
1294 int selected_index = GetSelectedPartIndex();
1296 if (selected_index == -1) {
1300 switch (part_data[selected_index].value)
1303 case "dd": // number day formats
1306 SetPart(DateTime.DaysInMonth(Value.Year, Value.Month), 'd');
1308 SetPart(Value.Day + delta, 'd');
1310 if (Value.Day == DateTime.DaysInMonth(Value.Year, Value.Month))
1313 SetPart(Value.Day + delta, 'd') ;
1317 case "dddd": // text day formats
1318 Value = Value.AddDays(delta);
1323 case "HH": // hour formats
1324 SetPart(Value.Hour + delta, 'h');
1327 case "mm": // minute formats
1328 SetPart(Value.Minute + delta, 'm');
1333 case "MMMM": // month formats
1334 SetPart(Value.Month + delta, 'M');
1337 case "ss": // second format
1338 SetPart(Value.Second + delta, 's');
1341 case "tt": // AM / PM specifier
1342 SetPart(Value.Hour + delta * 12, 'h');
1348 SetPart(Value.Year + delta, 'y');
1353 private void SelectNextPart()
1356 if (is_checkbox_selected) {
1357 for (int i = 0; i < part_data.Length; i++)
1359 if (!part_data[i].is_literal)
1361 is_checkbox_selected = false;
1362 part_data[i].is_selected = true;
1368 selected_index = GetSelectedPartIndex();
1369 if (selected_index >= 0)
1370 part_data[selected_index].is_selected = false;
1371 for (int i = selected_index + 1; i < part_data.Length; i++)
1373 if (!part_data[i].is_literal)
1375 part_data[i].is_selected = true;
1380 if (GetSelectedPartIndex() == -1)
1381 { // if no part was found before the end, look from the beginning
1384 is_checkbox_selected = true;
1389 for (int i = 0; i <= selected_index; i++)
1391 if (!part_data[i].is_literal)
1393 part_data[i].is_selected = true;
1404 private void SelectPreviousPart()
1406 if (is_checkbox_selected)
1408 for (int i = part_data.Length - 1; i >= 0; i--)
1410 if (!part_data[i].is_literal)
1412 is_checkbox_selected = false;
1413 part_data[i].is_selected = true;
1421 int selected_index = GetSelectedPartIndex();
1423 if (selected_index >= 0)
1424 part_data[selected_index].is_selected = false;
1426 for (int i = selected_index - 1; i >= 0; i--)
1428 if (!part_data[i].is_literal)
1430 part_data[i].is_selected = true;
1435 if (GetSelectedPartIndex() == -1)
1436 { // if no part was found before the beginning, look from the end
1439 is_checkbox_selected = true;
1444 for (int i = part_data.Length - 1; i >= selected_index; i--)
1446 if (!part_data[i].is_literal)
1448 part_data[i].is_selected = true;
1458 // raised by key down events.
1459 private void KeyDownHandler(object sender, KeyEventArgs e)
1466 if (ShowCheckBox && Checked == false)
1468 IncrementSelectedPart(1);
1475 if (ShowCheckBox && Checked == false)
1477 IncrementSelectedPart(-1);
1482 {// select the next part to the left
1483 if (ShowCheckBox && Checked == false)
1485 SelectPreviousPart();
1490 {// select the next part to the right
1491 if (ShowCheckBox && Checked == false)
1498 if (!e.Alt && !is_drop_down_visible) {
1499 DropDownMonthCalendar ();
1507 // raised by any key down events
1508 private void KeyPressHandler (object sender, KeyPressEventArgs e)
1510 switch (e.KeyChar) {
1512 if (is_checkbox_selected)
1527 int number = e.KeyChar - (int) '0';
1528 int selected_index = GetSelectedPartIndex();
1529 if (selected_index == -1)
1531 if (!part_data[selected_index].is_numeric_format)
1533 switch (part_data[selected_index].value)
1537 int newDay = Value.Day * 10 + number;
1538 if (DateTime.DaysInMonth(Value.Year, Value.Month) < newDay)
1540 SetPart(newDay, 'd');
1544 int newMonth = Value.Month * 10 + number;
1547 SetPart(newMonth, 'M');
1552 int newYear = Value.Year * 10 + number;
1555 SetPart(newYear, 'y');
1561 int newHour = Value.Hour * 10 + number;
1564 SetPart(newHour, 'h');
1568 int newMinute = Value.Minute* 10 + number;
1569 if (newMinute >= 60)
1571 SetPart(newMinute, 'm');
1575 int newSecond = Value.Second * 10 + number;
1576 if (newSecond >= 60)
1578 SetPart(newSecond, 's');
1589 // set the specified part of the date to the specified value
1590 private void SetPart(int value, char part)
1594 case 's': // seconds
1598 if (value >= 0 && value <= 59)
1599 Value = new DateTime(Value.Year, Value.Month, Value.Day, Value.Hour, Value.Minute, value, Value.Millisecond);
1601 case 'm': // minutes
1605 if (value >= 0 && value <= 59)
1606 Value = new DateTime(Value.Year, Value.Month, Value.Day, Value.Hour, value, Value.Second, Value.Millisecond);
1613 if (value >= 0 && value <= 23)
1614 Value = new DateTime(Value.Year, Value.Month, Value.Day, value, Value.Minute, Value.Second, Value.Millisecond);
1617 int max_days = DateTime.DaysInMonth(Value.Year, Value.Month);
1618 if (value > max_days)
1619 Value = new DateTime(Value.Year, Value.Month, max_days, Value.Hour, Value.Minute, Value.Second, Value.Millisecond);
1620 if (value >= 1 && value <= 31)
1621 Value = new DateTime(Value.Year, Value.Month, value, Value.Hour, Value.Minute, Value.Second, Value.Millisecond);
1628 if (value >= 1 && value <= 12) {
1629 // if we move from say december to november with days on 31, we must
1630 // remap to the maximum number of days
1631 int days_in_new_month = DateTime.DaysInMonth (Value.Year, value);
1633 if (Value.Day > days_in_new_month)
1634 Value = new DateTime (Value.Year, value, days_in_new_month, Value.Hour, Value.Minute, Value.Second, Value.Millisecond);
1636 Value = new DateTime (Value.Year, value, Value.Day, Value.Hour, Value.Minute, Value.Second, Value.Millisecond);
1642 if (value > 0 && value <= 9999) {
1643 // if we move to a leap year, the days in month could throw an exception
1644 int days_in_new_month = DateTime.DaysInMonth (value, Value.Month);
1646 if (Value.Day > days_in_new_month)
1647 Value = new DateTime (value, Value.Month, days_in_new_month, Value.Hour, Value.Minute, Value.Second, Value.Millisecond);
1649 Value = new DateTime (value, Value.Month, Value.Day, Value.Hour, Value.Minute, Value.Second, Value.Millisecond);
1655 // if we loose focus deselect any selected parts.
1656 private void LostFocusHandler (object sender, EventArgs e)
1658 int selected_index = GetSelectedPartIndex ();
1659 if (selected_index != -1)
1661 part_data [selected_index].is_selected = false;
1662 Rectangle invalidate_rect = Rectangle.Ceiling (part_data [selected_index].drawing_rectangle);
1663 invalidate_rect.Inflate (2, 2);
1664 Invalidate (invalidate_rect);
1666 else if (is_checkbox_selected)
1668 is_checkbox_selected = false;
1669 Invalidate (CheckBoxRect);
1673 // if month calendar looses focus and the drop down is up, then close it
1674 private void MonthCalendarLostFocusHandler(object sender, EventArgs e)
1676 if (is_drop_down_visible && !month_calendar.Focused)
1678 //this.HideMonthCalendar();
1679 //This is handled from the monthcalender itself,
1680 //it may loose focus, but still has to be visible,
1681 //for instance when the context menu is displayed.
1686 private void MonthCalendarDateChangedHandler (object sender, DateRangeEventArgs e)
1688 if (month_calendar.Visible)
1689 this.Value = e.Start.Date.Add (this.Value.TimeOfDay);
1692 // fired when a user clicks on the month calendar to select a date
1693 private void MonthCalendarDateSelectedHandler (object sender, DateRangeEventArgs e)
1695 this.HideMonthCalendar ();
1699 private void MouseUpHandler(object sender, MouseEventArgs e)
1703 if (is_up_pressed || is_down_pressed)
1705 updown_timer.Enabled = false;
1706 is_up_pressed = false;
1707 is_down_pressed = false;
1708 Invalidate (drop_down_arrow_rect);
1713 // to check if the mouse has come down on this control
1714 private void MouseDownHandler (object sender, MouseEventArgs e)
1716 // Only left clicks are handled.
1717 if (e.Button != MouseButtons.Left)
1720 is_checkbox_selected = false;
1722 if (ShowCheckBox && CheckBoxRect.Contains(e.X, e.Y))
1724 is_checkbox_selected = true;
1730 if (ShowUpDown && drop_down_arrow_rect.Contains (e.X, e.Y))
1732 if (!(ShowCheckBox && Checked == false))
1734 if (e.Y < this.Height / 2) {
1735 is_up_pressed = true;
1736 is_down_pressed = false;
1737 IncrementSelectedPart (1);
1739 is_up_pressed = false;
1740 is_down_pressed = true;
1741 IncrementSelectedPart (-1);
1743 Invalidate (drop_down_arrow_rect);
1744 updown_timer.Interval = initial_timer_delay;
1745 updown_timer.Enabled = true;
1747 } else if (is_drop_down_visible == false && drop_down_arrow_rect.Contains (e.X, e.Y)) {
1748 is_drop_down_visible = true;
1751 Invalidate (drop_down_arrow_rect);
1752 DropDownMonthCalendar ();
1754 // mouse down on this control anywhere else collapses it
1755 if (is_drop_down_visible) {
1756 HideMonthCalendar ();
1759 if (!(ShowCheckBox && Checked == false))
1761 // go through the parts to see if the click is in any of them
1762 bool invalidate_afterwards = false;
1763 for (int i = 0; i < part_data.Length; i++) {
1764 bool old = part_data [i].is_selected;
1766 if (part_data [i].is_literal)
1769 if (part_data [i].drawing_rectangle.Contains (e.X, e.Y)) {
1770 part_data [i].is_selected = true;
1772 part_data [i].is_selected = false;
1774 if (old != part_data [i].is_selected)
1775 invalidate_afterwards = true;
1777 if (invalidate_afterwards)
1785 // paint this control now
1786 private void PaintHandler (object sender, PaintEventArgs pe) {
1787 if (Width <= 0 || Height <= 0 || Visible == false)
1790 Draw (pe.ClipRectangle, pe.Graphics);
1795 #region internal classes
1796 internal class PartData
1798 internal string value;
1799 internal bool is_literal;
1800 internal bool is_selected;
1801 internal RectangleF drawing_rectangle;
1803 internal bool is_numeric_format
1835 internal PartData(string value, bool is_literal)
1838 this.is_literal = is_literal;
1841 // calculate the string to show for this data
1842 internal string GetText(DateTime date)
1847 return GetText (date, value);
1851 static internal string GetText(DateTime date, string format)
1853 if (format.StartsWith ("g"))
1855 else if (format.Length == 1)
1856 return date.ToString ("%" + format);
1857 else if (format == "yyyyy" || format == "yyyyyy" || format == "yyyyyyy" || format == "yyyyyyyy")
1858 return date.ToString ("yyyy");
1859 else if (format.Length > 1)
1860 return date.ToString (format);
1862 return string.Empty;