仅包含数字的 C# 文本框

C# Textbox with numbers only

我只想在文本框中对数字进行数学处理,为此我使用“System.Text.RegularExpressions”。

我现在使用的掩码是"^ [0-9] + \,? [0-9] * $"。我不能在这个使用中输入负数

如何创建仅支持以下数字格式的掩码。

它只适用于所有地方的整数。不幸的是,我找不到可以进行数学运算的数字格式。

我的代码

public static class Masking
{
    
    
    private static readonly DependencyPropertyKey _maskExpressionPropertyKey = DependencyProperty.RegisterAttachedReadOnly("MaskExpression",
        typeof(Regex),
        typeof(Masking),
        new FrameworkPropertyMetadata());

    /// <summary>
    /// Identifies the <see cref="Mask"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty MaskProperty = DependencyProperty.RegisterAttached("Mask",
        typeof(string),
        typeof(Masking),
        new FrameworkPropertyMetadata(OnMaskChanged));

    /// <summary>
    /// Identifies the <see cref="MaskExpression"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty MaskExpressionProperty = _maskExpressionPropertyKey.DependencyProperty;

    /// <summary>
    /// Gets the mask for a given <see cref="TextBox"/>.
    /// </summary>
    /// <param name="textBox">
    /// The <see cref="TextBox"/> whose mask is to be retrieved.
    /// </param>
    /// <returns>
    /// The mask, or <see langword="null"/> if no mask has been set.
    /// </returns>
    public static string GetMask(TextBox textBox)
    {
        if (textBox == null)
        {
            throw new ArgumentNullException("textBox");
        }

        return textBox.GetValue(MaskProperty) as string;
    }

    /// <summary>
    /// Sets the mask for a given <see cref="TextBox"/>.
    /// </summary>
    /// <param name="textBox">
    /// The <see cref="TextBox"/> whose mask is to be set.
    /// </param>
    /// <param name="mask">
    /// The mask to set, or <see langword="null"/> to remove any existing mask from <paramref name="textBox"/>.
    /// </param>
    public static void SetMask(TextBox textBox, string mask)
    {
        if (textBox == null)
        {
            throw new ArgumentNullException("textBox");
        }

        textBox.SetValue(MaskProperty, mask);
    }

    /// <summary>
    /// Gets the mask expression for the <see cref="TextBox"/>.
    /// </summary>
    /// <remarks>
    /// This method can be used to retrieve the actual <see cref="Regex"/> instance created as a result of setting the mask on a <see cref="TextBox"/>.
    /// </remarks>
    /// <param name="textBox">
    /// The <see cref="TextBox"/> whose mask expression is to be retrieved.
    /// </param>
    /// <returns>
    /// The mask expression as an instance of <see cref="Regex"/>, or <see langword="null"/> if no mask has been applied to <paramref name="textBox"/>.
    /// </returns>
    public static Regex GetMaskExpression(TextBox textBox)
    {
        if (textBox == null)
        {
            throw new ArgumentNullException("textBox");
        }

        return textBox.GetValue(MaskExpressionProperty) as Regex;
    }

    private static void SetMaskExpression(TextBox textBox, Regex regex)
    {
        textBox.SetValue(_maskExpressionPropertyKey, regex);
    }

    private static void OnMaskChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        var textBox = dependencyObject as TextBox;
        var mask = e.NewValue as string;
        textBox.PreviewTextInput -= textBox_PreviewTextInput;
        textBox.PreviewKeyDown -= textBox_PreviewKeyDown;
        textBox.GotFocus += textBox_GotFocus;
        DataObject.RemovePastingHandler(textBox, Pasting);

        if (mask == null)
        {
            textBox.ClearValue(MaskProperty);
            textBox.ClearValue(MaskExpressionProperty);
        }
        else
        {
            textBox.SetValue(MaskProperty, mask);
            SetMaskExpression(textBox, new Regex(mask, RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace));
            textBox.PreviewTextInput += textBox_PreviewTextInput;
            textBox.PreviewKeyDown += textBox_PreviewKeyDown;
            textBox.GotFocus += textBox_GotFocus;
            DataObject.AddPastingHandler(textBox, Pasting);
        }
    }

    private static void textBox_GotFocus(object sender, RoutedEventArgs e)
    {
        if(Globals.KeyboardActive.IsChecked == true)
        {
            Process process = Process.Start(new ProcessStartInfo(
        ((Environment.GetFolderPath(Environment.SpecialFolder.System) + @"\osk.exe"))));
        }
    }


    private static void textBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        var textBox = sender as TextBox;
        var maskExpression = GetMaskExpression(textBox);

        if (maskExpression == null)
        {
            return;
        }

        var proposedText = GetProposedText(textBox, e.Text);

        if (!maskExpression.IsMatch(proposedText))
        {
            e.Handled = true;
        }
    }

    private static void textBox_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        var textBox = sender as TextBox;
        var maskExpression = GetMaskExpression(textBox);

        if (maskExpression == null)
        {
            return;
        }

        if (e.Key == Key.Space)
        {
            var proposedText = GetProposedText(textBox, " ");

            if (!maskExpression.IsMatch(proposedText))
            {
                e.Handled = true;
            }
        }


        if (e.Key == Key.Enter)
        {
            var proposedText = GetProposedText(textBox, " ");

            if (!maskExpression.IsMatch(proposedText))
            {
                e.Handled = true;
            }
        }
    }

    private static void Pasting(object sender, DataObjectPastingEventArgs e)
    {
        var textBox = sender as TextBox;
        var maskExpression = GetMaskExpression(textBox);

        if (maskExpression == null)
        {
            return;
        }

        if (e.DataObject.GetDataPresent(typeof(string)))
        {
            var pastedText = e.DataObject.GetData(typeof(string)) as string;
            var proposedText = GetProposedText(textBox, pastedText);

            if (!maskExpression.IsMatch(proposedText))
            {
                e.CancelCommand();
            }
        }
        else
        {
            e.CancelCommand();
        }
    }

    private static string GetProposedText(TextBox textBox, string newText)
    {
        var text = textBox.Text;

        if (textBox.SelectionStart != -1)
        {
            text = text.Remove(textBox.SelectionStart, textBox.SelectionLength);
        }

        text = text.Insert(textBox.CaretIndex, newText);

        return text;
    }
}

Xaml代码

<TextBox Name="Text" VerticalAlignment="Top" HorizontalAlignment="Left"
                     Foreground="{DynamicResource ForegroundColor}" Height="25" Width="90"
                     materialDesign:TextFieldAssist.UnderlineBrush="{DynamicResource UnderlineColor}"
                     materialDesign:HintAssist.Foreground="#244886"
                     AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto"
                     materialDesign:HintAssist.Hint="Elevetion Angle" Margin="172,15,0,0"
                         materialDesign:TextFieldAssist.SuffixText="°"
                             b:Masking.Mask="^-?[0-9]+(?:\,[0-9]+)?$"
                             LostFocus="textboxsLostFocus"/>

我不推荐正则表达式来完成这个简单的任务。使用简单字符串比较时的性能要好得多。

一般情况下应该使用double.TryParse()来判断一个字符串是否为纯数值。

以下示例扩展 TextBox 并覆盖 OnTextInput 以取消非数字输入。过滤器允许使用单个小数点分隔符。它是 dynamic/customizable,因为在比较当前分隔符或其他数字符号时,它取决于当前文化的 NumberFormatInfo

class NumericTextBox : TextBox
{
  #region Overrides of TextBoxBase

  /// <inheritdoc />
  protected override void OnTextInput(TextCompositionEventArgs e)
  {
    CultureInfo culture = CultureInfo.CurrentCulture;
    if (TryHandleSpecialNonNumericCharacter(e, culture))
    {
      base.OnTextInput(e);
      return;
    }

    // Input is not a special character.
    // Cancel text input if non-numeric.
    e.Handled = !IsInputNumeric(e.Text, culture);

    base.OnTextInput(e);
  }

  private bool IsInputNumeric(string input, IFormatProvider culture) =>
    double.TryParse(input, NumberStyles.Number, culture, out _) ;

  private bool TryHandleSpecialNonNumericCharacter(TextCompositionEventArgs inputArgs, CultureInfo culture)
  {
    string input = inputArgs.Text;
    switch (input)
    {
      case var _ when input.Equals(culture.NumberFormat.NegativeSign, StringComparison.CurrentCultureIgnoreCase): 
      case var _ when input.Equals(culture.NumberFormat.PositiveSign, StringComparison.CurrentCultureIgnoreCase):
        break;

      // Allow single decimal separator
      case var _ when input.Equals(culture.NumberFormat.NumberDecimalSeparator, StringComparison.CurrentCultureIgnoreCase):
        inputArgs.Handled = this.Text.Contains(culture.NumberFormat.NumberDecimalSeparator);
        break;
      default:
        return false;
    }

    return true;
  }

  #endregion
}