user1697111 user1697111 - 3 months ago 26
Vb.net Question

DateTimePicker internal validation

I have such situations in my program often when using regular DateTimePicker supplied with VB.NET 2010 toolbox.

See this:

Public Class Form1

Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

Me.DateTimePicker1.Format = DateTimePickerFormat.Custom
Me.DateTimePicker1.CustomFormat = "dd.MM.yyyy."
Me.DateTimePicker1.Value = "01.09.2016."
End Sub
End Class


Now, I would like to type in this control 31 7 2016 and I can't, at least not easy or as expected.

I persume this is because 31.09 don't exists as valid date but at this point I am still not finished with entering my wished date.

Is here any trick to turn off this internal validation of dtp or any other way to get wanted (described) functionality in given circumstances?

Answer

Since you are using WinForms and the WinForm DateTimePicker control is just a wrapper around the native common DateTimePicker control, you can create a derived control to allow your application to parse the input string. This is done by setting the DTS_APPCANPARSE style on the control. You set this style by overriding the control's CreateParams property.

When DTS_APPCANPARSE style is set, the native control sends a DTN_USERSTRING notification to the control. This message is received in the control's WndProc method.

The majority of the code shown below is just definitions for the native structures that are used. The parsing function (TryParse_NMDATETIMESTRING) used in this example, reuses the DateTime.TryParse method to accommodate a format of "dd.MM.yyyy" by changing the Thread culture to "de-DE" as that culture setting supports this format. You can define any parsing logic you want.

Imports System.Runtime.InteropServices
Imports System.Globalization

Public Class DateTimePickerCustomParse : Inherits DateTimePicker

    Protected Overrides ReadOnly Property CreateParams As CreateParams
        Get
            Const DTS_APPCANPARSE As Int32 = &H10
            Dim cp As CreateParams = MyBase.CreateParams
            cp.Style = cp.Style Or DTS_APPCANPARSE
            Return cp
        End Get
    End Property

#Region "Native Structures"
    Structure NMDATETIMESTRING
        Public nmhdr As NMHDR
        Public pszUserString As IntPtr
        Public st As SYSTEMTIME
        Public dwFlags As GDT
    End Structure

    Public Enum GDT
        GDT_ERROR = -1
        GDT_VALID = 0
        GDT_NONE = 1
    End Enum

    <StructLayout(LayoutKind.Sequential)> _
    Public Structure SYSTEMTIME
         Public wYear As Short
         Public wMonth As Short
         Public wDayOfWeek As Short
         Public wDay As Short
         Public wHour As Short
         Public wMinute As Short
         Public wSecond As Short
         Public wMilliseconds As Short
    End Structure

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto, Pack:=1)> _
    Public Class NMHDR
         Public hwndFrom As IntPtr = IntPtr.Zero
         Public idFrom As Integer = 0
         Public code As Integer = 0
    End Class
#End Region

    Private Shared Function TryParse_NMDATETIMESTRING(ByRef nmDTS As NMDATETIMESTRING) As Boolean
        Dim ret As Boolean
        Dim enteredDate As String = Marshal.PtrToStringUni(nmDTS.pszUserString)
        Dim savedThreadCulture As CultureInfo = Threading.Thread.CurrentThread.CurrentCulture
        Try
            Threading.Thread.CurrentThread.CurrentCulture = New CultureInfo("de-DE")
            Dim dt As DateTime
            If DateTime.TryParse(enteredDate, dt) Then
                nmDTS.dwFlags = GDT.GDT_VALID
                nmDTS.st = DateTimeToSYSTEMTIME(dt)
                ret = True
            Else
                nmDTS.dwFlags = GDT.GDT_ERROR
            End If

        Finally
            Threading.Thread.CurrentThread.CurrentCulture = savedThreadCulture
        End Try

        Return ret
    End Function


    Private Shared Function DateTimeToSYSTEMTIME(dt As DateTime) As SYSTEMTIME
        Dim ret As New SYSTEMTIME
        ret.wYear = CShort(dt.Year)
        ret.wDay = CShort(dt.Day)
        ret.wMonth = CShort(dt.Month)
        ret.wDayOfWeek = CShort(dt.DayOfWeek)
        ret.wHour = CShort(dt.Hour)
        ret.wMinute = CShort(dt.Minute)
        ret.wSecond = CShort(dt.Second)
        ret.wMilliseconds = CShort(dt.Millisecond)
        Return ret
    End Function

    Protected Overrides Sub WndProc(ByRef m As Message)
        Const WM_NOTIFY As Int32 = &H4E
        Const WM_REFLECT_NOTIFY As Int32 = WM_NOTIFY + &H2000
        Const DTN_FIRST As Int32 = -740
        Const DTN_USERSTRINGW As Int32 = (DTN_FIRST - 5)

        If m.Msg = WM_REFLECT_NOTIFY OrElse m.Msg = WM_NOTIFY Then
            Dim hdr As New NMHDR
            Marshal.PtrToStructure(m.LParam, hdr)
            If hdr.code = DTN_USERSTRINGW Then
                Dim nmDTS As NMDATETIMESTRING = Marshal.PtrToStructure(Of NMDATETIMESTRING)(m.LParam)
                If TryParse_NMDATETIMESTRING(nmDTS) Then
                    Marshal.StructureToPtr(nmDTS, m.LParam, True)
                    Exit Sub
                End If
            End If
        End If
        MyBase.WndProc(m)
    End Sub

End Class