如何将 DateTimePicker 下拉列表设置为仅 select 年或月?
How can I set the DateTimePicker dropdown to select Years or Months only?
正如在 How can I set the datetimepicker dropdown to show Months only 中讨论的那样,有可能覆盖 DateTimePicker
以获得 MonthPicker
。
我已经阅读了很多网站,但没有弄清楚如何做类似的事情来获得 YearPicker
。
也许有人可以帮忙。
此自定义控件对标准 DateTimePicker 进行了一些调整,以仅获得年份或月份选择样式。
▶ 标准 DateTimePicker 的 CustomFormat
和 Format
属性 已禁用,内部设置前者为yyyy
或MMMM
(简单修改可以添加不同的格式),后者为DateTimePickerFormat.Custom
。这些属性在 PropertyGrid 中是隐藏的,无法更改。
▶ 浏览功能得以保留,但仅限于 Decade/Year 或 Decade/Year/月份选择。
单击 DTP 的标题区域,会出现十年选择器,上一个和下一个按钮当然可以使用(这些只能显示年份)。
▶ 当MCN_VIEWCHANGE
notification reveals, passing the current selection level in a NMVIEWCHANGE
结构时关闭DTP并设置当前值,即当前选择已达到SelectionMode
[=61=设置的View Mode ] 属性.
此 属性 值是一个枚举器,它反过来反映了 MonthCalendar 的 MCM_SETCURRENTVIEW
消息值。
▶ 当前视图设置为向 MonthCalendar 控件发送 MCM_SETCURRENTVIEW
消息,将默认视图更改为 MCMV_DECADE
或 MCMV_YEAR
(取决于当前 SelectionMode
)每次显示 MonthCalendar 控件时。然后保留开场动画。
▶ 唯一改变的样式是 MCS_NOTODAY
,在 OnHandleCreated
方法中设置。可以随时切换on/off,调用ShowMonCalToday()
方法
此样式在 DateTimerPicker 的底部显示 Today
日期。单击时设置当前年或月值。
这是它的工作原理:
在 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
}
}
正如在 How can I set the datetimepicker dropdown to show Months only 中讨论的那样,有可能覆盖 DateTimePicker
以获得 MonthPicker
。
我已经阅读了很多网站,但没有弄清楚如何做类似的事情来获得 YearPicker
。
也许有人可以帮忙。
此自定义控件对标准 DateTimePicker 进行了一些调整,以仅获得年份或月份选择样式。
▶ 标准 DateTimePicker 的 CustomFormat
和 Format
属性 已禁用,内部设置前者为yyyy
或MMMM
(简单修改可以添加不同的格式),后者为DateTimePickerFormat.Custom
。这些属性在 PropertyGrid 中是隐藏的,无法更改。
▶ 浏览功能得以保留,但仅限于 Decade/Year 或 Decade/Year/月份选择。
单击 DTP 的标题区域,会出现十年选择器,上一个和下一个按钮当然可以使用(这些只能显示年份)。
▶ 当MCN_VIEWCHANGE
notification reveals, passing the current selection level in a NMVIEWCHANGE
结构时关闭DTP并设置当前值,即当前选择已达到SelectionMode
[=61=设置的View Mode ] 属性.
此 属性 值是一个枚举器,它反过来反映了 MonthCalendar 的 MCM_SETCURRENTVIEW
消息值。
▶ 当前视图设置为向 MonthCalendar 控件发送 MCM_SETCURRENTVIEW
消息,将默认视图更改为 MCMV_DECADE
或 MCMV_YEAR
(取决于当前 SelectionMode
)每次显示 MonthCalendar 控件时。然后保留开场动画。
▶ 唯一改变的样式是 MCS_NOTODAY
,在 OnHandleCreated
方法中设置。可以随时切换on/off,调用ShowMonCalToday()
方法
此样式在 DateTimerPicker 的底部显示 Today
日期。单击时设置当前年或月值。
这是它的工作原理:
在 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
}
}