如何在WPF中使TextBox只接受数字输入?

411

我希望能够接受数字和小数点,但不允许输入符号。

我查看了使用Windows Forms中NumericUpDown控件的示例,以及Microsoft提供的NumericUpDown自定义控件示例。但目前看来,无论WPF是否支持,NumericUpDown都无法提供我想要的功能。因为我的应用程序设计方式,没有人会愿意去操作那些箭头,它们在我的应用程序背景下没有任何实际意义。

所以我正在寻找一种简单的方法,使标准的WPF TextBox只接受我想要的字符。这是可能的吗?这样做是否切实可行?

33个回答

501

添加一个预览文本输入事件,像这样:<TextBox PreviewTextInput="PreviewTextInput" />

然后在里面设置e.Handled,如果不允许文本的话。 e.Handled = !IsTextAllowed(e.Text);

我在IsTextAllowed方法中使用一个简单的正则表达式来判断是否应该允许他们输入的内容。在我的情况下,我只想允许数字、点和破折号。

private static readonly Regex _regex = new Regex("[^0-9.-]+"); //regex that matches disallowed text
private static bool IsTextAllowed(string text)
{
    return !_regex.IsMatch(text);
}

如果您想防止粘贴不正确的数据,请连接 DataObject.Pasting 事件 DataObject.Pasting="TextBoxPasting",如此处所示(代码摘录)

// Use the DataObject.Pasting Handler 
private void TextBoxPasting(object sender, DataObjectPastingEventArgs e)
{
    if (e.DataObject.GetDataPresent(typeof(String)))
    {
        String text = (String)e.DataObject.GetData(typeof(String));
        if (!IsTextAllowed(text))
        {
            e.CancelCommand();
        }
    }
    else
    {
        e.CancelCommand();
    }
}

6
你的正则表达式不允许使用科学计数法(如1e5), 如果这很重要的话。 - Ron Warholic
20
请注意,此答案仅检查您输入的内容,因此您可以输入3-.3 - David Sykes
182
答案的重点不在于指定完美的正则表达式,而是展示如何使用WPF来过滤用户输入。 - Ray
37
[空间]不会触发PreviewTextInput事件。 - peterG
15
类似于double.TryParse()的方法可能会用相同数量的行来实现,并且更加灵活。 - Thomas Weller
显示剩余16条评论

256

事件处理程序正在预览文本输入。这里的正则表达式仅匹配非数字的文本输入,然后将其禁止输入到文本框中。

如果你只想要字母,则将正则表达式替换为[^a-zA-Z]

XAML

<TextBox Name="NumberTextBox" PreviewTextInput="NumberValidationTextBox"/>

XAML.CS文件

using System.Text.RegularExpressions;
private void NumberValidationTextBox(object sender, TextCompositionEventArgs e)
{
    Regex regex = new Regex("[^0-9]+");
    e.Handled = regex.IsMatch(e.Text);
}

1
事件处理程序是预览文本输入。在这里,正则表达式仅匹配非数字的文本输入,然后将其排除在输入框之外。如果您只想要字母,则将正则表达式替换为[^a-zA-Z]。 - Kishor
8
我更喜欢这个比实际答案更简短、更简单的回答。实际答案需要使用两种方法才能得到相同的结果。 - Revils
2
@Jagd 建议的答案是更好的关注点分离。但是你也可以在此验证中设置尽可能多的文本框。只需添加 PreviewTextInput="NumberValidationTextBox"。(就像其他答案一样!) - Revils
2
处理加减和小数点,请使用以下正则表达式: https://dev59.com/cmYr5IYBdhLWcg3wb5rc#13686481 - Hans
1
已为WPF中的TextBox处理了PreviewTextInput事件,但用户仍然可以在输入框中输入数字之间添加空格,并且PreviewTextInput事件也不会触发。 - Sandeep Jadhav
显示剩余6条评论

95

我使用了一些已经存在的内容,并且加入了自己的想法,使用一个行为(behavior)来实现,这样我就不必在很多视图中传递此代码…

public class AllowableCharactersTextBoxBehavior : Behavior<TextBox>
{
    public static readonly DependencyProperty RegularExpressionProperty =
         DependencyProperty.Register("RegularExpression", typeof(string), typeof(AllowableCharactersTextBoxBehavior),
         new FrameworkPropertyMetadata(".*"));
    public string RegularExpression
    {
        get
        {
            return (string)base.GetValue(RegularExpressionProperty);
        }
        set
        {
            base.SetValue(RegularExpressionProperty, value);
        }
    }

    public static readonly DependencyProperty MaxLengthProperty =
        DependencyProperty.Register("MaxLength", typeof(int), typeof(AllowableCharactersTextBoxBehavior),
        new FrameworkPropertyMetadata(int.MinValue));
    public int MaxLength
    {
        get
        {
            return (int)base.GetValue(MaxLengthProperty);
        }
        set
        {
            base.SetValue(MaxLengthProperty, value);
        }
    }

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.PreviewTextInput += OnPreviewTextInput;
        DataObject.AddPastingHandler(AssociatedObject, OnPaste);
    }

    private void OnPaste(object sender, DataObjectPastingEventArgs e)
    {
        if (e.DataObject.GetDataPresent(DataFormats.Text))
        {
            string text = Convert.ToString(e.DataObject.GetData(DataFormats.Text));

            if (!IsValid(text, true))
            {
                e.CancelCommand();
            }
        }
        else
        {
            e.CancelCommand();
        }
    }

    void OnPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
    {
        e.Handled = !IsValid(e.Text, false);
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.PreviewTextInput -= OnPreviewTextInput;
        DataObject.RemovePastingHandler(AssociatedObject, OnPaste);
    }

    private bool IsValid(string newText, bool paste)
    {
        return !ExceedsMaxLength(newText, paste) && Regex.IsMatch(newText, RegularExpression);
    }

    private bool ExceedsMaxLength(string newText, bool paste)
    {
        if (MaxLength == 0) return false;

        return LengthOfModifiedText(newText, paste) > MaxLength;
    }

    private int LengthOfModifiedText(string newText, bool paste)
    {
        var countOfSelectedChars = this.AssociatedObject.SelectedText.Length;
        var caretIndex = this.AssociatedObject.CaretIndex;
        string text = this.AssociatedObject.Text;

        if (countOfSelectedChars > 0 || paste)
        {
            text = text.Remove(caretIndex, countOfSelectedChars);
            return text.Length + newText.Length;
        }
        else
        {
            var insert = Keyboard.IsKeyToggled(Key.Insert);

            return insert && caretIndex < text.Length ? text.Length : text.Length + newText.Length;
        }
    }
}

以下是相关的视图代码:

<TextBox MaxLength="50" TextWrapping="Wrap" MaxWidth="150" Margin="4"
 Text="{Binding Path=FileNameToPublish}" >
     <interactivity:Interaction.Behaviors>
         <v:AllowableCharactersTextBoxBehavior RegularExpression="^[0-9.\-]+$" MaxLength="50" />
     </interactivity:Interaction.Behaviors>
</TextBox>

1
受这个很棒的解决方案的启发,我实现了一些改进。请在下面的线程中查看。 - Alex Klaus
2
嗨。我知道有点晚了,但我正在尝试实现这个,但我一直在遇到错误。我猜我缺少一些引用。除了创建类后默认的引用之外,还有其他应该键入的引用吗? - Offer
1
@Offer 是的,请确保在您的XAML窗口顶部包含xmlns:interactivity="http://schemas.microsoft.com/expression/2010/interactivity"。 - Ebsan
表达式现已过时。虽然这种方法很简洁,但它使用的代码已不再维护。 - Robert Baker
我喜欢这里的想法,但在评估整个字符串时失败了(例如100.00.000会起作用)。使用更复杂的正则表达式(例如^$?(\d{1,3},?(\d{3},?)*\d{3}(.\d{0,3})?|\d{1,3}(.\d{0})|\d{1,3}(.\d{1})|\d{1,3}(.\d{2})?)$$)在输入句点时也会失败。仍在努力编辑代码使其正常工作。 - Rogala
1
所以,如果您编辑IsValid函数以返回!ExceedsMaxLength(newText,paste)&& Regex.IsMatch(String.Concat(this.AssociatedObject.Text,newText),RegularExpression);那么这将评估整个字符串。顺便说一句 - 喜欢使用behaviors选项!! - Rogala

62

这是WilP答案的改进版。 我的改进包括:

  • 在按下删除退格按钮时行为更佳
  • 添加了EmptyValue属性,如果空字符串不合适
  • 修正了一些小错别字
/// <summary>
///     Regular expression for Textbox with properties: 
///         <see cref="RegularExpression"/>, 
///         <see cref="MaxLength"/>,
///         <see cref="EmptyValue"/>.
/// </summary>
public class TextBoxInputRegExBehaviour : Behavior<TextBox>
{
    #region DependencyProperties
    public static readonly DependencyProperty RegularExpressionProperty =
        DependencyProperty.Register("RegularExpression", typeof(string), typeof(TextBoxInputRegExBehaviour), new FrameworkPropertyMetadata(".*"));

    public string RegularExpression
    {
        get { return (string)GetValue(RegularExpressionProperty); }
        set { SetValue(RegularExpressionProperty, value); }
    }

    public static readonly DependencyProperty MaxLengthProperty =
        DependencyProperty.Register("MaxLength", typeof(int), typeof(TextBoxInputRegExBehaviour),
                                        new FrameworkPropertyMetadata(int.MinValue));

    public int MaxLength
    {
        get { return (int)GetValue(MaxLengthProperty); }
        set { SetValue(MaxLengthProperty, value); }
    }

    public static readonly DependencyProperty EmptyValueProperty =
        DependencyProperty.Register("EmptyValue", typeof(string), typeof(TextBoxInputRegExBehaviour), null);

    public string EmptyValue
    {
        get { return (string)GetValue(EmptyValueProperty); }
        set { SetValue(EmptyValueProperty, value); }
    }
    #endregion

    /// <summary>
    ///     Attach our behaviour. Add event handlers
    /// </summary>
    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.PreviewTextInput += PreviewTextInputHandler;
        AssociatedObject.PreviewKeyDown += PreviewKeyDownHandler;
        DataObject.AddPastingHandler(AssociatedObject, PastingHandler);
    }

    /// <summary>
    ///     Deattach our behaviour. remove event handlers
    /// </summary>
    protected override void OnDetaching()
    {
        base.OnDetaching();

        AssociatedObject.PreviewTextInput -= PreviewTextInputHandler;
        AssociatedObject.PreviewKeyDown -= PreviewKeyDownHandler;
        DataObject.RemovePastingHandler(AssociatedObject, PastingHandler);
    }

    #region Event handlers [PRIVATE] --------------------------------------

    void PreviewTextInputHandler(object sender, TextCompositionEventArgs e)
    {
        string text;
        if (this.AssociatedObject.Text.Length < this.AssociatedObject.CaretIndex)
            text = this.AssociatedObject.Text;
        else
        {
            //  Remaining text after removing selected text.
            string remainingTextAfterRemoveSelection;

            text = TreatSelectedText(out remainingTextAfterRemoveSelection)
                ? remainingTextAfterRemoveSelection.Insert(AssociatedObject.SelectionStart, e.Text)
                : AssociatedObject.Text.Insert(this.AssociatedObject.CaretIndex, e.Text);
        }

        e.Handled = !ValidateText(text);
    }

    /// <summary>
    ///     PreviewKeyDown event handler
    /// </summary>
    void PreviewKeyDownHandler(object sender, KeyEventArgs e)
    {
        if (string.IsNullOrEmpty(this.EmptyValue))
            return;

        string text = null;

        // Handle the Backspace key
        if (e.Key == Key.Back)
        {
            if (!this.TreatSelectedText(out text))
            {
                if (AssociatedObject.SelectionStart > 0)
                    text = this.AssociatedObject.Text.Remove(AssociatedObject.SelectionStart - 1, 1);
            }
        }
        // Handle the Delete key
        else if (e.Key == Key.Delete)
        {
            // If text was selected, delete it
            if (!this.TreatSelectedText(out text) && this.AssociatedObject.Text.Length > AssociatedObject.SelectionStart)
            {
                // Otherwise delete next symbol
                text = this.AssociatedObject.Text.Remove(AssociatedObject.SelectionStart, 1);
            }
        }

        if (text == string.Empty)
        {
            this.AssociatedObject.Text = this.EmptyValue;
            if (e.Key == Key.Back)
                AssociatedObject.SelectionStart++;
            e.Handled = true;
        }
    }

    private void PastingHandler(object sender, DataObjectPastingEventArgs e)
    {
        if (e.DataObject.GetDataPresent(DataFormats.Text))
        {
            string text = Convert.ToString(e.DataObject.GetData(DataFormats.Text));

            if (!ValidateText(text))
                e.CancelCommand();
        }
        else
            e.CancelCommand();
    }
    #endregion Event handlers [PRIVATE] -----------------------------------

    #region Auxiliary methods [PRIVATE] -----------------------------------

    /// <summary>
    ///     Validate certain text by our regular expression and text length conditions
    /// </summary>
    /// <param name="text"> Text for validation </param>
    /// <returns> True - valid, False - invalid </returns>
    private bool ValidateText(string text)
    {
        return (new Regex(this.RegularExpression, RegexOptions.IgnoreCase)).IsMatch(text) && (MaxLength == int.MinValue || text.Length <= MaxLength);
    }

    /// <summary>
    ///     Handle text selection
    /// </summary>
    /// <returns>true if the character was successfully removed; otherwise, false. </returns>
    private bool TreatSelectedText(out string text)
    {
        text = null;
        if (AssociatedObject.SelectionLength <= 0) 
            return false;

        var length = this.AssociatedObject.Text.Length;
        if (AssociatedObject.SelectionStart >= length)
            return true;

        if (AssociatedObject.SelectionStart + AssociatedObject.SelectionLength >= length)
            AssociatedObject.SelectionLength = length - AssociatedObject.SelectionStart;

        text = this.AssociatedObject.Text.Remove(AssociatedObject.SelectionStart, AssociatedObject.SelectionLength);
        return true;
    }
    #endregion Auxiliary methods [PRIVATE] --------------------------------
}

使用起来非常简单:

<i:Interaction.Behaviors>
    <behaviours:TextBoxInputRegExBehaviour RegularExpression="^\d+$" MaxLength="9" EmptyValue="0" />
</i:Interaction.Behaviors>

2
这个解决方案相当不错。但是你犯了一个小错误:当没有设置 MaxLength 时,你的条件 (this.MaxLength == 0 || text.Length <= this.MaxLength) 在测试新文本时总是返回 false。更好的做法应该是 (this.MaxLength == int.MinValue || text.Length <= this.MaxLength),因为你将 int.MinValue 设置为 MaxLength 的默认值。 - Christoph Meißner
1
谢谢@Christoph,是的,你说得对。我已经修改了我的答案。 - Alex Klaus
@AlexKlaus 这段代码很好用,但是当我在绑定中添加 UpdateSourceTrigger=PropertyChanged 后就无法正常工作了。你有什么办法让这段代码在 UpdateSourceTrigger 设置为 PropertyChanged 时也能正常工作吗?谢谢你分享这段代码。 - Junior

43

这里有一个使用MVVM非常简单易行的方法。

将您的textBox与ViewModel中的整数属性绑定,这将像宝石一样工作......甚至在输入非整数时,它还会显示验证。

XAML代码:

<TextBox x:Name="contactNoTxtBox"  Text="{Binding contactNo}" />

查看模型代码:

private long _contactNo;
public long contactNo
{
    get { return _contactNo; }
    set
    {
        if (value == _contactNo)
            return;
        _contactNo = value;
        OnPropertyChanged();
    }
}

但问题中包含“我想要接受数字和小数点”,那么这个答案是否接受小数点呢? - Peter Mortensen
我尝试将“long”更改为“float”,但是它与即时验证不完全正确。 我在绑定中添加了“UpdateSourceTrigger =” PropertyChanged“”,以便它会在每输入一个字符时执行验证,并且除非存在非法字符(必须键入“1x.234”然后删除“x”),否则无法在TextBox中键入“。”。 在此模式下,它也感觉有点迟缓。 这似乎使用System.Number.ParseSingle()来完成工作,因此它接受各种符号。 - fadden
@wolle 可能没有得到点赞,因为它没有解释验证的工作原理。 - Paul McCarthy
我的支撑属性拥有一个可空属性。当用户删除文本框中的所有文本时,它会以红色显示,但支撑属性未发生更改。因此,我需要一个不同或修改后的解决方案。 - John Foll
1
@JohnFoll 如果定义了TargetNullValue,这不应该是一个问题,例如:<TextBox Text="{Binding SomeNullableProperty, TargetNullValue={x:Static sys:String.Empty}}"/>。别忘了引用系统命名空间 xmlns:sys="clr-namespace:System;assembly=System.Runtime" - kalenderdose

32

这里我有一个简单的解决方案,受到Ray的答案的启发。这应该足以识别任何形式的数字。

如果您只想要正数、整数值或精确到最大小数位数的值等,则可以轻松修改此解决方案。


Ray的答案所建议的,您首先需要添加一个PreviewTextInput事件:

<TextBox PreviewTextInput="TextBox_OnPreviewTextInput"/>

然后将以下代码放入后台代码中:

private void TextBox_OnPreviewTextInput(object sender, TextCompositionEventArgs e)
{
    var textBox = sender as TextBox;
    // Use SelectionStart property to find the caret position.
    // Insert the previewed text into the existing text in the textbox.
    var fullText = textBox.Text.Insert(textBox.SelectionStart, e.Text);

    double val;
    // If parsing is successful, set Handled to false
    e.Handled = !double.TryParse(fullText, out val);
}

为了去除无效的空格,我们可以使用NumberStyles

using System.Globalization;

private void TextBox_OnPreviewTextInput(object sender, TextCompositionEventArgs e)
{
    var textBox = sender as TextBox;
    // Use SelectionStart property to find the caret position.
    // Insert the previewed text into the existing text in the textbox.
    var fullText = textBox.Text.Insert(textBox.SelectionStart, e.Text);

    double val;
    // If parsing is successful, set Handled to false
    e.Handled = !double.TryParse(fullText, 
                                 NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, 
                                 CultureInfo.InvariantCulture,
                                 out val);
}

6
我非常喜欢这个答案,简单而有效+。 - user7861944
4
这仍然允许某人将字符串粘贴到文本框中。 - FCin
1
我通过在TextChanged事件处理程序方法的开头摆脱空格问题来解决了它。可能不是最优雅的解决方案,但是有效。 - z33k
1
@z33k 更新了答案以捕获空格。这可能比你的更简单。 - Anthony
当我按下空格键时,它会在文本框中显示。我已将其绑定到属性,当代码达到double.TryParse时,它可以解析,因为它不存在。 但是,如果我在空格后输入了一个新的数字/字母,那么它也会显示空格。 - PeterPazmandi
显示剩余5条评论

29

添加一个验证规则,以便在文本更改时检查数据是否为数字,如果是,则允许继续处理,如果不是,则提示用户该字段仅接受数字数据。

Windows Presentation Foundation中的验证中了解更多信息。


6
这并不符合SO标准的回答。 - Robert Baker
1
这似乎是使用 .net 的方式来完成它。 - Telemat
1
这是正确的答案:验证应该在视图模型或模型级别进行。此外,您可以简单地绑定到数字类型,如“double”,这已经为您提供了标准验证。 - user6996876

26

我尝试过这个控件,但在使用UpdateSourceTrigger=PropertyChanged参数时会出现问题,并且一般来说,它对于用户输入科学计数法很困难。 - Menno Deij - van Rijswijk
5
请注意,NumericUpDown现已过时。您可以使用更新的工具包Extended WPF Toolkit™ Community Edition中的DecimalUpDown - itsho

24

也可以简单地实现一个验证规则并将其应用于文本框:

  <TextBox>
    <TextBox.Text>
      <Binding Path="OnyDigitInput" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
        <Binding.ValidationRules>
          <conv:OnlyDigitsValidationRule />
        </Binding.ValidationRules>
      </Binding>
    </TextBox.Text>

使用以下规则实现(使用其他回答中提出的相同正则表达式):

public class OnlyDigitsValidationRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        var validationResult = new ValidationResult(true, null);

        if(value != null)
        {
            if (!string.IsNullOrEmpty(value.ToString()))
            {
                var regex = new Regex("[^0-9.-]+"); //regex that matches disallowed text
                var parsingOk = !regex.IsMatch(value.ToString());
                if (!parsingOk)
                {
                    validationResult = new ValidationResult(false, "Illegal Characters, Please Enter Numeric Value");
                }
            }
        }

        return validationResult;
    }
}

1
如果您想输入十进制数字,请在文本以“.”结尾时不要返回“valid”。请参考https://dev59.com/gGgu5IYBdhLWcg3wln8O#27838893。 - YantingChen

12
另一种方法是使用附加行为,我实现了自己的TextBoxHelper类,该类可在项目中的所有文本框上使用。因为我意识到为此目的订阅每个文本框和在每个单独的XAML文件中可能会耗费时间。

我实现的TextBoxHelper类具有以下功能:

  • DoubleIntUintNatural格式的数字过滤和接受
  • 过滤和接受仅为偶数奇数
  • 处理粘贴事件处理程序以防止将无效文本粘贴到我们的数字文本框中
  • 可以设置一个默认值,该值将通过订阅文本框的TextChanged事件作为最后的手段来防止无效数据

以下是TextBoxHelper类的实现:

public static class TextBoxHelper
{
    #region Enum Declarations

    public enum NumericFormat
    {
        Double,
        Int,
        Uint,
        Natural
    }

    public enum EvenOddConstraint
    {
        All,
        OnlyEven,
        OnlyOdd
    }

    #endregion

    #region Dependency Properties & CLR Wrappers

    public static readonly DependencyProperty OnlyNumericProperty =
        DependencyProperty.RegisterAttached("OnlyNumeric", typeof(NumericFormat?), typeof(TextBoxHelper),
            new PropertyMetadata(null, DependencyPropertiesChanged));
    public static void SetOnlyNumeric(TextBox element, NumericFormat value) =>
        element.SetValue(OnlyNumericProperty, value);
    public static NumericFormat GetOnlyNumeric(TextBox element) =>
        (NumericFormat) element.GetValue(OnlyNumericProperty);


    public static readonly DependencyProperty DefaultValueProperty =
        DependencyProperty.RegisterAttached("DefaultValue", typeof(string), typeof(TextBoxHelper),
            new PropertyMetadata(null, DependencyPropertiesChanged));
    public static void SetDefaultValue(TextBox element, string value) =>
        element.SetValue(DefaultValueProperty, value);
    public static string GetDefaultValue(TextBox element) => (string) element.GetValue(DefaultValueProperty);


    public static readonly DependencyProperty EvenOddConstraintProperty =
        DependencyProperty.RegisterAttached("EvenOddConstraint", typeof(EvenOddConstraint), typeof(TextBoxHelper),
            new PropertyMetadata(EvenOddConstraint.All, DependencyPropertiesChanged));
    public static void SetEvenOddConstraint(TextBox element, EvenOddConstraint value) =>
        element.SetValue(EvenOddConstraintProperty, value);
    public static EvenOddConstraint GetEvenOddConstraint(TextBox element) =>
        (EvenOddConstraint)element.GetValue(EvenOddConstraintProperty);

    #endregion

    #region Dependency Properties Methods

    private static void DependencyPropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (!(d is TextBox textBox))
            throw new Exception("Attached property must be used with TextBox.");

        switch (e.Property.Name)
        {
            case "OnlyNumeric":
            {
                var castedValue = (NumericFormat?) e.NewValue;

                if (castedValue.HasValue)
                {
                    textBox.PreviewTextInput += TextBox_PreviewTextInput;
                    DataObject.AddPastingHandler(textBox, TextBox_PasteEventHandler);
                }
                else
                {
                    textBox.PreviewTextInput -= TextBox_PreviewTextInput;
                    DataObject.RemovePastingHandler(textBox, TextBox_PasteEventHandler);
                }

                break;
            }

            case "DefaultValue":
            {
                var castedValue = (string) e.NewValue;

                if (castedValue != null)
                {
                    textBox.TextChanged += TextBox_TextChanged;
                }
                else
                {
                    textBox.TextChanged -= TextBox_TextChanged;
                }

                break;
            }
        }
    }

    #endregion

    private static void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        var textBox = (TextBox)sender;

        string newText;

        if (textBox.SelectionLength == 0)
        {
            newText = textBox.Text.Insert(textBox.SelectionStart, e.Text);
        }
        else
        {
            var textAfterDelete = textBox.Text.Remove(textBox.SelectionStart, textBox.SelectionLength);

            newText = textAfterDelete.Insert(textBox.SelectionStart, e.Text);
        }

        var evenOddConstraint = GetEvenOddConstraint(textBox);

        switch (GetOnlyNumeric(textBox))
        {
            case NumericFormat.Double:
            {
                if (double.TryParse(newText, out double number))
                {
                    switch (evenOddConstraint)
                    {
                        case EvenOddConstraint.OnlyEven:

                            if (number % 2 != 0)
                                e.Handled = true;
                            else
                                e.Handled = false;

                            break;

                        case EvenOddConstraint.OnlyOdd:

                            if (number % 2 == 0)
                                e.Handled = true;
                            else
                                e.Handled = false;

                            break;
                    }
                }
                else
                    e.Handled = true;

                break;
            }

            case NumericFormat.Int:
            {
                if (int.TryParse(newText, out int number))
                {
                    switch (evenOddConstraint)
                    {
                        case EvenOddConstraint.OnlyEven:

                            if (number % 2 != 0)
                                e.Handled = true;
                            else
                                e.Handled = false;

                            break;

                        case EvenOddConstraint.OnlyOdd:

                            if (number % 2 == 0)
                                e.Handled = true;
                            else
                                e.Handled = false;

                            break;
                    }
                }
                else
                    e.Handled = true;

                break;
            }

            case NumericFormat.Uint:
            {
                if (uint.TryParse(newText, out uint number))
                {
                    switch (evenOddConstraint)
                    {
                        case EvenOddConstraint.OnlyEven:

                            if (number % 2 != 0)
                                e.Handled = true;
                            else
                                e.Handled = false;

                            break;

                        case EvenOddConstraint.OnlyOdd:

                            if (number % 2 == 0)
                                e.Handled = true;
                            else
                                e.Handled = false;

                            break;
                    }
                }
                else
                    e.Handled = true;

                break;
            }

            case NumericFormat.Natural:
            {
                if (uint.TryParse(newText, out uint number))
                {
                    if (number == 0)
                        e.Handled = true;
                    else
                    {
                        switch (evenOddConstraint)
                        {
                            case EvenOddConstraint.OnlyEven:

                                if (number % 2 != 0)
                                    e.Handled = true;
                                else
                                    e.Handled = false;

                                break;

                            case EvenOddConstraint.OnlyOdd:

                                if (number % 2 == 0)
                                    e.Handled = true;
                                else
                                    e.Handled = false;

                                break;
                        }
                    }
                }
                else
                    e.Handled = true;

                break;
            }
        }
    }
    
    private static void TextBox_PasteEventHandler(object sender, DataObjectPastingEventArgs e)
    {
        var textBox = (TextBox)sender;

        if (e.DataObject.GetDataPresent(typeof(string)))
        {
            var clipboardText = (string) e.DataObject.GetData(typeof(string));

            var newText = textBox.Text.Insert(textBox.SelectionStart, clipboardText);

            var evenOddConstraint = GetEvenOddConstraint(textBox);

            switch (GetOnlyNumeric(textBox))
            {
                case NumericFormat.Double:
                {
                    if (double.TryParse(newText, out double number))
                    {
                        switch (evenOddConstraint)
                        {
                            case EvenOddConstraint.OnlyEven:

                                if (number % 2 != 0)
                                    e.CancelCommand();

                                break;

                            case EvenOddConstraint.OnlyOdd:

                                if (number % 2 == 0)
                                    e.CancelCommand();
                                
                                break;
                        }
                    }
                    else
                        e.CancelCommand();

                    break;
                }

                case NumericFormat.Int:
                {
                    if (int.TryParse(newText, out int number))
                    {
                        switch (evenOddConstraint)
                        {
                            case EvenOddConstraint.OnlyEven:

                                if (number % 2 != 0)
                                    e.CancelCommand();

                                break;

                            case EvenOddConstraint.OnlyOdd:

                                if (number % 2 == 0)
                                    e.CancelCommand();


                                break;
                        }
                    }
                    else
                        e.CancelCommand();

                    break;
                }

                case NumericFormat.Uint:
                {
                    if (uint.TryParse(newText, out uint number))
                    {
                        switch (evenOddConstraint)
                        {
                            case EvenOddConstraint.OnlyEven:

                                if (number % 2 != 0)
                                    e.CancelCommand();

                                break;

                            case EvenOddConstraint.OnlyOdd:

                                if (number % 2 == 0)
                                    e.CancelCommand();


                                break;
                        }
                    }
                    else
                        e.CancelCommand();

                    break;
                }
                    
                case NumericFormat.Natural:
                {
                    if (uint.TryParse(newText, out uint number))
                    {
                        if (number == 0)
                            e.CancelCommand();
                        else
                        {
                            switch (evenOddConstraint)
                            {
                                case EvenOddConstraint.OnlyEven:

                                    if (number % 2 != 0)
                                        e.CancelCommand();

                                    break;

                                case EvenOddConstraint.OnlyOdd:

                                    if (number % 2 == 0)
                                        e.CancelCommand();

                                    break;
                            }
                        }
                    }
                    else
                    {
                        e.CancelCommand();
                    }
                    
                    break;
                }
            }
        }
        else
        {
            e.CancelCommand();
        }
    }
    
    private static void TextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        var textBox = (TextBox)sender;
        
        var defaultValue = GetDefaultValue(textBox);

        var evenOddConstraint = GetEvenOddConstraint(textBox);

        switch (GetOnlyNumeric(textBox))
        {
            case NumericFormat.Double:
            {
                if (double.TryParse(textBox.Text, out double number))
                {
                    switch (evenOddConstraint)
                    {
                        case EvenOddConstraint.OnlyEven:

                            if (number % 2 != 0)
                                textBox.Text = defaultValue;

                            break;

                        case EvenOddConstraint.OnlyOdd:

                            if (number % 2 == 0)
                                textBox.Text = defaultValue;

                            break;
                    }
                }
                else
                    textBox.Text = defaultValue;

                break;
            }

            case NumericFormat.Int:
            {
                if (int.TryParse(textBox.Text, out int number))
                {
                    switch (evenOddConstraint)
                    {
                        case EvenOddConstraint.OnlyEven:

                            if (number % 2 != 0)
                                textBox.Text = defaultValue;

                            break;

                        case EvenOddConstraint.OnlyOdd:

                            if (number % 2 == 0)
                                textBox.Text = defaultValue;

                            break;
                    }
                }
                else
                    textBox.Text = defaultValue;

                break;
            }

            case NumericFormat.Uint:
            {
                if (uint.TryParse(textBox.Text, out uint number))
                {
                    switch (evenOddConstraint)
                    {
                        case EvenOddConstraint.OnlyEven:

                            if (number % 2 != 0)
                                textBox.Text = defaultValue;

                            break;

                        case EvenOddConstraint.OnlyOdd:

                            if (number % 2 == 0)
                                textBox.Text = defaultValue;

                            break;
                    }
                }
                else
                    textBox.Text = defaultValue;

                break;
            }

            case NumericFormat.Natural:
            {
                if (uint.TryParse(textBox.Text, out uint number))
                {
                    if(number == 0)
                        textBox.Text = defaultValue;
                    else
                    {
                        switch (evenOddConstraint)
                        {
                            case EvenOddConstraint.OnlyEven:

                                if (number % 2 != 0)
                                    textBox.Text = defaultValue;

                                break;

                            case EvenOddConstraint.OnlyOdd:

                                if (number % 2 == 0)
                                    textBox.Text = defaultValue;

                                break;
                        }
                    }
                }
                else
                {
                    textBox.Text = defaultValue;
                }

                break;
            }
        }
    }
}

以下是它简单使用的一些示例:

<TextBox viewHelpers:TextBoxHelper.OnlyNumeric="Double"
         viewHelpers:TextBoxHelper.DefaultValue="1"/>

或者

<TextBox viewHelpers:TextBoxHelper.OnlyNumeric="Natural"
         viewHelpers:TextBoxHelper.DefaultValue="3"
         viewHelpers:TextBoxHelper.EvenOddConstraint="OnlyOdd"/>

注意,我的TextBoxHelper驻留在viewHelpers的xmlns别名中。

我希望这个实现能够简化其他人的工作 :)


1
这在使用DataTemplate内的文本框时非常棒,谢谢! - NucS
4
回答很好,但我发现你的方法很难读懂。也许你应该将它们分解成较小的部分。请参见你认为一个方法的理想长度是多少? - Anthony
感谢 @Anthony 给予的建设性反馈。 - Amir Mahdi Nassiri
1
这很棒。我也同意@Anthony的看法,你的长方法应该拆分成多个方法。更好的做法是为文本框的类型创建多个类。 - gabnaim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接