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
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…