web-dev-qa-db-ja.com

10進数値を入力するWPF TextBox

decimal値にバインドされているWPFコントロールを取得する適切な方法はありますか?

TextBoxまたはDataGridTextColumndecimalにバインドするだけの場合、データ入力が問題になります。

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

このTextBoxに「0,5」と入力しようとすると、結果として「5」が返されます。 「1,5」を入力して「1」を「0」に置き換えることを除いて、「0,5」を入力することはほとんど不可能です。

StringFormatを使用すると、データ入力がわずかに改善されます。

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

ここで、「0,5」と入力しようとすると、「0,5,0」で終わりますが、これはまだ間違っていますが、少なくとも末尾の「、0」を問題なく削除できます。

それでも、WPFを使用してdecimal型を入力するのは非常に厄介です。これらのTextBoxesはveryデータ入力エラーが発生しやすいため、特に値の場合は本当に大変です。

それでは、WPFでの10進データ入力に何を使用するべきですか?または、Microsoftは10進データをサポートしていませんか?

17
Sam

私は現在、この動作をデジタル入力と10進数入力に使用しています。

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>
20
blindmeis
    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;
    }
8
CMarsden

WPF拡張ツールキット には、ニーズに合う DecimalUpDown コントロールがあります。使用は無料ですが、自分で試してロールするよりも使用することをお勧めします。

入力の検証については、検証を適用する方法がいくつかあります。MSDNで詳しく説明しています ここに1つあります 。私 別のアプローチの詳細 ブログの2つの投稿でのカスタムバインド可能な検証(DecimalUpDownコントロールのValueプロパティバインディングに検証を適用します)。

6
slugster

私もこの問題に遭遇しました。 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
testpattern

私は自分の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
            )
    );
}
3
pinki

私はこの投稿が古いことを知っていますが、この問題に対してGoogle検索で最初に表示されます。 system.windows.interactivityパッケージ(このパッケージの古いバージョン)でエラーが発生したため、検索を続行しました。

[〜#〜] msdn [〜#〜] に関するこの投稿は私の問題を修正し、メインウィンドウのinitializecomponentの直前の1行の解決策です。

    Public Sub New()

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

    ' Add any initialization after the InitializeComponent() call.

End Sub

これが他のグーグル検索者に役立つことを願っています。

2
JC Frigon

これにより、テキストボックスに入力できるのは小数のみです。

ビューモデルは次のようになります。

    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}" />
2
ian korkie

新しいので、彼の答えにコメントすることはできませんが、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);
                        }
                    }
                }
2
cheebacat

テキストボックスで10進数のみを許可する場合は、そのテキストボックスのpreviewinputtextイベントを記述します。その後、そのイベントでこのコードを書きます

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

この正規表現は機能します

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));
  }
1
Kavinda Gehan