(原创文章,转载请注明来源:http://blog.csdn.net/hulihui/article/details/5373252)
项目中经常要碰到日期输入,.NET 2.0提供了TDateTimePicker存在如下方面的不足:
- 数据源为空时,绑定的日期不匹配。
- 不能设定日期文本框的颜色。
网上找到的却不能满足数据源绑定的基本要求。
1、基本思路
构思定制日期编选控件时,笔者尝试过如下两个思路:
- 定制MonthCanlendar + 定制TextBox + 定制Button。实际使用时发现,定制MonthCalendar在年份往前选择时存在严重显示上的Bug,并且,在点击非控件区域时不能自动CloseUp。
- 定制DateTimePicker + 定制TextBox。该思路最后证明是成功的。
2、定制文本框控件TDateEditBox
由于笔者以前编写过TNumEditBox,对TextBox有一定的了解,如实直接从TextBox上定制了一个输入日期的控件TDateEditBox。实现的关键技术如下:
- 既然是输入日期,就只允许键盘,必须屏蔽掉上下文菜单。在构造函数中 this.ContextMenu = new ContextMenu()。
- 捕获OnKeyDown事件,处理方向键、数字键、BackSpace、Delete。
- 值需要区分Null、Valid情况。如果有值修改,则需要通知容器控件,满足绑定数据源更新的需求。
3、定制日期选择控件TDatePicker
从DateTimePicker继承,关键技术如下:
- 如何捕获四个输入操作:鼠标点击确认日期、键盘Enter确认日期、ESC放弃选择、鼠标点控件外时自动Close。
- 跟踪WndProc消息发现,鼠标点控件外时发生消息 WM_CAPTURCHANGED,此时表示放弃选择。
- 跟踪WndProc消息发现,Enter、ESC和鼠标点选日期,均产生WM_IME_SETCONTENT消息。于是在ProProcessMessage事件中捕获ESC、Enter消息,在WndProc捕获鼠标选择日期消息。
- ESC消息与鼠标点选日期相似,既有SetContent,从而确认,后有键盘消息,然后放弃。从而使得控件产生一次确认选择、然后放弃的消息。
4、定制UserControl
由前面两个定制控件组成,基本思路如下:
- TDatePicker放在TDateEditBox下,但露出其Button部分,是的下拉框左边对齐。
- 实现INotifyPropertyChanged,满足数据绑定源绑定与刷新要求。实际测试表明,该控件如果设置绑定更新方式为默认,则编辑框与直接给控件赋值时的刷新行为不一致。于是,固定为两种方式:Never、OnPropertyChanged。
有关代码的实现细节,请参考如下全部代码。
[c-sharp] view plain copy print ?
- using System;
- using System.Windows.Forms;
- using System.ComponentModel;
- using System.Drawing;
- using System.Text.RegularExpressions;
-
- namespace CSUST.Data
- {
- [ToolboxItem(false)]
- public class TDateEditBox : TextBox
- {
- private const int MaxTextLength = 10;
-
- private char[] textChars = new char[] { ' ', ' ', ' ', ' ', '-', ' ', ' ', '-', ' ', ' ' };
- private char[] lastInvidTextChars = new char[MaxTextLength];
-
- private char dateSeperator = '-';
-
- private int minDateYear = 1950;
- private int maxDateYear = 2060;
-
- private bool isNull = true;
- private bool isValid = false;
-
- private Color normalDateForeColor;
- private Color invalidDateForeColor = Color.Red;
-
- private DateTime date;
-
- public event EventHandler DateChanged;
-
- public TDateEditBox()
- {
- base.MaxLength = MaxTextLength;
- this.ContextMenu = new ContextMenu();
- normalDateForeColor = base.ForeColor;
- this.SetToNull();
- }
-
- public new int MaxLength
- {
- get
- {
- return base.MaxLength;
- }
- set
- {
- base.MaxLength = MaxTextLength;
- }
- }
-
- public int MinDateYear
- {
- get { return minDateYear; }
- set { minDateYear = value; }
- }
-
- public int MaxDateYear
- {
- get { return maxDateYear; }
- set { maxDateYear = value; }
- }
-
- public Color InvalidDateForeColor
- {
- get { return invalidDateForeColor; }
- set
- {
- invalidDateForeColor = value;
- if (isValid == false && isNull == false)
- {
- this.ShowWarnColor();
- }
- }
- }
-
- public override Color ForeColor
- {
- get { return base.ForeColor; }
- set
- {
- if (value != invalidDateForeColor)
- {
- base.ForeColor = value;
- normalDateForeColor = value;
- }
- if (isNull == true || isValid == true)
- {
- this.ShowNormalColor();
- }
- }
- }
-
- public char DateSeperator
- {
- get { return dateSeperator; }
- set
- {
- if (value != '-' && value != '/' && value != '.')
- {
- dateSeperator = '-';
- }
- else
- {
- dateSeperator = value;
- }
-
- textChars[4] = dateSeperator;
- textChars[7] = dateSeperator;
-
- this.ShowText();
- base.SelectionStart = 0;
- }
- }
-
- public bool IsNull
- {
- get { return isNull; }
- }
-
- public bool IsValid
- {
- get { return isValid; }
- }
-
- public object Date
- {
- get
- {
- if (isNull == true || isValid == false)
- {
- return null;
- }
- return date;
- }
- set
- {
- if (value == null || value == DBNull.Value)
- {
- this.SetToNull();
- }
- else
- {
- date = (DateTime)value;
- this.SetToDate(date);
- }
-
- if (base.ForeColor != normalDateForeColor)
- {
- base.ForeColor = normalDateForeColor;
- }
- this.Invalidate();
- }
- }
-
- public void OnDateChanged()
- {
- if (isValid == true || isNull == true)
- {
- if (this.DateChanged != null)
- {
- this.DateChanged(this, EventArgs.Empty);
- }
- }
- }
-
- protected override void OnKeyDown(KeyEventArgs e)
- {
- if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift && (e.KeyCode == Keys.Right) || e.KeyCode == Keys.Left || e.KeyCode == Keys.Home || e.KeyCode == Keys.End)
- {
- base.OnKeyDown(e);
- return;
- }
-
- if (e.KeyData == Keys.Tab || e.KeyData == Keys.Home || e.KeyData == Keys.End)
- {
- base.OnKeyDown(e);
- return;
- }
-
- if (e.KeyCode == Keys.Back)
- {
- this.BackSpace();
- }
- else if (e.KeyCode == Keys.Delete)
- {
- this.Delete();
- }
- else if (e.KeyData == Keys.Left)
- {
- this.MoveLeft();
- }
- else if (e.KeyData == Keys.Right)
- {
- this.MoveRight();
- }
- else if ((e.KeyValue >= '0' && e.KeyValue <= '9') || e.KeyValue == ' ')
- {
- this.InputDigit(e.KeyValue);
- }
- else if ((e.KeyCode >= Keys.NumPad0 && e.KeyCode <= Keys.NumPad9))
- {
- int keyValue = (int)e.KeyCode - (int)Keys.NumPad0 + (int)Keys.D0;
- this.InputDigit(keyValue);
- }
-
- e.SuppressKeyPress = true;
- e.Handled = true;
-
- this.ParseDateText();
- }
-
- protected override void OnLeave(EventArgs e)
- {
- this.NormalizeDateText();
- base.OnLeave(e);
- }
-
- protected override void OnGotFocus(EventArgs e)
- {
- base.OnGotFocus(e);
- base.SelectionLength = 0;
- }
-
- private void SetToNull()
- {
- textChars[0] = ' ';
- textChars[1] = ' ';
- textChars[2] = ' ';
- textChars[3] = ' ';
- textChars[4] = dateSeperator;
- textChars[5] = ' ';
- textChars[6] = ' ';
- textChars[7] = dateSeperator;
- textChars[8] = ' ';
- textChars[9] = ' ';
-
- isNull = true;
- isValid = false;
-
- this.ShowText();
- base.SelectionStart = 0;
- }
-
- private void SetToDate(DateTime date)
- {
- string today = date.ToString("yyyy-MM-dd");
- for (int k = 0; k < today.Length; k++)
- {
- if (k != 4 && k != 7)
- {
- textChars[k] = today[k];
- }
- }
-
- isNull = false;
- isValid = true;
-
- this.ShowText();
- base.SelectionStart = 0;
- }
-
- private void SaveLastInvlidTextChars()
- {
- if (isNull == true)
- {
- return;
- }
-
- for (int k = 0; k < textChars.Length; k++)
- {
- lastInvidTextChars[k] = textChars[k];
- }
- }
-
- private void ParseDateText()
- {
- string y = new string(textChars, 0, 4);
- string m = new string(textChars, 5, 2);
- string d = new string(textChars, 8, 2);
-
- string yy = y.Trim();
- string mm = m.Trim();
- string dd = d.Trim();
-
- if (string.IsNullOrEmpty(yy) == true && string.IsNullOrEmpty(mm) == true && string.IsNullOrEmpty(dd) == true)
- {
- bool preIsNull = isNull;
-
- isNull = true;
- isValid = false;
-
- if (preIsNull != isNull)
- {
- this.ShowNormalColor();
- this.OnDateChanged();
- }
-
- return;
- }
-
- isNull = false;
-
- if (string.IsNullOrEmpty(yy) == true || string.IsNullOrEmpty(mm) == true || string.IsNullOrEmpty(dd) == true)
- {
- isValid = false;
- this.SaveLastInvlidTextChars();
- this.ShowWarnColor();
- return;
- }
-
- if (Regex.IsMatch(yy, @"/d{4}") == false || Regex.IsMatch(mm, @"/d{1,2}") == false || Regex.IsMatch(dd, @"/d{1,2}") == false)
- {
- isValid = false;
- this.SaveLastInvlidTextChars();
- this.ShowWarnColor();
- return;
- }
-
- int year = int.Parse(yy);
- int month = int.Parse(mm);
- int day = int.Parse(dd);
-
- if (year < minDateYear || year > maxDateYear || month < 1 || month > 12 || day < 1 || day > DateTime.DaysInMonth(year, month))
- {
- isValid = false;
- this.SaveLastInvlidTextChars();
- this.ShowWarnColor();
- return;
- }
-
- isValid = true;
- this.ShowNormalColor();
-
- bool modified = false;
- if (year != date.Year || month != date.Month || day != date.Day)
- {
- modified = true;
- }
-
- date = new DateTime(year, month, day);
-
- if (modified == true)
- {
- this.OnDateChanged();
- }
- }
-
- private void NormalizeDateText()
- {
- if (isValid == false || isNull == true)
- {
- return;
- }
-
- if (textChars[5] == ' ' || textChars[6] == ' ' || textChars[8] == ' ' || textChars[9] == ' ')
- {
- if (textChars[5] == ' ')
- {
- textChars[5] = '0';
- }
-
- if (textChars[6] == ' ')
- {
- textChars[6] = textChars[5];
- textChars[5] = '0';
- }
-
- if (textChars[8] == ' ')
- {
- textChars[8] = '0';
- }
-
- if (textChars[9] == ' ')
- {
- textChars[9] = textChars[8];
- textChars[8] = '0';
- }
-
- ShowText();
- }
- }
-
- private void ShowText()
- {
- if (isNull == false && isValid == false)
- {
- if (base.ForeColor != invalidDateForeColor)
- {
- base.ForeColor = invalidDateForeColor;
- }
- }
- else
- {
- if (base.ForeColor != normalDateForeColor)
- {
- base.ForeColor = normalDateForeColor;
- }
- }
-
- base.Text = new string(textChars);
- }
-
- private void ShowNormalColor()
- {
- if (base.ForeColor != normalDateForeColor)
- {
- base.ForeColor = normalDateForeColor;
- }
- this.Invalidate();
- }
-
- private void ShowWarnColor()
- {
- if (base.ForeColor != invalidDateForeColor)
- {
- base.ForeColor = invalidDateForeColor;
- }
- this.Invalidate();
- }
-
- public void ResumeLastInvalidText()
- {
- for (int k = 0; k < textChars.Length; k++)
- {
- textChars[k] = lastInvidTextChars[k];
- }
-
- if (base.ForeColor != invalidDateForeColor)
- {
- base.ForeColor = invalidDateForeColor;
- }
-
- isValid = false;
- base.Text = new string(textChars);
- this.OnDateChanged();
- }
-
- private void Delete()
- {
- if (base.SelectionLength <= 1)
- {
- this.Delete(base.SelectionStart);
- }
- else
- {
- int start = base.SelectionStart + base.SelectionLength;
- int end = base.SelectionStart + 1;
- for (int k = start; k >= end; k--)
- {
- BackSpace(k);
- }
- }
- }
-
- private void Delete(int selectionStart)
- {
- if (AtTextEnd(selectionStart) == true)
- {
- return;
- }
-
- this.BackSpace(selectionStart + 1);
- }
-
- private void BackSpace()
- {
- this.BackSpace(base.SelectionStart);
- }
-
- private void BackSpace(int selectionStart)
- {
- int curPos = selectionStart;
-
- if (curPos == 0)
- {
- return;
- }
-
- if (AtSeperatorRight(curPos) == true)
- {
- base.SelectionStart = curPos - 1;
- return;
- }
-
- if (AtTextEnd(curPos) == true)
- {
- textChars[textChars.Length - 1] = ' ';
- ShowText();
- base.SelectionStart = curPos - 1;
- return;
- }
-
- if (AtSeperatorLeft(curPos) == true)
- {
- textChars[curPos - 1] = ' ';
- ShowText();
- base.SelectionStart = curPos - 1;
- return;
- }
-
- int k = curPos;
- while (AtSeperatorLeft(k) == false && AtTextEnd(k) == false)
- {
- textChars[k - 1] = textChars[k];
- k++;
- }
- textChars[k - 1] = ' ';
-
- ShowText();
- base.SelectionStart = curPos - 1;
- }
-
- private void InputDigit(int keyValue)
- {
- if (AtTextEnd() == true)
- {
- return;
- }
-
- int curPos = base.SelectionStart;
- int newPos = curPos;
-
- if (AtSeperatorLeft() == true)
- {
- textChars[base.SelectionStart + 1] = (char)keyValue;
- newPos = curPos + 2;
- }
- else if (AtSeperatorLeft(curPos + 1) == true)
- {
- textChars[base.SelectionStart] = (char)keyValue;
- newPos = curPos + 2;
- }
- else
- {
- textChars[base.SelectionStart] = (char)keyValue;
-
- if (AtSeperatorRight(curPos + 1) == true)
- {
- curPos++;
- }
- newPos = curPos + 1;
- }
-
- this.ShowText();
- base.SelectionStart = newPos;
- }
-
- private void MoveLeft()
- {
- if (base.SelectionStart == 0)
- {
- return;
- }
-
- if (AtSeperatorRight(base.SelectionStart) == true)
- {
- base.SelectionStart -= 2;
- }
- else
- {
- base.SelectionStart -= 1;
- }
- }
-
- private void MoveRight()
- {
- if (this.AtTextEnd() == true)
- {
- return;
- }
-
- if (AtSeperatorLeft(base.SelectionStart + 1) == true)
- {
- base.SelectionStart += 2;
- }
- else
- {
- base.SelectionStart += 1;
- }
- }
-
- private bool AtSeperatorLeft(int curPos)
- {
- if (curPos == 4 || curPos == 7)
- {
- return true;
- }
- return false;
- }
-
- private bool AtSeperatorLeft()
- {
- return AtSeperatorLeft(base.SelectionStart);
- }
-
- private bool AtSeperatorRight(int curPos)
- {
- if (curPos == 5 || curPos == 8)
- {
- return true;
- }
- return false;
- }
-
- private bool AtSeperatorRight()
- {
- return AtSeperatorRight(base.SelectionStart);
- }
-
- private bool AtTextEnd(int curPos)
- {
- if (curPos == textChars.Length)
- {
- return true;
- }
- return false;
- }
-
- private bool AtTextEnd()
- {
- return AtTextEnd(base.SelectionStart);
- }
- }
-
- [ToolboxItem(false)]
- public class TDatePicker : DateTimePicker
- {
- private bool isDropdown = false;
-
- private DateTime dateBeforeDropDown;
-
- private const int WM_IME_SETCONTENT = 0x0281;
- private const int WM_CAPTURECHANGED = 0x0215;
- private const int WM_KEY_DOWN = 0x100;
- private const int WM_KEY_UP = 0x101;
- private const int WM_KEY_ENTER = 0x000d;
- private const int WM_KEY_ESC = 0x001b;
-
- private bool msgHandledAfterCloseup = true;
- protected bool msgHandledByImeSetContent= false;
-
- public event EventHandler DateConfirmed;
- public event EventHandler DateAbandoned;
-
- protected override void OnDropDown(EventArgs eventargs)
- {
- msgHandledAfterCloseup = true;
- isDropdown = true;
-
- dateBeforeDropDown = this.Value.Date;
- base.OnDropDown(eventargs);
- }
-
- protected override void OnCloseUp(EventArgs eventargs)
- {
- isDropdown = false;
-
- msgHandledAfterCloseup = false;
- msgHandledByImeSetContent = false;
-
- base.OnCloseUp(eventargs);
- }
-
- protected override void OnKeyDown(KeyEventArgs e)
- {
- if (isDropdown == true && (e.KeyCode == Keys.Left || e.KeyCode == Keys.Right ||
- e.KeyCode == Keys.Up || e.KeyCode == Keys.Down ||
- e.KeyCode == Keys.Home || e.KeyCode == Keys.End ||
- e.KeyCode == Keys.PageUp || e.KeyCode == Keys.PageDown))
- {
- base.OnKeyDown(e);
- }
- else
- {
- e.SuppressKeyPress = true;
- e.Handled = true;
- }
- }
-
- private void OnDateConfirmed()
- {
- if (this.DateConfirmed != null)
- {
- this.DateConfirmed(this, EventArgs.Empty);
- }
- }
-
- private void OnDateAbandoned()
- {
- if (this.Value.Date != dateBeforeDropDown)
- {
- this.Value = dateBeforeDropDown;
- }
-
- if (this.DateConfirmed != null)
- {
- this.DateAbandoned(this, EventArgs.Empty);
- }
- }
-
- public override bool PreProcessMessage(ref Message msg)
- {
- if (msgHandledAfterCloseup == false && msg.Msg == WM_KEY_UP && msg.WParam.ToInt32() ==WM_KEY_ENTER)
- {
- msgHandledAfterCloseup = true;
- this.OnDateConfirmed();
- }
- if ((msgHandledAfterCloseup == false || msgHandledByImeSetContent == true) && (msg.Msg == WM_KEY_UP && msg.WParam.ToInt32() == WM_KEY_ESC))
- {
- msgHandledByImeSetContent = false;
- msgHandledAfterCloseup = true;
- this.OnDateAbandoned();
- }
- return base.PreProcessMessage(ref msg);
- }
-
- protected override void WndProc(ref Message m)
- {
- if (m.Msg == WM_CAPTURECHANGED && msgHandledAfterCloseup == false)
- {
- msgHandledAfterCloseup = true;
- msgHandledByImeSetContent = false;
- this.OnDateAbandoned();
- }
- else if (m.Msg == WM_IME_SETCONTENT && msgHandledAfterCloseup == false)
- {
- msgHandledAfterCloseup = true;
- msgHandledByImeSetContent = true;
- this.OnDateConfirmed();
- }
-
- if (m.Msg == WM_KEY_DOWN)
- {
- msgHandledByImeSetContent = false;
- }
-
- base.WndProc(ref m);
- }
- }
-
- public class TDateEditPicker : UserControl, INotifyPropertyChanged
- {
- private IContainer components = null;
-
- private const int MINDATEYEAR = 1949;
- private const int MAXDATEYEAR = 2100;
-
- private TDateEditBox dateEditBox;
- private TDatePicker datePicker;
-
- private bool isNullWhenDropDown;
- private bool isValidWhenDropDown;
- private string version = "1.2";
- private int nativeEditBoxWidth;
-
- public event PropertyChangedEventHandler PropertyChanged;
-
- public TDateEditPicker()
- {
- this.InitializeComponent();
-
- this.DataBindings.DefaultDataSourceUpdateMode = DataSourceUpdateMode.Never;
-
- this.dateEditBox.Leave += new EventHandler(this.DateEditBox_Leave);
- this.datePicker.DropDown += new EventHandler(this.DateTimePicker_DropDown);
- this.dateEditBox.DateChanged += new EventHandler(this.DateEditBox_DateChanged);
- this.datePicker.CloseUp += new EventHandler(this.DateTimePicker_CloseUp);
- this.datePicker.DateConfirmed += new EventHandler(this.DateTimePicker_DataConfirmed);
- this.datePicker.DateAbandoned += new EventHandler(this.DateTimePicker_DateAbandoned);
- }
-
- private void InitializeComponent()
- {
- this.dateEditBox = new TDateEditBox();
- this.datePicker = new TDatePicker();
-
- this.SuspendLayout();
-
- this.datePicker.Location = new Point(1, 0);
- this.datePicker.Name = "datePicker";
- this.datePicker.Value = DateTime.Now;
- this.datePicker.MinDate = new DateTime(this.dateEditBox.MinDateYear, 1, 1);
- this.datePicker.MaxDate = new DateTime(this.dateEditBox.MaxDateYear, 12, 31);
- this.datePicker.Format = DateTimePickerFormat.Custom;
- this.datePicker.CustomFormat = " ";
-
- this.dateEditBox.Location = new Point(0, 0);
- this.dateEditBox.Name = "dateEditBox";
- this.nativeEditBoxWidth = this.dateEditBox.Height;
-
- this.Controls.Add(this.datePicker);
- this.Controls.Add(this.dateEditBox);
- this.Name = "TDateEditPicker";
- this.Size = new Size(this.datePicker.Width + 2, this.dateEditBox.Height + 2);
-
- this.dateEditBox.BringToFront();
-
- this.ResumeLayout();
- }
-
- protected override void Dispose(bool disposing)
- {
- if (disposing == true)
- {
- if (components != null)
- {
- components.Dispose();
- }
- }
- base.Dispose(disposing);
- }
-
- protected override void OnResize(EventArgs e)
- {
- base.OnResize(e);
- this.datePicker.Width = this.Width - 2;
- this.dateEditBox.Width = this.datePicker.Width - this.nativeEditBoxWidth + 2;
- }
-
- protected override void OnForeColorChanged(EventArgs e)
- {
- base.OnForeColorChanged(e);
- dateEditBox.ForeColor = base.ForeColor;
- }
-
- private void DateEditBox_Leave(object sender, EventArgs e)
- {
- if (this.IsValid == true && this.IsNull == false)
- {
- this.datePicker.Value = (DateTime)dateEditBox.Date;
- }
- else
- {
- this.datePicker.Value = DateTime.Now.Date;
- }
- }
-
- private void DateEditBox_DateChanged(object sender, EventArgs e)
- {
- if (this.IsNull == true || this.IsValid == true)
- {
- this.NotifyPropertyChanged("Date");
- }
- }
-
- private void DateTimePicker_DropDown(object sender, EventArgs e)
- {
- isNullWhenDropDown = this.IsNull;
- isValidWhenDropDown = this.IsValid;
- }
-
- private void DateTimePicker_DataConfirmed(object sender, EventArgs e)
- {
- this.dateEditBox.Date = this.datePicker.Value.Date;
- this.NotifyPropertyChanged("Date");
-
- }
-
- private void DateTimePicker_DateAbandoned(object sender, EventArgs e)
- {
- if (isNullWhenDropDown == true)
- {
- this.dateEditBox.Date = null;
- }
- else if (isValidWhenDropDown == false)
- {
- this.dateEditBox.ResumeLastInvalidText();
- }
- }
-
- private void DateTimePicker_CloseUp(object sender, EventArgs e)
- {
- }
-
- private void NotifyPropertyChanged(string info)
- {
- if (this.DataBindings != null && this.DataBindings.Count > 0)
- {
- if (this.DataBindings[0].DataSourceUpdateMode == DataSourceUpdateMode.OnValidation)
- {
- return;
- }
- }
-
- if (this.PropertyChanged != null)
- {
- this.PropertyChanged(this, new PropertyChangedEventArgs(info));
- }
- }
-
- [Category("Custom"), Browsable(true)]
- public string Version
- {
- get { return version; }
- }
-
- [Category("Custom"), Browsable(true), DefaultValue('-')]
- [Description("Date seperator, only use three chars: .-/.")]
- public char DateSeperator
- {
- get { return dateEditBox.DateSeperator; }
- set { dateEditBox.DateSeperator = value; }
- }
-
- [Category("Custom"),Browsable(true)]
- public int MinDateYear
- {
- get { return this.dateEditBox.MinDateYear; }
- set
- {
- int tmpValue = value;
- if (tmpValue < MINDATEYEAR || tmpValue > MAXDATEYEAR)
- {
- tmpValue = MINDATEYEAR;
- }
- if (this.MinDateYear != tmpValue)
- {
- this.dateEditBox.MinDateYear = tmpValue;
- this.datePicker.MinDate = new DateTime(tmpValue, 1, 1);
- }
- }
- }
-
- [Category("Custom"), Browsable(true)]
- public int MaxDateYear
- {
- get { return this.dateEditBox.MaxDateYear; }
- set
- {
- int tmpValue = value;
- if (tmpValue < MINDATEYEAR || tmpValue > MAXDATEYEAR)
- {
- tmpValue = MAXDATEYEAR;
- }
- else
- {
- this.dateEditBox.MaxDateYear = tmpValue;
- this.datePicker.MaxDate = new DateTime(tmpValue, 12, 31);
- }
- }
- }
-
- [Category("Custom")]
- [Description("Is the date is null.")]
- public bool IsNull
- {
- get { return dateEditBox.IsNull; }
- }
-
- [Category("Custom")]
- [Description("Is the date is valid.")]
- public bool IsValid
- {
- get { return dateEditBox.IsValid; }
- }
-
- [Bindable(true), Browsable(false)]
- public Object Date
- {
- get
- {
- return this.dateEditBox.Date;
- }
- set
- {
- this.dateEditBox.Date = value;
- this.NotifyPropertyChanged("Date");
- }
- }
-
- [Category("Custom")]
- [Description("The font color when date is invalid.")]
- [DefaultValue("Red"), Browsable(true)]
- public Color InvalidDateForeColor
- {
- get { return this.dateEditBox.InvalidDateForeColor; }
- set { this.dateEditBox.InvalidDateForeColor = value; }
- }
-
- }
- }
using System; using System.Windows.Forms; using System.ComponentModel; using System.Drawing; using System.Text.RegularExpressions; namespace CSUST.Data { [ToolboxItem(false)] public class TDateEditBox : TextBox { private const int MaxTextLength = 10; // 固定10个字符 private char[] textChars = new char[] { ' ', ' ', ' ', ' ', '-', ' ', ' ', '-', ' ', ' ' }; private char[] lastInvidTextChars = new char[MaxTextLength]; private char dateSeperator = '-'; // 分隔 private int minDateYear = 1950; private int maxDateYear = 2060; private bool isNull = true; private bool isValid = false; private Color normalDateForeColor; private Color invalidDateForeColor = Color.Red; private DateTime date; public event EventHandler DateChanged; public TDateEditBox() { base.MaxLength = MaxTextLength; this.ContextMenu = new ContextMenu(); normalDateForeColor = base.ForeColor; this.SetToNull(); } public new int MaxLength { get { return base.MaxLength; } set { base.MaxLength = MaxTextLength; } } public int MinDateYear { get { return minDateYear; } set { minDateYear = value; } } public int MaxDateYear { get { return maxDateYear; } set { maxDateYear = value; } } public Color InvalidDateForeColor { get { return invalidDateForeColor; } set { invalidDateForeColor = value; if (isValid == false && isNull == false) { this.ShowWarnColor(); } } } public override Color ForeColor { get { return base.ForeColor; } set { if (value != invalidDateForeColor) { base.ForeColor = value; normalDateForeColor = value; } if (isNull == true || isValid == true) { this.ShowNormalColor(); } } } public char DateSeperator { get { return dateSeperator; } set { if (value != '-' && value != '/' && value != '.') { dateSeperator = '-'; } else { dateSeperator = value; } textChars[4] = dateSeperator; textChars[7] = dateSeperator; this.ShowText(); base.SelectionStart = 0; } } public bool IsNull { get { return isNull; } } public bool IsValid { get { return isValid; } } public object Date { get { if (isNull == true || isValid == false) { return null; } return date; } set { if (value == null || value == DBNull.Value) { this.SetToNull(); } else { date = (DateTime)value; this.SetToDate(date); } if (base.ForeColor != normalDateForeColor) { base.ForeColor = normalDateForeColor; } this.Invalidate(); } } public void OnDateChanged() { if (isValid == true || isNull == true) { if (this.DateChanged != null) { this.DateChanged(this, EventArgs.Empty); } } } protected override void OnKeyDown(KeyEventArgs e) { if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift && (e.KeyCode == Keys.Right) || e.KeyCode == Keys.Left || e.KeyCode == Keys.Home || e.KeyCode == Keys.End) { base.OnKeyDown(e); return; } if (e.KeyData == Keys.Tab || e.KeyData == Keys.Home || e.KeyData == Keys.End) { base.OnKeyDown(e); return; } if (e.KeyCode == Keys.Back) { this.BackSpace(); } else if (e.KeyCode == Keys.Delete) { this.Delete(); } else if (e.KeyData == Keys.Left) { this.MoveLeft(); } else if (e.KeyData == Keys.Right) { this.MoveRight(); } else if ((e.KeyValue >= '0' && e.KeyValue <= '9') || e.KeyValue == ' ') { this.InputDigit(e.KeyValue); } else if ((e.KeyCode >= Keys.NumPad0 && e.KeyCode <= Keys.NumPad9)) { int keyValue = (int)e.KeyCode - (int)Keys.NumPad0 + (int)Keys.D0; this.InputDigit(keyValue); } e.SuppressKeyPress = true; e.Handled = true; this.ParseDateText(); } protected override void OnLeave(EventArgs e) { this.NormalizeDateText(); base.OnLeave(e); } protected override void OnGotFocus(EventArgs e) { base.OnGotFocus(e); base.SelectionLength = 0; } private void SetToNull() { textChars[0] = ' '; textChars[1] = ' '; textChars[2] = ' '; textChars[3] = ' '; textChars[4] = dateSeperator; textChars[5] = ' '; textChars[6] = ' '; textChars[7] = dateSeperator; textChars[8] = ' '; textChars[9] = ' '; isNull = true; isValid = false; this.ShowText(); base.SelectionStart = 0; } private void SetToDate(DateTime date) { string today = date.ToString("yyyy-MM-dd"); for (int k = 0; k < today.Length; k++) { if (k != 4 && k != 7) { textChars[k] = today[k]; } } isNull = false; isValid = true; this.ShowText(); base.SelectionStart = 0; } private void SaveLastInvlidTextChars() { if (isNull == true) { return; } for (int k = 0; k < textChars.Length; k++) { lastInvidTextChars[k] = textChars[k]; } } private void ParseDateText() { string y = new string(textChars, 0, 4); string m = new string(textChars, 5, 2); string d = new string(textChars, 8, 2); string yy = y.Trim(); string mm = m.Trim(); string dd = d.Trim(); if (string.IsNullOrEmpty(yy) == true && string.IsNullOrEmpty(mm) == true && string.IsNullOrEmpty(dd) == true) { bool preIsNull = isNull; isNull = true; isValid = false; if (preIsNull != isNull) { this.ShowNormalColor(); this.OnDateChanged(); } return; } isNull = false; if (string.IsNullOrEmpty(yy) == true || string.IsNullOrEmpty(mm) == true || string.IsNullOrEmpty(dd) == true) { isValid = false; this.SaveLastInvlidTextChars(); this.ShowWarnColor(); return; } if (Regex.IsMatch(yy, @"/d{4}") == false || Regex.IsMatch(mm, @"/d{1,2}") == false || Regex.IsMatch(dd, @"/d{1,2}") == false) { isValid = false; this.SaveLastInvlidTextChars(); this.ShowWarnColor(); return; } int year = int.Parse(yy); int month = int.Parse(mm); int day = int.Parse(dd); if (year < minDateYear || year > maxDateYear || month < 1 || month > 12 || day < 1 || day > DateTime.DaysInMonth(year, month)) { isValid = false; this.SaveLastInvlidTextChars(); this.ShowWarnColor(); return; } isValid = true; this.ShowNormalColor(); bool modified = false; if (year != date.Year || month != date.Month || day != date.Day) { modified = true; } date = new DateTime(year, month, day); if (modified == true) { this.OnDateChanged(); } } private void NormalizeDateText() { if (isValid == false || isNull == true) { return; } if (textChars[5] == ' ' || textChars[6] == ' ' || textChars[8] == ' ' || textChars[9] == ' ') { if (textChars[5] == ' ') { textChars[5] = '0'; } if (textChars[6] == ' ') { textChars[6] = textChars[5]; textChars[5] = '0'; } if (textChars[8] == ' ') { textChars[8] = '0'; } if (textChars[9] == ' ') { textChars[9] = textChars[8]; textChars[8] = '0'; } ShowText(); } } private void ShowText() { if (isNull == false && isValid == false) { if (base.ForeColor != invalidDateForeColor) { base.ForeColor = invalidDateForeColor; } } else { if (base.ForeColor != normalDateForeColor) { base.ForeColor = normalDateForeColor; } } base.Text = new string(textChars); } private void ShowNormalColor() { if (base.ForeColor != normalDateForeColor) { base.ForeColor = normalDateForeColor; } this.Invalidate(); } private void ShowWarnColor() { if (base.ForeColor != invalidDateForeColor) { base.ForeColor = invalidDateForeColor; } this.Invalidate(); } public void ResumeLastInvalidText() { for (int k = 0; k < textChars.Length; k++) { textChars[k] = lastInvidTextChars[k]; } if (base.ForeColor != invalidDateForeColor) { base.ForeColor = invalidDateForeColor; } isValid = false; base.Text = new string(textChars); this.OnDateChanged(); } private void Delete() { if (base.SelectionLength <= 1) { this.Delete(base.SelectionStart); } else { int start = base.SelectionStart + base.SelectionLength; int end = base.SelectionStart + 1; for (int k = start; k >= end; k--) { BackSpace(k); } } } private void Delete(int selectionStart) { if (AtTextEnd(selectionStart) == true) { return; } this.BackSpace(selectionStart + 1); } private void BackSpace() { this.BackSpace(base.SelectionStart); } private void BackSpace(int selectionStart) { int curPos = selectionStart; if (curPos == 0) { return; } if (AtSeperatorRight(curPos) == true) { base.SelectionStart = curPos - 1; return; } if (AtTextEnd(curPos) == true) { textChars[textChars.Length - 1] = ' '; ShowText(); base.SelectionStart = curPos - 1; return; } if (AtSeperatorLeft(curPos) == true) { textChars[curPos - 1] = ' '; ShowText(); base.SelectionStart = curPos - 1; return; } int k = curPos; while (AtSeperatorLeft(k) == false && AtTextEnd(k) == false) { textChars[k - 1] = textChars[k]; k++; } textChars[k - 1] = ' '; ShowText(); base.SelectionStart = curPos - 1; } private void InputDigit(int keyValue) { if (AtTextEnd() == true) { return; } int curPos = base.SelectionStart; int newPos = curPos; if (AtSeperatorLeft() == true) { textChars[base.SelectionStart + 1] = (char)keyValue; newPos = curPos + 2; } else if (AtSeperatorLeft(curPos + 1) == true) { textChars[base.SelectionStart] = (char)keyValue; newPos = curPos + 2; } else { textChars[base.SelectionStart] = (char)keyValue; if (AtSeperatorRight(curPos + 1) == true) { curPos++; } newPos = curPos + 1; } this.ShowText(); base.SelectionStart = newPos; } private void MoveLeft() { if (base.SelectionStart == 0) { return; } if (AtSeperatorRight(base.SelectionStart) == true) { base.SelectionStart -= 2; } else { base.SelectionStart -= 1; } } private void MoveRight() { if (this.AtTextEnd() == true) { return; } if (AtSeperatorLeft(base.SelectionStart + 1) == true) { base.SelectionStart += 2; } else { base.SelectionStart += 1; } } private bool AtSeperatorLeft(int curPos) { if (curPos == 4 || curPos == 7) { return true; } return false; } private bool AtSeperatorLeft() { return AtSeperatorLeft(base.SelectionStart); } private bool AtSeperatorRight(int curPos) { if (curPos == 5 || curPos == 8) { return true; } return false; } private bool AtSeperatorRight() { return AtSeperatorRight(base.SelectionStart); } private bool AtTextEnd(int curPos) { if (curPos == textChars.Length) { return true; } return false; } private bool AtTextEnd() { return AtTextEnd(base.SelectionStart); } } [ToolboxItem(false)] public class TDatePicker : DateTimePicker { private bool isDropdown = false; private DateTime dateBeforeDropDown; private const int WM_IME_SETCONTENT = 0x0281; private const int WM_CAPTURECHANGED = 0x0215; private const int WM_KEY_DOWN = 0x100; private const int WM_KEY_UP = 0x101; private const int WM_KEY_ENTER = 0x000d; private const int WM_KEY_ESC = 0x001b; private bool msgHandledAfterCloseup = true; protected bool msgHandledByImeSetContent= false; public event EventHandler DateConfirmed; public event EventHandler DateAbandoned; protected override void OnDropDown(EventArgs eventargs) { msgHandledAfterCloseup = true; isDropdown = true; dateBeforeDropDown = this.Value.Date; base.OnDropDown(eventargs); } protected override void OnCloseUp(EventArgs eventargs) { isDropdown = false; msgHandledAfterCloseup = false; msgHandledByImeSetContent = false; base.OnCloseUp(eventargs); } protected override void OnKeyDown(KeyEventArgs e) { if (isDropdown == true && (e.KeyCode == Keys.Left || e.KeyCode == Keys.Right || e.KeyCode == Keys.Up || e.KeyCode == Keys.Down || e.KeyCode == Keys.Home || e.KeyCode == Keys.End || e.KeyCode == Keys.PageUp || e.KeyCode == Keys.PageDown)) { base.OnKeyDown(e); // 不需要捕获 ESC/Enter 健 } else { e.SuppressKeyPress = true; e.Handled = true; } } private void OnDateConfirmed() { if (this.DateConfirmed != null) { this.DateConfirmed(this, EventArgs.Empty); } } private void OnDateAbandoned() { if (this.Value.Date != dateBeforeDropDown) { this.Value = dateBeforeDropDown; } if (this.DateConfirmed != null) { this.DateAbandoned(this, EventArgs.Empty); } } public override bool PreProcessMessage(ref Message msg) { if (msgHandledAfterCloseup == false && msg.Msg == WM_KEY_UP && msg.WParam.ToInt32() ==WM_KEY_ENTER) { msgHandledAfterCloseup = true; this.OnDateConfirmed(); } if ((msgHandledAfterCloseup == false || msgHandledByImeSetContent == true) && (msg.Msg == WM_KEY_UP && msg.WParam.ToInt32() == WM_KEY_ESC)) { msgHandledByImeSetContent = false; msgHandledAfterCloseup = true; this.OnDateAbandoned(); } return base.PreProcessMessage(ref msg); } protected override void WndProc(ref Message m) { if (m.Msg == WM_CAPTURECHANGED && msgHandledAfterCloseup == false) { msgHandledAfterCloseup = true; msgHandledByImeSetContent = false; this.OnDateAbandoned(); } else if (m.Msg == WM_IME_SETCONTENT && msgHandledAfterCloseup == false) { msgHandledAfterCloseup = true; msgHandledByImeSetContent = true; this.OnDateConfirmed(); } if (m.Msg == WM_KEY_DOWN) // keydown { msgHandledByImeSetContent = false; } base.WndProc(ref m); } } public class TDateEditPicker : UserControl, INotifyPropertyChanged { private IContainer components = null; private const int MINDATEYEAR = 1949; private const int MAXDATEYEAR = 2100; private TDateEditBox dateEditBox; private TDatePicker datePicker; private bool isNullWhenDropDown; private bool isValidWhenDropDown; private string version = "1.2"; private int nativeEditBoxWidth; public event PropertyChangedEventHandler PropertyChanged; public TDateEditPicker() { this.InitializeComponent(); this.DataBindings.DefaultDataSourceUpdateMode = DataSourceUpdateMode.Never; // 强制默认方式 this.dateEditBox.Leave += new EventHandler(this.DateEditBox_Leave); this.datePicker.DropDown += new EventHandler(this.DateTimePicker_DropDown); this.dateEditBox.DateChanged += new EventHandler(this.DateEditBox_DateChanged); this.datePicker.CloseUp += new EventHandler(this.DateTimePicker_CloseUp); this.datePicker.DateConfirmed += new EventHandler(this.DateTimePicker_DataConfirmed); this.datePicker.DateAbandoned += new EventHandler(this.DateTimePicker_DateAbandoned); } private void InitializeComponent() { this.dateEditBox = new TDateEditBox(); this.datePicker = new TDatePicker(); this.SuspendLayout(); this.datePicker.Location = new Point(1, 0); this.datePicker.Name = "datePicker"; this.datePicker.Value = DateTime.Now; this.datePicker.MinDate = new DateTime(this.dateEditBox.MinDateYear, 1, 1); this.datePicker.MaxDate = new DateTime(this.dateEditBox.MaxDateYear, 12, 31); this.datePicker.Format = DateTimePickerFormat.Custom; this.datePicker.CustomFormat = " "; this.dateEditBox.Location = new Point(0, 0); this.dateEditBox.Name = "dateEditBox"; this.nativeEditBoxWidth = this.dateEditBox.Height; this.Controls.Add(this.datePicker); this.Controls.Add(this.dateEditBox); this.Name = "TDateEditPicker"; this.Size = new Size(this.datePicker.Width + 2, this.dateEditBox.Height + 2); this.dateEditBox.BringToFront(); this.ResumeLayout(); } protected override void Dispose(bool disposing) { if (disposing == true) { if (components != null) { components.Dispose(); } } base.Dispose(disposing); } protected override void OnResize(EventArgs e) { base.OnResize(e); this.datePicker.Width = this.Width - 2; this.dateEditBox.Width = this.datePicker.Width - this.nativeEditBoxWidth + 2; } protected override void OnForeColorChanged(EventArgs e) { base.OnForeColorChanged(e); dateEditBox.ForeColor = base.ForeColor; } private void DateEditBox_Leave(object sender, EventArgs e) { if (this.IsValid == true && this.IsNull == false) { this.datePicker.Value = (DateTime)dateEditBox.Date; } else { this.datePicker.Value = DateTime.Now.Date; } } private void DateEditBox_DateChanged(object sender, EventArgs e) { if (this.IsNull == true || this.IsValid == true) { this.NotifyPropertyChanged("Date"); } } private void DateTimePicker_DropDown(object sender, EventArgs e) { isNullWhenDropDown = this.IsNull; isValidWhenDropDown = this.IsValid; } private void DateTimePicker_DataConfirmed(object sender, EventArgs e) { this.dateEditBox.Date = this.datePicker.Value.Date; this.NotifyPropertyChanged("Date"); } private void DateTimePicker_DateAbandoned(object sender, EventArgs e) { if (isNullWhenDropDown == true) { this.dateEditBox.Date = null; } else if (isValidWhenDropDown == false) { this.dateEditBox.ResumeLastInvalidText(); } } private void DateTimePicker_CloseUp(object sender, EventArgs e) { } private void NotifyPropertyChanged(string info) { if (this.DataBindings != null && this.DataBindings.Count > 0) { if (this.DataBindings[0].DataSourceUpdateMode == DataSourceUpdateMode.OnValidation) // == never { return; } } if (this.PropertyChanged != null) { this.PropertyChanged(this, new PropertyChangedEventArgs(info)); } } [Category("Custom"), Browsable(true)] public string Version { get { return version; } } [Category("Custom"), Browsable(true), DefaultValue('-')] [Description("Date seperator, only use three chars: .-/.")] public char DateSeperator { get { return dateEditBox.DateSeperator; } set { dateEditBox.DateSeperator = value; } } [Category("Custom"),Browsable(true)] public int MinDateYear { get { return this.dateEditBox.MinDateYear; } set { int tmpValue = value; if (tmpValue < MINDATEYEAR || tmpValue > MAXDATEYEAR) { tmpValue = MINDATEYEAR; } if (this.MinDateYear != tmpValue) { this.dateEditBox.MinDateYear = tmpValue; this.datePicker.MinDate = new DateTime(tmpValue, 1, 1); } } } [Category("Custom"), Browsable(true)] public int MaxDateYear { get { return this.dateEditBox.MaxDateYear; } set { int tmpValue = value; if (tmpValue < MINDATEYEAR || tmpValue > MAXDATEYEAR) { tmpValue = MAXDATEYEAR; } else { this.dateEditBox.MaxDateYear = tmpValue; this.datePicker.MaxDate = new DateTime(tmpValue, 12, 31); } } } [Category("Custom")] [Description("Is the date is null.")] public bool IsNull { get { return dateEditBox.IsNull; } } [Category("Custom")] [Description("Is the date is valid.")] public bool IsValid { get { return dateEditBox.IsValid; } } [Bindable(true), Browsable(false)] public Object Date { get { return this.dateEditBox.Date; } set { this.dateEditBox.Date = value; this.NotifyPropertyChanged("Date"); } } [Category("Custom")] [Description("The font color when date is invalid.")] [DefaultValue("Red"), Browsable(true)] public Color InvalidDateForeColor { get { return this.dateEditBox.InvalidDateForeColor; } set { this.dateEditBox.InvalidDateForeColor = value; } } } }
4、结语
TDateEditPicker控件还有一些属性,如:设置日期分隔符、设置前景颜色、设置无效日期颜色、最大最小日期等。具体使用时,拷贝上述控件代码为一个类文件,加到自己的Project中然后编译,此时在工具条上将看到该控件。拖拉到窗体上即可。
由于项目中需要用到不少日期处理,原先做过的一个NullableDateTimePicker不能满足要求。在参考网上的一些资料基础上,花了约5天计50多小时。时间仓促,测试不充分,有同行使用时如果发现Bug,请不吝告之,笔者将进一步完善。
升级历史
- 2010-03-13:TDateEditPicker控件及演示(Ver1.3)。修改了部分代码,规范了字段和属性命名,整理了多余代码。
- 2010-03-14:TDateEditPicker演示及源码(Ver1.5)。增加了日期显示格式和分隔符号,增加了设计期初始日期属性,优化了代码。
- 2010-03-16:TDateEditPicker控件及演示(Ver1.6)。解决了通过Tab移动到该控件时输入框不获得聚焦的Bug。
- 2010-03-18:TDateEditPicker控件及演示(Ver1.8)。解决了直接给日期后下拉不显示该日期的一个Bug, 下拉关闭后聚焦的Bug。
- 2010-04-08:Version1.9。在 TabControl 容器中的几个TabPage上,只有一个Page显示DatePicker控件,其他的Page只有DateEditBox控件。
- 2010-04-17:Version1.11。去掉了DataPickerVisible属性(该属性导致控件在TabControl的多页面上异常),增加了ValueChanged事件。