// Copyright © 2006 by Wunnell Development // // This code may be used and distributed freely, either in part or in full, // under the condition that this copyright notice is included unchanged. using System; using System.Diagnostics; using System.Windows.Forms; namespace Wunnell.Windows.Forms { /// /// Displays a date/time picker in a DataGridView control. /// /// /// The DataGridViewDateTimePickerCell class is a specialised type of DataGridViewCell used to display a date/time picker control, which is a date/time editing field combined with a drop-down calendar field. The currently selected DataGridViewDateTimePickerCell hosts a DataGridViewDateTimePickerEditingControl in which the user can edit the cell's value, assuming the cell's ReadOnly property is set to false. /// public class DataGridViewDateTimePickerCell : DataGridViewTextBoxCell { #region Constants /// /// The default value of the Checked property. /// internal const bool DEFAULT_CHECKED = true; /// /// The default value of the CustomFormat property. /// internal const string DEFAULT_CUSTOM_FORMAT = null; /// /// The default value of the Format property. /// internal const DateTimePickerFormat DEFAULT_FORMAT = DateTimePickerFormat.Long; /// /// The default value of the ShowCheckBox property. /// internal const bool DEFAULT_SHOW_CHECK_BOX = false; /// /// The default value of the ShowUpDown property. /// internal const bool DEFAULT_SHOW_UP_DOWN = false; /// /// The default value of the ZerTime property. /// internal const bool DEFAULT_ZERO_TIME = false; #endregion // Constants #region Variables /// /// When ShowCheckBox is true, determines if the check box is checked, indicating that the user has selected a value. /// private bool _checked; /// /// The custom format string used to format the date and/or time displayed in the cell. /// private string _customFormat; /// /// Determines whether dates and times are displayed using standard or custom formatting. /// private DateTimePickerFormat _format; /// /// The maximum date that can be selected. /// private DateTime _maxDate; /// /// The minimum date that can be selected. /// private DateTime _minDate; /// /// Determines whether a check box is displayed in the control. When the box is unchecked, no value is selected. /// private bool _showCheckBox; /// /// Indicates whether a spin box rather than a drop-down calendar is displayed for modifying the control value. /// private bool _showUpDown; /// /// Indicates whether the time should be zeroed to provide a value containing effectively just a date. /// private bool _zeroTime; #endregion // Variables #region Properties /// /// Gets the DataGridView's current EditingControl as a DataGridViewDateTimePickerEditingControl reference. /// /// /// The DataGridView's current DataGridViewDateTimePickerEditingControl. /// private DataGridViewDateTimePickerEditingControl EditingDateTimePicker { get { return this.DataGridView.EditingControl as DataGridViewDateTimePickerEditingControl; } } /// /// Gets or sets a value indicating whether the Value property has been set with a valid date/time value and the displayed value is able to be updated. /// /// /// true if the Value property has been set with a valid DateTime value and the displayed value is able to be updated; otherwise, false. The default is true. /// public bool Checked { get { if (this.RowIndex < 0) { // The row is not displayed in the grid so use the Value property is not available. return this._checked; } else { return this.Value != DBNull.Value; } } set { if (value != (this.Value != DBNull.Value)) { // The value has changed so set the new value and refresh the grid. this.SetChecked(this.RowIndex, value); this.OnPropertyChange(); } } } /// /// Gets or sets the custom date/time format string. /// /// /// A string that represents the custom date/time format. The default is a null reference. /// public string CustomFormat { get { return this._customFormat; } set { if (value != this._customFormat) { // The value has changed so set the new value and refresh the grid. this.SetCustomFormat(this.RowIndex, value); this.OnPropertyChange(); } } } /// /// Overridden. Gets the default value for a cell in the row for new records. /// /// /// null if the cell is not in edit mode and the editing control would display an unchecked check box by default; otherwise, the current date and time. /// public override object DefaultNewRowValue { get { if (this.IsInEditMode || !this._showCheckBox || this._checked) { //DateTime.Now : // The default value for a populated cell is the current date and, optionally, time. return this.ZeroTime ? this.MinDate : this.MinDate; // DateTime.Now : DateTime.Now; } else { // An unchecked check box in a cell that is not being edited indicates a null value. return DBNull.Value; } } } /// /// Overridden. Gets the type of the cell's hosted editing control. /// /// /// A Type representing the DataGridViewDateTimePickerEditingControl type. /// public override Type EditType { get { // Return the type of the control that DataGridViewDateTimePickerCell contains. return typeof(DataGridViewDateTimePickerEditingControl); } } /// /// Gets or sets the format of the date and time displayed in the control. /// /// /// One of the DateTimePickerFormat values. The default is Long. /// public DateTimePickerFormat Format { get { return this._format; } set { if (value != this._format) { // The value has changed so set the new value and refresh the grid. this.SetFormat(this.RowIndex, value); this.OnPropertyChange(); } } } /// /// Gets or sets the maximum date and time that can be selected in the control. /// /// /// The maximum date and time that can be selected in the control. The default is 12/31/9998 00:00:00. /// public DateTime MaxDate { get { return this._maxDate; } set { if (value != this._maxDate) { // The value has changed so set the new value and refresh the grid. this.SetMaxDate(this.RowIndex, value); this.OnPropertyChange(); } } } /// /// Gets or sets the minimum date and time that can be selected in the control. /// /// /// The minimum date and time that can be selected in the control. The default is 1/1/1753 00:00:00. /// public DateTime MinDate { get { return this._minDate; } set { if (value != this._minDate) { // The value has changed so set the new value and refresh the grid. this.SetMinDate(this.RowIndex, value); this.OnPropertyChange(); } } } /// /// Gets or sets a value indicating whether a check box is displayed to the left of the selected date. /// /// /// true if a check box is displayed to the left of the selected date; otherwise, false. The default is false. /// public bool ShowCheckBox { get { return this._showCheckBox; } set { if (value != this._showCheckBox) { // The value has changed so set the new value and refresh the grid. this.SetShowCheckBox(this.RowIndex, value); this.OnPropertyChange(); } } } /// /// Gets or sets a value indicating whether a spin button control (also known as an up-down control) is used to adjust the date/time value. /// /// /// true if a spin button control is used to adjust the date/time value; otherwise, false. The default is false. /// public bool ShowUpDown { get { return this._showUpDown; } set { if (value != this._showUpDown) { // The value has changed so set the new value and refresh the grid. this.SetShowUpDown(this.RowIndex, value); this.OnPropertyChange(); } } } /// /// Overridden. Gets the data type of the values in the cell. /// /// /// A Type representing the DateTime type. /// public override Type ValueType { get { // Return the type of the value that DataGridViewDateTimePickerCell contains. return typeof(DateTime); } } /// /// Gets or sets a value indicating whether the time should be zeroed to provide a value containing effectively just a date. /// /// /// true if the time portion of the date/time value is zeroed; otherwise, false. The default is false. /// /// /// This property always returns true when a standard date form, i.e. Long or Short, is in use. It always returns false if a standard time format, i.e. Time, is in use. It is user-configurable when a custom format is in use. /// public bool ZeroTime { get { bool zeroTime; switch (this.Format) { // The time is always zeroed when a standard date format is used. case DateTimePickerFormat.Long: case DateTimePickerFormat.Short: zeroTime = true; break; // The time is never zeroed when a standard time format is used. case DateTimePickerFormat.Time: zeroTime = false; break; // The property is user-configurable when a custom format is used. case DateTimePickerFormat.Custom: default: zeroTime = this._zeroTime; break; } return zeroTime; } set { if (value != this._zeroTime) { // The value has changed so set the new value. this.SetZeroTime(this.RowIndex, value); if (this.Format == DateTimePickerFormat.Custom) { // Refresh the grid only if a custom format is in use, otherwise the property's return value will not have changed. this.OnPropertyChange(); } } } } #endregion // Properties #region Constructors /// /// Initialises a new instance of the DataGridViewTextBoxCell class. /// /// /// This constructor initialises the following property values. /// Checked: true /// CustomFormat: null reference /// Format: Long /// MaxDate: 31/12/9998 /// MinDate: 1/01/1753 /// ShowCheckBox: false /// ShowUpDown: false /// public DataGridViewDateTimePickerCell() : base() { this._checked = DEFAULT_CHECKED; this._customFormat = DEFAULT_CUSTOM_FORMAT; this._format = DEFAULT_FORMAT; this._maxDate = DateTimePicker.MaximumDateTime; this._minDate = DateTimePicker.MinimumDateTime; this._showCheckBox = DEFAULT_SHOW_CHECK_BOX; this._showUpDown = DEFAULT_SHOW_UP_DOWN; this._zeroTime = DEFAULT_ZERO_TIME; } #endregion // Constructors #region Methods /// /// Overridden. Creates an exact copy of this cell. /// /// /// An Object that represents the cloned DataGridViewDateTimePickerCell. /// public override object Clone() { DataGridViewDateTimePickerCell clone = base.Clone() as DataGridViewDateTimePickerCell; if (clone != null) { clone.Checked = this.Checked; clone.CustomFormat = this.CustomFormat; clone.Format = this.Format; clone.MaxDate = this.MaxDate; clone.MinDate = this.MinDate; clone.ShowCheckBox = this.ShowCheckBox; clone.ShowUpDown = this.ShowUpDown; clone.ZeroTime = this.ZeroTime; } return clone; } /// /// Gets the value closest to the specified value that is also within the constraints of the minimum and maximum allowable values. /// /// /// The proposed value. /// /// /// The valid value closest to the proposed value. /// private DateTime Constrain(DateTime value) { Debug.Assert(this._minDate <= this._maxDate); if (value < this._minDate) { value = this._minDate; } else if (value > this._maxDate) { value = this._maxDate; } return value; } /// /// Overrideen. Removes the cell's editing control from the DataGridView. /// public override void DetachEditingControl() { DateTimePicker editingControl = this.EditingDateTimePicker; if (editingControl != null) { // Update the cell's value based on the value in the editing control and whether or not it is checked. this.Value = this.ZeroTime ? editingControl.Value.Date : editingControl.Value; this.Checked = editingControl.Checked; } base.DetachEditingControl(); } /// /// Converts the current cell content alignment into the corresponding drop-down alignment for the editing control. /// /// /// One of the LeftRightAlignment values. /// /// /// If the cell alignment is not set default column alignment is used. If this is not set then the default value Left is used. /// private LeftRightAlignment GetEditingControlDropDownAlign() { // Get the content alignment of the cell. If it is not set then get the default alignment for the column. DataGridViewContentAlignment cellAlignment = this.Style.Alignment == DataGridViewContentAlignment.NotSet ? this.OwningColumn.DefaultCellStyle.Alignment : this.Style.Alignment; LeftRightAlignment controlAlignment; switch (cellAlignment) { case DataGridViewContentAlignment.MiddleRight: case DataGridViewContentAlignment.BottomRight: case DataGridViewContentAlignment.TopRight: controlAlignment = LeftRightAlignment.Right; break; case DataGridViewContentAlignment.MiddleLeft: case DataGridViewContentAlignment.BottomLeft: case DataGridViewContentAlignment.TopLeft: default: controlAlignment = LeftRightAlignment.Left; break; } return controlAlignment; } /// /// Overrideen. Gets the formatted value of the cell's data. /// /// /// The value to be formatted. /// /// /// The index of the cell's parent row. /// /// /// The DataGridViewCellStyle in effect for the cell. /// /// /// A TypeConverter associated with the value type that provides custom conversion to the formatted value type, or a null reference if no such custom conversion is needed. /// /// /// A TypeConverter associated with the formatted value type that provides custom conversion from the value type, or a null reference if no such custom conversion is needed. /// /// /// A bitwise combination of DataGridViewDataErrorContexts values describing the context in which the formatted value is needed. /// /// /// The value of the cell's data after formatting has been applied or a null reference if the cell is not part of a DataGridView control. /// protected override object GetFormattedValue(object value, int rowIndex, ref DataGridViewCellStyle cellStyle, System.ComponentModel.TypeConverter valueTypeConverter, System.ComponentModel.TypeConverter formattedValueTypeConverter, DataGridViewDataErrorContexts context) { object formattedValue = base.GetFormattedValue(value, rowIndex, ref cellStyle, valueTypeConverter, formattedValueTypeConverter, context); string formattedString = formattedValue as string; if (!string.IsNullOrEmpty(formattedString) && value != null) { DateTime unformattedValue = (DateTime)value; switch (this.Format) { case DateTimePickerFormat.Custom: formattedValue = unformattedValue.ToString(this.CustomFormat); break; case DateTimePickerFormat.Long: formattedValue = unformattedValue.ToLongDateString(); break; case DateTimePickerFormat.Short: formattedValue = unformattedValue.ToShortDateString(); break; case DateTimePickerFormat.Time: formattedValue = unformattedValue.ToLongTimeString(); break; } } return formattedValue; } /// /// Overrideen. Initializes the control used to edit the cell. /// /// /// The zero-based row index of the cell's location. /// /// /// An Object that represents the value displayed by the cell when editing is started. /// /// /// A DataGridViewCellStyle that represents the style of the cell. /// /// /// After initialising the control as per the base implementation, this method sets the properties of the DateTimePicker control in accordance with the cell properties. /// public override void InitializeEditingControl(int rowIndex, object initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle) { base.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle); DataGridViewDateTimePickerEditingControl editingControl = this.DataGridView.EditingControl as DataGridViewDateTimePickerEditingControl; if (editingControl != null) { string initialFormattedString = initialFormattedValue as string; // Set the specific properties of the DateTimePicker control. if (string.IsNullOrEmpty(initialFormattedString)) { editingControl.Value = (DateTime)this.DefaultNewRowValue; } else { editingControl.Value = this.Constrain(DateTime.Parse(initialFormattedString)); } editingControl.Checked = this.Checked; editingControl.CustomFormat = this.CustomFormat; editingControl.DropDownAlign = this.GetEditingControlDropDownAlign(); editingControl.Format = this.Format; editingControl.MaxDate = this.MaxDate; editingControl.MinDate = this.MinDate; editingControl.ShowCheckBox = this.ShowCheckBox; editingControl.ShowUpDown = this.ShowUpDown; } } ///// ///// Refreshes the DataGridView as needed to account for the new property value. ///// private void OnPropertyChange() { if (this.DataGridView != null && !this.DataGridView.IsDisposed && !this.DataGridView.Disposing) { if (this.RowIndex == -1) { // Invalidate and autosize the parent column this.DataGridView.InvalidateColumn(this.ColumnIndex); // TODO: Explicitly resize the parent row and the row and column headers as required. } else { // Invalidate and autosize the parent row and column, including headers. this.DataGridView.UpdateCellValue(this.ColumnIndex, this.RowIndex); } } } // //Determines whether the current cell is displaying the the grid's editing control at the specified row index. // // //The zero-based index of the row to test. This is required because the current cell may be shared among multiple rows. // // //false if the cell or row is detached; true if the current cell is displaying the grid's editing control in the specified row; otherwise, false. // private bool OwnsEditingDateTimePicker(int rowIndex) { bool result; if (rowIndex == -1 || this.DataGridView == null) { result = false; } else { DataGridViewDateTimePickerEditingControl editingControl = this.DataGridView.EditingControl as DataGridViewDateTimePickerEditingControl; result = editingControl != null && rowIndex == ((IDataGridViewEditingControl)editingControl).EditingControlRowIndex; } return result; } /// /// Sets a value indicating whether the Value property has been set with a valid date/time value and the displayed value is able to be updated. /// /// /// The index of the row containing the current cell. /// /// /// A value indicating whether the Value property has been set with a valid date/time value and the displayed value is able to be updated. /// internal void SetChecked(int rowIndex, bool value) { this._checked = value; if (this._checked) { if (this.Value == DBNull.Value) { // The cell is checked but does not contain a value so assign the default value. this.Value = this.DefaultNewRowValue; } } else { // The cell is not checked so set the value to null. this.Value = DBNull.Value; } if (this.OwnsEditingDateTimePicker(rowIndex)) { // Update the property of the current editing control. this.EditingDateTimePicker.Checked = value; } } /// /// Sets the custom date/time format string. /// /// /// The index of the row containing the current cell. /// /// /// A string that represents the custom date/time format. /// internal void SetCustomFormat(int rowIndex, string value) { this._customFormat = value; if (this.OwnsEditingDateTimePicker(rowIndex)) { // Update the property of the current editing control. this.EditingDateTimePicker.CustomFormat = value; } } /// /// Sets the maximum date and time that can be selected in the control. /// /// /// The index of the row containing the current cell. /// /// /// The maximum date and time that can be selected in the control. /// internal void SetFormat(int rowIndex, DateTimePickerFormat value) { Debug.Assert(Enum.GetName(typeof(DateTimePickerFormat), value) != null); this._format = value; if (this.OwnsEditingDateTimePicker(rowIndex)) { // Update the property of the current editing control. this.EditingDateTimePicker.Format = value; } } /// /// Sets the format of the date and time displayed in the control. /// /// /// The index of the row containing the current cell. /// /// /// One of the DateTimePickerFormat values. /// internal void SetMaxDate(int rowIndex, DateTime value) { Debug.Assert(value >= DateTimePicker.MinimumDateTime && value <= DateTimePicker.MaximumDateTime); this._maxDate = value; if (this.OwnsEditingDateTimePicker(rowIndex)) { // Update the property of the current editing control. this.EditingDateTimePicker.MaxDate = value; } } /// /// Sets the minimum date and time that can be selected in the control. /// /// /// The index of the row containing the current cell. /// /// /// The minimum date and time that can be selected in the control. /// internal void SetMinDate(int rowIndex, DateTime value) { Debug.Assert(value >= DateTimePicker.MinimumDateTime && value <= DateTimePicker.MaximumDateTime); this._minDate = value; if (this.OwnsEditingDateTimePicker(rowIndex)) { // Update the property of the current editing control. this.EditingDateTimePicker.MinDate = value; } } /// /// Sets a value indicating whether a check box is displayed to the left of the selected date. /// /// /// The index of the row containing the current cell. /// /// /// A value indicating whether a check box is displayed to the left of the selected date. /// internal void SetShowCheckBox(int rowIndex, bool value) { this._showCheckBox = value; if (this.OwnsEditingDateTimePicker(rowIndex)) { // Update the property of the current editing control. this.EditingDateTimePicker.ShowCheckBox = value; } } /// /// Sets a value indicating whether a spin button control (also known as an up-down control) is used to adjust the date/time value. /// /// /// The index of the row containing the current cell. /// /// /// A value indicating whether a spin button control (also known as an up-down control) is used to adjust the date/time value. /// internal void SetShowUpDown(int rowIndex, bool value) { this._showUpDown = value; if (this.OwnsEditingDateTimePicker(rowIndex)) { // Update the property of the current editing control. this.EditingDateTimePicker.ShowUpDown = value; } } /// /// Sets a value indicating whether the time should be zeroed to provide a value containing effectively just a date. /// /// /// The index of the row containing the current cell. /// /// /// A value indicating whether the time should be zeroed to provide a value containing effectively just a date. /// internal void SetZeroTime(int rowIndex, bool value) { this._zeroTime = value; } #endregion // Methods } }