WPF TextBox输入十进制值

时间:2013-06-04 09:26:56

标签: wpf decimal data-entry

有没有合适的方法来获得一个绑定到十进制值的WPF控件?

当我将TextBox或DataGridTextColumn绑定到小数时,数据输入会耗费大量时间。

<TextBox Text="{Binding MyDecimal, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>

当我尝试在此TextBox中输入“0,5”时,我会得到“5”。根本不可能输入“0,5”(除了输入1,5并用“0”替换“1”)。

当我使用StringFormat时,数据输入仍然很少:

<TextBox Text="{Binding MyDecimal, StringFormat=F1, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>

现在,当我尝试输入“0,5”时,我最终会得到“0,5,0”,这仍然是错误的,但至少我可以删除尾随的“0”而没有太多问题。

尽管如此,使用WPF输入小数会吸引大量时间,因为此输入字段非常容易出现数据输入错误,这对于值来说真的很痛苦!

那么我应该在wpf中用于十进制数据输入?或者Microsoft不支持十进制数据?

13 个答案:

答案 0 :(得分:18)

我目前使用此行为进行数字和十进制输入:

public class TextBoxInputBehavior : Behavior<TextBox>
{
    const NumberStyles validNumberStyles = NumberStyles.AllowDecimalPoint |
                                               NumberStyles.AllowThousands |
                                               NumberStyles.AllowLeadingSign;
    public TextBoxInputBehavior()
    {
        this.InputMode = TextBoxInputMode.None;
        this.JustPositivDecimalInput = false;
    }

    public TextBoxInputMode InputMode { get; set; }


    public static readonly DependencyProperty JustPositivDecimalInputProperty =
     DependencyProperty.Register("JustPositivDecimalInput", typeof(bool),
     typeof(TextBoxInputBehavior), new FrameworkPropertyMetadata(false));

    public bool JustPositivDecimalInput
    {
        get { return (bool)GetValue(JustPositivDecimalInputProperty); }
        set { SetValue(JustPositivDecimalInputProperty, value); }
    }

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.PreviewTextInput += AssociatedObjectPreviewTextInput;
        AssociatedObject.PreviewKeyDown += AssociatedObjectPreviewKeyDown;

        DataObject.AddPastingHandler(AssociatedObject, Pasting);

    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.PreviewTextInput -= AssociatedObjectPreviewTextInput;
        AssociatedObject.PreviewKeyDown -= AssociatedObjectPreviewKeyDown;

        DataObject.RemovePastingHandler(AssociatedObject, Pasting);
    }

    private void Pasting(object sender, DataObjectPastingEventArgs e)
    {
        if (e.DataObject.GetDataPresent(typeof(string)))
        {
            var pastedText = (string)e.DataObject.GetData(typeof(string));

            if (!this.IsValidInput(this.GetText(pastedText)))
            {
                System.Media.SystemSounds.Beep.Play();
                e.CancelCommand();
            }
        }
        else
        {
            System.Media.SystemSounds.Beep.Play();
            e.CancelCommand();
        }
     }

     private void AssociatedObjectPreviewKeyDown(object sender, KeyEventArgs e)
     {
        if (e.Key == Key.Space)
        {
            if (!this.IsValidInput(this.GetText(" ")))
            {
                System.Media.SystemSounds.Beep.Play();
                e.Handled = true;
            }
        }
     }

     private void AssociatedObjectPreviewTextInput(object sender, TextCompositionEventArgs e)
     {
        if (!this.IsValidInput(this.GetText(e.Text)))
        {
            System.Media.SystemSounds.Beep.Play();
            e.Handled = true;
        }
     }

     private string GetText(string input)
     {
        var txt = this.AssociatedObject;

        int selectionStart = txt.SelectionStart;
        if (txt.Text.Length < selectionStart) 
            selectionStart = txt.Text.Length;

        int selectionLength = txt.SelectionLength;
        if (txt.Text.Length < selectionStart + selectionLength) 
            selectionLength = txt.Text.Length - selectionStart;

        var realtext = txt.Text.Remove(selectionStart, selectionLength);

        int caretIndex = txt.CaretIndex;
        if (realtext.Length < caretIndex) 
            caretIndex = realtext.Length;

        var newtext = realtext.Insert(caretIndex, input);

        return newtext;
     }

     private bool IsValidInput(string input)
     {
        switch (InputMode)
        {
            case TextBoxInputMode.None:
                return true;
            case TextBoxInputMode.DigitInput:
                return CheckIsDigit(input);

            case TextBoxInputMode.DecimalInput:
                decimal d;
                //wen mehr als ein Komma
                if (input.ToCharArray().Where(x => x == ',').Count() > 1)
                    return false;


                if (input.Contains("-"))
                {
                     if (this.JustPositivDecimalInput) 
                        return false;


                     if (input.IndexOf("-",StringComparison.Ordinal) > 0) 
                          return false;

                      if(input.ToCharArray().Count(x=>x=='-') > 1)
                          return false;

                        //minus einmal am anfang zulässig
                       if (input.Length == 1) 
                           return true;
                    }

                    var result = decimal.TryParse(input, validNumberStyles, CultureInfo.CurrentCulture, out d);
                    return result;



            default: throw new ArgumentException("Unknown TextBoxInputMode");

        }
        return true;
     }

     private bool CheckIsDigit(string wert)
     {
        return wert.ToCharArray().All(Char.IsDigit);
     }
}

 public enum TextBoxInputMode
 {
  None,
  DecimalInput,
  DigitInput
  }

XAML用法如下所示:

<TextBox Text="{Binding Sum}">
    <i:Interaction.Behaviors>
        <Behaviors:TextBoxInputBehavior InputMode="DecimalInput"/>
    </i:Interaction.Behaviors>
</TextBox>

答案 1 :(得分:7)

    private void DecimalTextBox_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
    {
        bool approvedDecimalPoint = false;

        if (e.Text == ".")
        {
            if (!((TextBox)sender).Text.Contains("."))
                approvedDecimalPoint = true;
        }

        if (!(char.IsDigit(e.Text, e.Text.Length - 1) || approvedDecimalPoint))
            e.Handled = true;
    }

答案 2 :(得分:5)

WPF Extended toolkitDecimalUpDown控件,可能符合您的需求。它可以免费使用,最好使用它而不是尝试自己动手。

至于验证输入,有许多方法可以应用验证,here is one详见MSDN。我detail another approach在我的博客上的两个帖子中进行自定义绑定验证(您可以将验证应用于DecimalUpDown控件上的Value属性绑定)。

答案 3 :(得分:4)

我也遇到过这个问题;使用UpdateSourceTrigger=PropertyChanged似乎绑定尝试在您键入文本时更新文本。为解决此问题,我们更改了输入字段,以便UpdateSourceTrigger=LostFocus,例如:

<TextBox Text="{Binding MyDecimal, UpdateSourceTrigger=LostFocus, ValidatesOnDataErrors=True, StringFormat=n1}" />

您可以使用IDataErrorInfo界面定义自己的验证错误。您只需将以下内容添加到支持模型中:

 public class MyModel : IDataErrorInfo
 {
    /* my properties */

    public string Error { get { return null; } }
    public string this[string name]
    {
       get
       {
          switch (name)
          {
             case "MyDecimal":
                return NumberHelper.IsValidValue(MyDecimal) ? message : null;
             default: return null;
          }
       }
    }
    private string message = "Invalid value";
 }

答案 4 :(得分:2)

我实现了自己的TextBox。当文本中有数字时,它会更新源,否则不会。在丢失焦点时,我读取了源属性。您所要做的就是用此类替换TextBox并绑定类型为double的“Number”属性。

public class DoubleTextBox: TextBox
{
    public DoubleTextBox()
    {
        TextChanged += DoubleTextBox_TextChanged;
        LostFocus += DoubleTextBox_LostFocus;
    }

    void DoubleTextBox_LostFocus(object sender, System.Windows.RoutedEventArgs e)
    {
        Text = Number.ToString("N2");
    }

    void DoubleTextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        double zahl;
        if (string.IsNullOrWhiteSpace(Text))
        {
            Number = 0;
        }
        else if (double.TryParse(Text, out zahl))
        {
            Number = Double.Parse(zahl.ToString("N2"));
        }
        else
        {
            ValidationError validationError =
                new ValidationError(new ExceptionValidationRule(), GetBindingExpression(NumberProperty));

            validationError.ErrorContent = "Keine gültige Zahl";

            Validation.MarkInvalid(
                GetBindingExpression(NumberProperty),
                validationError);

        }
    }

    public double Number
    {
        get { return (double)this.GetValue(NumberProperty); }
        set { this.SetValue(NumberProperty, value); }
    }

    public static readonly DependencyProperty NumberProperty = DependencyProperty.Register(
        "Number", typeof(double), typeof(DoubleTextBox), 
        new FrameworkPropertyMetadata
            (
                0d,
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault
            )
    );
}

答案 5 :(得分:2)

这将只允许在文本框中输入小数,而不能输入其他任何内容。

viewmodel看起来像这样:

    private string _decimalVal = "0";
    public string decimalVal
    {
        get { return _decimalVal.ToString(); }
        set
        {
            if (string.IsNullOrEmpty(value) || value == "-")
                SetProperty(ref _decimalVal, value);
            else if (Decimal.TryParse(value, out decimal newVal))
            {
                if (newVal == 0)
                    value = "0";

                SetProperty(ref _decimalVal, value = (value.Contains(".")) ? Convert.ToDecimal(value).ToString("0.00") : value);
            }
        }
    }

XAML的用法如下:

<TextBox Text="{Binding decimalVal,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />

答案 6 :(得分:1)

我很新,所以我不能评论他的答案,但我修正了 blindmeis 代码中的负数问题。

只需修改

即可
if (input.Contains("-"))
IsValidInput ()到...

的部分
                if (input.Contains("-"))
                {
                    if (this.JustPositivDecimalInput)
                        return false;

                    //minus einmal am anfang zulässig
                    //minus once at the beginning
                    if (input.IndexOf("-", StringComparison.Ordinal) == 0 && input.ToCharArray().Count(x => x == '-') == 1)
                    {
                        if(input.Length == 1)
                        {
                            //INPUT IS "-"
                            return true;
                        }
                        else if (input.Length == 2)
                        {
                            //VALIDATE NEGATIVE DECIMALS...INPUT IS "-."
                            if (input.IndexOf(".", StringComparison.Ordinal) == 1)
                            {
                                return true;
                            }
                        }
                        else 
                        {
                            return decimal.TryParse(input, validNumberStyles, CultureInfo.CurrentCulture, out d);
                        }
                    }
                }

答案 7 :(得分:1)

如果您希望文本框仅允许小数,则为该文本框编写previewinputtext事件。 那么在那个事件中写下这段代码

decimal result;
e.Handled=!decimal.TryParse((sender as TextBox).Text + e.Text, out result)

答案 8 :(得分:1)

这个正则表达式起作用

private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
  {
   Regex regex = new Regex("^[.][0-9]+$|^[0-9]*[.]{0,1}[0-9]*$");
   e.Handled = !regex.IsMatch((sender as TextBox).Text.Insert((sender as TextBox).SelectionStart,e.Text));
  }

答案 9 :(得分:1)

我知道这则帖子很旧,但首先出现在Google搜索中。由于system.windows.interactivity程序包(此程序包的旧版本)出错,我继续搜索。

MSDN上的这篇帖子解决了我的问题,这是一个一行的解决方案,就在像这样在主窗口上的initializecomponent之前:

    Public Sub New()

    ' This call is required by the designer.
    FrameworkCompatibilityPreferences.KeepTextBoxDisplaySynchronizedWithTextProperty = False
    InitializeComponent()

    ' Add any initialization after the InitializeComponent() call.

End Sub

希望这会对其他Google搜索者有所帮助。

答案 10 :(得分:1)

从 .NET 4.5 开始,有一个更简单的修复方法,为绑定添加“延迟”

 <TextBox  Text="{Binding MyDouble, UpdateSourceTrigger=PropertyChanged, Delay=1000}" />

在绑定系统尝试替换句点(将“1.”更改为“1”)之前,用户现在有 1 秒(1000 毫秒)的时间。 这应该让他们有时间在 '.' 之后输入额外的字符。以免被删除。

答案 11 :(得分:0)

我发现仅使用PreviewTextInput事件只会在您输入一些数字后想要输入负数时引起问题 1-> 12-> 123->-123(向后移动光标)

在PreviewTextInput事件中,移动插入符号将不起作用(发送者为TextBox)。Text+ e.Text

使用以下命令获取正则表达式表达式链接作为基础Decimal number regular expression, where digit after decimal is optional

确定@“ ^ [+-]?\ d *。?\ d * $”最适合我。

    string previousText = "";
    int previousCaretIndex = 0;
    private void txtB_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {   
        previousText = ((System.Windows.Controls.TextBox)sender).Text;
        previousCaretIndex = ((System.Windows.Controls.TextBox)sender).CaretIndex;
    }

    private void txtB_TextChanged(object sender, TextChangedEventArgs e)
    {
        if(!Regex.IsMatch(((System.Windows.Controls.TextBox)sender).Text, @"^[+-]?\d*\.?\d*$"))
        {
            ((System.Windows.Controls.TextBox)sender).Text = previousText;
            ((System.Windows.Controls.TextBox)sender).CaretIndex = previousCaretIndex;
            e.Handled = true;
        }
    }

答案 12 :(得分:0)

通过这种方法将防止将非整数和非十进制值复制和粘贴到 TextBox 中,我在任何其他答案中都没有看到:

private void TextBox_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
{
    var textBoxText = ((System.Windows.Controls.TextBox)sender).Text;
    var regex = new System.Text.RegularExpressions.Regex(@"^\d+\.?\d*$");
    if (textBoxText.Length > 0)
    {
        textBoxText += e.Text;
        e.Handled = !regex.IsMatch(textBoxText);
    }
    else
    {
        e.Handled = !regex.IsMatch(e.Text);
    }
}

private void TextBox_PreviewExecuted(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
{
    if (e.Command == System.Windows.Input.ApplicationCommands.Paste)
    {
        if (System.Windows.Clipboard.ContainsText())
        {
            e.Handled = !new System.Text.RegularExpressions.Regex(@"^\d+\.?\d*$").IsMatch(System.Windows.Clipboard.GetText());
        }
    }
}

// In case user copies and pastes 2 times or more.
// E.G. "1.0" might give "1.01.0" and so on.
// E.G. if the regex expression is for the range of 1-100.
// Then user might delete some numbers from the input which would give "0" or "00" etc.
private void TextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
    var textBox = (System.Windows.Controls.TextBox)sender;
    if (!new System.Text.RegularExpressions.Regex(@"^\d+\.?\d*$").IsMatch(textBox.Text.Trim()))
    {
        textBox.Clear();
    }
}

XAML:

<TextBox PreviewTextInput="TextBox_PreviewTextInput" CommandManager.PreviewExecuted="TextBox_PreviewExecuted" TextChanged="TextBox_TextChanged" HorizontalAlignment="Left" VerticalAlignment="Top" Width="120"/>

顺便说一句,如果您想更改其行为以接受其他模式(例如正则表达式),您可以将正则表达式:@"^\d+\.?\d*$" 更改为适合您需要的其他内容,这种方法似乎更多简单可靠。

编辑

在某些情况下取决于正则表达式,例如HH:mm:ss 的日期时间正则表达式,其中 TextChanged 不会接受类似 00: 的内容,当您键入尝试达到 00:20:00 时,该时间会在第三个数字 00: 处停止,因此在这种情况下,如果您没有更好的正则表达式,而不是使用 TextChanged 使用以下内容:

private void TextBox_LostFocus(object sender, System.Windows.RoutedEventArgs e)
{
    var textBox = (System.Windows.Controls.TextBox)sender;
    var textBoxText = textBox.Text.Trim();
    if (textBoxText.Length == 0)
    {
        this.error = false; // It can be true too, depends on your logic.
    }
    else
    {
        this.error = !new System.Text.RegularExpressions.Regex(@"^\d+\.?\d*$").IsMatch(textBoxText);

        if (this.error)
        {
            textBox.Background = System.Windows.Media.Brushes.Red;
        }
        else
        {
            textBox.ClearValue(System.Windows.Controls.TextBox.BackgroundProperty);
        }
    }
}

error 变量是一个成员变量,您应该使用它在表单末尾进行验证,例如通过点击按钮。