How can I set the DateTimePicker dropdown to select Years or Months only?
Asked Answered
B

1

2

Like discussed in How can I set the datetimepicker dropdown to show Months only there was a possibility to overwrite DateTimePicker to get a MonthPicker.

I have read a lot of sites but didn't figure out how to do similar to get a YearPicker.
Maybe someone can help out.

Barnstorm answered 17/4, 2020 at 17:32 Comment(1)
Not an option. Consider a NumericUpDown or ComboBox to pick years.Lithuanian
T
4

This Custom Control tweaks a little the standard DateTimePicker to get a Year or Month selection style only.

▶ The standard DateTimePicker's CustomFormat and Format properties are disabled, setting internally the former to yyyy or MMMM (a simple modification can add different formats) and the latter to DateTimePickerFormat.Custom. These properties are hidden from the PropertyGrid an cannot be changed.

▶ The browsing functionality is maintained, but limited to the Decade/Year or Decade/Year/Month selection.
Clicking the DTP's Title Area, brings on the Decade selector and the previous and next Buttons are of course functional (these can only show years).

▶ The DTP is closed and the current value set when a MCN_VIEWCHANGE notification reveals, passing the current selection level in a NMVIEWCHANGE structure, that the current selection has reached the View Mode set by the SelectionMode property.
This property value is an enumerator which, in turn, reflects the MonthCalendar's MCM_SETCURRENTVIEW message values.

▶ The current View is set sending a MCM_SETCURRENTVIEW message to the MonthCalendar control, changing the default View to MCMV_DECADE or MCMV_YEAR (depending on the current SelectionMode) each time the MonthCalendar control is shown. The opening animation is then preserved.

▶ The only style changed is MCS_NOTODAY, set in the OnHandleCreated method. It can be switched on/off at any time, calling the ShowMonCalToday() method.
This style shows the Today date, at the bottom of the DateTimerPicker. It sets the current Year or Month value when clicked.


This is how it works:

DateTimePicker Years Month Only

Tested on VisualStudio 2017.
.Net Framework 4.8 (only).

using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows.Forms;

[DesignerCategory("Code")]
public class MonthYearPicker : DateTimePicker
{
    private string m_CustomFormat = "yyyy";
    private DateTimePickerFormat m_Format = DateTimePickerFormat.Custom;
    private SelectionViewMode m_SelectionMode = SelectionViewMode.Year;
    private bool m_ShowToday = false;
    private IntPtr hWndCal = IntPtr.Zero;

    public MonthYearPicker() {
        base.CustomFormat = m_CustomFormat;
        base.Format = m_Format;
    }

    [DefaultValue(SelectionViewMode.Year)]
    [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
    [Category("Appearance"), Description("Set the current selection mode to either Month or Year")]
    public SelectionViewMode SelectionMode {
        get => m_SelectionMode;
        set {
            if (value != m_SelectionMode) {
                m_SelectionMode = value;
                m_CustomFormat = m_SelectionMode == SelectionViewMode.Year ? "yyyy" : "MMMM";
                base.CustomFormat = m_CustomFormat;
            }
        }
    }

    [DefaultValue(false)]
    [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
    [Category("Appearance"), Description("Shows or hides \"Today\" date at the bottom of the Calendar Control")]
    public bool ShowToday {
        get => m_ShowToday;
        set {
            if (value != m_ShowToday) {
                m_ShowToday = value;
                ShowMonCalToday(m_ShowToday);
            }
        }
    }

    [DefaultValue("yyyy")]
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    public new string CustomFormat {
        get => base.CustomFormat;
        set => base.CustomFormat = m_CustomFormat;
    }

    [DefaultValue(DateTimePickerFormat.Custom)]
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    public new DateTimePickerFormat Format {
        get => base.Format;
        set => base.Format = m_Format;
    }

    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);
        ShowMonCalToday(m_ShowToday);
    }

    protected override void OnDropDown(EventArgs e)
    {
        hWndCal = SendMessage(this.Handle, DTM_GETMONTHCAL, 0, 0);
        if (hWndCal != IntPtr.Zero) {
            SendMessage(hWndCal, MCM_SETCURRENTVIEW, 0, (int)(MonCalStyles)m_SelectionMode);
        }
        base.OnDropDown(e);
    }

    private void ShowMonCalToday(bool show)
    {
        int styles = SendMessage(this.Handle, DTM_GETMCSTYLE, 0, 0).ToInt32();
        styles = show ? styles &~(int)MonCalStyles.MCS_NOTODAY : styles | (int)MonCalStyles.MCS_NOTODAY;
        SendMessage(this.Handle, DTM_SETMCSTYLE, 0, styles);
    }

    protected override void WndProc(ref Message m)
    {
        switch (m.Msg) {
            case WM_NOTIFY:
                var nmh = (NMHDR)m.GetLParam(typeof(NMHDR));
                switch (nmh.code) {
                    case MCN_VIEWCHANGE:
                        var nmView = (NMVIEWCHANGE)m.GetLParam(typeof(NMVIEWCHANGE));
                        if (nmView.dwNewView < (MonCalView)m_SelectionMode) {
                            SendMessage(this.Handle, DTM_CLOSEMONTHCAL, 0, 0);
                        }
                        break;
                    default:
                        // NOP: Add more notifications handlers...
                        break;
                }
                break;
            default:
                // NOP: Add more message handlers...
                break;
        }
        base.WndProc(ref m);
    }

    public enum SelectionViewMode : int
    {
        Month = MonCalView.MCMV_YEAR,
        Year = MonCalView.MCMV_DECADE,
    }

    // Move to a NativeMethods class, eventually
    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    internal static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);

    internal const int WM_NOTIFY = 0x004E;
    internal const int MCN_VIEWCHANGE = -750;

    internal const int DTM_FIRST = 0x1000;
    internal const int DTM_GETMONTHCAL = DTM_FIRST + 8;
    internal const int DTM_SETMCSTYLE = DTM_FIRST + 11;
    internal const int DTM_GETMCSTYLE = DTM_FIRST + 12;
    internal const int DTM_CLOSEMONTHCAL = DTM_FIRST + 13;

    internal const int MCM_FIRST = 0x1000;
    internal const int MCM_GETCURRENTVIEW = MCM_FIRST + 22;
    internal const int MCM_SETCURRENTVIEW = MCM_FIRST + 32;

    [StructLayout(LayoutKind.Sequential)]
    internal struct NMHDR
    {
        public IntPtr hwndFrom;
        public UIntPtr idFrom;
        public int code;
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct NMVIEWCHANGE
    {
        public NMHDR nmhdr;
        public MonCalView dwOldView;
        public MonCalView dwNewView;
    }

    internal enum MonCalView : int
    {
        MCMV_MONTH = 0,
        MCMV_YEAR = 1,
        MCMV_DECADE = 2,
        MCMV_CENTURY = 3
    }

    internal enum MonCalStyles : int
    {
        MCS_DAYSTATE = 0x0001,
        MCS_MULTISELECT = 0x0002,
        MCS_WEEKNUMBERS = 0x0004,
        MCS_NOTODAYCIRCLE = 0x0008,
        MCS_NOTODAY = 0x0010,
        MCS_NOTRAILINGDATES = 0x0040,
        MCS_SHORTDAYSOFWEEK = 0x0080,
        MCS_NOSELCHANGEONNAV = 0x0100
    }
}
Tonsil answered 18/4, 2020 at 9:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.