首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# TextBox 限制僅能輸入數字:TextChanged 事件過濾非數值字元完整解析

     編輯:關於C語言

需求背景

在表單驗證、資料錄入介面中,常需限制 TextBox 僅接受數字(0-9)。本文分析常見作法優缺點,並提供健全的 TextChanged 過濾實作,解決中文輸入法、貼上、拖曳等繞過鍵盤事件的問題。

常見作法比較

方法優點缺點
KeyPress 攔截簡單、即時無法阻擋貼上、拖曳、中文輸入法確認、自動完成
KeyDown + e.SuppressKeyPress可攔截功能鍵同樣無法處理非鍵盤輸入
MaskedTextBox內建遮罩彈性低、體驗差、不支援動態長度
TextChanged 過濾攔截所有文字變更來源需處理游標位置、避免無限迴圈
WPF PreviewTextInput + 資料繫結驗證架構乾淨僅限 WPF

健全的 TextChanged 實作

private void TxtNumeric_TextChanged(object sender, EventArgs e)
{
    var tb = (TextBox)sender;
    string text = tb.Text;
    
    // 快速路徑:已為純數字或空字串
    if (string.IsNullOrEmpty(text) || IsAllDigits(text)) return;

    // 記錄游標位置(相對於過濾後文字)
    int selectionStart = tb.SelectionStart;
    int digitsBeforeCursor = CountDigits(text, 0, selectionStart);

    // 過濾非數字
    string filtered = new string(text.Where(char.IsDigit).ToArray());
    
    // 避免觸發遞迴:暫時移除事件
    tb.TextChanged -= TxtNumeric_TextChanged;
    tb.Text = filtered;
    tb.TextChanged += TxtNumeric_TextChanged;

    // 還原游標:盡量靠近原位置
    tb.SelectionStart = Math.Min(digitsBeforeCursor, filtered.Length);
}

private bool IsAllDigits(string s) => s.All(char.IsDigit);

private int CountDigits(string s, int start, int count)
{
    int end = Math.Min(start + count, s.Length);
    int c = 0;
    for (int i = start; i < end; i++) if (char.IsDigit(s[i])) c++;
    return c;
}

關鍵細節解析

  1. 移除/重新掛載事件:防止 tb.Text = filtered 再次觸發 TextChanged 造成無限迴圈或堆疊溢位
  2. 游標位置還原:計算游標前的數字個數,過濾後定位到對應位置,避免游標跳到尾端
  3. 使用 char.IsDigit:支援全形數字(0-9),若僅要 ASCII 可改 c >= '0' && c <= '9'
  4. 效能考量:短字串 LINQ 開銷可接受;長字串可改用 StringBuilder 或 char 陣列 就地修改

進階版:支援負號、小數點、千分位

private void TxtDecimal_TextChanged(object sender, EventArgs e)
{
    var tb = (TextBox)sender;
    string text = tb.Text;
    if (string.IsNullOrEmpty(text)) return;

    int selStart = tb.SelectionStart;
    int validCharsBefore = CountValidChars(text, 0, selStart);

    // 允許:數字、單一小數點、開頭負號
    var sb = new StringBuilder();
    bool decimalSeen = false;
    for (int i = 0; i < text.Length; i++)
    {
        char c = text[i];
        if (char.IsDigit(c)) sb.Append(c);
        else if (c == '.' && !decimalSeen && sb.Length > 0) { sb.Append(c); decimalSeen = true; }
        else if (c == '-' && i == 0 && sb.Length == 0) sb.Append(c);
    }

    string filtered = sb.ToString();
    if (filtered == text) return;

    tb.TextChanged -= TxtDecimal_TextChanged;
    tb.Text = filtered;
    tb.TextChanged += TxtDecimal_TextChanged;
    tb.SelectionStart = Math.Min(validCharsBefore, filtered.Length);
}

private int CountValidChars(string s, int start, int count)
{
    int end = Math.Min(start + count, s.Length);
    int c = 0, dec = 0;
    for (int i = start; i < end; i++)
    {
        char ch = s[i];
        if (char.IsDigit(ch) || (ch == '.' && dec++ == 0) || (ch == '-' && i == 0)) c++;
    }
    return c;
}

替代方案:NTextBox 自訂控制項

封裝為可重用元件:

public class NumericTextBox : TextBox
{
    public bool AllowDecimal { get; set; } = false;
    public bool AllowNegative { get; set; } = false;

    protected override void OnTextChanged(EventArgs e)
    {
        // 同前邏輯...
        base.OnTextChanged(e);
    }
}

WPF 版本(附錄)

<TextBox PreviewTextInput="OnPreviewTextInput" 
         Text="{Binding Amount, UpdateSourceTrigger=PropertyChanged, 
              ValidatesOnDataErrors=True}" />

// Code-behind
void OnPreviewTextInput(object sender, TextCompositionEventArgs e)
{
    e.Handled = !Regex.IsMatch(e.Text, @"^\d+$"); // 僅允許數字
}

// ViewModel 驗證
public string Amount
{
    get => _amount;
    set
    {
        if (Regex.IsMatch(value, @"^\d*\.?\d*$")) _amount = value;
        // else 保持舊值或顯示錯誤
    }
}

總結建議

  • WinForms:使用 TextChanged 過濾 + 事件暫停 + 游標還原,最健全
  • 需求簡單:MaskedTextBox 或 KeyPress 足夠
  • WPF/UWP:優先用資料繫結 + 驗證屬性 + PreviewTextInput
  • 無障礙:提供視覺提示(紅框、Tooltip)告知輸入限制
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved