bird

 

MaskedEdit Control

轉自:http://www.codeproject.com/cs/miscctrl/maskededit.asp

Introduction前言

Visual Studio.NET and the .NET Framework includes many very useful controls. Unfortunately a MaskedEdit control was not among them. Anyone who has used the VS 6.0 MaskedEdit control knows that Microsoft's last attempt was not without its problems. MS Access developers have always had a useable MaskedEdit control. I wonder sometimes why Microsoft hasn't adapted the Access control to their other tools. Anyway I decided to tackle this very useful but missing control.

After spending a few hours working to handle character selection in the TextBox based control, I began to understand why someone might avoid building this control. Then came setting the MaskedEdit control value which introduced a new set of challenges. The difficulty of building a bullet proof MaskedEdit became apparent very rapidly - so this is fair warning, don't forget to test, test, test.

Background背景

The MaskedEdit control is loosely(輕松的) based(基于) on the Access mask behavior(行為) and includes(包含) the following(以下) features(特征):

  • Standard InputMasks
    • Social Security Number (SSN)
    • Phone Numbers
    • Zip Codes
  • Custom InputMasks
  • Runtime changable InputMask
  • IsValid Property
  • Text Property (includes(包含) literals(分隔符))
  • Value Property (excludes(排除) literals)
  • Input Char Property (defaults to '_') 默認的分隔符

Using the MaskedEdit Control使用控件

The following(下列的) characters have been defined as mask characters for the MaskedEdit control

0 - digit required

9 - digit optional

L - lower case letter (a-z) required

l - lower case letter optional

U - upper case letter (A-Z) required

u - upper case letter optional

A - any case letter required

a - any case letter optional

D - letter or digit required

d - letter or digit optional

C - any char including punctuation

If you are not familiar(熟悉) with mask characters(格式字符), you can find information in the MSDN library under "InputMask Property"

Regular expressions(正則表達式) are used to define each mask char above(在上面). So for example '0' and '9' mask characters are coded as:

m_regexps.Add('0', @"[0-9]");        // digit required
m_regexps.Add('9', @"[0-9 ]");        // digit/space not required

You can find all the other mask character RegExp's in the source code for the MaskedEdit control. The space ' ' character is used as the optional(可選擇的) character. Validation(確認) code in other parts(部分) of the MaskedEdit control use the space character to determine(確定) if an entry is required(必須的). This is a key consideration(考慮) if you decide(決定) to define your own(特有的) input mask characters.

The input mask chars that I've defined are basically(基本的) groups of digits(阿拉伯數字), upper and lower case letters along(以及) with ANY mask character 'C'. I think these should be suitable(適合的) for many circumstances(環境) but if you have a need for more finely tuned() input restrictions(限製,約束), the MaskedEdit control can handle(實現) it. For example, suppose(假設) you have a need to input hexadecimal(十六進製的) values. None of the current input mask chars handle(實現) hex(十六進製) digits (0 through() 9 plus(加上) A through F). So lets add a new mask char for hex digits, which would look like

m_regexps.Add('H', @"[0-9A-F]");        // hex digit required

This regexp(驗証格式Hashtable集) would permit(允許) any of these characters "0123456789ABCDEF". So an InputMask(自訂格式字串) for a 16 bit hex value might(可能) look like ""0xHHHH" would be displayed as 0x____ with 4 positions(位置) for hex digit entry. This means(手段,方法) that you can adapt(合適應) the MaskedEdit control to handle(實現) new input requirements(需求). Don't forget(忘記) that if you add an input mask char '-', that the standard InputMasks will be adversely(反對地) affected(受到影響的).

If you're paying close attention(注意) you may have a question(問題) about the InputMask(自訂格式字串) above(在上面). What is the '"' char? If you need to add a literal char that is already defined as a mask char then you will need to use the escape(轉義) char '"'. This causes the MaskedEdit control to consider(考慮) the next InputMask character a literal instead(代替) of an input mask character. For example, let suppose(假設) we want an InputMask to allow the user to input part numbers in the form

UL-1234

where UL- are literals and 1234 represent(聲明) required digits. Both 'U' and 'L' are input mask characters for Upper and Lower case characters. Adding literal 'U' or 'L' means that we must add the escape character to designate(指明,區分) them as literals instead(代替) of input mask characters, so our InputMask would be

"U"L-0000

Note that the '-' does not need the escape char prefix(前綴) since(因為) it is not defined as a mask char.

MaskedEdit Properties控件屬性

Next lets look at how to get and set the value contained(包含) in the MaskedEdit control.

Text Property - This property sets/gets the entire(全部) string contained(包含) in the control including(包括) literals. So an SSN input mask with data filled(加載) in such as

123-45-6789

will return "123-45-6789".

Value Property - This property sets/gets ONLY the input chars from the control excluding(排除) literals(分隔符). So the example above(上述例子) would return "123456789". The value prop(支持) does NOT return optional chars that have not been entered by the user so for example:

(___) 123-4567

entered into a phone number mask would return "1234567" from the Value prop. Optional input mask chars cause some interesting(有趣的) problems(問題) that I have worked to handle(控製). I would recommend(介紹) using the MaskedEditTest application(請求) to test any InputMask you plan(計劃) to use to make sure(確信) it works as you would expect(期待的).

The remainder(剩余的) of the MaskedEdit properties are pretty(不錯的) sraight forward.

·                     IsValid Property - Returns true if all required input chars have been entered correctly(通過驗証的).

·                     InputChar Property - Defaulted to '_' underscore(底線) char but can be set to any other char except(除了) input mask chars or the escape(轉義) char.

·                     ErrorInvalid Property - Defaulted to false. Setting this to true will cause(導至,引起) the MaskedEdit control to throw errors if attempts(企圖) are made(進行) to set Text or Value props to an invalid(無效的) string. Setting this to true during(期間) debugging could prove(檢驗) very useful(有用的).

·                     StdInputMask Property – enum(枚舉) to select a standard input mask including(包括)

o        None - makes the MaskedEdit control work like a standard text box

o        SSN - Social Security Number(社會保障號)

o        Phone - Phone Numbers

o        Zip - Zip Codes

o        Custom - use this to define custom input mask

·                     InputMask Property - Use this to set custom InputMasks. The InputMask CAN be changed during(期間) runtime. The MaskedEdit control attempts(企圖) to convert the current text into the new mask. This is LIKELY(有可能) to cause(引起) problems(問題). If you need to reset the mask during runtime, I would recommend(推荐) setting the Value to an empty string prior(之前) to resetting the InputMask.

Usage Considerations使用方法

The MaskedEdit should work well for inputting "known" values such(例如) as SSN, phone and zip. The important point(重點) is that the user must KNOW what is to be entered since the mask doesn't give any clues as to the type of input expected (期望的)(digits?, alpha(希臘) chars?, ???). Tool tips or help files might(可能) be useful(有用的) to explain(解釋,說明) complex(複雜) input masks. Providing(倘若) messages when invalid(無效的) characters are entered(錄入) by the user is a possible(可能的) improvement(改進). I am considering(認為) adding an InValid event, and an InValidMessage property to allow developers(開發者) to add customized(定製的,用戶的) messages fired() during(..期間) invalid entry. Let me know your thoughts.

Setting the InputMask to only literal chars makes this a label control. I considered(考慮) adding handling(處理) for this possibility(可能性) but decided(堅決的) against(反對) it. If you try this, be prepared(準備好) to catch(捕捉) errors :-"

Selection of characters in the MaskedEdit control is designed(原有的) to keep the selection on input chars only and to skip over(略過) literals. This causes(動機) a somewhat(稍微) peculiar(特殊的) selection behavior(行為).

The base class TextBox has an extensive(廣闊的) number of properties(道具). I have added handling(控製) for some of these properties that I know affect(影響) the MaskedEdit control. There may be other props(小道具) that I haven't considered(考慮) that will cause problems or errors. Please post a note if you find one.

TODO

  1. Test, Test, Test I've spent(用盡的) more time than I would like to admit(接納) testing this control but as anything(任何事) with this level(級別,標準) of complexity(複雜性), undiscovered(未被發現的) bugs may exist. Please post a note if you find a bug.
  2. Add data binding features(特征). I would anticipate(期望) binding the Text, Value, and InputMask properties. Let me know your thoughts on this.
  3. The MS Access has a feature(特征) to automatically(自動的) upper/lower case input '<' = lower case, '>' = uppercase. This feature is not included(包括) in the MaskedEdit control. Let me know if you have a need for this feature, and I will work to add it on Rev. 2

以下為我修改后的代碼:

 using System;
using System.Collections;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.ComponentModel;

namespace ChenYueJun.WindowsControlLibrary
{
 /// <summary>
 /// MaskedEdit Control based on TextBox
 ///
 /// Author(作家): Oscar Bowyer (oscarbow@earthlink.net)
 ///
 /// History:
 ///  Initial Release 4/3/2003
 ///  
 /// InputMask Char definition(定義) below(在...下面中)
 ///  0 digit(阿拉伯數字) required(必須的)
 ///  9 digit optional(可選擇的)
 ///  L lower(較低的) case letter (a-z) required
 ///  l lower case letter optional//以上字符都是可選擇的
 ///  U upper case letter (A-Z) required
 ///  u upper case letter optional
 ///  A any case letter required//任意字符
 ///  a any case letter optional
 ///  D letter or digit required//字母和數字
 ///  d letter or digit optional
 ///  C any char including(包括) punctuation(標點符號)
 ///  " escape(轉義) char Used for literal mask chars ex: "0 for literal 0 in mask
 /// </summary>
 public class CYJ_MaskedEdit : TextBox
 {
  /// <summary>
  /// input mask
  /// </summary>
  private string m_mask;
  /// <summary>
  /// display format (mask with input chars replaced(取代) by input char)
  /// </summary>
  private string m_format;
  /// <summary>
  /// 格式類型
  /// </summary>
  private InputMaskType m_maskType;
  /// <summary>
  /// Sets the Input Char default '_'(格式位置標記符.注意此值不能跟InputMask中的分隔符相同,否則,出錯)"
  /// </summary>
  private char m_inpChar;
  private bool m_maskChg;
  private bool m_stdmaskChg;
  private Hashtable m_regexps;
  /// <summary>
  /// hold(保持) position(位置) translation(轉換) map(地圖)
  /// </summary>
  private Hashtable m_posNdx; 
  private int m_caret;
  /// <summary>
  /// Throw Error On Invalid Text/Value Property? -> true throws error, false ignore(在驗証出錯時是否拋出錯誤.)
  /// </summary>
  private bool m_errInvalid;
  /// <summary>
  /// required char count(輸入字符統計)
  /// </summary>
  private int m_reqdCnt;
  /// <summary>
  /// optional char count(字符中分隔符統計)
  /// </summary>
  private int m_optCnt;

  // allowed(允許) mask chars
  private const char MASK_KEY = '@';

  private bool isValidForDateTime=false;

  #region 自定義格式類型枚舉值
  // predefined(預定義) masks
  private const string SSN = "000-00-0000";
  private const string PHONE = "(999) 000-0000";
  private const string ZIP = "00000-9999";

  /// <summary>
  /// 自定義格式類型
  /// </summary>
  public enum InputMaskType
  {
   /// <summary>
   /// makes the MaskedEdit control work like a standard text box
   /// </summary>
   None,
   /// <summary>
   /// Social Security Number(社會保障號)
   /// </summary>
   SSN,
   /// <summary>
   /// Phone Numbers
   /// </summary>
   Phone,
   /// <summary>
   /// Zip Codes
   /// </summary>
   Zip,
   /// <summary>
   /// use this to define custom input mask
   /// </summary>
   Custom
  }
  #endregion

  //構造函數
  /// <summary>
  /// MaskedEdit Control based on TextBox
  /// </summary>
  public CYJ_MaskedEdit()
  {
   // set default mask, input char
   m_maskType = InputMaskType.None;
   m_inpChar = '_';
   m_mask = "";
   m_format = "";
   m_caret = 0;
   m_errInvalid = false;
   base.Multiline = false;
  }

  /// <summary>
  /// 內容是否驗証通過
  /// </summary>
  [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), Browsable(false)]
  public bool IsValid
  {
   get{return IsValidString(base.Text);}
  }

  /// <summary>
  /// 內容是否驗証為日期類型
  /// </summary>
  [DescriptionAttribute("內容是否驗証為日期類型."),CategoryAttribute("擴展屬性"),DefaultValueAttribute(false)]
  public bool IsValidForDateTime
  {
   get{return isValidForDateTime;}
   set{isValidForDateTime=value;}
  }

  /// <summary>
  /// Throw Error On Invalid Text/Value Property? -> true throws error, false ignore(在驗証出錯時是否拋出錯誤.)
  /// </summary>
  [Description("Throw Error On Invalid Text/Value Property? -> true throws error, false ignore"), Category("Behavior")]
  public bool ErrorInvalid
  {
   get{return m_errInvalid;}
   set{m_errInvalid = value;}
  }

  /// <summary>
  /// 取得或設定數值,指示這是否為多行文字方塊控制項。
  /// </summary>
  [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  public override bool Multiline
  {
   get{return base.Multiline;}
   // ignore set
  }

  /// <summary>
  /// 取得或設定文字方塊中選取文字的起點。
  /// </summary>
  [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  //使用 new 修飾詞以明確隱藏繼承自基底類別的成員。
  //若要隱藏繼承的成員,請在衍生類別 (Derived Class) 裡使用相同名稱為其宣告,並且以 new 修飾詞修飾。
  public new int SelectionStart
  {
   get{return base.SelectionStart;}
  }

  /// <summary>
  /// 取得或設定文字方塊中所選取的字元數。
  /// </summary>
  [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  public override int SelectionLength
  {
   get{return base.SelectionLength;}
  }

  /// <summary>
  /// 取得或設定文字方塊中目前的文字。
  /// </summary>
  [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  public override string Text
  {
   get{return base.Text;}
   set
   {
    if(m_maskType == InputMaskType.None)
     base.Text = value;
    else
    {
     // if input string length doesn't match format length
     // then we have a problem(問題)! this means(方法) that Text input
     // string MUST have optional missing chars
     if(value == "")
      base.Text = m_format;
     else if(IsValidDateTime(value) && m_format.Length>0)//值為DataTime型
     {
      //1.獲得InputMask中包含InputChar個數量
      int chars=0;
      for(int i=0;i<m_format.Length;i++)
      {
       if(!this.InputChar.Equals(m_format[i]))
        chars+=1;
      }
      //2.將DateTime值轉化為字符串,精確到毫秒
      string dateTime=Convert.ToDateTime(value).ToString("yyyyMMddHHmmssfffffff");
      //3.將取得的值賦給Value屬性
      this.Value=dateTime.Substring(0,this.InputMask.Length-chars);
     }
     else if(IsValidString(value) && value.Length == m_format.Length)
     {
      // must check optional input chars
      bool ok = true;
      int fpos = 0;
      while(ok && fpos < m_format.Length)
      {
       if(m_format[fpos] == m_inpChar
        && !IsValidChar(value[fpos], (int)m_posNdx[fpos]))
        ok &= value[fpos] == m_inpChar;
       fpos++;
      }

      if(ok)
       base.Text = value;
     }
     else if(m_errInvalid)
      throw new ApplicationException("Input String Does Not Match Input Mask");
    }
   }
  }

  /// <summary>
  /// 取得或設定文字方塊中目前的文字去除格式字符后的值。
  /// </summary>
  [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), Browsable(false)]
  public string Value
  {
   get
   {
    if(m_maskType == InputMaskType.None)
     return base.Text;
    else
    {
     // return text with literals(文字,分隔符可能理容易理解)/spaces striped(剝去后)
     string ret = "";
     string m = InputMask;
     string t = base.Text;
     if(IsValidString(t))
     {
      // strip(剝去) literals(文字,分隔符可能理容易理解)/spaces
      int tPos = 0;
      for(int i = 0; i < m.Length; i++)
      {
       //1.位置i的驗証碼為有效的驗証碼
       //2.位置i的值不得為空字符' '
       //3.位置i的值不得為位置標記符
       if(IsMaskChar(m[i]) && t[tPos] != ' ' && t[tPos] != m_inpChar)
        ret += t[tPos];
       else if(m[i] == '""')
        i++;
       tPos++;
      }
     }    
     return ret;
    }
   }
   set
   {
    if(m_maskType == InputMaskType.None)
     base.Text = value;
    else
    {
     // Merge(合並) input chars with literals(文字,分隔符可能理容易理解)
     string t = ""; // text being assembled from input Value and m_format string
     int ipos = 0; // input value position
     int dif = value.Length - m_reqdCnt;
     if(value == "")
      base.Text = m_format;
     else if(value.Length >= m_reqdCnt && value.Length <= m_reqdCnt + m_optCnt)//值.Lenght>輸入字符統計數 and 值.Lenght<=(輸入字符統計數+分隔符統計數)
     {
      for(int fpos=0; fpos < m_format.Length; fpos++)
      {
       if(ipos < value.Length && m_format[fpos] == m_inpChar)
       {
        // input char (not literal)
        if(((string)RegExps[InputMask[(int)m_posNdx[fpos]]]).IndexOf(' ') != -1)
        {
         // optional
         if(dif > 0)
         {
          t += value[ipos++];
          dif--;
         }
         else t += m_format[fpos];
        }
        else t += value[ipos++];
       }
       else t += m_format[fpos];
      }
     }
     else if(m_errInvalid)
      throw new ApplicationException("Input String Does Not Match Input Mask");

     // validate input
     if(IsValidString(t))
      base.Text = t;
     else if(m_errInvalid)
      throw new ApplicationException("Input String Does Not Match Input Mask");
    }
   }
  }

  #region 設置格式屬性(StdInputMask/InputChar/InputMask)
  /// <summary>
  /// Sets Predefined Input Mask(決定控件是使用標準或自訂的格式顯示)
  /// </summary>
  [Description("Sets Predefined Input Mask(決定控件是使用標準或自訂的格式顯示)"), Category("Behavior"),
  RefreshProperties(RefreshProperties.All)]
  public InputMaskType StdInputMask
  {
   get{return m_maskType;}
   set
   {
    m_stdmaskChg = true;
    m_maskType = value;

    // set mask string
    if(!m_maskChg)
    {
     switch(value)
     {
      case InputMaskType.None:
       InputMask = "";
       break;
      case InputMaskType.SSN:
       InputMask = SSN;
       break;
      case InputMaskType.Phone:
       InputMask = PHONE;
       break;
      case InputMaskType.Zip:
       InputMask = ZIP;
       break;
      case InputMaskType.Custom:
       // User responsible for setting InputMask
       break;
      default:
       throw new ApplicationException("Invalid InputMaskType");
     }
    }
    m_stdmaskChg = false;
   }
  }

  /// <summary>
  /// Sets the Input Char default '_'(格式位置標記符.注意此值不能跟InputMask中的分隔符相同,否則,出錯)"
  /// </summary>
  [Description("Sets the Input Char default '_'(格式位置標記符.注意此值不能跟InputMask中的分隔符相同,否則,出錯)"), Category("Behavior"),
  RefreshProperties(RefreshProperties.All)]
  public char InputChar
  {
   //此值不能跟InputMask中的分隔符相同,否則,出錯.需要增加此檢測功能
   // "_" default
   get{return m_inpChar;}
   set
   {
    m_inpChar = value;
    //'"0'為空字符
    if(m_inpChar=='"0' || m_inpChar.ToString().Trim()==string.Empty)
     this.StdInputMask=InputMaskType.None;
    else
     InputMask = m_mask;
   }
  }
  
  /// <summary>
  /// Sets the Input Mask(用來格式化于控製項中值的顯示的自訂格式字串)
  /// </summary>
  [Description("Sets the Input Mask(用來格式化于控製項中值的顯示的自訂格式字串)"), Category("Behavior"),
  RefreshProperties(RefreshProperties.All)]
  public string InputMask
  {
   get{return m_mask;}
   set
   {
    m_maskChg = true;
    m_mask = value;

    if(!m_stdmaskChg)
    {
     // sync InputMask with StdInputMask
     switch(value)
     {
      case "":
       StdInputMask = InputMaskType.None;
       break;
      case SSN:
       StdInputMask = InputMaskType.SSN;
       break;
      case PHONE:
       StdInputMask = InputMaskType.Phone;
       break;
      case ZIP:
       StdInputMask = InputMaskType.Zip;
       break;
      default:
       StdInputMask = InputMaskType.Custom;
       break;
     }
    }
    SetupMask();

    // runtime handling, reset text if current text is not valid
    if(DesignMode == true || base.Text.Length == 0 || !IsValidString(base.Text))
     base.Text = m_format;
    else
    {
     // reformat(重定格式) current text with new mask
     this.Value = this.Value;
    }
    
    base.MaxLength = m_format.Length;
    m_maskChg = false;
   }
  }
  #endregion

  #region 驗証字符串
  /// <summary>
  /// 驗証格式Hashtable集
  /// </summary>
  private Hashtable RegExps
  {
   get
   {
    if(m_regexps == null)
    {
     m_regexps = new Hashtable();

     // build regexps
     m_regexps.Add('0', @"[0-9]");  // digit required
     m_regexps.Add('9', @"[0-9 ]");  // digit/space not required
     
     m_regexps.Add('L', @"[a-z]");  // letter a-z required
     m_regexps.Add('l', @"[a-z ]");  // letter a-z not required

     m_regexps.Add('U', @"[A-Z]");  // letter A-Z required
     m_regexps.Add('u', @"[A-Z ]");  // letter A-Z not required

     m_regexps.Add('A', @"[a-zA-Z]"); // letter required
     m_regexps.Add('a', @"[a-zA-Z ]"); // letter not required

     m_regexps.Add('D', @"[a-zA-Z0-9]");  // letter or digit required
     m_regexps.Add('d', @"[a-zA-Z0-9 ]"); // letter or digit not required

     m_regexps.Add('C', @".");  // any char

     // IMPORTANT: You MUST add and new mask chars to this regexp!
     m_regexps.Add('@', @"[09LlUuAaDdC]"); // used for input char testing
    }

    return m_regexps;
   }
  }

  /// <summary>
  /// 驗証字符串是否符合驗証規則
  /// </summary>
  /// <param name="s">需要驗証的字符串</param>
  /// <returns>true/false</returns>
  private bool IsValidString(string s)
  {
   bool ret = true;
   int pos = 0;
   // validate(驗証) considering optional chars
   while(ret && pos < m_format.Length)
   {
    if(m_format[pos] == m_inpChar)
    {
     // check input is valid(有效的) including(包括) "optional" -> space in regexp
     if(pos >= s.Length)
     {
      // must be optional input
      ret = ((string)RegExps[InputMask[(int)m_posNdx[pos]]]).IndexOf(' ') != -1;
     }
     else
     {
      // valid or optional
      ret = IsValidChar(s[pos], (int)m_posNdx[pos]);
      if(!ret)
       ret |= ((string)RegExps[InputMask[(int)m_posNdx[pos]]]).IndexOf(' ') != -1
        && (s[pos] == ' ' || s[pos] == m_inpChar);
     }
    }
    else//檢查用戶輸入標記是否跟設置標記相同
    {
     // check literal match
     if(pos < s.Length)
      ret = s[pos] == m_format[pos];
    }
    pos++;
   }
   return ret;
  }

  /// <summary>
  /// 驗証指定位置輸入的字符是否符合此位置的驗証規則
  /// </summary>
  /// <param name="input">需要驗証的字符</param>
  /// <param name="pos">需要驗証字符的位置索引</param>
  /// <returns>true/false</returns>
  private bool IsValidChar(char input, int pos)
  {
   // validate input char against(逆著) mask
   return Regex.IsMatch(input.ToString(), (string)RegExps[InputMask[pos]]);
  }

  /// <summary>
  /// 驗証格式碼是否是驗証格式集RegExps中的格式碼字符
  /// </summary>
  /// <param name="input">格式碼字符</param>
  /// <returns>true/false</returns>
  private bool IsMaskChar(char input)
  {
   // check char
   return Regex.IsMatch(input.ToString(), (string)RegExps[MASK_KEY]);
  }

  /// <summary>
  /// 驗証字符串是否為DataTime型
  /// </summary>
  /// <param name="s">需要驗証的字符串</param>
  /// <returns>true/false</returns>
  private bool IsValidDateTime(string s)
  {
   bool result=false;
   try
   {
    DateTime tmp=Convert.ToDateTime(s);
    result=true;
   }
   catch
   {
   }
   return result;
  }

  #endregion

  /// <summary>
  /// 取得或設定使用者能夠輸入或貼入文字方塊控制項中的最大字元數。
  /// </summary>
  public override int MaxLength
  {
   get{return base.MaxLength;}
   set
   {
    // prevent setting if Mask is defined
    if(m_maskChg || m_stdmaskChg || StdInputMask == InputMaskType.None)
     base.MaxLength = value;
   }
  }

  /// <summary>
  /// 設置控伯中顯示的樣式的位置(用戶設置的格式,如:9999-99-99 99:99,則顯示為____-__-__ __:__)
  /// </summary>
  private void SetupMask()
  {
   // used to build position translation map from mask string
   // and input format
   string s = InputMask;
   m_format = "";

   // reset index
   if(m_posNdx == null)
    m_posNdx = new Hashtable();
   else
    m_posNdx.Clear();

   int cnt = 0;
   m_reqdCnt = 0;
   m_optCnt = 0;

   for(int i = 0; i < s.Length; i++)
   {
    if(IsMaskChar(s[i]))
    {
     m_posNdx.Add(cnt, i);
     m_format += m_inpChar;
     // update optional/required char counts
     if(((string)RegExps[InputMask[i]]).IndexOf(' ') != -1)
      m_optCnt++;
     else
      m_reqdCnt++;
    }
    else if(s[i] == '""')
    {
     // escape char
     i++;
     m_format += s[i].ToString();
    }
    else
     m_format += s[i].ToString();

    cnt++;
   }
  }

  #region 重寫方法
  /// <summary>
  /// 引發 Validating 事件。(發生於控制項進行驗證時。)
  /// </summary>
  /// <param name="e"></param>
  protected override void OnValidating(CancelEventArgs e)
  {
   if(isValidForDateTime && this.Value != "")
   {
    e.Cancel=!IsValidDateTime(this.Text);
    if(e.Cancel)
     MessageBox.Show(this,"字符串<"+this.Text+">不是有效的日期時間類型!","Error",MessageBoxButtons.OK,MessageBoxIcon.Error);
   }
   base.OnValidating (e);
  }

  /// <summary>
  /// 引發控件的KeyDown事件
  /// </summary>
  /// <param name="e">KeyEventArgs事件資料</param>
  protected override void OnKeyDown(System.Windows.Forms.KeyEventArgs e)
  {
   /*
   SendKeys.Send直接在control内部处理消息,不再向外转发;
   而ProcessDialogKey则不一定,会转发到control的parent。
   */
   if(e.KeyCode==Keys.Enter)
    SendKeys.Send("{Tab}");//ProcessDialogKey(Keys.Tab);
   else
    base.OnKeyDown(e);
  }

  /// <summary>
  /// 重寫.發生於滑鼠指標位於控制項上,並且放開滑鼠鍵時。
  /// </summary>
  /// <param name="e">包含事件資料的 MouseEventArgs 。 </param>
  protected override void OnMouseUp(MouseEventArgs e)
  {
   if(StdInputMask == InputMaskType.None)
   {
    base.OnMouseUp(e);
    return;
   }

   // reset selection to include input chars
   int strt = base.SelectionStart;
   int orig = strt;
   int len = base.SelectionLength;

   // reset selection start
   if(strt == base.MaxLength || m_format[strt] != m_inpChar)
   {
    // reset start
    if(Next(strt) == strt)
     strt = Prev(strt);
    else
     strt = Next(strt);

    base.SelectionStart = strt;
   }

   // reset selection length
   if(len < 1)
    base.SelectionLength = 1;
   else if(m_format[orig + len - 1] != m_inpChar)
   {
    len += Next(strt + len) - (strt + len);
    base.SelectionLength = len;
   }

   m_caret = strt;
   base.OnMouseUp(e);
  }

  /// <summary>
  /// 重寫.處理命令按鍵。
  /// </summary>
  /// <param name="msg">以傳址方式傳遞的 Message ,表示要處理的視窗訊息。</param>
  /// <param name="keyData">其中一個 Keys 值,表示要處理的按鍵。</param>
  /// <returns>如果字元由控制項處理,為 true ;否則為 false 。</returns>
  /*
   這個方法是在前置處理訊息時呼叫以處理命令按鍵。命令鍵是永遠在一般輸入鍵之前的按鍵。
  命令鍵的範例包括對應鍵 (Accelerator) 和功能表快速鍵。這個方法必須傳回 true 以指示它已經處理了命令鍵,或者傳回 false 指示按鍵並非命令鍵。
  只有控制項是裝載於 Windows Form 應用程式或 ActiveX 控制項中時,才會呼叫這個方法。
   ProcessCmdKey 方法會先判斷控制項是否有 ContextMenu ,如果有,則允許 ContextMenu 處理命令鍵。
  如果命令鍵不是功能表快速鍵,而且控制項有父控制項,按鍵會傳遞至父控制項的 ProcessCmdKey 方法。
  最後的結果是命令鍵會充滿控制項階層。除了使用者按下的按鍵之外,按鍵資料也會指示同時還按下了哪些輔助按鍵 (如果有)。
  輔助按鍵包括 SHIFT、CTRL 和 ALT 鍵。
   繼承者注意事項:  當在衍生類別中覆寫 ProcessCmdKey 方法時,控制項應傳回 true 以指示它已處理了按鍵。
  至於控制項尚未處理的按鍵,應該傳回呼叫基底類別的 ProcessCmdKey 方法所產生的結果。控制項很少需要覆寫這個方法。
  */
  protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
  {
   // return true to discontinue processing
   if(StdInputMask == InputMaskType.None)
    return base.ProcessCmdKey(ref msg, keyData);

   // NOTES:
   // 1) break; causes warnings below
   // 2) m_caret tracks caret location, always the start of selected char
   int strt = base.SelectionStart;
   int len = base.SelectionLength;
   int end = strt + base.SelectionLength - 1;
   string s = base.Text;
   int p;
   
   // handle startup, runs once
   if(m_format[strt] != m_inpChar)
   {
    strt = Next(-1);
    len = 1;
   }

   switch(keyData)
   {
    case Keys.Left:
    case Keys.Up:
     p = Prev(strt);
     if(p != strt)
     {
      base.SelectionStart = p;
      base.SelectionLength = 1;
     }
     m_caret = p;
     return true;
    case Keys.Left | Keys.Shift:
    case Keys.Up | Keys.Shift:
     if((strt < m_caret) || (strt == m_caret && len <= 1))
     {
      // enlarge left
      p = Prev(strt);
      base.SelectionStart -= (strt - p);
      base.SelectionLength = len + (strt - p);
     }
     else
     {
      // shrink right
      base.SelectionLength = len - (end - Prev(end));
     }
     return true;
    case Keys.Right:
    case Keys.Down:
     p = Next(strt);
     if(p != strt)
     {
      base.SelectionStart = p;
      base.SelectionLength = 1;
     }
     m_caret = p;
     return true;
    case Keys.Right | Keys.Shift:
    case Keys.Down | Keys.Shift:
     if(strt < m_caret)
     {
      // shrink left
      p = Next(strt);
      base.SelectionStart += (p - strt);
      base.SelectionLength = len - (p - strt);
     }
     else if(strt == m_caret)
     {
      // enlarge right
      p = Next(end);
      base.SelectionLength = len + (p - end);
     }
     return true;
    case Keys.Delete:
     // delete selection, replace with input format
     base.Text = s.Substring(0, strt) + m_format.Substring(strt, len) + s.Substring(strt + len);
     base.SelectionStart = strt;
     base.SelectionLength = 1;
     m_caret = strt;
     return true;
    case Keys.Home:
     base.SelectionStart = Next(-1);
     base.SelectionLength = 1;
     m_caret = base.SelectionStart;
     return true;
    case Keys.Home | Keys.Shift:
     if(strt <= m_caret && len <= 1)
     {
      // enlarge left
      p = Next(-1);
      base.SelectionStart -= (strt - p);
      base.SelectionLength = len + (strt - p);
     }
     else
     {
      // shrink right
      p = Next(-1);
      base.SelectionStart = p;
      base.SelectionLength = (m_caret - p) + 1;
     }
     return true;
    case Keys.End:
     base.SelectionStart = Prev(base.MaxLength);
     base.SelectionLength = 1;
     m_caret = base.SelectionStart;
     return true;
    case Keys.End | Keys.Shift:
     if(strt < m_caret)
     {
      // shrink left
      p = Prev(base.MaxLength);
      base.SelectionStart = m_caret;
      base.SelectionLength = (p - m_caret + 1);
     }
     else if(strt == m_caret)
     {
      // enlarge right
      p = Prev(base.MaxLength);
      base.SelectionLength = len + (p - end);
     }
     return true;
    case Keys.V | Keys.Control:
    case Keys.Insert | Keys.Shift:
     // attempt paste
     // NOTES:
     // 1) Paste is likely to have literals since it must be copied from somewhere
     IDataObject iData = Clipboard.GetDataObject();

     // assemble new text
     string t = s.Substring(0, strt)
      + (string)iData.GetData(DataFormats.Text)
      + s.Substring(strt + len);

     // check if data to be pasted is convertable to inputType
     if(IsValidString(t))
      base.Text = t;
     else if(m_errInvalid)
      throw new ApplicationException("Input String Does Not Match Input Mask");

     return true;
    default:
     return base.ProcessCmdKey(ref msg, keyData);
   }
  }

  /// <summary>
  /// 重寫.發生於當控制項擁有焦點,且按下按鍵時。
  /// </summary>
  /// <param name="e">包含事件資料的 KeyPressEventArgs 。</param>
  protected override void OnKeyPress(KeyPressEventArgs e)
  {
   if(StdInputMask == InputMaskType.None)
   {
    base.OnKeyPress(e);
    return;
   }

   int strt = base.SelectionStart;
   int len = base.SelectionLength;
   int p;

   // Handle Backspace -> replace previous char with inpchar and select
   if(e.KeyChar == 0x08)
   {
    string s = base.Text;
    p = Prev(strt);
    if(p != strt)
    {
     base.Text = s.Substring(0, p) + m_inpChar.ToString() + s.Substring(p + 1);
     base.SelectionStart = p;
     base.SelectionLength = 1;
     
    }
    m_caret = p;
    e.Handled = true;
    return;
   }
   
   // handle startup, runs once
   if(m_format[strt] != m_inpChar)
   {
    strt = Next(-1);
    len = 1;
   }

   // update display if valid char entered
   if(IsValidChar(e.KeyChar, (int)m_posNdx[strt]))
   {
    // assemble new text
    string t = "";
    t = base.Text.Substring(0, strt);
    t += e.KeyChar.ToString();

    if(strt + len != base.MaxLength)
    {
     t += m_format.Substring(strt + 1, len - 1);
     t += base.Text.Substring(strt + len);
    }
    else
     t += m_format.Substring(strt + 1);

    base.Text = t;

    // select next input char
    strt = Next(strt);
    base.SelectionStart = strt;
    m_caret = strt;
    base.SelectionLength = 1;
   }
   e.Handled = true;
  }

  /// <summary>
  /// 返回指定位置的上一個位置標記符的位置
  /// </summary>
  /// <param name="startPos">指定位置</param>
  /// <returns>上一個位置標記符的位置</returns>
  private int Prev(int startPos)
  {
   // return previous input char position
   // returns current position if no input chars to the left
   // caller must decide what to do with this
   int strt = startPos;
   int ret = strt;

   while(strt > 0)
   {
    strt--;
    if(m_format[strt] == m_inpChar)
     return strt;
   }
   return ret;   
  }
  
  /// <summary>
  /// 返回指定位置的下一個位置標記符的位置
  /// </summary>
  /// <param name="startPos">指定位置</param>
  /// <returns>下一個位置標記符的位置</returns>
  private int Next(int startPos)
  {
   // return next input char position
   // returns current position if no input chars to the left
   // caller(訪問) must(必須) decide(判定) what to do with this
   int strt = startPos;
   int ret = strt;
   
   while(strt < base.MaxLength - 1)
   {
    strt++;
    if(m_format[strt] == m_inpChar)
     return strt;
   }

   return ret;   
  }
  #endregion
 }
}


posted on 2007-08-24 17:33  鸟人  阅读(393)  评论(0编辑  收藏  举报

导航