Imports System
Imports System.ComponentModel
Imports System.Drawing
Imports System.Windows.Forms

Namespace Wunnell.Windows.Forms
    ''' <summary>
    ''' Represents a Windows control that allows the user to select a date
    ''' and a time and to display the date and time with a specified format.
    ''' </summary>
    ''' <remarks>
    ''' The <b>NullableDateTimePicker</b> extends the <b>DateTimePicker</b> control by adding support for binding null
    ''' values through the <see cref="DataValue"/> property.  When a check box is displayed in the control and
    ''' is not checked, the control is displayed blank instead of with greyed text.  This represents a null value.
    ''' </remarks>
    <ToolboxBitmap(GetType(DateTimePicker))> _
    Public Class NullableDateTimePicker
        Inherits System.Windows.Forms.DateTimePicker
#Region "Constants"

        ''' <summary>
        ''' The default value for the Format property.
        ''' </summary>
        Private Const DEFAULT_FORMAT As DateTimePickerFormat = DateTimePickerFormat.Long

#End Region ' Constants

#Region "Variables"

        ''' <summary>
        ''' The custom format string used to format the date and/or time in the control.
        ''' </summary>
        Private _customFormat As String
        ''' <summary>
        ''' Determines whether dates and times are displayed using standard or custom formatting.
        ''' </summary>
        Private _format As DateTimePickerFormat
        Private oldDataValue As Object

#End Region ' Variables

#Region "Properties"

        ''' <summary>
        ''' Gets or sets a value indicating whether the <see cref="Value"/> property has been
        ''' set with a valid date/time value and the displayed value is able to be updated.
        ''' </summary>
        ''' <value>
        ''' <b>true</b> if the <b>Value</b> property has been set with a valid <b>DateTime</b> value and
        ''' the displayed value is able to be updated; otherwise, <b>false</b>. The default is <b>true</b>.
        ''' </value>
        ''' <remarks>
        ''' This property is used to obtain the state of the check box that is displayed if the <see cref="ShowCheckBox"/>
        ''' property value is <b>true</b>.  If the <b>Checked</b> property value is <b>true</b>, the
        ''' <see cref="NullableDateTimePicker"/> control displays the properly formatted <b>Value</b> property value and the
        ''' <see cref="DataValue"/> property value is the same as the <b>Value</b> property value; otherwise, if the <b>ShowCheckBox</b>
        ''' property value is true, the control is blank and the <b>DataValue</b> property value is <b>DBNull.Value</b>.
        ''' </remarks>
        Public Shadows Property Checked() As Boolean
            Get
                Return MyBase.Checked
            End Get
            Set(ByVal value As Boolean)
                If MyBase.Checked <> Value Then
                    MyBase.Checked = Value
                    Me.SetBaseFormat()
                End If
            End Set
        End Property

        ''' <summary>
        ''' Gets or sets the custom date/time format string.
        ''' </summary>
        ''' <value>
        ''' A string that represents the custom date/time format. The default is a null reference (<b>Nothing</b> in Visual Basic).
        ''' </value>
        Public Shadows Property CustomFormat() As String
            Get
                Return Me._customFormat
            End Get
            Set(ByVal value As String)
                If Value <> Me._customFormat Then
                    Me._customFormat = Value
                    Me.SetBaseFormat()
                End If
            End Set
        End Property

        ''' <summary>
        ''' Gets or sets the data value of the control.
        ''' </summary>
        ''' <value>
        ''' The date and time contained in the control if there is no checkbox displayed
        ''' or the displayed check box is checked; otherwise, <b>DBNull.Value</b>.
        ''' </value>
        ''' <remarks>
        ''' This property is intended to be bound to <b>DataRow</b> fields, allowing null values from databases to be
        ''' entered and displayed.  It accepts a null reference (<b>Nothing</b> in Visual Basic) or <b>DBNull.Value</b>
        ''' to clear the date/time value contained in the control or a <b>DateTime</b> object to set it.
        ''' </remarks>
        <Bindable(BindableSupport.Yes), Browsable(False)> _
        Public Property DataValue() As Object
            Get
                If MyBase.Checked OrElse (Not MyBase.ShowCheckBox) Then
                    ' Return the actual date and time when there is not an unchecked check box.
                    Return MyBase.Value
                Else
                    ' Return a null database value when there is an unchecked check box.
                    Return DBNull.Value
                End If
            End Get
            Set(ByVal value As Object)

                If Value Is Nothing OrElse Value Is DBNull.Value Then
                    ' Uncheck the control to indicate a null value.
                    Me.Checked = False
                ElseIf TypeOf Value Is DateTime Then
                    Try
                        ' Set the specified value.
                        Me.Value = CDate(Value)
                    Catch ex As Exception
                        ' Most likely the value assigned was outside the limits set by MinDate and MaxDate
                        Throw New ArgumentException(String.Format("'{0}' is an invalid value for 'DataValue'.  See the inner exception for more information.", Value.ToString()), ex)
                    End Try

                    ' Explicitly check the control in case the specified value was the same as the current Value property.
                    Me.Checked = True
                Else
                    ' An invalid type of object was assigned.
                    Throw New ArgumentException(String.Format("'{0}' is an invalid type for 'DataValue'.  'DataValue' accepts only a null reference, DBNull.Value or a 'DateTime' object.", Value.GetType().Name))
                End If
            End Set
        End Property

        ''' <summary>
        ''' Gets or sets the format of the date and time displayed in the control.
        ''' </summary>
        ''' <value>
        ''' One of the <b>DateTimePickerFormat</b> values. The default is <b>Long</b>.
        ''' </value>
        <DefaultValue(DEFAULT_FORMAT)> _
        Public Shadows Property Format() As DateTimePickerFormat
            Get
                Return Me._format
            End Get
            Set(ByVal value As DateTimePickerFormat)
                If Value <> Me._format Then
                    Me._format = Value
                    Me.SetBaseFormat()
                End If
            End Set
        End Property

        ''' <summary>
        ''' Gets or sets a value indicating whether a check box is displayed to the left of the selected date.
        ''' </summary>
        ''' <value>
        ''' <b>true</b> if a check box is displayed to the left of the selected date; otherwise, <b>false</b>. The default is <b>false</b>.
        ''' </value>
        Public Shadows Property ShowCheckBox() As Boolean
            Get
                Return MyBase.ShowCheckBox
            End Get
            Set(ByVal value As Boolean)
                If Value <> MyBase.ShowCheckBox Then
                    MyBase.ShowCheckBox = Value
                    Me.SetBaseFormat()
                End If
            End Set
        End Property

#End Region ' Properties

#Region "Constructors"

        ''' <summary>
        ''' Initializes a new instance of the <see cref="NullableDateTimePicker"/> class.
        ''' </summary>
        Public Sub New()
            MyBase.New()
            Me._customFormat = MyBase.CustomFormat
            Me._format = MyBase.Format
        End Sub

#End Region ' Constructors

#Region "Events"

        ''' <summary>
        ''' Occurs when the <see cref="DataValue"/> property changes.
        ''' </summary>
        ''' <remarks>
        ''' This event is raised whenever the apparent value of the control changes.
        ''' This may occur when the <see cref="Checked"/>, <see cref="ShowCheckBox"/> or <b>Value</b> property changes.
        ''' </remarks>
        <Category("Action"), Description("Occurs when the apparent value of the control changes.")> _
        Public Event DataValueChanged As EventHandler

#End Region ' Events

#Region "Methods"

        ''' <summary>
        ''' Raises the <b>DataValueChanged</b> event.
        ''' </summary>
        ''' <param name="e">
        ''' An <b>EventArgs</b> that contains the event data.
        ''' </param>
        Protected Overridable Sub OnDataValueChanged(ByVal e As EventArgs)
            ' Refresh the previous value.
            Me.oldDataValue = Me.DataValue

            If Not Me.DataValueChangedEvent Is Nothing Then
                ' Raise the DataValueChanged event
                RaiseEvent DataValueChanged(Me, e)
            End If
        End Sub

        ''' <summary>
        ''' Raises the <b>ValueChanged</b> event.
        ''' </summary>
        ''' <param name="e">
        ''' An <b>EventArgs</b> that contains the event data.
        ''' </param>
        Protected Overrides Sub OnValueChanged(ByVal e As EventArgs)
            Me.SetBaseFormat()
            MyBase.OnValueChanged(e)
        End Sub

        ''' <summary>
        ''' Sets the <b>Format</b> and <b>CustomFormat</b> base properties to either
        ''' hide or display the date and/or time value contained in the control.
        ''' </summary>
        Private Sub SetBaseFormat()
            'INSTANT VB NOTE: The local variable customFormat was renamed since Visual Basic will not uniquely identify class members when local variables have the same name:
            Dim customFormat_Renamed As String
            'INSTANT VB NOTE: The local variable format was renamed since Visual Basic will not uniquely identify class members when local variables have the same name:
            Dim format_Renamed As DateTimePickerFormat

            If MyBase.Checked OrElse (Not MyBase.ShowCheckBox) Then
                ' Use the actual format values.
                customFormat_Renamed = Me._customFormat
                format_Renamed = Me._format
            Else
                ' Set the format to hide the text.
                customFormat_Renamed = " "
                format_Renamed = DateTimePickerFormat.Custom
            End If

            If MyBase.CustomFormat <> customFormat_Renamed Then
                MyBase.CustomFormat = customFormat_Renamed
            End If

            If MyBase.Format <> format_Renamed Then
                MyBase.Format = format_Renamed
            End If

            Dim DataValueChangedEvent As Boolean

            If TypeOf Me.oldDataValue Is DateTime AndAlso TypeOf Me.DataValue Is DateTime Then
                ' Compare the old and current values as DateTime instances.
                DataValueChangedEvent = (CDate(Me.oldDataValue) <> CDate(Me.DataValue))
            Else
                ' Compare the old and current values as Object instances.
                DataValueChangedEvent = (Not Me.oldDataValue Is Me.DataValue)
            End If

            If DataValueChangedEvent Then
                Me.OnDataValueChanged(EventArgs.Empty)
            End If
        End Sub

#End Region ' Methods
    End Class
End Namespace
