WPF TextBox для ввода десятичных значений

Есть ли достойный способ получить элемент управления WPF, связанный с десятичным значением?

Когда я просто привязываю TextBox или DataGridTextColumn к десятичной, запись данных засасывает большое время.

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

Когда я пытаюсь ввести "0,5" в этом текстовом поле, я получу "5". Почти невозможно ввести "0,5" вообще (кроме ввода 1,5 и замены "1" на "0" ).

Когда я использую запись данных StringFormat, все еще отсасывает небольшое время:

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

Теперь, когда я пытаюсь ввести "0,5" , я получаю "0,5,0", что по-прежнему не так, но, по крайней мере, я могу удалить конечную "0" без особых проблем.

Тем не менее, ввод десятичных знаков с использованием WPF засасывает большое время, потому что эти поля ввода очень подвержены ошибкам ввода данных, что является реальной болью особенно для значений!

Итак, что я должен использовать для ввода десятичных данных в wpf? Или Microsoft не поддерживает десятичные данные?

Ответ 1

В настоящее время я использую это поведение для цифрового и десятичного ввода:

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>

Ответ 2

    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;
    }

Ответ 3

WPF Extended toolkit имеет DecimalUpDown который может удовлетворить ваши потребности. Он свободен в использовании, и лучше использовать его, чем пытаться катиться самостоятельно.

Что касается проверки ввода на нем, существует несколько способов применения валидации, здесь один, подробно описанный в MSDN. я подробно описать другой подход для персонализированной проверки привязки в двух сообщениях в моем блоге (вы применили бы проверку на привязку свойства Value для элемента управления DecimalUpDown).

Ответ 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";
 }

Ответ 5

Я реализовал свой собственный TextBox. Он обновляет источник, когда в тексте есть число, иначе нет. Потерянный Фокус, я прочитал свойство источника. Все, что вам нужно сделать, это заменить TextBox этим классом и связать свойство "Number", которое имеет тип double.

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
            )
    );
}

Ответ 6

если вы хотите, чтобы текстовое поле допускало только десятичное значение, тогда напишите событие previewinputtext для этого текстового поля. то в этом случае напишите этот код

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

Ответ 7

Это позволит вводить только десятичные дроби в текстовое поле и ничего больше.

Модель представления выглядит так:

    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}" />

Ответ 8

Im new, поэтому я не могу ответить на его ответ, но я исправил проблемы с отрицательным числом в коде 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);
                        }
                    }
                }

Ответ 9

Это регулярное выражение работает

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));
  }

Ответ 10

Я знаю, что это сообщение устарело, но оно сначала появляется в Поиске Google по этой проблеме. Так как у меня была ошибка с пакетом system.windows.interactivity (старая версия этого пакета), я продолжил поиск.

Этот пост на MSDN исправил мою проблему, и это решение в одну строку только перед инициализацией компонента в главном окне следующим образом:

    Public Sub New()

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

    ' Add any initialization after the InitializeComponent() call.

End Sub

Надеюсь, что это поможет другим поисковикам Google.