[.Net] 自製數字TextBox

  • 23820
  • 0
  • .Net
  • 2017-03-27

本來想用MaskTextBox,但發現障礙太多,還是用TextBox好作
2010/9/29 考量處理效能和數值範圍,修正使用型別由decimal改為double

2010/9/29 考量處理效能和數值範圍,修正使用型別由decimal改為double

繼承.Net內建TextBox再拿來繼承,增加以下:

key-in或貼上同時排除不合格文字或數字
一些不適合數字的屬性和Text改唯讀,
增加屬性: 最大,
                最小值,
                預設值,
                整數位數上限,
                小數位數上限,
                Value
                Format(點進NumBox時顯示原數字,離開後顯示格式化數字),
                允許空值,
                錯誤嗶聲,
                輸入值型別{所有數值型別}台灣是主權獨立的國家
增加事件:OnValueChanged

public class SanNumBox : 繼承TextBox
{
    public SanNumBox()
    {//建構子直接對變數存取就好了~
        textType = typeof(Int32);
        min = Convert.ToDouble(Int32.MinValue);//最小
        max = Convert.ToDouble(Int32.MaxValue);//最大
        def = 0;//預設
        DecLen = 2;//小數幾位數
        int1 = 10;//整數幾位數;至少1位
        AllowNull = true;
        HasComma = false;
        this.TextAlign = HorizontalAlignment.Right;
    }

    #region Properties
    private const char Hyphen = '-';
    private const char Dot = '.';
    private const string CommaFormat = "#,##0.######";
    private const string O = "0";

    #region Hidden base不可修改
    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [ReadOnly(true)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    public override bool Multiline
    { get { return base.Multiline; } }

    //繼承TextBox時建立的
    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [ReadOnly(true)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    public override bool MaxLenByByte
    { get { return base.MaxLenByByte; } }

    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    public new bool WordWrap
    { get { return base.WordWrap; } }

    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    [ReadOnly(true)]
    public override int MaxLength
    { get { return base.MaxLength; } }

    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    [ReadOnly(true)]
    public new string[] Lines
    { get { return base.Lines; } }
    #endregion

    #region overried 可改
    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [ReadOnly(true)]
    public override string Text
    {
        get { return base.Text; }
        set { this.Value = value; }
    }

    [DefaultValue(HorizontalAlignment.Right)]
    public new HorizontalAlignment TextAlign
    {
        set { base.TextAlign = value; }
        get { return base.TextAlign; }
    }
    #endregion

    #region 自訂Field 可修改
    #region Max
    private Double max;
    /// <summary>
    /// 最大值(上限值)
    /// </summary>
    [Description(@"最大值(上限值)")]
    [Category("Customize")]
    [EditorBrowsable(EditorBrowsableState.Always)]
    public Double MaxValue
    {
        set
        {
            OverflowException exc = this.SetMax(value);
            OverflowException exc2 = this.SetDefault(this.DefaultValue);
            this.Value = this.Value;//有例外直接丟
            if (exc != null) { throw exc; }
            if (exc2 != null) { throw exc2; }
        }
        get { return max; }
    }

    /// <summary>
    /// 設最大值
    /// </summary>
    /// <param name="value">設定值</param>
    /// <param name="throwExc">有例外是否放在return值</param>
    /// <returns></returns>
    private OverflowException SetMax(Double value)
    {
        max = Math.Min(this.GetMax(), value);
        if (max < min) { return new OverflowException("MaxValue小於MinValue"); ; }
        if (max == value) { return null; }
        return new OverflowException("MaxValue超出該設定型態之最大值");
    }
    #endregion

    #region Min
    private Double min;
    /// <summary>
    /// 最小值(下限值)
    /// </summary>
    [Description(@"最小值(下限值)")]
    [Category("Customize")]
    [EditorBrowsable(EditorBrowsableState.Always)]
    public Double MinValue
    {
        set
        {
            OverflowException exc = this.SetMin(value);
            OverflowException exc2 = this.SetDefault(this.DefaultValue);
            this.Value = this.Value;//有例外直接丟
            if (exc != null) { throw exc; }
            if (exc2 != null) { throw exc2; }
        }
        get
        { return min; }
    }

    /// <summary>
    /// 設最小值
    /// </summary>
    /// <param name="value">設定值</param>
    /// <param name="throwExc">有例外是否放在return值</param>
    /// <returns></returns>
    private OverflowException SetMin(Double value)
    {
        min = Math.Max(this.GetMin(), value);
        if (max < min) { return new OverflowException("MinValue大於MaxValue"); ; }
        if (min == value) { return null; }
        return new OverflowException("MinValue超出該設定型態之最小值");
    }
    #endregion

    #region default
    private Double def;
    /// <summary>
    /// 預設值
    /// </summary>
    [Description(@"預設值")]
    [Category("Customize")]
    [EditorBrowsable(EditorBrowsableState.Always)]
    public Double DefaultValue
    {
        set
        {//預設值不超過上下限
            OverflowException exc = this.SetDefault(value);
            if (exc != null) { throw exc; }
        }
        get
        { return def; }
    }

    /// <summary>
    /// 設default值
    /// </summary>
    /// <param name="value">設定值</param>
    /// <param name="throwExc">有例外是否放在return值</param>
    /// <returns></returns>
    private OverflowException SetDefault(Double value)
    {
        def = Math.Min(Math.Max(value, this.MinValue), this.MaxValue);
        if (def == value) { return null; }
        return new OverflowException("DefaultValue超出所設定之最大或最小值");
    }
    #endregion

    #region 值
    private object m_value;//值
    /// <summary>
    /// 值
    /// </summary>
    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public object Value
    {
        set
        {
            Exception exc = this.SetValue(value);
            if (exc != null) { throw exc; }
        }
        get
        { return m_value; }
    }

    /// <summary>
    /// 設value值
    /// </summary>
    /// <param name="value">設定值</param>
    /// <returns></returns>
    private Exception SetValue(object value)
    {
        Exception exc = null;
        object odValue = this.m_value;
        //空值或是只輸入"-"
        if (!Information.IsNumeric(value))
        {
            if (this.AllowNull) { this.m_value = null; }
            else { this.m_value = Convert.ChangeType(this.DefaultValue, TextType); }
        }
        else
        {
            Double dec = Convert.ToDouble(value);
            if (dec < this.MinValue || dec > this.MaxValue)
            #region 超過上下限
            {
                //依設定處理
                switch (this.OverFlowToDo)
                {
                    case OverFlow.returnDefault:
                        m_value = Convert.ChangeType(this.DefaultValue, TextType);
                        break;
                    case OverFlow.returnLimit:
                        m_value = Convert.ChangeType(Math.Min(Math.Max(dec, this.MinValue), this.MaxValue), TextType);
                        break;
                    case OverFlow.returnNull:
                        m_value = null;
                        break;
                    case OverFlow.throwException:
                        exc = new OverflowException("Value超過設定之最大值或最小值");
                        break;
                }
            }
            #endregion
            else
            { m_value = Convert.ChangeType(Convert.ToDouble(value), TextType); }
        }

        ShowText(IsFocused);
        //觸發ValueChanged事件
        if (this.m_value != odValue)
        { this.OnValueChanged(new EventArgs()); }
        return exc;
    }
    #endregion

    #region Mask
    /// <summary>
    /// 小數幾位數
    /// </summary>
    [Description(@"小數幾位數")]
    [Category("Mask")]
    [EditorBrowsable(EditorBrowsableState.Always)]
    public byte DecLen
    { set; get; }

    private byte int1;
    /// <summary>
    /// 整數幾位數
    /// </summary>
    [Description(@"整數幾位數")]
    [Category("Mask")]
    [EditorBrowsable(EditorBrowsableState.Always)]
    public byte IntLen
    {
        set
        { int1 = Math.Min(Byte.MaxValue, value); }
        get
        { return int1; }
    }

    /// <summary>
    /// 輸入時是否要show千分位
    /// </summary>
    [Description(@"輸入時是否要show千分位")]
    [Category("Mask")]
    [DefaultValue(false)]
    [EditorBrowsable(EditorBrowsableState.Always)]
    public bool HasComma
    { set; get; }
    #endregion

    /// <summary>
    /// 超出上下限時
    /// </summary>
    [Description(@"超出上下限時")]
    [Category("Customize")]
    [Bindable(true)]
    [Browsable(true)]
    [DefaultValue(OverFlow.throwException)]
    [EditorBrowsable(EditorBrowsableState.Always)]
    public OverFlow OverFlowToDo//OverFlow是Enum,寫在code最下方
    { set; get; }

    /// <summary>
    /// FormatString
    /// </summary>
    [Description(@"FormatString")]
    [Category("Customize")]
    [Bindable(true)]
    [Browsable(true)]
    [DefaultValue("")]
    [EditorBrowsable(EditorBrowsableState.Always)]
    public string Format
    { set; get; }

    /// <summary>
    /// 允許key空值
    /// </summary>
    [Description(@"允許key空值")]
    [Category("Customize")]
    [Bindable(true)]
    [Browsable(true)]
    [DefaultValue(true)]
    [EditorBrowsable(EditorBrowsableState.Always)]
    public bool AllowNull
    { set; get; }

    private Type textType;
    /// <summary>
    /// 輸入值型別
    /// </summary>
    [Description(@"輸入值型別")]
    [Category("Customize")]
    [Bindable(true)]
    [Browsable(true)]
    [DefaultValue(typeof(int))]
    [EditorBrowsable(EditorBrowsableState.Always)]
    public Type TextType
    {
        set
        {
            //public static bool IsNum(Type ty)
            //{
            //if (ty == typeof(DateTime)) { return false; }
            //if (ty.GetField("MinValue") == null || ty.GetField("MaxValue") == null) { return false; }
            //return ty.IsValueType;
            //} 另外有提出來寫
            if (IsNum(value))
            {
                this.textType = value;//改型別之後,要接著改最大最小和預設值
                OverflowException exc = this.SetMax(MaxValue);
                OverflowException exc1 = this.SetMin(MinValue);
                OverflowException exc2 = this.SetDefault(this.DefaultValue);
                this.Value = this.Value;//有例外直接丟
                if (exc != null) { throw exc; }
                if (exc1 != null) { throw exc1; }
                if (exc2 != null) { throw exc2; }
            }
        }
        get { return this.textType; }
    }
    #endregion
    #endregion

    #region Events
    #region ValueChanged Events
    /// <summary>
    /// ValueChange事件
    /// </summary>
    [Description(@"值變更時")]
    [Category("Focus")]
    [EditorBrowsable(EditorBrowsableState.Advanced)]
    public event EventHandler ValueChanged;

    protected virtual void OnValueChanged(EventArgs e)
    { if (this.ValueChanged != null) { ValueChanged(this, e); } }
    #endregion

    /// <summary>
    /// 是否被focus.
    /// enter:true, leave:false, validating取消: true, validated判斷為true時,格式化Text,
    /// enter判斷為false時,格式化Text
    /// </summary>
    private bool IsFocused = false;
    protected override void OnEnter(EventArgs e)
    {
        if (!IsFocused)
        {
            IsFocused = true;
            ShowText(true);
        }
        base.OnEnter(e);
    }

    #region 編輯
    /// <summary>
    /// 不要作動作
    /// </summary>
    private bool dontSet = false;

    /// <summary>
    /// 只是個負號
    /// </summary>
    /// <returns></returns>
    private bool JustNSign(string str)
    { return (str.TrimStart(Hyphen) == string.Empty); }

    protected override void OnTextChanged(EventArgs e)
    {
        if (dontSet || JustNSign(base.Text) || base.Text.EndsWith(Dot.ToString()) ||
            (base.Text == string.Empty && this.Focused)) { return; }

        int idx = this.SelectionStart;//原游標停留位置
        idx = base.Text.Length - idx;//倒過來數數
        string str = base.Text;//原值
        Exception ex = SetValue(str);
        if (ex != null) { this.beep(); }
        //在繼承TextBox已有寫
        ///// <summary>
        ///// 嗶一聲
        ///// </summary>
        //protected virtual void beep()
        //{ if (this.BeepOnError) { Interaction.Beep(); } }

        if (base.Text != str)
        { this.SelectionStart = Math.Max(0, this.Text.Length - idx); }
    }

    #region keyboad事件
    /// <summary>
    /// 表示按著不放
    /// </summary>
    public bool LongClick
    { set; get; }

    /// <summary>
    /// 檢查每個key-in
    /// </summary>
    /// <param name="e"></param>
    protected override void OnKeyPress(KeyPressEventArgs e)
    {
        if (!onpaste && LongClick)
        {//因code觸發,不處理 
            e.Handled = true;
            return;
        }
        try
        {
            if (ReadOnly) return; //唯讀不處理 
            if (char.IsControl(e.KeyChar))
            {//倒退鍵可以一直按著
                if (e.KeyChar != ValueUtil.back) { LongClick = true; }
                return;
            } //Backspace, Enter...等控制鍵不處理 
            if (e.KeyChar <= '9' && e.KeyChar >= '0')
            #region 判斷整數和小數有沒有打超過
            {
                int idx = base.Text.IndexOf(Dot);//小數點位置
                if (idx < 0)//沒打小數
                {
                    if (base.Text.Trim(Hyphen).Length
                        - this.SelectedText.Trim(Hyphen).Length < IntLen) { return; }
                }
                else//有打小數的話
                {
                    if (this.SelectionStart <= idx)//打在整數位置
                    {
                        if (base.Text.StartsWith(Hyphen.ToString())) { idx--; }//負號不算入整數長度
                        if (idx < IntLen) { return; }//已輸入整數比設的整數長度少:可再輸入
                    }
                    //且已輸入小數長度base.Text.Length-1-idx比設的小數位少:可再輸入
                    else if (base.Text.Length - 1 - idx < DecLen) { return; }//打在小數位置
                }
            }
            #endregion
            //若能key負號,負號可以key在開頭
            else if (this.MinValue < 0 && this.SelectionStart == 0 && e.KeyChar == Hyphen) { return; }
            else if (this.DecLen > 0 && e.KeyChar == Dot)
            #region 若能key小數,只能打一個小數點
            {
                int idx = base.Text.IndexOf(Dot);//小數點位置
                if (idx < 0) { return; }//未輸入
                else
                {
                    if (this.SelectionStart == idx)
                    {//若輸入的位置後就有小數點, 往後移一位
                        this.SelectionStart = idx + 1;
                        e.Handled = true;
                        return;
                    }
                    //else不能有第2個小數點
                }
            }
            #endregion
            #region 其它都不可key
            if (!onpaste) { LongClick = true; }
            e.Handled = true;
            beep();
            #endregion
        }
        finally
        { if (!e.Handled) { base.OnKeyPress(e); } }
    }

    protected override void OnKeyUp(KeyEventArgs e)
    {
        LongClick = false;//放開按鍵
        base.OnKeyUp(e);
    }
    #endregion

    /// <summary>
    /// 不觸發keypass,此方法在繼承TextBox時新建的
    /// </summary>
    private bool onpaste = false;
    protected override void OnPasteText(ClipboardEventArgs e)
    {
        onpaste = true;
        e.Cancel = true;
        base.SendCharFromClipboard();
        onpaste = false;
    }

    #region select All
    protected override void OnKeyDown(KeyEventArgs e)
    {
        base.OnKeyDown(e);
        if (!e.Handled && e.Control && e.KeyCode == Keys.A)//ctl+a 全選
        { this.SelectAll(); }
    }
    #endregion
    #endregion

    #region 離開
    protected override void OnLeave(EventArgs e)
    {
        IsFocused = false;
        base.OnLeave(e);
    }

    /// <summary>
    /// 若不允許null不可離開
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected override void OnValidating(CancelEventArgs e)
    {
        if (base.Text == string.Empty)
        {
            if (AllowNull)
            {
                Exception exc = this.SetValue(null);
                if (exc != null) { beCancel(e); }
            }
            else
            { beCancel(e); }
        }
        if (!e.Cancel) { base.OnValidating(e); }
    }

    private void beCancel(CancelEventArgs e)
    {
        beep();
        e.Cancel = true;
        IsFocused = true;
    }

    protected override void OnValidated(EventArgs e)
    {
        base.OnValidated(e);
        if (!IsFocused) { ShowText(false); }
    }
    #endregion
    #endregion

    #region Private Methods
    #region 顯示文字
    /// <summary>
    /// 顯示文字格式化
    /// </summary>
    /// <param name="focus"></param>
    private void ShowText(bool focus)
    {
        dontSet = true;
        if (this.Value == null)
        { base.Text = string.Empty; }
        else if (focus)
        { base.Text = Convert.ToDouble(this.Value).ToString(formatNum()); }
        else
        { base.Text = Convert.ToDouble(this.Value).ToString(this.Format); }
        dontSet = false;
    }

    /// <summary>
    /// 依設定能輸入的整數和小數與是否要千分位來格式化數字
    /// </summary>
    /// <returns></returns>
    private string formatNum()
    {
        return (this.HasComma ? CommaFormat : O)
                 + (this.DecLen > 0 ? Dot.ToString().PadRight(this.DecLen + 1, '#') : string.Empty);
    }
    #endregion

    #region 取其type內建值
    /// <summary>
    /// 取得最大值
    /// </summary>
    /// <returns></returns>
    private Double GetMax()
    { try { return GetFieldValue("MaxValue"); } catch { return Double.MaxValue; } }

    /// <summary>
    /// 取得最小值
    /// </summary>
    /// <returns></returns>
    private Double GetMin()
    { try { return GetFieldValue("MinValue"); } catch { return Double.MinValue; } }

    /// <summary>
    /// 取得變數值
    /// </summary>
    /// <param name="FieldName"></param>
    /// <returns></returns>
    private Double GetFieldValue(string FieldName)
    { return Convert.ToDouble(TextType.GetField(FieldName).GetValue(null)); }
    #endregion
    #endregion
}

/// <summary>
/// 超出上下限時所作的動作
/// </summary>
public enum OverFlow
{
    /// <summary>
    /// 丟出例外
    /// </summary>
    throwException,
    /// <summary>
    /// 回傳null值
    /// </summary>
    returnNull,
    /// <summary>
    /// 回傳預設值
    /// </summary>
    returnDefault,
    /// <summary>
    /// 回傳上下限值
    /// </summary>
    returnLimit
}

 

Taiwan is a country. 臺灣是我的國家