Search code examples
vb.netwinformsdatetimepickermonthcalendar

The Date of the MonthCalendar of a DateTimePicker is not updated


At startup, I set DateTimePicker Value to the Minimum Date value.
The Date is changed to the current Date when a User clicks the DateTimePicker, using the MouseDown event.
A TextBox is changed to the current date, but the pop-up Calendar is not updated and still shows the original Date.

Private Sub frm1_Load(sender As Object, e As EventArgs) Handles Me.Load        
        dtpReceiveDate.Value = clsCommon.NULL_DATE     
End Sub

 Private Sub dtpReceiveDate_MouseDown(sender As Object, e As MouseEventArgs) Handles dtpReceiveDate.MouseDown       
        dtpReceiveDate.Value = Today       
End Sub

enter image description here


Solution

  • The MouseDown event is raised after the MonthCalendar Control of your DateTimePicker has been updated with the current DateTime value.
    In case the MouseDown event is generated on the Button that opens the MonthCalendar.
    Clicking on the date section of the Control, doesn't cause the MonthCalendar to open.

    I suggest not to use the MouseDown event to update the Value of the DateTimePicker, since this will cause the Date to reset each time a User clicks anywhere on the Control. It could be unnerving (it's up to you anyway).
    You can use the DropDown event instead: it's raised only when a User Clicks on the down-arrow Button.

    ▶ You cannot use the ValueChanged event with the code shown here: this might cause a deadlock (given the nature of the Win32 Controls involved and the use of SendMessage, it's the type of deadlock that may spread to the entire System, so, don't).

    The DtpUpdateMonthCalendardDate() method here sends a DTM_GETMONTHCAL message to the DateTimePicker passed as argument.
    If the Handle of the MonthCalendar is acquired successfully, it then sends a MCM_SETCURSEL message to the MonthCalendar Control to align its Date to the Date of the parent DateTimePicker.
    The new Date is passed using a SYSTEMTIME structure initialized with the Value of the DateTimePicker.

    Private Sub dtpReceiveDate_DropDown(sender As Object, e As EventArgs) Handles dtpReceiveDate.DropDown
        dtpReceiveDate.Value = Date.Today
        ' If result is False, something went wrong and the Date is not set
        Dim result = DtpUpdateMonthCalendardDate(dtpReceiveDate)
    End Sub
    
    Private Function DtpUpdateMonthCalendardDate(dtp As DateTimePicker) As Boolean
        If Not dtp.IsHandleCreated OrElse dtp.ShowUpDown Then Return False
    
        Dim hWndCal = SendMessage(dtp.Handle, DTM_GETMONTHCAL, 0, 0)
        If hWndCal = IntPtr.Zero Then Return False
    
        Dim sysTime = New SYSTEMTIME(dtp.Value)
        Return SendMessage(hWndCal, MCM_SETCURSEL, 0, sysTime) <> 0
    End Function
    

    Win32 declarations:

    Friend Const DTM_FIRST As Integer = &H1000
    Friend Const DTM_GETMONTHCAL As Integer = DTM_FIRST + 8
    
    Friend Const MCM_FIRST As Integer = &H1000
    Friend Const MCM_GETCURSEL As Integer = MCM_FIRST + 1
    Friend Const MCM_SETCURSEL As Integer = MCM_FIRST + 2
    
    <StructLayout(LayoutKind.Sequential)>
    Friend Structure SYSTEMTIME
        Public Year As Short
        Public Month As Short
        Public DayOfWeek As Short
        Public Day As Short
        Public Hour As Short
        Public Minute As Short
        Public Second As Short
        Public Milliseconds As Short
    
        Public Sub New(dt As Date)
            Year = CShort(dt.Year)
            Month = CShort(dt.Month)
            DayOfWeek = CShort(dt.DayOfWeek)
            Day = CShort(dt.Day)
            Hour = CShort(dt.Hour)
            Minute = CShort(dt.Minute)
            Second = CShort(dt.Second)
            Milliseconds = CShort(dt.Millisecond)
        End Sub
    
        Public Function ToDateTime() As Date
            Return New DateTime(Year, Month, Day, Hour, Minute, Second, Milliseconds)
        End Function
    End Structure
    
    <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
    Friend Shared Function SendMessage(hWnd As IntPtr, Msg As Integer, wParam As Integer, lParam As Integer) As IntPtr
    End Function
    
    <DllImport("User32", SetLastError:=True, CharSet:=CharSet.Auto)>
    Friend Shared Function SendMessage(hWnd As IntPtr, msg As Integer, wParam As Integer, lParam As SYSTEMTIME) As Integer
    End Function