WPFでTextBoxにdoubleをBindingする


WPFでTextBoxにdoubleをBindingすると

  • 文字の末尾に小数点を入力できない
  • 1.002の2を消すと1.00ではなく1になる

などの動作になります。

これはBindingしている値をTextBoxにも即反映させるという動作になっているからです。

1.002の末尾の2を消した際の動作は下記のようになります。

  1. Text1.00に更新
  2. Binding Sourceのdoubleが(double)1に更新
  3. Text1に更新

雑な解決

起動時に

App.xaml.cs
System.Windows.FrameworkCompatibilityPreferences
                          .KeepTextBoxDisplaySynchronizedWithTextProperty = false;

と設定すれば解決です。
Bindingしている値をTextBoxにも即反映させないようにします。

.NET Framework 4.5以前はこれがデフォルトです。

解決

Textプロパティを更新する前に別のプロパティでチェックを行うことで期待する動作を実現します。

Textは設定せずにDoubleTextのみを使います。

<myControl:DoubleTextBox DoubleText="{Binding Double1, UpdateSourceTrigger=PropertyChanged}" />
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

public class DoubleTextBox : TextBox
{
    public string DoubleText
    {
        get => (string)GetValue(DoubleTextProperty);
        set => SetValue(DoubleTextProperty, value);
    }
    public static readonly DependencyProperty DoubleTextProperty =
          DependencyProperty.Register(
              nameof(DoubleText),
              typeof(string),
              typeof(DoubleTextBox),
              new FrameworkPropertyMetadata(
                  string.Empty,
                  FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal,
                  new PropertyChangedCallback(OnDoubleTextChanged),
                  null,
                  true,
                  UpdateSourceTrigger.LostFocus));

    private static void OnDoubleTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is TextBox textBox)
        {
            var currentText = textBox.Text;
            var newText = (string)e.NewValue;
            if (currentText == newText)
                return;
            if (
                double.TryParse(currentText, out var currentDouble) &&
                double.TryParse(newText, out var newDouble) &&
                currentDouble == newDouble
                )
                return;

            textBox.Text = newText;
        }
    }

    protected override void OnTextChanged(TextChangedEventArgs e)
    {
        base.OnTextChanged(e);
        this.DoubleText = this.Text;
    }
}

動作の解説

これによって、1.002の末尾の2を消した際の動作は下記のようになります。

  1. Text1.00に更新
  2. DoubleText1.00に更新
  3. Binding Sourceのdoubleが(double)1に更新
  4. DoubleText1に更新
  5. 11.00はともにdouble値として等しいのでTextは更新されない

Textが更新された場合の動作

キーの入力などでTextが更新されたときはその値がそのままDoubleTextに反映されます。
Binding Sourceへも同様に反映されます。

DoubleText(あるいはBinding Source)が更新された場合の動作

基本的にはTextに反映されます。
ただし、DoubleTextTextをそれぞれdoubleに変換して同じ値になる場合はTextを更新しません。