|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.Windows.Forms.Layout;
using Microsoft.Win32;
using SourceGenerated;
using Windows.Win32.System.Variant;
using Windows.Win32.UI.Accessibility;
namespace System.Windows.Forms;
/// <summary>
/// Date/DateTime picker control.
/// </summary>
[DefaultProperty(nameof(Value))]
[DefaultEvent(nameof(ValueChanged))]
[DefaultBindingProperty(nameof(Value))]
[Designer($"System.Windows.Forms.Design.DateTimePickerDesigner, {AssemblyRef.SystemDesign}")]
[SRDescription(nameof(SR.DescriptionDateTimePicker))]
public partial class DateTimePicker : Control
{
/// <summary>
/// Specifies the default title back color. This field is read-only.
/// </summary>
protected static readonly Color DefaultTitleBackColor = SystemColors.ActiveCaption;
/// <summary>
/// Specifies the default foreground color. This field is read-only.
/// </summary>
protected static readonly Color DefaultTitleForeColor = SystemColors.ActiveCaptionText;
/// <summary>
/// Specifies the default month background color. This field is read-only.
/// </summary>
protected static readonly Color DefaultMonthBackColor = SystemColors.Window;
/// <summary>
/// Specifies the default trailing foreground color. This field is read-only.
/// </summary>
protected static readonly Color DefaultTrailingForeColor = SystemColors.GrayText;
private static readonly object s_formatChangedEvent = new();
private static readonly string s_dateTimePickerLocalizedControlTypeString = SR.DateTimePickerLocalizedControlType;
private const uint TIMEFORMAT_NOUPDOWN = PInvoke.DTS_TIMEFORMAT & (~PInvoke.DTS_UPDOWN);
private EventHandler? _onCloseUp;
private EventHandler? _onDropDown;
private EventHandler? _onValueChanged;
private EventHandler? _onRightToLeftLayoutChanged;
private ExpandCollapseState _expandCollapseState;
// We need to restrict the available dates because of limitations in the comctl DateTime and MonthCalendar controls
/// <summary>
/// Specifies the minimum date value. This field is read-only.
/// </summary>
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public static readonly DateTime MinDateTime = new(1753, 1, 1);
/// <summary>
/// Specifies the maximum date value. This field is read-only.
/// </summary>
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public static readonly DateTime MaxDateTime = new(9998, 12, 31);
private uint _style;
private short _prefHeightCache = -1;
/// <summary>
/// Determines whether the CheckBox in the DTP is checked. The CheckBox is only
/// displayed when ShowCheckBox is true.
/// </summary>
private bool _validTime = true;
// DateTime changeover: DateTime is a value class, not an object, so we need to keep track
// of whether or not its values have been initialised in a separate boolean.
private bool _userHasSetValue;
private DateTime _value = DateTime.Now;
private DateTime _creationTime = DateTime.Now;
// Reconcile out-of-range min/max values in the property getters.
private DateTime _maxDateTime = DateTime.MaxValue;
private DateTime _minDateTime = DateTime.MinValue;
private Color _calendarForeColor = DefaultForeColor;
private Color _calendarTitleBackColor = DefaultTitleBackColor;
private Color _calendarTitleForeColor = DefaultTitleForeColor;
private Color _calendarMonthBackground = DefaultMonthBackColor;
private Color _calendarTrailingText = DefaultTrailingForeColor;
private Font? _calendarFont;
private FontHandleWrapper? _calendarFontHandleWrapper;
// Since there is no way to get the customFormat from the DTP, we need to cache it. Also we have to track if
// the user wanted customFormat or shortDate format (shortDate is the lack of being in Long or DateTime format
// without a customFormat).
private string? _customFormat;
private DateTimePickerFormat _format;
private bool _rightToLeftLayout;
/// <summary>
/// Initializes a new instance of the <see cref="DateTimePicker"/> class.
/// </summary>
public DateTimePicker() : base()
{
// This class overrides GetPreferredSizeCore, let Control automatically cache the result.
SetExtendedState(ExtendedStates.UserPreferredSizeCache, true);
SetStyle(ControlStyles.FixedHeight, true);
// Since DateTimePicker does its own mouse capturing, we do not want to receive standard click events, or
// we end up with mismatched mouse button up and button down messages.
SetStyle(ControlStyles.UserPaint | ControlStyles.StandardClick, false);
_format = DateTimePickerFormat.Long;
SetStyle(ControlStyles.UseTextForAccessibility, false);
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override Color BackColor
{
get => ShouldSerializeBackColor() ? base.BackColor : SystemColors.Window;
set => base.BackColor = 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 override Image? BackgroundImage
{
get => base.BackgroundImage;
set => base.BackgroundImage = 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 override ImageLayout BackgroundImageLayout
{
get => base.BackgroundImageLayout;
set => base.BackgroundImageLayout = value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler? BackgroundImageLayoutChanged
{
add => base.BackgroundImageLayoutChanged += value;
remove => base.BackgroundImageLayoutChanged -= value;
}
/// <summary>
/// The current value of the CalendarForeColor property.
/// </summary>
[SRCategory(nameof(SR.CatAppearance))]
[SRDescription(nameof(SR.DateTimePickerCalendarForeColorDescr))]
public Color CalendarForeColor
{
get => _calendarForeColor;
set
{
if (value.IsEmpty)
{
throw new ArgumentException(string.Format(SR.InvalidNullArgument, nameof(value)));
}
if (!value.Equals(_calendarForeColor))
{
_calendarForeColor = value;
SetControlColor(PInvoke.MCSC_TEXT, value);
}
}
}
/// <summary>
/// The current value of the CalendarFont property.
/// </summary>
[SRCategory(nameof(SR.CatAppearance))]
[Localizable(true)]
[AmbientValue(null)]
[AllowNull]
[SRDescription(nameof(SR.DateTimePickerCalendarFontDescr))]
public Font CalendarFont
{
get => _calendarFont ?? Font;
set
{
if ((value is null && _calendarFont is not null) || (value is not null && !value.Equals(_calendarFont)))
{
_calendarFont = value;
_calendarFontHandleWrapper = null;
SetControlCalendarFont();
}
}
}
private HFONT CalendarFontHandle
{
get
{
if (_calendarFont is null)
{
Debug.Assert(_calendarFontHandleWrapper is null, "font handle out of sync with Font");
return FontHandle;
}
_calendarFontHandleWrapper ??= new FontHandleWrapper(CalendarFont);
return _calendarFontHandleWrapper.Handle;
}
}
/// <summary>
/// The current value of the CalendarTitleBackColor property.
/// </summary>
[SRCategory(nameof(SR.CatAppearance))]
[SRDescription(nameof(SR.DateTimePickerCalendarTitleBackColorDescr))]
public Color CalendarTitleBackColor
{
get => _calendarTitleBackColor;
set
{
if (value.IsEmpty)
{
throw new ArgumentException(string.Format(SR.InvalidNullArgument, nameof(value)));
}
if (!value.Equals(_calendarTitleBackColor))
{
_calendarTitleBackColor = value;
SetControlColor(PInvoke.MCSC_TITLEBK, value);
}
}
}
/// <summary>
/// The current value of the CalendarTitleForeColor property.
/// </summary>
[SRCategory(nameof(SR.CatAppearance))]
[SRDescription(nameof(SR.DateTimePickerCalendarTitleForeColorDescr))]
public Color CalendarTitleForeColor
{
get => _calendarTitleForeColor;
set
{
if (value.IsEmpty)
{
throw new ArgumentException(string.Format(SR.InvalidNullArgument, nameof(value)));
}
if (!value.Equals(_calendarTitleForeColor))
{
_calendarTitleForeColor = value;
SetControlColor(PInvoke.MCSC_TITLETEXT, value);
}
}
}
/// <summary>
/// The current value of the CalendarTrailingForeColor property.
/// </summary>
[SRCategory(nameof(SR.CatAppearance))]
[SRDescription(nameof(SR.DateTimePickerCalendarTrailingForeColorDescr))]
public Color CalendarTrailingForeColor
{
get => _calendarTrailingText;
set
{
if (value.IsEmpty)
{
throw new ArgumentException(string.Format(SR.InvalidNullArgument, nameof(value)));
}
if (!value.Equals(_calendarTrailingText))
{
_calendarTrailingText = value;
SetControlColor(PInvoke.MCSC_TRAILINGTEXT, value);
}
}
}
/// <summary>
/// The current value of the CalendarMonthBackground property.
/// </summary>
[SRCategory(nameof(SR.CatAppearance))]
[SRDescription(nameof(SR.DateTimePickerCalendarMonthBackgroundDescr))]
public Color CalendarMonthBackground
{
get => _calendarMonthBackground;
set
{
if (value.IsEmpty)
{
throw new ArgumentException(string.Format(SR.InvalidNullArgument, nameof(value)));
}
if (!value.Equals(_calendarMonthBackground))
{
_calendarMonthBackground = value;
SetControlColor(PInvoke.MCSC_MONTHBK, value);
}
}
}
/// <summary>
/// Indicates whether the <see cref="Value"/> property has been set.
/// </summary>
[SRCategory(nameof(SR.CatBehavior))]
[DefaultValue(true)]
[Bindable(true)]
[SRDescription(nameof(SR.DateTimePickerCheckedDescr))]
public bool Checked
{
get
{
// The information from win32 DateTimePicker is reliable only when ShowCheckBoxes is True
if (ShowCheckBox && IsHandleCreated)
{
SYSTEMTIME systemTime = default;
nint result = PInvokeCore.SendMessage(this, PInvoke.DTM_GETSYSTEMTIME, 0, ref systemTime);
return result == (nint)NMDATETIMECHANGE_FLAGS.GDT_VALID;
}
else
{
return _validTime;
}
}
set
{
if (Checked != value)
{
// set the information into the win32 DateTimePicker only if ShowCheckBoxes is True
if (ShowCheckBox && IsHandleCreated)
{
if (value)
{
SYSTEMTIME systemTime = (SYSTEMTIME)_value;
PInvokeCore.SendMessage(this, PInvoke.DTM_SETSYSTEMTIME, (WPARAM)(uint)NMDATETIMECHANGE_FLAGS.GDT_VALID, ref systemTime);
}
else
{
PInvokeCore.SendMessage(this, PInvoke.DTM_SETSYSTEMTIME, (WPARAM)(uint)NMDATETIMECHANGE_FLAGS.GDT_NONE);
}
}
// this.validTime is used when the DateTimePicker receives date time change notification
// from the Win32 control. this.validTime will be used to know when we transition from valid time to unvalid time
// also, validTime will be used when ShowCheckBox == false
_validTime = value;
}
}
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler? Click
{
add => base.Click += value;
remove => base.Click -= value;
}
/// <summary>
/// Returns the CreateParams used to create this window.
/// </summary>
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ClassName = PInvoke.DATETIMEPICK_CLASS;
cp.Style |= (int)_style;
switch (_format)
{
case DateTimePickerFormat.Long:
cp.Style |= (int)PInvoke.DTS_LONGDATEFORMAT;
break;
case DateTimePickerFormat.Short:
break;
case DateTimePickerFormat.Time:
cp.Style |= (int)TIMEFORMAT_NOUPDOWN;
break;
case DateTimePickerFormat.Custom:
break;
}
cp.ExStyle |= (int)WINDOW_EX_STYLE.WS_EX_CLIENTEDGE;
if (RightToLeft == RightToLeft.Yes && RightToLeftLayout)
{
// We want to turn on mirroring for DateTimePicker explicitly.
cp.ExStyle |= (int)WINDOW_EX_STYLE.WS_EX_LAYOUTRTL;
// Don't need these styles when mirroring is turned on.
cp.ExStyle &= ~(int)(WINDOW_EX_STYLE.WS_EX_RTLREADING | WINDOW_EX_STYLE.WS_EX_RIGHT | WINDOW_EX_STYLE.WS_EX_LEFTSCROLLBAR);
}
return cp;
}
}
/// <summary>
/// Returns the custom format.
/// </summary>
[DefaultValue(null)]
[Localizable(true)]
[RefreshProperties(RefreshProperties.Repaint)]
[SRCategory(nameof(SR.CatBehavior))]
[SRDescription(nameof(SR.DateTimePickerCustomFormatDescr))]
public string? CustomFormat
{
get => _customFormat;
set
{
if ((value is not null && !value.Equals(_customFormat)) ||
(value is null && _customFormat is not null))
{
_customFormat = value;
if (IsHandleCreated)
{
if (_format == DateTimePickerFormat.Custom)
{
PInvokeCore.SendMessage(this, PInvoke.DTM_SETFORMATW, 0, _customFormat);
}
}
}
}
}
protected override Size DefaultSize => new(200, PreferredHeight);
[EditorBrowsable(EditorBrowsableState.Never)]
protected override bool DoubleBuffered
{
get => base.DoubleBuffered;
set => base.DoubleBuffered = value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler? DoubleClick
{
add => base.DoubleClick += value;
remove => base.DoubleClick -= value;
}
/// <summary>
/// The calendar dropdown can be aligned to the left or right of the control.
/// </summary>
[DefaultValue(LeftRightAlignment.Left)]
[SRCategory(nameof(SR.CatAppearance))]
[Localizable(true)]
[SRDescription(nameof(SR.DateTimePickerDropDownAlignDescr))]
public LeftRightAlignment DropDownAlign
{
get => (_style & PInvoke.DTS_RIGHTALIGN) != 0 ? LeftRightAlignment.Right : LeftRightAlignment.Left;
set
{
// Valid values are 0x0 to 0x1
EnumValidator.Validate(value);
SetStyleBit(value == LeftRightAlignment.Right, PInvoke.DTS_RIGHTALIGN);
}
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override Color ForeColor
{
#pragma warning disable WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates.
get => ShouldSerializeForeColor()
|| Application.IsDarkModeEnabled
? base.ForeColor
: SystemColors.WindowText;
#pragma warning restore WFO5001
set => base.ForeColor = value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler? ForeColorChanged
{
add => base.ForeColorChanged += value;
remove => base.ForeColorChanged -= value;
}
/// <summary>
/// Returns the current value of the format property. This determines the
/// style of format the date is displayed in.
/// </summary>
[SRCategory(nameof(SR.CatAppearance))]
[RefreshProperties(RefreshProperties.Repaint)]
[SRDescription(nameof(SR.DateTimePickerFormatDescr))]
public DateTimePickerFormat Format
{
get => _format;
set
{
EnumValidator.Validate(value);
if (_format != value)
{
_format = value;
RecreateHandle();
OnFormatChanged(EventArgs.Empty);
}
}
}
[SRCategory(nameof(SR.CatPropertyChanged))]
[SRDescription(nameof(SR.DateTimePickerOnFormatChangedDescr))]
public event EventHandler? FormatChanged
{
add => Events.AddHandler(s_formatChangedEvent, value);
remove => Events.RemoveHandler(s_formatChangedEvent, value);
}
/// <summary>
/// DateTimePicker Paint.
/// </summary>
/// <hideinheritance/>
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event PaintEventHandler? Paint
{
add => base.Paint += value;
remove => base.Paint -= value;
}
// Make sure the passed in minDate respects the current culture: this
// is especially important if the culture changes from a Gregorian or
// other calendar with a lowish minDate (see comment on MinimumDateTime)
// to a calendar, which has a minimum date of 1/1/1912.
internal static DateTime EffectiveMinDate(DateTime minDate)
{
DateTime minSupportedDate = MinimumDateTime;
if (minDate < minSupportedDate)
{
return minSupportedDate;
}
return minDate;
}
// Similarly, make sure the maxDate respects the current culture. No
// problems are anticipated here: I don't believe there are calendars
// around that have max dates on them. But if there are, we'll deal with
// them correctly.
internal static DateTime EffectiveMaxDate(DateTime maxDate)
{
DateTime maxSupportedDate = MaximumDateTime;
if (maxDate > maxSupportedDate)
{
return maxSupportedDate;
}
return maxDate;
}
/// <summary>
/// Indicates the maximum date and time selectable in the control.
/// </summary>
[SRCategory(nameof(SR.CatBehavior))]
[SRDescription(nameof(SR.DateTimePickerMaxDateDescr))]
public DateTime MaxDate
{
get
{
return EffectiveMaxDate(_maxDateTime);
}
set
{
if (value == _maxDateTime)
{
return;
}
ArgumentOutOfRangeException.ThrowIfLessThan(value, EffectiveMinDate(_minDateTime));
if (value > MaximumDateTime)
{
throw new ArgumentOutOfRangeException(
nameof(value),
value,
string.Format(SR.DateTimePickerMaxDate, FormatDateTime(MaxDateTime)));
}
_maxDateTime = value;
SetRange();
// If Value (which was once valid) is suddenly greater than the max (since we just set it) then adjust this.
if (Value > _maxDateTime)
{
Value = _maxDateTime;
}
}
}
/// <summary>
/// Specifies the maximum date value. This property is read-only.
/// </summary>
public static DateTime MaximumDateTime
{
get
{
DateTime maxSupportedDateTime = CultureInfo.CurrentCulture.Calendar.MaxSupportedDateTime;
if (maxSupportedDateTime.Year > MaxDateTime.Year)
{
return MaxDateTime;
}
return maxSupportedDateTime;
}
}
/// <summary>
/// Indicates the minimum date and time selectable in the control.
/// </summary>
[SRCategory(nameof(SR.CatBehavior))]
[SRDescription(nameof(SR.DateTimePickerMinDateDescr))]
public DateTime MinDate
{
get
{
return EffectiveMinDate(_minDateTime);
}
set
{
if (value == _minDateTime)
{
return;
}
ArgumentOutOfRangeException.ThrowIfGreaterThan(value, EffectiveMaxDate(_maxDateTime));
if (value < MinimumDateTime)
{
throw new ArgumentOutOfRangeException(
nameof(value),
value,
string.Format(SR.DateTimePickerMinDate, FormatDateTime(MinimumDateTime)));
}
_minDateTime = value;
SetRange();
// If Value (which was once valid) is suddenly less than the min (since we just set it) then adjust this.
if (Value < _minDateTime)
{
Value = _minDateTime;
}
}
}
// We restrict the available dates to >= 1753 because of oddness in the Gregorian calendar about
// that time. We do this even for cultures that don't use the Gregorian calendar -- we're not
// really that worried about calendars for >250 years ago.
/// <summary>
/// Specifies the minimum date value. This property is read-only.
/// </summary>
public static DateTime MinimumDateTime
{
get
{
DateTime minSupportedDateTime = CultureInfo.CurrentCulture.Calendar.MinSupportedDateTime;
if (minSupportedDateTime.Year < 1753)
{
return new DateTime(1753, 1, 1);
}
return minSupportedDateTime;
}
}
[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)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public new Padding Padding
{
get => base.Padding;
set => base.Padding = value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler? PaddingChanged
{
add => base.PaddingChanged += value;
remove => base.PaddingChanged -= value;
}
/// <summary>
/// Indicates the preferred height of the DateTimePicker control. This property is read-only.
/// </summary>
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public int PreferredHeight
{
get
{
if (_prefHeightCache > -1)
{
return _prefHeightCache;
}
// Base the preferred height on the current font
int height = FontHeight;
// Adjust for the border
height += SystemInformation.BorderSize.Height * 4 + 3;
_prefHeightCache = (short)height;
return height;
}
}
/// <summary>
/// This is used for international applications where the language is written from RightToLeft.
/// When this property is true, and the RightToLeft is true, mirroring will be turned on on
/// the form, and control placement and text will be from right to left.
/// </summary>
[SRCategory(nameof(SR.CatAppearance))]
[Localizable(true)]
[DefaultValue(false)]
[SRDescription(nameof(SR.ControlRightToLeftLayoutDescr))]
public virtual bool RightToLeftLayout
{
get => _rightToLeftLayout;
set
{
if (value != _rightToLeftLayout)
{
_rightToLeftLayout = value;
using (new LayoutTransaction(this, this, PropertyNames.RightToLeftLayout))
{
OnRightToLeftLayoutChanged(EventArgs.Empty);
}
}
}
}
/// <summary>
/// Indicates whether a check box is displayed to toggle the NoValueSelected property value.
/// </summary>
[DefaultValue(false)]
[SRCategory(nameof(SR.CatAppearance))]
[SRDescription(nameof(SR.DateTimePickerShowNoneDescr))]
public bool ShowCheckBox
{
get => (_style & PInvoke.DTS_SHOWNONE) != 0;
set => SetStyleBit(value, PInvoke.DTS_SHOWNONE);
}
/// <summary>
/// Indicates whether an up-down control is used to adjust the time values.
/// </summary>
[DefaultValue(false)]
[SRCategory(nameof(SR.CatAppearance))]
[SRDescription(nameof(SR.DateTimePickerShowUpDownDescr))]
public bool ShowUpDown
{
get => (_style & PInvoke.DTS_UPDOWN) != 0;
set
{
if (ShowUpDown != value)
{
SetStyleBit(value, PInvoke.DTS_UPDOWN);
}
}
}
internal override bool SupportsUiaProviders => true;
/// <summary>
/// Overrides Text to allow for setting of the value via a string. Also, returns
/// a formatted Value when getting the text. The DateTime class will throw
/// an exception if the string (value) being passed in is invalid.
/// </summary>
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Advanced)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[AllowNull]
public override string Text
{
get => base.Text;
set
{
// Clause to check length
if (value is null || value.Length == 0)
{
ResetValue();
}
else
{
Value = DateTime.Parse(value, CultureInfo.CurrentCulture);
}
}
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public new event EventHandler? TextChanged
{
add => base.TextChanged += value;
remove => base.TextChanged -= value;
}
/// <summary>
/// Indicates the DateTime value assigned to the control.
/// </summary>
[SRCategory(nameof(SR.CatBehavior))]
[Bindable(true)]
[RefreshProperties(RefreshProperties.All)]
[SRDescription(nameof(SR.DateTimePickerValueDescr))]
public DateTime Value
{
get
{
// Checkbox clicked, no value set - no value set state should never occur, but just in case.
return !_userHasSetValue && _validTime ? _creationTime : _value;
}
set
{
bool valueChanged = !DateTime.Equals(Value, value);
// Check for value set here; if we've not set the value yet, it'll be Now, so the second part of the
// test will fail. So, if userHasSetValue isn't set, we don't care if the value is still the same -
// and we'll update anyway.
if (_userHasSetValue && !valueChanged)
{
return;
}
if ((value < MinDate) || (value > MaxDate))
{
throw new ArgumentOutOfRangeException(
nameof(value),
value,
string.Format(
SR.InvalidBoundArgument,
nameof(Value),
FormatDateTime(value),
$"'{nameof(MinDate)}'",
$"'{nameof(MaxDate)}'"));
}
string oldText = Text;
_value = value;
_userHasSetValue = true;
if (IsHandleCreated)
{
// Make sure any changes to this code get propagated to createHandle
SYSTEMTIME systemTime = (SYSTEMTIME)value;
PInvokeCore.SendMessage(this, PInvoke.DTM_SETSYSTEMTIME, (WPARAM)(uint)NMDATETIMECHANGE_FLAGS.GDT_VALID, ref systemTime);
}
if (valueChanged)
{
OnValueChanged(EventArgs.Empty);
}
if (!oldText.Equals(Text))
{
OnTextChanged(EventArgs.Empty);
}
}
}
/// <summary>
/// Occurs when the dropdown calendar is dismissed and disappears.
/// </summary>
[SRCategory(nameof(SR.CatAction))]
[SRDescription(nameof(SR.DateTimePickerOnCloseUpDescr))]
public event EventHandler? CloseUp
{
add => _onCloseUp += value;
remove => _onCloseUp -= value;
}
[SRCategory(nameof(SR.CatPropertyChanged))]
[SRDescription(nameof(SR.ControlOnRightToLeftLayoutChangedDescr))]
public event EventHandler? RightToLeftLayoutChanged
{
add => _onRightToLeftLayoutChanged += value;
remove => _onRightToLeftLayoutChanged -= value;
}
/// <summary>
/// Occurs when the value for the control changes.
/// </summary>
[SRCategory(nameof(SR.CatAction))]
[SRDescription(nameof(SR.valueChangedEventDescr))]
public event EventHandler? ValueChanged
{
add => _onValueChanged += value;
remove => _onValueChanged -= value;
}
/// <summary>
/// Occurs when the drop down calendar is shown.
/// </summary>
[SRCategory(nameof(SR.CatAction))]
[SRDescription(nameof(SR.DateTimePickerOnDropDownDescr))]
public event EventHandler? DropDown
{
add => _onDropDown += value;
remove => _onDropDown -= value;
}
protected override AccessibleObject CreateAccessibilityInstance() => new DateTimePickerAccessibleObject(this);
/// <summary>
/// Creates the physical window handle.
/// </summary>
protected override unsafe void CreateHandle()
{
if (!RecreatingHandle)
{
using ThemingScope scope = new(Application.UseVisualStyles);
PInvoke.InitCommonControlsEx(new INITCOMMONCONTROLSEX
{
dwSize = (uint)sizeof(INITCOMMONCONTROLSEX),
dwICC = INITCOMMONCONTROLSEX_ICC.ICC_DATE_CLASSES
});
}
_creationTime = DateTime.Now;
base.CreateHandle();
if (_userHasSetValue && _validTime)
{
// Make sure any changes to this code get propagated to setValue
SYSTEMTIME systemTime = (SYSTEMTIME)Value;
PInvokeCore.SendMessage(this, PInvoke.DTM_SETSYSTEMTIME, (uint)NMDATETIMECHANGE_FLAGS.GDT_VALID, ref systemTime);
}
else if (!_validTime)
{
PInvokeCore.SendMessage(this, PInvoke.DTM_SETSYSTEMTIME, (uint)NMDATETIMECHANGE_FLAGS.GDT_NONE);
}
if (_format == DateTimePickerFormat.Custom)
{
PInvokeCore.SendMessage(this, PInvoke.DTM_SETFORMATW, 0, _customFormat);
}
UpdateUpDown();
SetAllControlColors();
SetControlCalendarFont();
SetRange();
}
/// <summary>
/// Destroys the physical window handle.
/// </summary>
protected override void DestroyHandle()
{
_value = Value;
base.DestroyHandle();
}
/// <summary>
/// Return a localized string representation of the given DateTime value.
/// </summary>
private static string FormatDateTime(DateTime value)
{
return value.ToString("G", CultureInfo.CurrentCulture);
}
// GetPreferredSize and SetBoundsCore call this method to allow controls to self impose
// constraints on their size.
internal override Rectangle ApplyBoundsConstraints(int suggestedX, int suggestedY, int proposedWidth, int proposedHeight)
{
// Lock DateTimePicker to its preferred height.
return base.ApplyBoundsConstraints(suggestedX, suggestedY, proposedWidth, PreferredHeight);
}
internal override Size GetPreferredSizeCore(Size proposedConstraints)
{
int height = PreferredHeight;
int width = CommonProperties.GetSpecifiedBounds(this).Width;
return new Size(width, height);
}
/// <summary>
/// Handling special input keys, such as pgup, pgdown, home, end, etc...
/// </summary>
protected override bool IsInputKey(Keys keyData)
{
if ((keyData & Keys.Alt) == Keys.Alt)
{
return false;
}
return (keyData & Keys.KeyCode) switch
{
Keys.PageUp or Keys.PageDown or Keys.Home or Keys.End => true,
_ => base.IsInputKey(keyData),
};
}
/// <summary>
/// Raises the <see cref="CloseUp"/> event.
/// </summary>
protected virtual void OnCloseUp(EventArgs eventargs)
{
_onCloseUp?.Invoke(this, eventargs);
_expandCollapseState = ExpandCollapseState.ExpandCollapseState_Collapsed;
// Raise automation event to annouce new state.
if (IsAccessibilityObjectCreated)
{
AccessibilityObject.RaiseAutomationPropertyChangedEvent(
UIA_PROPERTY_ID.UIA_ExpandCollapseExpandCollapseStatePropertyId,
oldValue: (VARIANT)(int)ExpandCollapseState.ExpandCollapseState_Expanded,
newValue: (VARIANT)(int)ExpandCollapseState.ExpandCollapseState_Collapsed);
}
}
/// <summary>
/// Raises the <see cref="DropDown"/> event.
/// </summary>
protected virtual void OnDropDown(EventArgs eventargs)
{
_onDropDown?.Invoke(this, eventargs);
_expandCollapseState = ExpandCollapseState.ExpandCollapseState_Expanded;
// Raise automation event to announce new state.
if (IsAccessibilityObjectCreated)
{
AccessibilityObject.RaiseAutomationPropertyChangedEvent(
UIA_PROPERTY_ID.UIA_ExpandCollapseExpandCollapseStatePropertyId,
oldValue: (VARIANT)(int)ExpandCollapseState.ExpandCollapseState_Collapsed,
newValue: (VARIANT)(int)ExpandCollapseState.ExpandCollapseState_Expanded);
}
}
protected virtual void OnFormatChanged(EventArgs e)
{
if (Events[s_formatChangedEvent] is EventHandler eh)
{
eh(this, e);
}
}
protected override void OnGotFocus(EventArgs e)
{
base.OnGotFocus(e);
// Raise automation event to annouce the control.
if (IsAccessibilityObjectCreated)
{
_expandCollapseState = ExpandCollapseState.ExpandCollapseState_Collapsed;
AccessibilityObject.RaiseAutomationEvent(UIA_EVENT_ID.UIA_AutomationFocusChangedEventId);
}
}
/// <summary>
/// Add/remove SystemEvents in OnHandleCreated/Destroyed for robustness.
/// </summary>
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
SystemEvents.UserPreferenceChanged += MarshaledUserPreferenceChanged;
}
/// <summary>
/// Add/remove SystemEvents in OnHandleCreated/Destroyed for robustness.
/// </summary>
protected override void OnHandleDestroyed(EventArgs e)
{
SystemEvents.UserPreferenceChanged -= MarshaledUserPreferenceChanged;
base.OnHandleDestroyed(e);
}
protected override void OnEnabledChanged(EventArgs e)
{
base.OnEnabledChanged(e);
if (IsHandleCreated && Application.RenderWithVisualStyles)
{
// The SysDateTimePick32 control caches the style and uses that directly to determine whether the
// border should be drawn disabled when theming (VisualStyles) is enabled. Setting the window
// style to itself (which will have the proper WS_DISABLED setting after calling base) will
// flush the cached value and render the border as one would expect.
PInvokeCore.SetWindowLong(
this,
WINDOW_LONG_PTR_INDEX.GWL_STYLE,
PInvokeCore.GetWindowLong(this, WINDOW_LONG_PTR_INDEX.GWL_STYLE));
}
}
/// <summary>
/// Raises the <see cref="ValueChanged"/> event.
/// </summary>
protected virtual void OnValueChanged(EventArgs eventargs)
{
_onValueChanged?.Invoke(this, eventargs);
// Raise automation event to annouce changed value.
if (IsAccessibilityObjectCreated)
{
// If date is changed so dtp value is changed too.
// But I can't receive the previous value here,
// so I have to use current value twice.
// Anyway it doesn't matter because the Narrator pronounces actual AO state.
string? value = AccessibilityObject.Value;
using VARIANT variantValue = value is null ? default : (VARIANT)value;
AccessibilityObject.RaiseAutomationPropertyChangedEvent(
UIA_PROPERTY_ID.UIA_ValueValuePropertyId,
oldValue: variantValue,
newValue: variantValue);
}
}
[EditorBrowsable(EditorBrowsableState.Advanced)]
protected virtual void OnRightToLeftLayoutChanged(EventArgs e)
{
if (GetAnyDisposingInHierarchy())
{
return;
}
if (RightToLeft == RightToLeft.Yes)
{
RecreateHandle();
}
_onRightToLeftLayoutChanged?.Invoke(this, e);
}
protected override void OnFontChanged(EventArgs e)
{
base.OnFontChanged(e);
// clear the pref height cache
_prefHeightCache = -1;
Height = PreferredHeight;
if (_calendarFont is null)
{
_calendarFontHandleWrapper = null;
SetControlCalendarFont();
}
}
private void ResetCalendarForeColor()
{
CalendarForeColor = DefaultForeColor;
}
private void ResetCalendarFont()
{
CalendarFont = null;
}
private void ResetCalendarMonthBackground()
{
CalendarMonthBackground = DefaultMonthBackColor;
}
private void ResetCalendarTitleBackColor()
{
CalendarTitleBackColor = DefaultTitleBackColor;
}
private void ResetCalendarTitleForeColor()
{
CalendarTitleBackColor = DefaultForeColor;
}
private void ResetCalendarTrailingForeColor()
{
CalendarTrailingForeColor = DefaultTrailingForeColor;
}
/// <summary>
/// Resets the <see cref="Format"/> property to its default value.
/// </summary>
private void ResetFormat()
{
Format = DateTimePickerFormat.Long;
}
/// <summary>
/// Resets the <see cref="MaxDate"/> property to its default value.
/// </summary>
private void ResetMaxDate()
{
MaxDate = MaximumDateTime;
}
/// <summary>
/// Resets the <see cref="MinDate"/> property to its default value.
/// </summary>
private void ResetMinDate()
{
MinDate = MinimumDateTime;
}
/// <summary>
/// Resets the <see cref="Value"/> property to its default value.
/// </summary>
private void ResetValue()
{
// always update on reset with ShowNone = false -- as it'll take the current time.
_value = DateTime.Now;
// If ShowCheckBox = true, then userHasSetValue can be false (null value).
// otherwise, userHasSetValue is valid...
// userHasSetValue = !ShowCheckBox;
// After ResetValue() the flag indicating whether the user has set the value should be false.
_userHasSetValue = false;
// Update the text displayed in the DateTimePicker.
if (IsHandleCreated)
{
SYSTEMTIME systemTime = (SYSTEMTIME)_value;
PInvokeCore.SendMessage(this, PInvoke.DTM_SETSYSTEMTIME, (uint)NMDATETIMECHANGE_FLAGS.GDT_VALID, ref systemTime);
}
// Updating Checked to false will set the control to "no date" and clear its checkbox.
Checked = false;
OnValueChanged(EventArgs.Empty);
OnTextChanged(EventArgs.Empty);
}
/// <summary>
/// If the handle has been created, this applies the color to the control
/// </summary>
private void SetControlColor(uint colorIndex, Color value)
{
if (IsHandleCreated)
{
PInvokeCore.SendMessage(this, PInvoke.DTM_SETMCCOLOR, (WPARAM)(int)colorIndex, (LPARAM)value);
}
}
/// <summary>
/// If the handle has been created, this applies the font to the control.
/// </summary>
private void SetControlCalendarFont()
{
if (IsHandleCreated)
{
PInvokeCore.SendMessage(this, PInvoke.DTM_SETMCFONT, (WPARAM)CalendarFontHandle, (LPARAM)(-1));
}
}
/// <summary>
/// Applies all the colors to the control.
/// </summary>
private void SetAllControlColors()
{
SetControlColor(PInvoke.MCSC_MONTHBK, _calendarMonthBackground);
SetControlColor(PInvoke.MCSC_TEXT, _calendarForeColor);
SetControlColor(PInvoke.MCSC_TITLEBK, _calendarTitleBackColor);
SetControlColor(PInvoke.MCSC_TITLETEXT, _calendarTitleForeColor);
SetControlColor(PInvoke.MCSC_TRAILINGTEXT, _calendarTrailingText);
}
/// <summary>
/// Updates the window handle with the min/max ranges if it has been created.
/// </summary>
private void SetRange()
{
SetRange(EffectiveMinDate(_minDateTime), EffectiveMaxDate(_maxDateTime));
}
private void SetRange(DateTime min, DateTime max)
{
if (IsHandleCreated)
{
Span<SYSTEMTIME> times = [(SYSTEMTIME)min, (SYSTEMTIME)max];
uint flags = PInvoke.GDTR_MIN | PInvoke.GDTR_MAX;
PInvokeCore.SendMessage(this, PInvoke.DTM_SETRANGE, (WPARAM)flags, ref times[0]);
}
}
/// <summary>
/// Turns on or off a given style bit.
/// </summary>
private void SetStyleBit(bool flag, uint bit)
{
if (((_style & bit) != 0) == flag)
{
return;
}
if (flag)
{
_style |= bit;
}
else
{
_style &= ~bit;
}
if (IsHandleCreated)
{
RecreateHandle();
Invalidate();
Update();
}
}
/// <summary>
/// Determines if the <see cref="CalendarForeColor"/> property needs to be persisted.
/// </summary>
private bool ShouldSerializeCalendarForeColor()
{
return !CalendarForeColor.Equals(DefaultForeColor);
}
/// <summary>
/// Determines if the <see cref="CalendarFont"/> property needs to be persisted.
/// </summary>
private bool ShouldSerializeCalendarFont()
{
return _calendarFont is not null;
}
/// <summary>
/// Determines if the <see cref="CalendarTitleBackColor"/> property needs to be persisted.
/// </summary>
private bool ShouldSerializeCalendarTitleBackColor()
{
return !_calendarTitleBackColor.Equals(DefaultTitleBackColor);
}
/// <summary>
/// Determines if the <see cref="CalendarTitleForeColor"/> property needs to be persisted.
/// </summary>
private bool ShouldSerializeCalendarTitleForeColor()
{
return !_calendarTitleForeColor.Equals(DefaultTitleForeColor);
}
/// <summary>
/// Determines if the <see cref="CalendarTrailingForeColor"/> property needs to be persisted.
/// </summary>
private bool ShouldSerializeCalendarTrailingForeColor()
{
return !_calendarTrailingText.Equals(DefaultTrailingForeColor);
}
/// <summary>
/// Determines if the <see cref="CalendarMonthBackground"/> property needs to be persisted.
/// </summary>
private bool ShouldSerializeCalendarMonthBackground()
{
return !_calendarMonthBackground.Equals(DefaultMonthBackColor);
}
/// <summary>
/// Determines if the <see cref="MaxDate"/> property needs to be persisted.
/// </summary>
private bool ShouldSerializeMaxDate()
{
return _maxDateTime != MaximumDateTime && _maxDateTime != DateTime.MaxValue;
}
/// <summary>
/// Determines if the <see cref="MinDate"/> property needs to be persisted.
/// </summary>
private bool ShouldSerializeMinDate()
{
return _minDateTime != MinimumDateTime && _minDateTime != DateTime.MinValue;
}
/// <summary>
/// Determines if the <see cref="Value"/> property needs to be persisted.
/// </summary>
private bool ShouldSerializeValue()
{
return _userHasSetValue;
}
/// <summary>
/// Determines if the <see cref="Format"/> property needs to be persisted.
/// </summary>
private bool ShouldSerializeFormat()
{
return (Format != DateTimePickerFormat.Long);
}
public override string ToString() => $"{base.ToString()}, Value: {Value:G}";
/// <summary>
/// Forces a repaint of the updown control if it is displayed.
/// </summary>
private unsafe void UpdateUpDown()
{
// The upDown control doesn't repaint correctly.
if (ShowUpDown)
{
EnumChildren c = new();
PInvokeCore.EnumChildWindows(this, c.enumChildren);
if (!c.hwndFound.IsNull)
{
PInvoke.InvalidateRect(c.hwndFound, lpRect: (RECT*)null, bErase: true);
PInvoke.UpdateWindow(c.hwndFound);
}
}
}
private void MarshaledUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs pref)
{
try
{
// Use begininvoke instead of invoke in case the destination thread is not processing messages.
BeginInvoke(new UserPreferenceChangedEventHandler(UserPreferenceChanged), [sender, pref]);
}
catch (InvalidOperationException) { } // If the destination thread does not exist, don't send.
}
private void UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs pref)
{
if (pref.Category == UserPreferenceCategory.Locale)
{
// We need to recreate the monthcalendar handle when the locale changes, because
// the day names etc. are only updated on a handle recreate (comctl32 limitation).
RecreateHandle();
}
}
/// <summary>
/// Handles the DTN_DATETIMECHANGE notification.
/// </summary>
private unsafe void WmDateTimeChange(ref Message m)
{
NMDATETIMECHANGE* nmdtc = (NMDATETIMECHANGE*)(nint)m.LParamInternal;
DateTime temp = _value;
bool oldvalid = _validTime;
if (nmdtc->dwFlags != NMDATETIMECHANGE_FLAGS.GDT_NONE)
{
_validTime = true;
_value = (DateTime)nmdtc->st;
_userHasSetValue = true;
}
else
{
_validTime = false;
}
if (_value != temp || oldvalid != _validTime)
{
OnValueChanged(EventArgs.Empty);
OnTextChanged(EventArgs.Empty);
}
}
/// <summary>
/// Handles the DTN_DROPDOWN notification.
/// </summary>
private void WmDropDown()
{
if (RightToLeftLayout && RightToLeft == RightToLeft.Yes)
{
HWND handle = (HWND)PInvokeCore.SendMessage(this, PInvoke.DTM_GETMONTHCAL);
if (handle != IntPtr.Zero)
{
WINDOW_EX_STYLE style = (WINDOW_EX_STYLE)PInvokeCore.GetWindowLong(handle, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE);
style |= WINDOW_EX_STYLE.WS_EX_LAYOUTRTL | WINDOW_EX_STYLE.WS_EX_NOINHERITLAYOUT;
style &= ~(WINDOW_EX_STYLE.WS_EX_RIGHT | WINDOW_EX_STYLE.WS_EX_RTLREADING);
PInvokeCore.SetWindowLong(handle, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (nint)style);
GC.KeepAlive(this);
}
}
OnDropDown(EventArgs.Empty);
}
/// <summary>
/// Handles system color changes.
/// </summary>
protected override void OnSystemColorsChanged(EventArgs e)
{
SetAllControlColors();
base.OnSystemColorsChanged(e);
}
/// <summary>
/// Handles the WM_COMMAND messages reflected from the parent control.
/// </summary>
private unsafe void WmReflectCommand(ref Message m)
{
if (m.HWnd == Handle)
{
NMHDR* nmhdr = (NMHDR*)(nint)m.LParamInternal;
switch (nmhdr->code)
{
case PInvoke.DTN_CLOSEUP:
OnCloseUp(EventArgs.Empty);
break;
case PInvoke.DTN_DATETIMECHANGE:
WmDateTimeChange(ref m);
break;
case PInvoke.DTN_DROPDOWN:
WmDropDown();
break;
}
}
}
protected override void WndProc(ref Message m)
{
switch (m.MsgInternal)
{
case PInvokeCore.WM_LBUTTONDOWN:
Focus();
if (!ValidationCancelled)
{
base.WndProc(ref m);
}
break;
case MessageId.WM_REFLECT_NOTIFY:
WmReflectCommand(ref m);
base.WndProc(ref m);
break;
case PInvokeCore.WM_WINDOWPOSCHANGED:
base.WndProc(ref m);
UpdateUpDown();
break;
default:
base.WndProc(ref m);
break;
}
}
}
|