2007-03-19 Chris Toshok <toshok@ximian.com>
[mono-project.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / DateTimePicker.cs
blobcfdc343b2272d0f8221144e39fe3915381ded816
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:
8 //
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
11 //
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.
22 // Authors:
23 // John BouAntoun jba-mono@optusnet.com.au
24 // Rolf Bjarne Kvinge rolfkvinge@ya.com
26 // TODO:
27 // - wire in all events from monthcalendar
30 using System;
31 using System.Drawing;
32 using System.Collections;
33 using System.ComponentModel;
34 using System.Runtime.InteropServices;
35 using System.Windows.Forms;
37 namespace System.Windows.Forms {
38 #if NET_2_0
39 [ClassInterface (ClassInterfaceType.AutoDispatch)]
40 [DefaultBindingProperty ("Value")]
41 [ComVisible (true)]
42 #endif
43 [DefaultEvent("ValueChanged")]
44 [DefaultProperty("Value")]
45 [Designer("System.Windows.Forms.Design.DateTimePickerDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")]
46 public class DateTimePicker : Control {
48 #region Public variables
50 // this class has to have the specified hour, minute and second, as it says in msdn
51 #if NET_2_0
52 [EditorBrowsable (EditorBrowsableState.Never)]
53 [Browsable (false)]
54 #endif
55 public static readonly DateTime MaxDateTime = new DateTime (9998, 12, 31, 0, 0, 0);
56 #if NET_2_0
57 [EditorBrowsable (EditorBrowsableState.Never)]
58 [Browsable (false)]
59 #endif
60 public static readonly DateTime MinDateTime = new DateTime (1753, 1, 1);
62 internal const int check_box_size = 13;
63 internal const int check_box_space = 4;
65 #endregion // Public variables
67 #region Local variables
69 protected static readonly Color DefaultMonthBackColor = ThemeEngine.Current.ColorWindow;
70 protected static readonly Color DefaultTitleBackColor = ThemeEngine.Current.ColorActiveCaption;
71 protected static readonly Color DefaultTitleForeColor = ThemeEngine.Current.ColorActiveCaptionText;
72 protected static readonly Color DefaultTrailingForeColor = SystemColors.GrayText;
74 internal MonthCalendar month_calendar;
75 bool is_checked;
76 string custom_format;
77 LeftRightAlignment drop_down_align;
78 DateTimePickerFormat format;
79 DateTime max_date;
80 DateTime min_date;
81 bool show_check_box;
82 bool show_up_down;
83 DateTime date_value;
84 #if NET_2_0
85 bool right_to_left_layout;
86 #endif
87 // variables used for drawing and such
88 internal const int up_down_width = check_box_size;
89 internal bool is_drop_down_visible;
90 internal bool is_up_pressed;
91 internal bool is_down_pressed;
92 internal Timer updown_timer;
93 internal const int initial_timer_delay = 500;
94 internal const int subsequent_timer_delay = 100;
95 internal bool is_checkbox_selected;
97 // variables for determining how to format the string
98 internal PartData[] part_data;
100 #endregion // Local variables
102 #region DateTimePickerAccessibleObject Subclass
103 [ComVisible(true)]
104 public class DateTimePickerAccessibleObject : ControlAccessibleObject {
105 #region DateTimePickerAccessibleObject Local Variables
106 private new DateTimePicker owner;
107 #endregion // DateTimePickerAccessibleObject Local Variables
109 #region DateTimePickerAccessibleObject Constructors
110 public DateTimePickerAccessibleObject(DateTimePicker owner) : base(owner) {
111 this.owner = owner;
113 #endregion // DateTimePickerAccessibleObject Constructors
115 #region DateTimePickerAccessibleObject Properties
116 #if NET_2_0
117 public override string KeyboardShortcut {
118 get {
119 return base.KeyboardShortcut;
123 public override AccessibleRole Role {
124 get {
125 return base.Role;
128 #endif
129 public override AccessibleStates State {
130 get {
131 AccessibleStates retval;
133 retval = AccessibleStates.Default;
135 if (owner.Checked) {
136 retval |= AccessibleStates.Checked;
139 return retval;
143 public override string Value {
144 get {
145 return owner.Text;
148 #endregion // DateTimePickerAccessibleObject Properties
150 #endregion // DateTimePickerAccessibleObject Sub-class
152 #region public constructors
154 // only public constructor
155 public DateTimePicker () {
157 // initialise the month calendar
158 month_calendar = new MonthCalendar (this);
159 month_calendar.CalendarDimensions = new Size (1, 1);
160 month_calendar.MaxSelectionCount = 1;
161 month_calendar.ForeColor = Control.DefaultForeColor;
162 month_calendar.BackColor = DefaultMonthBackColor;
163 month_calendar.TitleBackColor = DefaultTitleBackColor;
164 month_calendar.TitleForeColor = DefaultTitleForeColor;
165 month_calendar.TrailingForeColor = DefaultTrailingForeColor;
166 month_calendar.Visible = false;
167 // initialize the timer
168 updown_timer = new Timer();
169 updown_timer.Interval = initial_timer_delay;
172 // initialise other variables
173 is_checked = true;
174 custom_format = null;
175 drop_down_align = LeftRightAlignment.Left;
176 format = DateTimePickerFormat.Long;
177 max_date = MaxDateTime;
178 min_date = MinDateTime;
179 show_check_box = false;
180 show_up_down = false;
181 date_value = DateTime.Now;
183 is_drop_down_visible = false;
184 BackColor = SystemColors.Window;
185 ForeColor = SystemColors.WindowText;
187 month_calendar.DateChanged += new DateRangeEventHandler (MonthCalendarDateChangedHandler);
188 month_calendar.DateSelected += new DateRangeEventHandler (MonthCalendarDateSelectedHandler);
189 month_calendar.LostFocus += new EventHandler (MonthCalendarLostFocusHandler);
190 updown_timer.Tick += new EventHandler (UpDownTimerTick);
191 KeyPress += new KeyPressEventHandler (KeyPressHandler);
192 KeyDown += new KeyEventHandler (KeyDownHandler);
193 LostFocus += new EventHandler (LostFocusHandler);
194 MouseDown += new MouseEventHandler (MouseDownHandler);
195 MouseUp += new MouseEventHandler (MouseUpHandler);
196 Paint += new PaintEventHandler (PaintHandler);
197 Resize += new EventHandler (ResizeHandler);
198 SetStyle (ControlStyles.UserPaint | ControlStyles.StandardClick, false);
199 SetStyle (ControlStyles.FixedHeight, true);
200 SetStyle (ControlStyles.Selectable, true);
202 CalculateFormats ();
205 #endregion
207 #region public properties
209 [Browsable(false)]
210 [EditorBrowsable(EditorBrowsableState.Never)]
211 public override Color BackColor {
212 set {
213 base.BackColor = value;
215 get {
216 return base.BackColor;
220 [Browsable(false)]
221 [EditorBrowsable(EditorBrowsableState.Never)]
222 public override Image BackgroundImage {
223 set {
224 base.BackgroundImage = value;
226 get {
227 return base.BackgroundImage;
231 #if NET_2_0
232 [Browsable (false)]
233 [EditorBrowsable (EditorBrowsableState.Never)]
234 public override ImageLayout BackgroundImageLayout {
235 get{
236 return base.BackgroundImageLayout;
238 set {
239 base.BackgroundImageLayout = value;
242 #endif
244 [AmbientValue(null)]
245 [Localizable(true)]
246 public Font CalendarFont {
247 set {
248 month_calendar.Font = value;
250 get {
251 return month_calendar.Font;
255 public Color CalendarForeColor {
256 set {
257 month_calendar.ForeColor = value;
259 get {
260 return month_calendar.ForeColor;
264 public Color CalendarMonthBackground {
265 set {
266 month_calendar.BackColor = value;
268 get {
269 return month_calendar.BackColor;
273 public Color CalendarTitleBackColor {
274 set {
275 month_calendar.TitleBackColor = value;
277 get {
278 return month_calendar.TitleBackColor;
282 public Color CalendarTitleForeColor {
283 set {
284 month_calendar.TitleForeColor = value;
286 get {
287 return month_calendar.TitleForeColor;
291 public Color CalendarTrailingForeColor {
292 set {
293 month_calendar.TrailingForeColor = value;
295 get {
296 return month_calendar.TrailingForeColor;
300 // when checked the value is grayed out
301 [Bindable(true)]
302 [DefaultValue(true)]
303 public bool Checked {
304 set {
305 if (is_checked != value) {
306 is_checked = value;
307 // invalidate the value inside this control
308 if (ShowCheckBox) {
309 for (int i = 0; i < part_data.Length; i++)
310 part_data [i].is_selected = false;
311 Invalidate (date_area_rect);
315 get {
316 return is_checked;
320 // the custom format string to format this control with
321 #if NET_2_0
322 [Localizable (true)]
323 #endif
324 [DefaultValue(null)]
325 [RefreshProperties(RefreshProperties.Repaint)]
326 public string CustomFormat {
327 set {
328 if (custom_format != value) {
329 custom_format = value;
330 if (this.Format == DateTimePickerFormat.Custom) {
331 CalculateFormats ();
335 get {
336 return custom_format;
340 #if NET_2_0
341 [EditorBrowsable (EditorBrowsableState.Never)]
342 protected override bool DoubleBuffered {
343 get {
344 return base.DoubleBuffered;
346 set {
347 base.DoubleBuffered = value;
350 #endif
352 // which side the drop down is to be aligned on
353 [DefaultValue(LeftRightAlignment.Left)]
354 [Localizable(true)]
355 public LeftRightAlignment DropDownAlign {
356 set {
357 if (drop_down_align != value) {
358 drop_down_align = value;
361 get {
362 return drop_down_align;
366 [Browsable(false)]
367 [EditorBrowsable(EditorBrowsableState.Never)]
368 public override Color ForeColor {
369 set {
370 base.ForeColor = value;
372 get {
373 return base.ForeColor;
377 // the format of the date time picker text, default is long
378 [RefreshProperties(RefreshProperties.Repaint)]
379 public DateTimePickerFormat Format {
380 set {
381 if (format != value) {
382 format = value;
383 RecreateHandle (); // MS recreates the handle on every format change.
384 CalculateFormats ();
385 this.OnFormatChanged (EventArgs.Empty);
386 // invalidate the value inside this control
387 this.Invalidate (date_area_rect);
390 get {
391 return format;
395 public DateTime MaxDate {
396 set {
397 if (value < min_date) {
398 throw new ArgumentException ();
400 if (value > MaxDateTime) {
401 throw new SystemException ();
403 if (max_date != value) {
404 max_date = value;
406 // TODO: verify this is correct behaviour when value > max date
407 if (Value > max_date) {
408 Value = max_date;
409 // invalidate the value inside this control
410 this.Invalidate (date_area_rect);
414 get {
415 return max_date;
419 #if NET_2_0
420 public static DateTime MaximumDateTime {
421 get {
422 return MaxDateTime;
425 #endif
427 public DateTime MinDate {
428 set {
429 if (value < min_date) {
430 throw new ArgumentException ();
432 if (value < MinDateTime) {
433 throw new SystemException ();
435 if (min_date != value) {
436 min_date = value;
438 // TODO: verify this is correct behaviour when value > max date
439 if (Value < min_date) {
440 Value = min_date;
441 // invalidate the value inside this control
442 this.Invalidate (date_area_rect);
446 get {
447 return min_date;
450 #if NET_2_0
451 public static DateTime MinimumDateTime {
452 get {
453 return MinDateTime;
456 #endif
457 #if NET_2_0
458 [EditorBrowsable (EditorBrowsableState.Never)]
459 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
460 [Browsable (false)]
461 public new Padding Padding {
462 get { return base.Padding; }
463 set { base.Padding = value; }
465 #endif
467 // the prefered height to draw this control using current font
468 [Browsable(false)]
469 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
470 public int PreferredHeight {
471 get {
472 // Make it proportional
473 return (int) Math.Ceiling (Font.Height * 1.5);
477 #if NET_2_0
478 [DefaultValue (false)]
479 [Localizable (true)]
480 public virtual bool RightToLeftLayout {
481 get {
482 return right_to_left_layout;
484 set {
485 if (right_to_left_layout != value) {
486 right_to_left_layout = value;
487 OnRightToLeftLayoutChanged (EventArgs.Empty);
491 #endif
493 // whether or not the check box is shown
494 [DefaultValue(false)]
495 public bool ShowCheckBox {
496 set {
497 if (show_check_box != value) {
498 show_check_box = value;
499 // invalidate the value inside this control
500 this.Invalidate (date_area_rect);
503 get {
504 return show_check_box;
508 // if true show the updown control, else popup the monthcalendar
509 [DefaultValue(false)]
510 public bool ShowUpDown {
511 set {
512 if (show_up_down != value) {
513 show_up_down = value;
514 // need to invalidate the whole control
515 this.Invalidate ();
518 get {
519 return show_up_down;
523 [Browsable(false)]
524 [EditorBrowsable(EditorBrowsableState.Advanced)]
525 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
526 public override string Text {
527 set {
528 DateTime parsed_value;
530 if (value == null || value == string.Empty) {
531 Value = DateTime.Now;
532 OnTextChanged (EventArgs.Empty);
533 return;
536 if (format == DateTimePickerFormat.Custom) {
537 // TODO: if the format is a custom format we need to do a custom parse here
538 // This implementation will fail if the custom format is set to something that can
539 // be a standard datetime format string
540 // http://msdn2.microsoft.com/en-us/library/az4se3k1.aspx
541 parsed_value = DateTime.ParseExact (value, GetExactFormat (), null);
542 } else {
543 parsed_value = DateTime.ParseExact (value, GetExactFormat (), null);
546 if (date_value != parsed_value) {
547 Value = parsed_value;
550 get {
551 if (!IsHandleCreated)
552 return "";
554 if (format == DateTimePickerFormat.Custom) {
555 System.Text.StringBuilder result = new System.Text.StringBuilder ();
556 for (int i = 0; i < part_data.Length; i++) {
557 result.Append(part_data[i].GetText(date_value));
559 return result.ToString ();
560 } else {
561 return Value.ToString (GetExactFormat ());
566 [Bindable(true)]
567 [RefreshProperties(RefreshProperties.All)]
568 public DateTime Value {
569 set {
570 if (date_value != value) {
571 date_value = value;
572 this.OnValueChanged (EventArgs.Empty);
573 this.Invalidate (date_area_rect);
576 get {
577 return date_value;
581 #endregion // public properties
583 #region public methods
585 // just return the text value
586 public override string ToString () {
587 return this.Text;
590 #endregion // public methods
592 #region public events
593 static object CloseUpEvent = new object ();
594 static object DropDownEvent = new object ();
595 static object FormatChangedEvent = new object ();
596 static object ValueChangedEvent = new object ();
597 #if NET_2_0
598 static object RightToLeftLayoutChangedEvent = new object ();
599 #endif
601 // raised when the monthcalendar is closed
602 public event EventHandler CloseUp {
603 add { Events.AddHandler (CloseUpEvent, value); }
604 remove { Events.RemoveHandler (CloseUpEvent, value); }
607 // raised when the monthcalendar is opened
608 public event EventHandler DropDown {
609 add { Events.AddHandler (DropDownEvent, value); }
610 remove { Events.RemoveHandler (DropDownEvent, value); }
613 // raised when the format of the value is changed
614 public event EventHandler FormatChanged {
615 add { Events.AddHandler (FormatChangedEvent, value); }
616 remove { Events.RemoveHandler (FormatChangedEvent, value); }
619 // raised when the date Value is changed
620 public event EventHandler ValueChanged {
621 add { Events.AddHandler (ValueChangedEvent, value); }
622 remove { Events.RemoveHandler (ValueChangedEvent, value); }
625 [Browsable(false)]
626 [EditorBrowsable(EditorBrowsableState.Never)]
627 public new event EventHandler BackColorChanged {
628 add {
629 base.BackColorChanged += value;
632 remove {
633 base.BackColorChanged -= value;
637 [Browsable(false)]
638 [EditorBrowsable(EditorBrowsableState.Never)]
639 public new event EventHandler BackgroundImageChanged {
640 add {
641 base.BackgroundImageChanged += value;
644 remove {
645 base.BackgroundImageChanged -= value;
648 #if NET_2_0
649 [Browsable (false)]
650 [EditorBrowsable (EditorBrowsableState.Never)]
651 public new event EventHandler BackgroundImageLayoutChanged {
654 base.BackgroundImageLayoutChanged += value;
657 remove
659 base.BackgroundImageLayoutChanged -= value;
663 [Browsable (false)]
664 [EditorBrowsable (EditorBrowsableState.Never)]
665 public new event EventHandler Click {
666 add {
667 base.Click += value;
669 remove {
670 base.Click -= value;
674 [Browsable (false)]
675 [EditorBrowsable (EditorBrowsableState.Never)]
676 public new event EventHandler DoubleClick {
677 add {
678 base.DoubleClick += value;
680 remove {
681 base.DoubleClick -= value;
684 #endif
686 [Browsable(false)]
687 [EditorBrowsable(EditorBrowsableState.Never)]
688 public new event EventHandler ForeColorChanged {
689 add {
690 base.ForeColorChanged += value;
693 remove {
694 base.ForeColorChanged -= value;
697 #if NET_2_0
698 [Browsable (false)]
699 [EditorBrowsable (EditorBrowsableState.Never)]
700 public new event MouseEventHandler MouseClick {
701 add {
702 base.MouseClick += value;
704 remove {
705 base.MouseClick -= value;
709 [Browsable (false)]
710 [EditorBrowsable (EditorBrowsableState.Never)]
711 public new event MouseEventHandler MouseDoubleClick {
712 add {
713 base.MouseDoubleClick += value;
715 remove {
716 base.MouseDoubleClick -= value;
720 [Browsable (false)]
721 [EditorBrowsable (EditorBrowsableState.Never)]
722 public new event EventHandler PaddingChanged {
725 base.PaddingChanged += value;
727 remove
729 base.PaddingChanged -= value;
732 #endif
734 [Browsable(false)]
735 [EditorBrowsable(EditorBrowsableState.Never)]
736 public new event PaintEventHandler Paint {
737 add {
738 base.Paint += value;
741 remove {
742 base.Paint -= value;
745 #if NET_2_0
746 public event EventHandler RightToLeftLayoutChanged {
747 add {
748 Events.AddHandler (RightToLeftLayoutChangedEvent, value);
750 remove {
751 Events.RemoveHandler (RightToLeftLayoutChangedEvent, value);
754 #endif
756 [Browsable(false)]
757 [EditorBrowsable(EditorBrowsableState.Advanced)]
758 public new event EventHandler TextChanged {
759 add {
760 base.TextChanged += value;
763 remove {
764 base.TextChanged -= value;
767 #endregion // public events
769 #region protected properties
771 // not sure why we're overriding this one
772 protected override CreateParams CreateParams {
773 get {
774 return base.CreateParams;
778 // specify the default size for this control
779 protected override Size DefaultSize {
780 get {
781 // todo actually measure this properly
782 return new Size (200, PreferredHeight);
786 #endregion // protected properties
788 #region protected methods
790 // not sure why we're overriding this one
791 protected override AccessibleObject CreateAccessibilityInstance () {
792 return base.CreateAccessibilityInstance ();
795 // not sure why we're overriding this one
796 protected override void CreateHandle () {
797 base.CreateHandle ();
800 // not sure why we're overriding this one
801 protected override void DestroyHandle () {
802 base.DestroyHandle ();
805 #if !NET_2_0
806 // not sure why we're overriding this one
807 protected override void Dispose (bool disposing) {
808 updown_timer.Dispose ();
809 base.Dispose (disposing);
811 #endif
813 // find out if this key is an input key for us, depends on which date part is focused
814 protected override bool IsInputKey (Keys keyData) {
815 switch (keyData)
817 case Keys.Up:
818 case Keys.Down:
819 case Keys.Left:
820 case Keys.Right:
821 return true;
823 return false;
826 // raises the CloseUp event
827 protected virtual void OnCloseUp (EventArgs eventargs) {
828 EventHandler eh = (EventHandler)(Events [CloseUpEvent]);
829 if (eh != null)
830 eh (this, eventargs);
833 // raise the drop down event
834 protected virtual void OnDropDown (EventArgs eventargs) {
835 EventHandler eh = (EventHandler)(Events [DropDownEvent]);
836 if (eh != null)
837 eh (this, eventargs);
840 protected override void OnFontChanged(EventArgs e) {
841 // FIXME - do we need to update/invalidate/recalc our stuff?
842 month_calendar.Font = Font;
843 Size = new Size (Size.Width, PreferredHeight);
845 base.OnFontChanged (e);
848 // raises the format changed event
849 protected virtual void OnFormatChanged (EventArgs e) {
850 EventHandler eh = (EventHandler)(Events [FormatChangedEvent]);
851 if (eh != null)
852 eh (this, e);
854 #if NET_2_0
855 protected override void OnHandleCreated (EventArgs e) {
856 base.OnHandleCreated(e);
858 protected override void OnHandleDestroyed (EventArgs e) {
859 base.OnHandleDestroyed(e);
862 [EditorBrowsable (EditorBrowsableState.Advanced)]
863 protected virtual void OnRightToLeftLayoutChanged (EventArgs e) {
864 EventHandler eh = (EventHandler) Events [RightToLeftLayoutChangedEvent];
865 if (eh != null)
866 eh (this, e);
868 #endif
869 // not sure why we're overriding this one
870 protected override void OnSystemColorsChanged (EventArgs e) {
871 base.OnSystemColorsChanged (e);
874 // raise the ValueChanged event
875 protected virtual void OnValueChanged (EventArgs eventargs) {
876 EventHandler eh = (EventHandler)(Events [ValueChangedEvent]);
877 if (eh != null)
878 eh (this, eventargs);
881 // overridden to set the bounds of this control properly
882 protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified) {
883 // TODO: ensure I implemented the bounds core setting properly.
884 if ((specified & BoundsSpecified.Height) == BoundsSpecified.Height ||
885 (specified & BoundsSpecified.Size) == BoundsSpecified.Size) {
886 base.SetBoundsCore (x, y, width, DefaultSize.Height, specified);
887 } else {
888 base.SetBoundsCore (x, y, width, height, specified);
891 // need to set the rectangles for all the support internal rects
892 // this is done here as a optimisation since this is an array of rects
893 if ((specified & BoundsSpecified.X) == BoundsSpecified.X ||
894 (specified & BoundsSpecified.Y) == BoundsSpecified.Y) {
895 // TODO set up all the datepart rects
899 // not sure why we're overriding this
900 protected override void WndProc (ref Message m) {
901 base.WndProc (ref m);
904 #endregion // protected methods
906 #region internal / private properties
908 // this is the region that the date and the check box is drawn on
909 internal Rectangle date_area_rect {
910 get {
911 Rectangle rect = this.ClientRectangle;
912 if (ShowUpDown) {
913 // set the space to the left of the up/down button
914 if (rect.Width > (up_down_width + 4)) {
915 rect.Width -= (up_down_width + 4);
916 } else {
917 rect.Width = 0;
919 } else {
920 // set the space to the left of the up/down button
921 // TODO make this use up down button
922 if (rect.Width > (SystemInformation.VerticalScrollBarWidth + 4)) {
923 rect.Width -= SystemInformation.VerticalScrollBarWidth;
924 } else {
925 rect.Width = 0;
929 rect.Inflate (-2, -2);
930 return rect;
934 internal Rectangle CheckBoxRect {
935 get {
936 Rectangle retval = new Rectangle (check_box_space, ClientSize.Height / 2 - check_box_size / 2,
937 check_box_size, check_box_size);
938 return retval;
942 // the rectangle for the drop down arrow
943 internal Rectangle drop_down_arrow_rect {
944 get {
945 Rectangle rect = this.ClientRectangle;
946 rect.X = rect.Right - SystemInformation.VerticalScrollBarWidth - 2;
947 if (rect.Width > (SystemInformation.VerticalScrollBarWidth + 2)) {
948 rect.Width = SystemInformation.VerticalScrollBarWidth;
949 } else {
950 rect.Width = Math.Max (rect.Width - 2, 0);
953 rect.Inflate (0, -2);
954 return rect;
958 // the part of the date that is currently hilighted
959 internal Rectangle hilight_date_area {
960 get {
961 // TODO: put hilighted part calculation in here
962 return Rectangle.Empty;
966 #endregion
968 #region internal / private methods
970 private void ResizeHandler (object sender, EventArgs e)
972 Invalidate ();
975 private void UpDownTimerTick (object sender, EventArgs e)
977 if (updown_timer.Interval == initial_timer_delay)
978 updown_timer.Interval = subsequent_timer_delay;
980 if (is_down_pressed)
981 IncrementSelectedPart (-1);
982 else if (is_up_pressed)
983 IncrementSelectedPart (1);
984 else
985 updown_timer.Enabled = false;
988 // calculates the maximum width
989 internal Single CalculateMaxWidth(string format, Graphics gr, StringFormat string_format)
991 SizeF size;
992 float result = 0;
993 string text;
994 Font font = this.Font;
996 switch (format)
998 case "M":
999 case "MM":
1000 case "MMM":
1001 case "MMMM":
1002 for (int i = 1; i <= 12; i++) {
1003 text = PartData.GetText (Value.AddMonths (i), format);
1004 size = gr.MeasureString (text, font, int.MaxValue, string_format);
1005 result = Math.Max (result, size.Width);
1007 return result;
1008 case "d":
1009 case "dd":
1010 case "ddd":
1011 case "dddd":
1012 for (int i = 1; i <= 12; i++) {
1013 text = PartData.GetText (Value.AddDays (i), format);
1014 size = gr.MeasureString (text, font, int.MaxValue, string_format);
1015 result = Math.Max (result, size.Width);
1017 return result;
1018 case "h":
1019 case "hh":
1020 for (int i = 1; i <= 12; i++) {
1021 text = PartData.GetText (Value.AddHours (i), format);
1022 size = gr.MeasureString (text, font, int.MaxValue, string_format);
1023 result = Math.Max (result, size.Width);
1025 return result;
1026 case "H":
1027 case "HH":
1028 for (int i = 1; i <= 24; i++) {
1029 text = PartData.GetText (Value.AddDays (i), format);
1030 size = gr.MeasureString (text, font, int.MaxValue, string_format);
1031 result = Math.Max (result, size.Width);
1033 return result;
1034 case "m":
1035 case "mm":
1036 for (int i = 1; i <= 60; i++) {
1037 text = PartData.GetText (Value.AddMinutes (i), format);
1038 size = gr.MeasureString (text, font, int.MaxValue, string_format);
1039 result = Math.Max (result, size.Width);
1041 return result;
1042 case "s":
1043 case "ss":
1044 for (int i = 1; i <= 60; i++) {
1045 text = PartData.GetText (Value.AddSeconds (i), format);
1046 size = gr.MeasureString (text, font, int.MaxValue, string_format);
1047 result = Math.Max (result, size.Width);
1049 return result;
1050 case "t":
1051 case "tt":
1052 for (int i = 1; i <= 2; i++) {
1053 text = PartData.GetText (Value.AddHours (i * 12), format);
1054 size = gr.MeasureString (text, font, int.MaxValue, string_format);
1055 result = Math.Max (result, size.Width);
1057 return result;
1058 case "y":
1059 case "yy":
1060 case "yyyy":
1061 for (int i = 1; i <= 10; i++) {
1062 text = PartData.GetText (Value.AddYears (i), format);
1063 size = gr.MeasureString (text, font, int.MaxValue, string_format);
1064 result = Math.Max (result, size.Width);
1066 return result;
1067 default:
1068 return gr.MeasureString (format, font, int.MaxValue, string_format).Width;
1072 // returns the format of the date as a string
1073 // (i.e. resolves the Format enum values to it's corresponding string format)
1074 // Why CurrentCulture and not CurrentUICulture is explained here:
1075 // http://blogs.msdn.com/michkap/archive/2007/01/11/1449754.aspx
1076 private string GetExactFormat()
1078 switch (this.format) {
1079 case DateTimePickerFormat.Long:
1080 return Threading.Thread.CurrentThread.CurrentCulture.DateTimeFormat.LongDatePattern;
1081 case DateTimePickerFormat.Short:
1082 return Threading.Thread.CurrentThread.CurrentCulture.DateTimeFormat.ShortDatePattern;
1083 case DateTimePickerFormat.Time:
1084 return Threading.Thread.CurrentThread.CurrentCulture.DateTimeFormat.LongTimePattern;
1085 case DateTimePickerFormat.Custom:
1086 return this.custom_format == null ? String.Empty : this.custom_format;
1087 default:
1088 return Threading.Thread.CurrentThread.CurrentCulture.DateTimeFormat.LongDatePattern;
1092 private void CalculateFormats()
1094 string real_format;
1095 System.Text.StringBuilder literal = new System.Text.StringBuilder ();
1096 System.Collections.ArrayList formats = new ArrayList ();
1097 bool is_literal = false;
1098 char lastch = (char) 0;
1099 char ch;
1101 real_format = GetExactFormat ();
1103 // parse the format string
1104 for (int i = 0; i < real_format.Length; i++)
1106 ch = real_format [i];
1108 if (is_literal && ch != '\'')
1110 literal.Append (ch);
1111 continue;
1114 switch (ch)
1116 case 't':
1117 case 'd':
1118 case 'h':
1119 case 'H':
1120 case 'm':
1121 case 'M':
1122 case 's':
1123 case 'y':
1124 case 'g': // Spec says nothing about g, but it seems to be treated like spaces.
1125 if (!(lastch == ch || lastch == 0) && literal.Length != 0)
1127 formats.Add (new PartData(literal.ToString (), false));
1128 literal.Length = 0;
1130 literal.Append (ch);
1131 break;
1132 case '\'':
1133 if (is_literal && i < real_format.Length - 1 && real_format [i + 1] == '\'') {
1134 literal.Append (ch);
1135 i++;
1136 break;
1138 if (literal.Length == 0) {
1139 is_literal = !is_literal;
1140 break;
1142 formats.Add (new PartData (literal.ToString (), is_literal));
1143 literal.Length = 0;
1144 is_literal = !is_literal;
1145 break;
1146 default:
1147 if (literal.Length != 0)
1149 formats.Add (new PartData(literal.ToString (), false));
1150 literal.Length = 0;
1152 formats.Add (new PartData (ch.ToString(), true));
1153 break;
1156 lastch = ch;
1158 if (literal.Length >= 0)
1159 formats.Add (new PartData (literal.ToString (), is_literal));
1161 part_data = new PartData [formats.Count];
1162 formats.CopyTo (part_data);
1165 private Point CalculateDropDownLocation (Rectangle parent_control_rect, Size child_size, bool align_left)
1167 // default bottom left
1168 Point location = new Point(parent_control_rect.Left + 5, parent_control_rect.Bottom);
1169 // now adjust the alignment
1170 if (!align_left) {
1171 location.X = parent_control_rect.Right - child_size.Width;
1174 Point screen_location = PointToScreen (location);
1175 Rectangle working_area = Screen.FromControl(this).WorkingArea;
1176 // now adjust if off the right side of the screen
1177 if (screen_location.X < working_area.X) {
1178 screen_location.X = working_area.X;
1180 // now adjust if it should be displayed above control
1181 if (screen_location.Y + child_size.Height > working_area.Bottom) {
1182 screen_location.Y -= (parent_control_rect.Height + child_size.Height);
1185 // since the parent of the month calendar is the form, adjust accordingly.
1186 screen_location = month_calendar.Parent.PointToClient(screen_location);
1188 return screen_location;
1191 // actually draw this control
1192 internal void Draw (Rectangle clip_rect, Graphics dc)
1194 ThemeEngine.Current.DrawDateTimePicker (dc, clip_rect, this);
1197 // drop the calendar down
1198 internal void DropDownMonthCalendar ()
1200 // ensure the right date is set for the month_calendar
1201 month_calendar.SetDate (this.date_value);
1202 // get a rectangle that has the dimensions of the text area,
1203 // but the height of the dtp control.
1204 Rectangle align_area = this.date_area_rect;
1205 align_area.Y = this.ClientRectangle.Y;
1206 align_area.Height = this.ClientRectangle.Height;
1208 month_calendar.Parent = this.FindForm ();
1209 // establish the month calendar's location
1210 month_calendar.Location = CalculateDropDownLocation (
1211 align_area,
1212 month_calendar.Size,
1213 (this.DropDownAlign == LeftRightAlignment.Left));
1214 month_calendar.Show ();
1215 month_calendar.Focus ();
1216 month_calendar.Capture = true;
1218 // fire any registered events
1219 // XXX should this just call OnDropDown?
1220 EventHandler eh = (EventHandler)(Events [DropDownEvent]);
1221 if (eh != null)
1222 eh (this, EventArgs.Empty);
1225 // hide the month calendar
1226 internal void HideMonthCalendar ()
1228 this.is_drop_down_visible = false;
1229 Invalidate (drop_down_arrow_rect);
1230 month_calendar.Capture = false;
1231 if (month_calendar.Visible) {
1232 month_calendar.Hide ();
1236 private int GetSelectedPartIndex()
1238 for (int i = 0; i < part_data.Length; i++)
1240 if (part_data[i].is_selected && !part_data[i].is_literal)
1241 return i;
1243 return -1;
1246 private void IncrementSelectedPart(int delta)
1248 int selected_index = GetSelectedPartIndex();
1250 if (selected_index == -1) {
1251 return;
1254 switch (part_data[selected_index].value)
1256 case "d":
1257 case "dd": // number day formats
1258 if (delta < 0) {
1259 if (Value.Day == 1)
1260 SetPart(DateTime.DaysInMonth(Value.Year, Value.Month), 'd');
1261 else
1262 SetPart(Value.Day + delta, 'd');
1263 } else {
1264 if (Value.Day == DateTime.DaysInMonth(Value.Year, Value.Month))
1265 SetPart(1, 'd');
1266 else
1267 SetPart(Value.Day + delta, 'd') ;
1269 break;
1270 case "ddd":
1271 case "dddd": // text day formats
1272 Value = Value.AddDays(delta);
1273 break;
1274 case "h":
1275 case "hh":
1276 case "H":
1277 case "HH": // hour formats
1278 SetPart(Value.Hour + delta, 'h');
1279 break;
1280 case "m":
1281 case "mm": // minute formats
1282 SetPart(Value.Minute + delta, 'm');
1283 break;
1284 case "M":
1285 case "MM":
1286 case "MMM":
1287 case "MMMM": // month formats
1288 SetPart(Value.Month + delta, 'M');
1289 break;
1290 case "s":
1291 case "ss": // second format
1292 SetPart(Value.Second + delta, 's');
1293 break;
1294 case "t":
1295 case "tt": // AM / PM specifier
1296 SetPart(Value.Hour + delta * 12, 'h');
1297 break;
1298 case "y":
1299 case "yy":
1300 case "yyy":
1301 case "yyyy":
1302 SetPart(Value.Year + delta, 'y');
1303 break;
1307 private void SelectNextPart()
1309 int selected_index;
1310 if (is_checkbox_selected) {
1311 for (int i = 0; i < part_data.Length; i++)
1313 if (!part_data[i].is_literal)
1315 is_checkbox_selected = false;
1316 part_data[i].is_selected = true;
1317 Invalidate();
1318 break;
1321 } else {
1322 selected_index = GetSelectedPartIndex();
1323 if (selected_index >= 0)
1324 part_data[selected_index].is_selected = false;
1325 for (int i = selected_index + 1; i < part_data.Length; i++)
1327 if (!part_data[i].is_literal)
1329 part_data[i].is_selected = true;
1330 Invalidate();
1331 break;
1334 if (GetSelectedPartIndex() == -1)
1335 { // if no part was found before the end, look from the beginning
1336 if (ShowCheckBox)
1338 is_checkbox_selected = true;
1339 Invalidate();
1341 else
1343 for (int i = 0; i <= selected_index; i++)
1345 if (!part_data[i].is_literal)
1347 part_data[i].is_selected = true;
1348 Invalidate();
1349 break;
1358 private void SelectPreviousPart()
1360 if (is_checkbox_selected)
1362 for (int i = part_data.Length - 1; i >= 0; i--)
1364 if (!part_data[i].is_literal)
1366 is_checkbox_selected = false;
1367 part_data[i].is_selected = true;
1368 Invalidate();
1369 break;
1373 else
1375 int selected_index = GetSelectedPartIndex();
1377 if (selected_index >= 0)
1378 part_data[selected_index].is_selected = false;
1380 for (int i = selected_index - 1; i >= 0; i--)
1382 if (!part_data[i].is_literal)
1384 part_data[i].is_selected = true;
1385 Invalidate();
1386 break;
1389 if (GetSelectedPartIndex() == -1)
1390 { // if no part was found before the beginning, look from the end
1391 if (ShowCheckBox)
1393 is_checkbox_selected = true;
1394 Invalidate();
1396 else
1398 for (int i = part_data.Length - 1; i >= selected_index; i--)
1400 if (!part_data[i].is_literal)
1402 part_data[i].is_selected = true;
1403 Invalidate();
1404 break;
1412 // raised by key down events.
1413 private void KeyDownHandler(object sender, KeyEventArgs e)
1415 switch (e.KeyCode)
1417 case Keys.Add:
1418 case Keys.Up:
1420 if (ShowCheckBox && Checked == false)
1421 break;
1422 IncrementSelectedPart(1);
1423 e.Handled = true;
1424 break;
1426 case Keys.Subtract:
1427 case Keys.Down:
1429 if (ShowCheckBox && Checked == false)
1430 break;
1431 IncrementSelectedPart(-1);
1432 e.Handled = true;
1433 break;
1435 case Keys.Left:
1436 {// select the next part to the left
1437 if (ShowCheckBox && Checked == false)
1438 break;
1439 SelectPreviousPart();
1440 e.Handled = true;
1441 break;
1443 case Keys.Right:
1444 {// select the next part to the right
1445 if (ShowCheckBox && Checked == false)
1446 break;
1447 SelectNextPart();
1448 e.Handled = true;
1449 break;
1451 case Keys.F4:
1452 if (!is_drop_down_visible)
1453 DropDownMonthCalendar();
1454 break;
1458 // raised by any key down events
1459 private void KeyPressHandler (object sender, KeyPressEventArgs e)
1461 switch (e.KeyChar) {
1462 case ' ':
1463 if (is_checkbox_selected)
1465 Checked = !Checked;
1467 break;
1468 case '0':
1469 case '1':
1470 case '2':
1471 case '3':
1472 case '4':
1473 case '5':
1474 case '6':
1475 case '7':
1476 case '8':
1477 case '9':
1478 int number = e.KeyChar - (int) '0';
1479 int selected_index = GetSelectedPartIndex();
1480 if (selected_index == -1)
1481 break;
1482 if (!part_data[selected_index].is_numeric_format)
1483 break;
1484 switch (part_data[selected_index].value)
1486 case "d":
1487 case "dd":
1488 int newDay = Value.Day * 10 + number;
1489 if (DateTime.DaysInMonth(Value.Year, Value.Month) < newDay)
1490 newDay = number;
1491 SetPart(newDay, 'd');
1492 break;
1493 case "M":
1494 case "MM":
1495 int newMonth = Value.Month * 10 + number;
1496 if (newMonth > 12)
1497 newMonth = number;
1498 SetPart(newMonth, 'M');
1499 break;
1500 case "y":
1501 case "yy":
1502 case "yyyy":
1503 int newYear = Value.Year * 10 + number;
1504 if (newYear > 9999)
1505 newYear = number;
1506 SetPart(newYear, 'y');
1507 break;
1508 case "h":
1509 case "hh":
1510 case "H":
1511 case "HH":
1512 int newHour = Value.Hour * 10 + number;
1513 if (newHour >= 24)
1514 newHour = number;
1515 SetPart(newHour, 'h');
1516 break;
1517 case "m":
1518 case "mm":
1519 int newMinute = Value.Minute* 10 + number;
1520 if (newMinute >= 60)
1521 newMinute = number;
1522 SetPart(newMinute, 'm');
1523 break;
1524 case "s":
1525 case "ss":
1526 int newSecond = Value.Second * 10 + number;
1527 if (newSecond >= 60)
1528 newSecond = number;
1529 SetPart(newSecond, 's');
1530 break;
1533 break;
1534 default:
1535 break;
1537 e.Handled = true;
1540 // set the specified part of the date to the specified value
1541 private void SetPart(int value, char part)
1543 switch (part)
1545 case 's': // seconds
1546 value %= 60;
1547 if (value == -1)
1548 value = 59;
1549 if (value >= 0 && value <= 59)
1550 Value = new DateTime(Value.Year, Value.Month, Value.Day, Value.Hour, Value.Minute, value, Value.Millisecond);
1551 break;
1552 case 'm': // minutes
1553 value %= 60;
1554 if (value == -1)
1555 value = 59;
1556 if (value >= 0 && value <= 59)
1557 Value = new DateTime(Value.Year, Value.Month, Value.Day, Value.Hour, value, Value.Second, Value.Millisecond);
1558 break;
1559 case 'h':
1560 case 'H': // hours
1561 value %= 24;
1562 if (value == -1)
1563 value = 23;
1564 if (value >= 0 && value <= 23)
1565 Value = new DateTime(Value.Year, Value.Month, Value.Day, value, Value.Minute, Value.Second, Value.Millisecond);
1566 break;
1567 case 'd': // days
1568 int max_days = DateTime.DaysInMonth(Value.Year, Value.Month);
1569 if (value > max_days)
1570 Value = new DateTime(Value.Year, Value.Month, max_days, Value.Hour, Value.Minute, Value.Second, Value.Millisecond);
1571 if (value >= 1 && value <= 31)
1572 Value = new DateTime(Value.Year, Value.Month, value, Value.Hour, Value.Minute, Value.Second, Value.Millisecond);
1573 break;
1574 case 'M': // months
1575 value %= 12;
1576 if (value == 0)
1577 value = 12;
1578 if (value >= 1 && value <= 12)
1579 Value = new DateTime(Value.Year, value, Value.Day, Value.Hour, Value.Minute, Value.Second, Value.Millisecond);
1580 break;
1581 case 'y': // years
1582 value %= 10000;
1583 if (value > 0 && value <= 9999)
1584 Value = new DateTime(value, Value.Month, Value.Day, Value.Hour, Value.Minute, Value.Second, Value.Millisecond);
1585 break;
1589 // if we loose focus deselect any selected parts.
1590 private void LostFocusHandler (object sender, EventArgs e)
1592 int selected_index = GetSelectedPartIndex ();
1593 if (selected_index != -1)
1595 part_data [selected_index].is_selected = false;
1596 Rectangle invalidate_rect = Rectangle.Ceiling (part_data [selected_index].drawing_rectangle);
1597 invalidate_rect.Inflate (2, 2);
1598 Invalidate (invalidate_rect);
1600 else if (is_checkbox_selected)
1602 is_checkbox_selected = false;
1603 Invalidate (CheckBoxRect);
1607 // if month calendar looses focus and the drop down is up, then close it
1608 private void MonthCalendarLostFocusHandler(object sender, EventArgs e)
1610 if (is_drop_down_visible && !month_calendar.Focused)
1612 //this.HideMonthCalendar();
1613 //This is handled from the monthcalender itself,
1614 //it may loose focus, but still has to be visible,
1615 //for instance when the context menu is displayed.
1620 private void MonthCalendarDateChangedHandler (object sender, DateRangeEventArgs e)
1622 if (month_calendar.Visible)
1623 this.Value = e.Start.Date.Add (this.Value.TimeOfDay);
1626 // fired when a user clicks on the month calendar to select a date
1627 private void MonthCalendarDateSelectedHandler (object sender, DateRangeEventArgs e)
1629 this.HideMonthCalendar ();
1630 this.Focus ();
1633 private void MouseUpHandler(object sender, MouseEventArgs e)
1635 if (ShowUpDown)
1637 if (is_up_pressed || is_down_pressed)
1639 updown_timer.Enabled = false;
1640 is_up_pressed = false;
1641 is_down_pressed = false;
1642 Invalidate (drop_down_arrow_rect);
1647 // to check if the mouse has come down on this control
1648 private void MouseDownHandler (object sender, MouseEventArgs e)
1650 // Only left clicks are handled.
1651 if (e.Button != MouseButtons.Left)
1652 return;
1654 is_checkbox_selected = false;
1656 if (ShowCheckBox && CheckBoxRect.Contains(e.X, e.Y))
1658 is_checkbox_selected = true;
1659 Checked = !Checked;
1660 return;
1664 if (ShowUpDown && drop_down_arrow_rect.Contains (e.X, e.Y))
1666 if (!(ShowCheckBox && Checked == false))
1668 if (e.Y < this.Height / 2) {
1669 is_up_pressed = true;
1670 is_down_pressed = false;
1671 IncrementSelectedPart (1);
1672 } else {
1673 is_up_pressed = false;
1674 is_down_pressed = true;
1675 IncrementSelectedPart (-1);
1677 Invalidate (drop_down_arrow_rect);
1678 updown_timer.Interval = initial_timer_delay;
1679 updown_timer.Enabled = true;
1681 } else if (is_drop_down_visible == false && drop_down_arrow_rect.Contains (e.X, e.Y)) {
1682 is_drop_down_visible = true;
1683 if (!Checked)
1684 Checked = true;
1685 Invalidate (drop_down_arrow_rect);
1686 DropDownMonthCalendar ();
1687 } else {
1688 // mouse down on this control anywhere else collapses it
1689 if (is_drop_down_visible) {
1690 HideMonthCalendar ();
1691 this.Focus ();
1693 if (!(ShowCheckBox && Checked == false))
1695 // go through the parts to see if the click is in any of them
1696 bool invalidate_afterwards = false;
1697 for (int i = 0; i < part_data.Length; i++) {
1698 bool old = part_data [i].is_selected;
1700 if (part_data [i].is_literal)
1701 continue;
1703 if (part_data [i].drawing_rectangle.Contains (e.X, e.Y)) {
1704 part_data [i].is_selected = true;
1705 } else {
1706 part_data [i].is_selected = false;
1708 if (old != part_data [i].is_selected)
1709 invalidate_afterwards = true;
1711 if (invalidate_afterwards)
1712 Invalidate ();
1719 // paint this control now
1720 private void PaintHandler (object sender, PaintEventArgs pe) {
1721 if (Width <= 0 || Height <= 0 || Visible == false)
1722 return;
1724 Draw (pe.ClipRectangle, pe.Graphics);
1727 #endregion
1729 #region internal classes
1730 internal class PartData
1732 internal string value;
1733 internal bool is_literal;
1734 internal bool is_selected;
1735 internal RectangleF drawing_rectangle;
1737 internal bool is_numeric_format
1741 if (is_literal)
1742 return false;
1743 switch (value) {
1744 case "m":
1745 case "mm":
1746 case "d":
1747 case "dd":
1748 case "h":
1749 case "hh":
1750 case "H":
1751 case "HH":
1752 case "M":
1753 case "MM":
1754 case "s":
1755 case "ss":
1756 case "y":
1757 case "yy":
1758 case "yyyy":
1759 return true;
1760 case "ddd":
1761 case "dddd":
1762 return false;
1763 default:
1764 return false;
1769 internal PartData(string value, bool is_literal)
1771 this.value = value;
1772 this.is_literal = is_literal;
1775 // calculate the string to show for this data
1776 internal string GetText(DateTime date)
1778 if (is_literal) {
1779 return value;
1780 } else {
1781 return GetText (date, value);
1785 static internal string GetText(DateTime date, string format)
1787 if (format.StartsWith ("g"))
1788 return " ";
1789 else if (format.Length == 1)
1790 return date.ToString ("%" + format);
1791 else
1792 return date.ToString (format);
1796 #endregion