Как настроить раскрывающийся список DateTimePicker, чтобы выбрать только годы или месяцы? - PullRequest
2 голосов
/ 17 апреля 2020

Как обсуждалось в Как я могу установить раскрывающийся список datetimepicker, чтобы показывать только месяцы была возможность перезаписать DateTimePicker, чтобы получить MonthPicker.

Я читал много сайтов, но не понял, как сделать подобное, чтобы получить YearPicker.
Может быть, кто-то может помочь.

1 Ответ

4 голосов
/ 18 апреля 2020

Этот пользовательский элемент управления немного настраивает стандартный DateTimePicker, чтобы получить только стиль выбора года или месяца.

► Стандартные свойства DateTimePicker CustomFormat и Format имеют значение отключено , для внутренней настройки первого установлено значение yyyy или MMMM (простая модификация может добавлять различные форматы), а последняя - DateTimePickerFormat.Custom. Эти свойства скрыты от PropertyGrid и не могут быть изменены.

► Функциональность просмотра поддерживается, но ограничивается выбором «Десятилетие / Год» или «Десятилетие / Год / Месяц».
При нажатии на область заголовка DTP открывается селектор «Десятилетие», а предыдущая и следующая кнопки имеют Курс функционален (могут показывать только годы).

► DTP закрывается, и текущее значение устанавливается, когда обнаруживается уведомление MCN_VIEWCHANGE, передавая текущий уровень выбора в структуре NMVIEWCHANGE, что текущий выбор достиг режима просмотра, установленного свойством SelectionMode.
Это значение свойства является перечислителем, который, в свою очередь, отражает значения сообщения MonthCalendar MCM_SETCURRENTVIEW.

► Текущий просмотр настроен на отправку сообщения MCM_SETCURRENTVIEW в элемент управления MonthCalendar, изменяя вид по умолчанию на MCMV_DECADE или MCMV_YEAR (в зависимости от текущий SelectionMode) каждый раз, когда отображается элемент управления MonthCalendar. Анимация открытия сохраняется.

► Изменен единственный стиль: MCS_NOTODAY, установленный в методе OnHandleCreated. Его можно включить / выключить в любое время, вызвав метод ShowMonCalToday().
Этот стиль показывает дату Today в нижней части DateTimerPicker. Устанавливает текущее значение года или месяца при нажатии.

РЕДАКТИРОВАТЬ :
→ Добавлен режим выбора «Только для месяца» и свойство SelectionMode, позволяющее переключаться между режимами года и месяца.
→ Код упрощенный.


Вот как это работает:

DateTimePicker Years Month Only

Протестировано на VisualStudio 2017.
. Net Framework 4.8 (только).

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
    }
}
...