// Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // // Copyright (c) 2004-2005 Novell, Inc. // // Authors: // John BouAntoun jba-mono@optusnet.com.au // Rolf Bjarne Kvinge rolfkvinge@ya.com // // TODO: // - wire in all events from monthcalendar using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Globalization; using System.Runtime.InteropServices; using System.Windows.Forms; namespace System.Windows.Forms { [ClassInterface (ClassInterfaceType.AutoDispatch)] [DefaultBindingProperty ("Value")] [ComVisible (true)] [DefaultEvent("ValueChanged")] [DefaultProperty("Value")] [Designer("System.Windows.Forms.Design.DateTimePickerDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")] public class DateTimePicker : Control { #region Public variables // this class has to have the specified hour, minute and second, as it says in msdn [EditorBrowsable (EditorBrowsableState.Never)] [Browsable (false)] public static readonly DateTime MaxDateTime = new DateTime (9998, 12, 31, 0, 0, 0); [EditorBrowsable (EditorBrowsableState.Never)] [Browsable (false)] public static readonly DateTime MinDateTime = new DateTime (1753, 1, 1); internal const int check_box_size = 13; internal const int check_box_space = 4; #endregion // Public variables #region Local variables protected static readonly Color DefaultMonthBackColor = ThemeEngine.Current.ColorWindow; protected static readonly Color DefaultTitleBackColor = ThemeEngine.Current.ColorActiveCaption; protected static readonly Color DefaultTitleForeColor = ThemeEngine.Current.ColorActiveCaptionText; protected static readonly Color DefaultTrailingForeColor = SystemColors.GrayText; internal MonthCalendar month_calendar; bool is_checked; string custom_format; LeftRightAlignment drop_down_align; DateTimePickerFormat format; DateTime max_date; DateTime min_date; bool show_check_box; bool show_up_down; DateTime date_value; bool right_to_left_layout; // variables used for drawing and such internal const int up_down_width = check_box_size; internal bool is_drop_down_visible; internal bool is_up_pressed; internal bool is_down_pressed; internal Timer updown_timer; internal const int initial_timer_delay = 500; internal const int subsequent_timer_delay = 100; internal bool is_checkbox_selected; // variables for determining how to format the string internal PartData[] part_data; internal int editing_part_index = -1; internal int editing_number = -1; internal string editing_text; bool drop_down_button_entered; #endregion // Local variables #region DateTimePickerAccessibleObject Subclass [ComVisible(true)] public class DateTimePickerAccessibleObject : ControlAccessibleObject { #region DateTimePickerAccessibleObject Local Variables private new DateTimePicker owner; #endregion // DateTimePickerAccessibleObject Local Variables #region DateTimePickerAccessibleObject Constructors public DateTimePickerAccessibleObject(DateTimePicker owner) : base(owner) { this.owner = owner; } #endregion // DateTimePickerAccessibleObject Constructors #region DateTimePickerAccessibleObject Properties public override string KeyboardShortcut { get { return base.KeyboardShortcut; } } public override AccessibleRole Role { get { return base.Role; } } public override AccessibleStates State { get { AccessibleStates retval; retval = AccessibleStates.Default; if (owner.Checked) { retval |= AccessibleStates.Checked; } return retval; } } public override string Value { get { return owner.Text; } } #endregion // DateTimePickerAccessibleObject Properties } #endregion // DateTimePickerAccessibleObject Sub-class #region public constructors // only public constructor public DateTimePicker () { // initialise the month calendar month_calendar = new MonthCalendar (this); month_calendar.CalendarDimensions = new Size (1, 1); month_calendar.MaxSelectionCount = 1; month_calendar.ForeColor = Control.DefaultForeColor; month_calendar.BackColor = DefaultMonthBackColor; month_calendar.TitleBackColor = DefaultTitleBackColor; month_calendar.TitleForeColor = DefaultTitleForeColor; month_calendar.TrailingForeColor = DefaultTrailingForeColor; month_calendar.Visible = false; // initialize the timer updown_timer = new Timer(); updown_timer.Interval = initial_timer_delay; // initialise other variables is_checked = true; custom_format = null; drop_down_align = LeftRightAlignment.Left; format = DateTimePickerFormat.Long; max_date = MaxDateTime; min_date = MinDateTime; show_check_box = false; show_up_down = false; date_value = DateTime.Now; is_drop_down_visible = false; BackColor = SystemColors.Window; ForeColor = SystemColors.WindowText; month_calendar.DateChanged += new DateRangeEventHandler (MonthCalendarDateChangedHandler); month_calendar.DateSelected += new DateRangeEventHandler (MonthCalendarDateSelectedHandler); month_calendar.LostFocus += new EventHandler (MonthCalendarLostFocusHandler); updown_timer.Tick += new EventHandler (UpDownTimerTick); KeyPress += new KeyPressEventHandler (KeyPressHandler); KeyDown += new KeyEventHandler (KeyDownHandler); GotFocus += new EventHandler (GotFocusHandler); LostFocus += new EventHandler (LostFocusHandler); MouseDown += new MouseEventHandler (MouseDownHandler); MouseUp += new MouseEventHandler (MouseUpHandler); MouseEnter += new EventHandler (OnMouseEnter); MouseLeave += new EventHandler (OnMouseLeave); MouseMove += new MouseEventHandler (OnMouseMove); Paint += new PaintEventHandler (PaintHandler); Resize += new EventHandler (ResizeHandler); SetStyle (ControlStyles.UserPaint | ControlStyles.StandardClick, false); SetStyle (ControlStyles.FixedHeight, true); SetStyle (ControlStyles.Selectable, true); CalculateFormats (); } #endregion #region public properties [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] public override Color BackColor { set { base.BackColor = value; } get { return base.BackColor; } } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] public override Image BackgroundImage { set { base.BackgroundImage = value; } get { return base.BackgroundImage; } } [Browsable (false)] [EditorBrowsable (EditorBrowsableState.Never)] public override ImageLayout BackgroundImageLayout { get{ return base.BackgroundImageLayout; } set { base.BackgroundImageLayout = value; } } [AmbientValue(null)] [Localizable(true)] public Font CalendarFont { set { month_calendar.Font = value; } get { return month_calendar.Font; } } public Color CalendarForeColor { set { month_calendar.ForeColor = value; } get { return month_calendar.ForeColor; } } public Color CalendarMonthBackground { set { month_calendar.BackColor = value; } get { return month_calendar.BackColor; } } public Color CalendarTitleBackColor { set { month_calendar.TitleBackColor = value; } get { return month_calendar.TitleBackColor; } } public Color CalendarTitleForeColor { set { month_calendar.TitleForeColor = value; } get { return month_calendar.TitleForeColor; } } public Color CalendarTrailingForeColor { set { month_calendar.TrailingForeColor = value; } get { return month_calendar.TrailingForeColor; } } // when checked the value is grayed out [Bindable(true)] [DefaultValue(true)] public bool Checked { set { if (is_checked != value) { is_checked = value; // invalidate the value inside this control if (ShowCheckBox) { for (int i = 0; i < part_data.Length; i++) part_data [i].Selected = false; Invalidate (date_area_rect); OnUIAChecked (); OnUIASelectionChanged (); } } } get { return is_checked; } } // the custom format string to format this control with [Localizable (true)] [DefaultValue(null)] [RefreshProperties(RefreshProperties.Repaint)] public string CustomFormat { set { if (custom_format != value) { custom_format = value; if (this.Format == DateTimePickerFormat.Custom) { CalculateFormats (); } } } get { return custom_format; } } [EditorBrowsable (EditorBrowsableState.Never)] protected override bool DoubleBuffered { get { return base.DoubleBuffered; } set { base.DoubleBuffered = value; } } // which side the drop down is to be aligned on [DefaultValue(LeftRightAlignment.Left)] [Localizable(true)] public LeftRightAlignment DropDownAlign { set { if (drop_down_align != value) { drop_down_align = value; } } get { return drop_down_align; } } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] public override Color ForeColor { set { base.ForeColor = value; } get { return base.ForeColor; } } // the format of the date time picker text, default is long [RefreshProperties(RefreshProperties.Repaint)] public DateTimePickerFormat Format { set { if (format != value) { format = value; RecreateHandle (); // MS recreates the handle on every format change. CalculateFormats (); this.OnFormatChanged (EventArgs.Empty); // invalidate the value inside this control this.Invalidate (date_area_rect); } } get { return format; } } public DateTime MaxDate { set { if (value < min_date) { string msg = string.Format (CultureInfo.CurrentCulture, "'{0}' is not a valid value for 'MaxDate'. 'MaxDate' " + "must be greater than or equal to MinDate.", value.ToString ("G")); throw new ArgumentOutOfRangeException ("MaxDate", msg); } if (value > MaxDateTime) { string msg = string.Format (CultureInfo.CurrentCulture, "DateTimePicker does not support dates after {0}.", MaxDateTime.ToString ("G", CultureInfo.CurrentCulture)); throw new ArgumentOutOfRangeException ("MaxDate", msg); } if (max_date != value) { max_date = value; if (Value > max_date) { Value = max_date; // invalidate the value inside this control this.Invalidate (date_area_rect); } OnUIAMaximumChanged (); } } get { return max_date; } } public static DateTime MaximumDateTime { get { return MaxDateTime; } } public DateTime MinDate { set { // If the user tries to set DateTime.MinValue, fix it to // DateTimePicker's minimum. if (value == DateTime.MinValue) value = MinDateTime; if (value > MaxDate) { string msg = string.Format (CultureInfo.CurrentCulture, "'{0}' is not a valid value for 'MinDate'. 'MinDate' " + "must be less than MaxDate.", value.ToString ("G")); throw new ArgumentOutOfRangeException ("MinDate", msg); } if (value < MinDateTime) { string msg = string.Format (CultureInfo.CurrentCulture, "DateTimePicker does not support dates before {0}.", MinDateTime.ToString ("G", CultureInfo.CurrentCulture)); throw new ArgumentOutOfRangeException ("MinDate", msg); } if (min_date != value) { min_date = value; if (Value < min_date) { Value = min_date; // invalidate the value inside this control this.Invalidate (date_area_rect); } OnUIAMinimumChanged (); } } get { return min_date; } } public static DateTime MinimumDateTime { get { return MinDateTime; } } [EditorBrowsable (EditorBrowsableState.Never)] [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] [Browsable (false)] public new Padding Padding { get { return base.Padding; } set { base.Padding = value; } } // the prefered height to draw this control using current font [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public int PreferredHeight { get { // Make it proportional return (int) Math.Ceiling (Font.Height * 1.5); } } [DefaultValue (false)] [Localizable (true)] public virtual bool RightToLeftLayout { get { return right_to_left_layout; } set { if (right_to_left_layout != value) { right_to_left_layout = value; OnRightToLeftLayoutChanged (EventArgs.Empty); } } } // whether or not the check box is shown [DefaultValue(false)] public bool ShowCheckBox { set { if (show_check_box != value) { show_check_box = value; // invalidate the value inside this control this.Invalidate (date_area_rect); OnUIAShowCheckBoxChanged (); } } get { return show_check_box; } } // if true show the updown control, else popup the monthcalendar [DefaultValue(false)] public bool ShowUpDown { set { if (show_up_down != value) { show_up_down = value; // need to invalidate the whole control this.Invalidate (); OnUIAShowUpDownChanged (); } } get { return show_up_down; } } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Advanced)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public override string Text { set { DateTime parsed_value; if (value == null || value == string.Empty) { date_value = DateTime.Now; OnValueChanged (EventArgs.Empty); OnTextChanged (EventArgs.Empty); return; } if (format == DateTimePickerFormat.Custom) { // TODO: if the format is a custom format we need to do a custom parse here // This implementation will fail if the custom format is set to something that can // be a standard datetime format string // http://msdn2.microsoft.com/en-us/library/az4se3k1.aspx parsed_value = DateTime.ParseExact (value, GetExactFormat (), null); } else { parsed_value = DateTime.ParseExact (value, GetExactFormat (), null); } if (date_value != parsed_value) { Value = parsed_value; } } get { if (!IsHandleCreated) return ""; if (format == DateTimePickerFormat.Custom) { System.Text.StringBuilder result = new System.Text.StringBuilder (); for (int i = 0; i < part_data.Length; i++) { result.Append(part_data[i].GetText(date_value)); } return result.ToString (); } else { return Value.ToString (GetExactFormat ()); } } } [Bindable(true)] [RefreshProperties(RefreshProperties.All)] public DateTime Value { set { if (date_value != value) { if (value < MinDate || value > MaxDate) throw new ArgumentOutOfRangeException ("value", "value must be between MinDate and MaxDate"); date_value = value; this.OnValueChanged (EventArgs.Empty); this.Invalidate (date_area_rect); } } get { return date_value; } } #endregion // public properties #region public methods // just return the text value public override string ToString () { return this.Text; } #endregion // public methods #region public events static object CloseUpEvent = new object (); static object DropDownEvent = new object (); static object FormatChangedEvent = new object (); static object ValueChangedEvent = new object (); static object RightToLeftLayoutChangedEvent = new object (); // raised when the monthcalendar is closed public event EventHandler CloseUp { add { Events.AddHandler (CloseUpEvent, value); } remove { Events.RemoveHandler (CloseUpEvent, value); } } // raised when the monthcalendar is opened public event EventHandler DropDown { add { Events.AddHandler (DropDownEvent, value); } remove { Events.RemoveHandler (DropDownEvent, value); } } // raised when the format of the value is changed public event EventHandler FormatChanged { add { Events.AddHandler (FormatChangedEvent, value); } remove { Events.RemoveHandler (FormatChangedEvent, value); } } // raised when the date Value is changed public event EventHandler ValueChanged { add { Events.AddHandler (ValueChangedEvent, value); } remove { Events.RemoveHandler (ValueChangedEvent, value); } } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] public new event EventHandler BackColorChanged { add { base.BackColorChanged += value; } remove { base.BackColorChanged -= value; } } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] public new event EventHandler BackgroundImageChanged { add { base.BackgroundImageChanged += value; } remove { base.BackgroundImageChanged -= value; } } [Browsable (false)] [EditorBrowsable (EditorBrowsableState.Never)] public new event EventHandler BackgroundImageLayoutChanged { add { base.BackgroundImageLayoutChanged += value; } remove { base.BackgroundImageLayoutChanged -= value; } } [Browsable (false)] [EditorBrowsable (EditorBrowsableState.Never)] public new event EventHandler Click { add { base.Click += value; } remove { base.Click -= value; } } [Browsable (false)] [EditorBrowsable (EditorBrowsableState.Never)] public new event EventHandler DoubleClick { add { base.DoubleClick += value; } remove { base.DoubleClick -= value; } } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] public new event EventHandler ForeColorChanged { add { base.ForeColorChanged += value; } remove { base.ForeColorChanged -= value; } } [Browsable (false)] [EditorBrowsable (EditorBrowsableState.Never)] public new event MouseEventHandler MouseClick { add { base.MouseClick += value; } remove { base.MouseClick -= value; } } [Browsable (false)] [EditorBrowsable (EditorBrowsableState.Never)] public new event MouseEventHandler MouseDoubleClick { add { base.MouseDoubleClick += value; } remove { base.MouseDoubleClick -= value; } } [Browsable (false)] [EditorBrowsable (EditorBrowsableState.Never)] public new event EventHandler PaddingChanged { add { base.PaddingChanged += value; } remove { base.PaddingChanged -= value; } } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] public new event PaintEventHandler Paint { add { base.Paint += value; } remove { base.Paint -= value; } } public event EventHandler RightToLeftLayoutChanged { add { Events.AddHandler (RightToLeftLayoutChangedEvent, value); } remove { Events.RemoveHandler (RightToLeftLayoutChangedEvent, value); } } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Advanced)] public new event EventHandler TextChanged { add { base.TextChanged += value; } remove { base.TextChanged -= value; } } #endregion // public events #region protected properties // not sure why we're overriding this one protected override CreateParams CreateParams { get { return base.CreateParams; } } // specify the default size for this control protected override Size DefaultSize { get { // todo actually measure this properly return new Size (200, PreferredHeight); } } #endregion // protected properties #region protected methods // not sure why we're overriding this one protected override AccessibleObject CreateAccessibilityInstance () { return base.CreateAccessibilityInstance (); } // not sure why we're overriding this one protected override void CreateHandle () { base.CreateHandle (); } // not sure why we're overriding this one protected override void DestroyHandle () { base.DestroyHandle (); } // find out if this key is an input key for us, depends on which date part is focused protected override bool IsInputKey (Keys keyData) { switch (keyData) { case Keys.Up: case Keys.Down: case Keys.Left: case Keys.Right: return true; } return false; } // raises the CloseUp event protected virtual void OnCloseUp (EventArgs eventargs) { EventHandler eh = (EventHandler)(Events [CloseUpEvent]); if (eh != null) eh (this, eventargs); } // raise the drop down event protected virtual void OnDropDown (EventArgs eventargs) { EventHandler eh = (EventHandler)(Events [DropDownEvent]); if (eh != null) eh (this, eventargs); } protected override void OnFontChanged(EventArgs e) { // FIXME - do we need to update/invalidate/recalc our stuff? month_calendar.Font = Font; Size = new Size (Size.Width, PreferredHeight); base.OnFontChanged (e); } // raises the format changed event protected virtual void OnFormatChanged (EventArgs e) { EventHandler eh = (EventHandler)(Events [FormatChangedEvent]); if (eh != null) eh (this, e); } protected override void OnHandleCreated (EventArgs e) { base.OnHandleCreated(e); } protected override void OnHandleDestroyed (EventArgs e) { base.OnHandleDestroyed(e); } [EditorBrowsable (EditorBrowsableState.Advanced)] protected virtual void OnRightToLeftLayoutChanged (EventArgs e) { EventHandler eh = (EventHandler) Events [RightToLeftLayoutChangedEvent]; if (eh != null) eh (this, e); } // not sure why we're overriding this one protected override void OnSystemColorsChanged (EventArgs e) { base.OnSystemColorsChanged (e); } // raise the ValueChanged event protected virtual void OnValueChanged (EventArgs eventargs) { EventHandler eh = (EventHandler)(Events [ValueChangedEvent]); if (eh != null) eh (this, eventargs); } // SetBoundsCore was removed from the 2.0 public API, so // I had to do this hack instead. :/ internal override int OverrideHeight (int height) { return DefaultSize.Height; } // not sure why we're overriding this protected override void WndProc (ref Message m) { base.WndProc (ref m); } #endregion // protected methods #region internal / private properties // this is the region that the date and the check box is drawn on internal Rectangle date_area_rect { get { return ThemeEngine.Current.DateTimePickerGetDateArea (this); } } internal Rectangle CheckBoxRect { get { Rectangle retval = new Rectangle (check_box_space, ClientSize.Height / 2 - check_box_size / 2, check_box_size, check_box_size); return retval; } } // the rectangle for the drop down arrow internal Rectangle drop_down_arrow_rect { get { return ThemeEngine.Current.DateTimePickerGetDropDownButtonArea (this); } } // the part of the date that is currently hilighted internal Rectangle hilight_date_area { get { // TODO: put hilighted part calculation in here return Rectangle.Empty; } } internal bool DropDownButtonEntered { get { return drop_down_button_entered; } } #endregion #region internal / private methods private void ResizeHandler (object sender, EventArgs e) { Invalidate (); } private void UpDownTimerTick (object sender, EventArgs e) { if (updown_timer.Interval == initial_timer_delay) updown_timer.Interval = subsequent_timer_delay; if (is_down_pressed) IncrementSelectedPart (-1); else if (is_up_pressed) IncrementSelectedPart (1); else updown_timer.Enabled = false; } // calculates the maximum width internal Single CalculateMaxWidth(string format, Graphics gr, StringFormat string_format) { SizeF size; float result = 0; string text; Font font = this.Font; switch (format) { case "M": case "MM": case "MMM": case "MMMM": for (int i = 1; i <= 12; i++) { text = PartData.GetText (Value.AddMonths (i), format); size = gr.MeasureString (text, font, int.MaxValue, string_format); result = Math.Max (result, size.Width); } return result; case "d": case "dd": case "ddd": case "dddd": for (int i = 1; i <= 12; i++) { text = PartData.GetText (Value.AddDays (i), format); size = gr.MeasureString (text, font, int.MaxValue, string_format); result = Math.Max (result, size.Width); } return result; case "h": case "hh": for (int i = 1; i <= 12; i++) { text = PartData.GetText (Value.AddHours (i), format); size = gr.MeasureString (text, font, int.MaxValue, string_format); result = Math.Max (result, size.Width); } return result; case "H": case "HH": for (int i = 1; i <= 24; i++) { text = PartData.GetText (Value.AddDays (i), format); size = gr.MeasureString (text, font, int.MaxValue, string_format); result = Math.Max (result, size.Width); } return result; case "m": case "mm": for (int i = 1; i <= 60; i++) { text = PartData.GetText (Value.AddMinutes (i), format); size = gr.MeasureString (text, font, int.MaxValue, string_format); result = Math.Max (result, size.Width); } return result; case "s": case "ss": for (int i = 1; i <= 60; i++) { text = PartData.GetText (Value.AddSeconds (i), format); size = gr.MeasureString (text, font, int.MaxValue, string_format); result = Math.Max (result, size.Width); } return result; case "t": case "tt": for (int i = 1; i <= 2; i++) { text = PartData.GetText (Value.AddHours (i * 12), format); size = gr.MeasureString (text, font, int.MaxValue, string_format); result = Math.Max (result, size.Width); } return result; case "y": case "yy": case "yyyy": // Actually all the allowed year values are between MinDateTime and MaxDateTime, // which are 4 digits always text = PartData.GetText (Value, format); size = gr.MeasureString (text, font, int.MaxValue, string_format); result = Math.Max (result, size.Width); return result; default: return gr.MeasureString (format, font, int.MaxValue, string_format).Width; } } // returns the format of the date as a string // (i.e. resolves the Format enum values to it's corresponding string format) // Why CurrentCulture and not CurrentUICulture is explained here: // http://blogs.msdn.com/michkap/archive/2007/01/11/1449754.aspx private string GetExactFormat() { switch (this.format) { case DateTimePickerFormat.Long: return Threading.Thread.CurrentThread.CurrentCulture.DateTimeFormat.LongDatePattern; case DateTimePickerFormat.Short: return Threading.Thread.CurrentThread.CurrentCulture.DateTimeFormat.ShortDatePattern; case DateTimePickerFormat.Time: return Threading.Thread.CurrentThread.CurrentCulture.DateTimeFormat.LongTimePattern; case DateTimePickerFormat.Custom: return this.custom_format == null ? String.Empty : this.custom_format; default: return Threading.Thread.CurrentThread.CurrentCulture.DateTimeFormat.LongDatePattern; } } private void CalculateFormats() { string real_format; System.Text.StringBuilder literal = new System.Text.StringBuilder (); System.Collections.ArrayList formats = new ArrayList (); bool is_literal = false; char lastch = (char) 0; char ch; real_format = GetExactFormat (); // parse the format string for (int i = 0; i < real_format.Length; i++) { ch = real_format [i]; if (is_literal && ch != '\'') { literal.Append (ch); continue; } switch (ch) { case 't': case 'd': case 'h': case 'H': case 'm': case 'M': case 's': case 'y': case 'g': // Spec says nothing about g, but it seems to be treated like spaces. if (!(lastch == ch || lastch == 0) && literal.Length != 0) { formats.Add (new PartData(literal.ToString (), false, this)); literal.Length = 0; } literal.Append (ch); break; case '\'': if (is_literal && i < real_format.Length - 1 && real_format [i + 1] == '\'') { literal.Append (ch); i++; break; } if (literal.Length == 0) { is_literal = !is_literal; break; } formats.Add (new PartData (literal.ToString (), is_literal, this)); literal.Length = 0; is_literal = !is_literal; break; default: if (literal.Length != 0) { formats.Add (new PartData(literal.ToString (), false, this)); literal.Length = 0; } formats.Add (new PartData (ch.ToString(), true, this)); break; } lastch = ch; } if (literal.Length >= 0) formats.Add (new PartData (literal.ToString (), is_literal, this)); part_data = new PartData [formats.Count]; formats.CopyTo (part_data); } private Point CalculateDropDownLocation (Rectangle parent_control_rect, Size child_size, bool align_left) { // default bottom left Point location = new Point(parent_control_rect.Left + 5, parent_control_rect.Bottom); // now adjust the alignment if (!align_left) { location.X = parent_control_rect.Right - child_size.Width; } Point screen_location = PointToScreen (location); Rectangle working_area = Screen.FromControl(this).WorkingArea; // now adjust if off the right side of the screen if (screen_location.X < working_area.X) { screen_location.X = working_area.X; } // now adjust if it should be displayed above control if (screen_location.Y + child_size.Height > working_area.Bottom) { screen_location.Y -= (parent_control_rect.Height + child_size.Height); } // since the parent of the month calendar is the form, adjust accordingly. if (month_calendar.Parent != null) { screen_location = month_calendar.Parent.PointToClient(screen_location); } return screen_location; } // actually draw this control internal void Draw (Rectangle clip_rect, Graphics dc) { ThemeEngine.Current.DrawDateTimePicker (dc, clip_rect, this); } // drop the calendar down internal void DropDownMonthCalendar () { EndDateEdit (true); // ensure the right date is set for the month_calendar month_calendar.SetDate (this.date_value); // get a rectangle that has the dimensions of the text area, // but the height of the dtp control. Rectangle align_area = this.date_area_rect; align_area.Y = this.ClientRectangle.Y; align_area.Height = this.ClientRectangle.Height; // establish the month calendar's location month_calendar.Location = CalculateDropDownLocation ( align_area, month_calendar.Size, (this.DropDownAlign == LeftRightAlignment.Left)); month_calendar.Show (); month_calendar.Focus (); month_calendar.Capture = true; // fire any registered events // XXX should this just call OnDropDown? EventHandler eh = (EventHandler)(Events [DropDownEvent]); if (eh != null) eh (this, EventArgs.Empty); } // hide the month calendar internal void HideMonthCalendar () { this.is_drop_down_visible = false; Invalidate (drop_down_arrow_rect); month_calendar.Capture = false; if (month_calendar.Visible) { month_calendar.Hide (); } Focus (); } private int GetSelectedPartIndex() { for (int i = 0; i < part_data.Length; i++) { if (part_data[i].Selected && !part_data[i].is_literal) return i; } return -1; } internal void IncrementSelectedPart(int delta) { int selected_index = GetSelectedPartIndex(); if (selected_index == -1) { return; } EndDateEdit (false); DateTimePart dt_part = part_data [selected_index].date_time_part; switch (dt_part) { case DateTimePart.Day: if (delta < 0) { if (Value.Day == 1) SetPart(DateTime.DaysInMonth(Value.Year, Value.Month), dt_part); else SetPart(Value.Day + delta, dt_part); } else { if (Value.Day == DateTime.DaysInMonth(Value.Year, Value.Month)) SetPart(1, dt_part); else SetPart(Value.Day + delta, dt_part) ; } break; case DateTimePart.DayName: Value = Value.AddDays(delta); break; case DateTimePart.AMPMHour: case DateTimePart.Hour: SetPart(Value.Hour + delta, dt_part); break; case DateTimePart.Minutes: SetPart(Value.Minute + delta, dt_part); break; case DateTimePart.Month: SetPart (Value.Month + delta, dt_part, true); break; case DateTimePart.Seconds: SetPart(Value.Second + delta, dt_part); break; case DateTimePart.AMPMSpecifier: int hour = Value.Hour; hour = hour >= 0 && hour <= 11 ? hour + 12 : hour - 12; SetPart (hour, DateTimePart.Hour); break; case DateTimePart.Year: SetPart(Value.Year + delta, dt_part); break; } } internal void SelectPart (int index) { is_checkbox_selected = false; for (int i = 0; i < part_data.Length; i++) { part_data[i].Selected = (i == index); } Invalidate (); OnUIASelectionChanged (); } internal void SelectNextPart() { int selected_index; if (is_checkbox_selected) { for (int i = 0; i < part_data.Length; i++) { if (!part_data[i].is_literal) { is_checkbox_selected = false; part_data[i].Selected = true; Invalidate(); break; } } } else { selected_index = GetSelectedPartIndex(); if (selected_index >= 0) part_data [selected_index].Selected = false; for (int i = selected_index + 1; i < part_data.Length; i++) { if (!part_data[i].is_literal) { part_data [i].Selected = true; Invalidate(); break; } } if (GetSelectedPartIndex() == -1) { // if no part was found before the end, look from the beginning if (ShowCheckBox) { is_checkbox_selected = true; Invalidate(); } else { for (int i = 0; i <= selected_index; i++) { if (!part_data[i].is_literal) { part_data[i].Selected = true; Invalidate(); break; } } } } } OnUIASelectionChanged (); } internal void SelectPreviousPart() { if (is_checkbox_selected) { for (int i = part_data.Length - 1; i >= 0; i--) { if (!part_data[i].is_literal) { is_checkbox_selected = false; part_data[i].Selected = true; Invalidate(); break; } } } else { int selected_index = GetSelectedPartIndex(); if (selected_index >= 0) part_data[selected_index].Selected = false; for (int i = selected_index - 1; i >= 0; i--) { if (!part_data[i].is_literal) { part_data[i].Selected = true; Invalidate(); break; } } if (GetSelectedPartIndex() == -1) { // if no part was found before the beginning, look from the end if (ShowCheckBox) { is_checkbox_selected = true; Invalidate(); } else { for (int i = part_data.Length - 1; i >= selected_index; i--) { if (!part_data[i].is_literal) { part_data[i].Selected = true; Invalidate(); break; } } } } } OnUIASelectionChanged (); } // raised by key down events. private void KeyDownHandler(object sender, KeyEventArgs e) { switch (e.KeyCode) { case Keys.Add: case Keys.Up: { if (ShowCheckBox && Checked == false) break; IncrementSelectedPart(1); e.Handled = true; break; } case Keys.Subtract: case Keys.Down: { if (ShowCheckBox && Checked == false) break; IncrementSelectedPart(-1); e.Handled = true; break; } case Keys.Left: {// select the next part to the left if (ShowCheckBox && Checked == false) break; SelectPreviousPart(); e.Handled = true; break; } case Keys.Right: {// select the next part to the right if (ShowCheckBox && Checked == false) break; SelectNextPart(); e.Handled = true; break; } case Keys.F4: if (!e.Alt && !is_drop_down_visible) { DropDownMonthCalendar (); e.Handled = true; } break; } } // raised by any key down events private void KeyPressHandler (object sender, KeyPressEventArgs e) { switch (e.KeyChar) { case ' ': if (show_check_box && is_checkbox_selected) Checked = !Checked; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': int number = e.KeyChar - (int) '0'; int selected_index = GetSelectedPartIndex(); if (selected_index == -1) break; if (!part_data[selected_index].is_numeric_format) break; DateTimePart dt_part = part_data [selected_index].date_time_part; if (editing_part_index < 0) { editing_part_index = selected_index; editing_number = 0; editing_text = String.Empty; } editing_text += number.ToString (); int date_part_max_length = 0; switch (dt_part) { case DateTimePart.Day: case DateTimePart.Month: case DateTimePart.Seconds: case DateTimePart.Minutes: case DateTimePart.AMPMHour: case DateTimePart.Hour: date_part_max_length = 2; break; case DateTimePart.Year: date_part_max_length = 4; break; } editing_number = editing_number * 10 + number; if (editing_text.Length >= date_part_max_length) EndDateEdit (false); Invalidate (date_area_rect); break; default: break; } e.Handled = true; } private void EndDateEdit (bool invalidate) { if (editing_part_index == -1) return; PartData part = part_data [editing_part_index]; if (part.date_time_part == DateTimePart.Year) { // Special case // Infer, like .Net does if (editing_number > 0 && editing_number < 30) editing_number += 2000; else if (editing_number >= 30 && editing_number < 100) editing_number += 1900; } SetPart (editing_number, part.date_time_part); editing_part_index = editing_number = -1; editing_text = null; if (invalidate) Invalidate (date_area_rect); } internal void SetPart (int value, DateTimePart dt_part) { SetPart (value, dt_part, false); } // set the specified part of the date to the specified value internal void SetPart (int value, DateTimePart dt_part, bool adjust) { switch (dt_part) { case DateTimePart.Seconds: if (value == -1) value = 59; if (value >= 0 && value <= 59) Value = new DateTime(Value.Year, Value.Month, Value.Day, Value.Hour, Value.Minute, value, Value.Millisecond); break; case DateTimePart.Minutes: if (value == -1) value = 59; if (value >= 0 && value <= 59) Value = new DateTime(Value.Year, Value.Month, Value.Day, Value.Hour, value, Value.Second, Value.Millisecond); break; case DateTimePart.AMPMHour: if (value == -1) value = 23; if (value >= 0 && value <= 23) { int prev_hour = Value.Hour; if ((prev_hour >= 12 && prev_hour <= 23) && value < 12) // Adjust to p.m. value += 12; Value = new DateTime (Value.Year, Value.Month, Value.Day, value, Value.Minute, Value.Second, Value.Millisecond); } break; case DateTimePart.Hour: if (value == -1) value = 23; if (value >= 0 && value <= 23) Value = new DateTime(Value.Year, Value.Month, Value.Day, value, Value.Minute, Value.Second, Value.Millisecond); break; case DateTimePart.Day: int max_days = DateTime.DaysInMonth(Value.Year, Value.Month); if (value >= 1 && value <= 31 && value <= max_days) Value = new DateTime(Value.Year, Value.Month, value, Value.Hour, Value.Minute, Value.Second, Value.Millisecond); break; case DateTimePart.Month: DateTime date = Value; if (adjust) { if (value == 0) { date = date.AddYears (-1); value = 12; } else if (value == 13) { date = date.AddYears (1); value = 1; } } if (value >= 1 && value <= 12) { // if we move from say december to november with days on 31, we must // remap to the maximum number of days int days_in_new_month = DateTime.DaysInMonth (date.Year, value); if (date.Day > days_in_new_month) Value = new DateTime (date.Year, value, days_in_new_month, date.Hour, date.Minute, date.Second, date.Millisecond); else Value = new DateTime (date.Year, value, date.Day, date.Hour, date.Minute, date.Second, date.Millisecond); } break; case DateTimePart.Year: if (value >= min_date.Year && value <= max_date.Year) { // if we move to a leap year, the days in month could throw an exception int days_in_new_month = DateTime.DaysInMonth (value, Value.Month); if (Value.Day > days_in_new_month) Value = new DateTime (value, Value.Month, days_in_new_month, Value.Hour, Value.Minute, Value.Second, Value.Millisecond); else Value = new DateTime (value, Value.Month, Value.Day, Value.Hour, Value.Minute, Value.Second, Value.Millisecond); } break; } } private void GotFocusHandler (object sender, EventArgs e) { if (ShowCheckBox) { is_checkbox_selected = true; Invalidate (CheckBoxRect); OnUIASelectionChanged (); } } // if we loose focus deselect any selected parts. private void LostFocusHandler (object sender, EventArgs e) { int selected_index = GetSelectedPartIndex (); if (selected_index != -1) { part_data [selected_index].Selected = false; Rectangle invalidate_rect = Rectangle.Ceiling (part_data [selected_index].drawing_rectangle); invalidate_rect.Inflate (2, 2); Invalidate (invalidate_rect); OnUIASelectionChanged (); } else if (is_checkbox_selected) { is_checkbox_selected = false; Invalidate (CheckBoxRect); OnUIASelectionChanged (); } } // if month calendar looses focus and the drop down is up, then close it private void MonthCalendarLostFocusHandler(object sender, EventArgs e) { if (is_drop_down_visible && !month_calendar.Focused) { //this.HideMonthCalendar(); //This is handled from the monthcalender itself, //it may loose focus, but still has to be visible, //for instance when the context menu is displayed. } } private void MonthCalendarDateChangedHandler (object sender, DateRangeEventArgs e) { if (month_calendar.Visible) this.Value = e.Start.Date.Add (this.Value.TimeOfDay); } // fired when a user clicks on the month calendar to select a date private void MonthCalendarDateSelectedHandler (object sender, DateRangeEventArgs e) { this.HideMonthCalendar (); } private void MouseUpHandler(object sender, MouseEventArgs e) { if (ShowUpDown) { if (is_up_pressed || is_down_pressed) { updown_timer.Enabled = false; is_up_pressed = false; is_down_pressed = false; Invalidate (drop_down_arrow_rect); } } } // to check if the mouse has come down on this control private void MouseDownHandler (object sender, MouseEventArgs e) { // Only left clicks are handled. if (e.Button != MouseButtons.Left) return; if (ShowCheckBox && CheckBoxRect.Contains(e.X, e.Y)) { is_checkbox_selected = true; Checked = !Checked; OnUIASelectionChanged (); return; } // Deselect the checkbox only if the pointer is not on it // *and* the other parts are enabled (Checked as true) if (Checked) { is_checkbox_selected = false; OnUIASelectionChanged (); } if (ShowUpDown && drop_down_arrow_rect.Contains (e.X, e.Y)) { if (!(ShowCheckBox && Checked == false)) { if (e.Y < this.Height / 2) { is_up_pressed = true; is_down_pressed = false; IncrementSelectedPart (1); } else { is_up_pressed = false; is_down_pressed = true; IncrementSelectedPart (-1); } Invalidate (drop_down_arrow_rect); updown_timer.Interval = initial_timer_delay; updown_timer.Enabled = true; } } else if (is_drop_down_visible == false && drop_down_arrow_rect.Contains (e.X, e.Y)) { DropDownButtonClicked (); } else { // mouse down on this control anywhere else collapses it if (is_drop_down_visible) { HideMonthCalendar (); } if (!(ShowCheckBox && Checked == false)) { // go through the parts to see if the click is in any of them bool invalidate_afterwards = false; for (int i = 0; i < part_data.Length; i++) { bool old = part_data [i].Selected; if (part_data [i].is_literal) continue; if (part_data [i].drawing_rectangle.Contains (e.X, e.Y)) { part_data [i].Selected = true; } else part_data [i].Selected = false; if (old != part_data [i].Selected) invalidate_afterwards = true; } if (invalidate_afterwards) { Invalidate (); OnUIASelectionChanged (); } } } } internal void DropDownButtonClicked () { if (!is_drop_down_visible) { is_drop_down_visible = true; if (!Checked) Checked = true; Invalidate (drop_down_arrow_rect); DropDownMonthCalendar (); } else { HideMonthCalendar (); } } // paint this control now private void PaintHandler (object sender, PaintEventArgs pe) { if (Width <= 0 || Height <= 0 || Visible == false) return; Draw (pe.ClipRectangle, pe.Graphics); } void OnMouseEnter (object sender, EventArgs e) { if (ThemeEngine.Current.DateTimePickerBorderHasHotElementStyle) Invalidate (); } void OnMouseLeave (object sender, EventArgs e) { drop_down_button_entered = false; if (ThemeEngine.Current.DateTimePickerBorderHasHotElementStyle) Invalidate (); } void OnMouseMove (object sender, MouseEventArgs e) { if (!is_drop_down_visible && ThemeEngine.Current.DateTimePickerDropDownButtonHasHotElementStyle && drop_down_button_entered != drop_down_arrow_rect.Contains (e.Location)) { drop_down_button_entered = !drop_down_button_entered; Invalidate (drop_down_arrow_rect); } } #endregion #region internal classes internal enum DateTimePart { Seconds, Minutes, AMPMHour, Hour, Day, DayName, Month, Year, AMPMSpecifier, Literal } internal class PartData { internal string value; internal bool is_literal; bool is_selected; internal RectangleF drawing_rectangle; internal DateTimePart date_time_part; DateTimePicker owner; internal bool is_numeric_format { get { if (is_literal) return false; switch (value) { case "m": case "mm": case "d": case "dd": case "h": case "hh": case "H": case "HH": case "M": case "MM": case "s": case "ss": case "y": case "yy": case "yyyy": return true; case "ddd": case "dddd": return false; default: return false; } } } internal PartData(string value, bool is_literal, DateTimePicker owner) { this.value = value; this.is_literal = is_literal; this.owner = owner; date_time_part = GetDateTimePart (value); } internal bool Selected { get { return is_selected; } set { if (value == is_selected) return; owner.EndDateEdit (false); is_selected = value; } } // calculate the string to show for this data internal string GetText(DateTime date) { if (is_literal) { return value; } else { return GetText (date, value); } } static DateTimePart GetDateTimePart (string value) { switch (value) { case "s": case "ss": return DateTimePart.Seconds; case "m": case "mm": return DateTimePart.Minutes; case "h": case "hh": return DateTimePart.AMPMHour; case "H": case "HH": return DateTimePart.Hour; case "d": case "dd": return DateTimePart.Day; case "ddd": case "dddd": return DateTimePart.DayName; case "M": case "MM": case "MMMM": return DateTimePart.Month; case "y": case "yy": case "yyy": case "yyyy": return DateTimePart.Year; case "t": case "tt": return DateTimePart.AMPMSpecifier; } return DateTimePart.Literal; } static internal string GetText(DateTime date, string format) { if (format.StartsWith ("g")) return " "; else if (format.Length == 1) return date.ToString ("%" + format); else if (format == "yyyyy" || format == "yyyyyy" || format == "yyyyyyy" || format == "yyyyyyyy") return date.ToString ("yyyy"); else if (format.Length > 1) return date.ToString (format); else return string.Empty; } } #endregion #region UIA Framework: Methods, Properties and Events internal bool UIAIsCheckBoxSelected { get { return is_checkbox_selected; } } static object UIAMinimumChangedEvent = new object (); static object UIAMaximumChangedEvent = new object (); static object UIASelectionChangedEvent = new object (); static object UIACheckedEvent = new object (); static object UIAShowCheckBoxChangedEvent = new object (); static object UIAShowUpDownChangedEvent = new object (); internal event EventHandler UIAMinimumChanged { add { Events.AddHandler (UIAMinimumChangedEvent, value); } remove { Events.RemoveHandler (UIAMinimumChangedEvent, value); } } internal event EventHandler UIAMaximumChanged { add { Events.AddHandler (UIAMinimumChangedEvent, value); } remove { Events.RemoveHandler (UIAMinimumChangedEvent, value); } } internal event EventHandler UIASelectionChanged { add { Events.AddHandler (UIASelectionChangedEvent, value); } remove { Events.RemoveHandler (UIASelectionChangedEvent, value); } } internal event EventHandler UIAChecked { add { Events.AddHandler (UIACheckedEvent, value); } remove { Events.RemoveHandler (UIACheckedEvent, value); } } internal event EventHandler UIAShowCheckBoxChanged { add { Events.AddHandler (UIAShowCheckBoxChangedEvent, value); } remove { Events.RemoveHandler (UIAShowCheckBoxChangedEvent, value); } } internal event EventHandler UIAShowUpDownChanged { add { Events.AddHandler (UIAShowUpDownChangedEvent, value); } remove { Events.RemoveHandler (UIAShowUpDownChangedEvent, value); } } internal void OnUIAMinimumChanged () { EventHandler eh = (EventHandler)(Events [UIAMinimumChangedEvent]); if (eh != null) eh (this, EventArgs.Empty); } internal void OnUIAMaximumChanged () { EventHandler eh = (EventHandler)(Events [UIAMaximumChangedEvent]); if (eh != null) eh (this, EventArgs.Empty); } internal void OnUIASelectionChanged () { EventHandler eh = (EventHandler)(Events [UIASelectionChangedEvent]); if (eh != null) eh (this, EventArgs.Empty); } internal void OnUIAChecked () { EventHandler eh = (EventHandler)(Events [UIACheckedEvent]); if (eh != null) eh (this, EventArgs.Empty); } internal void OnUIAShowCheckBoxChanged () { EventHandler eh = (EventHandler)(Events [UIAShowCheckBoxChangedEvent]); if (eh != null) eh (this, EventArgs.Empty); } internal void OnUIAShowUpDownChanged () { EventHandler eh = (EventHandler)(Events [UIAShowUpDownChangedEvent]); if (eh != null) eh (this, EventArgs.Empty); } #endregion } }