Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
947 views
in Technique[技术] by (71.8m points)

.net - 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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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

Edit: There is one particularly annoying thing about using the DTS_APPCANPARSE style and that is that precludes the use of the Tab key to leave the control. This is documented in: datetimepicker class - with dts_appcanparse style - can't tab out. Entered text is sent for validation either by loss of focus or by pressing the Enter key. However, the Enter key still leaves the DTP focused with no keyboard way of leaving it. Additionally, if you Tab into the control, it behaves like the standard DTP; pressing the F2 key puts it into edit mode and selects the entire text. To address these issues with control defined above you can add the following.

Public Property MoveNextOnEnterKey As Boolean = True
Public Property SelectAllOnEnter As Boolean = True

Protected Overrides Sub OnEnter(e As EventArgs)
    MyBase.OnEnter(e)
    If SelectAllOnEnter Then SendKeys.Send("{F2}")
End Sub

Private Sub MoveNext()
    Me.Parent.SelectNextControl(Me, True, True, True, True)
End Sub

You will need to replace the WndProc method with this:

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)
                If MoveNextOnEnterKey Then
                    Me.BeginInvoke(New Action(AddressOf MoveNext))
                End If
                Exit Sub
            End If
        End If
    End If
    MyBase.WndProc(m)
End Sub

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...