◆ PropertyChangedCallback で再代入してもダメな場合があった
  ◆ CoerceValueCallback の結果が同じだと PropertyChangedCallback が発生しない
  ◆ -1 を 0 に修正 → -2 を 0 に修正 でどっちも 0 だからソース側は -2 のままになる 

になくていいかもと言う記事を書きましたが完全に使うべきじゃないと思いました

おさらい

使った場合の問題を再確認します

準備

CoerceValueCallback を使うための UserControl とその UserControl をホストする Window の 2 つを準備します

UserControl の方の UI は単純にテキストボックスだけ

[SampleControl.xaml]
<UserControl 略>
    <TextBox x:Name="textbox"/>
</UserControl

C# コード部分も単純です
今回は テキストボックスに int.Parse できない数字かマイナスの数字が入ると 0 を強制するようにします

[SampleControl.xaml.cs]
public partial class SampleControl : UserControl
{
    public SampleControl()
    {
        InitializeComponent();
        this.textbox.SetBinding(TextBox.TextProperty, new Binding("text") { Source = this });
    }

    public string text
    {
        get { return (string)GetValue(textProperty); }
        set { SetValue(textProperty, value); }
    }

    // Using a DependencyProperty as the backing store for text.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty textProperty =
        DependencyProperty.Register("text", typeof(string), typeof(SampleControl), new FrameworkPropertyMetadata(
            "0",
            (d, e) => { },
            (d, c) =>
            {
                var text = (string)c;
                int i;
                if (int.TryParse(text, out i))
                {
                    return i < 0 ? "0" : i.ToString();
                }
                return "0";
            }));
}

(target)                                       (source)
SampleControl.textbox.Text <---> SampleControl.text

という形で Binding されています

外部からは SampleControl のインスタンスの text プロパティを Binding して使えるようになっています


次に SampleWindow

[SampleWindow.xaml]
<Window 略>
    <StackPanel>
        <local:SampleControl text="{Binding num_text,UpdateSourceTrigger=LostFocus,Mode=OneWayToSource}"/>
        <TextBox>フォーカス切り替え用</TextBox>
        <Button Click="Button_Click">button</Button>
    </StackPanel>
</Window>

SampleControl とフォーカス切り替え用の TextBox とボタンがあります
ボタンを押すと SampleControl の text に Binding している num_text が表示されます

C# コードは Binding するプロパティ作るのと ボタン押したときにそのプロパティを表示する処理だけです

[SampleWindow.xaml.cs]
public partial class SampleWindow : Window
{
    public class BindingData
    {
        public string num_text { get; set; }
    }

    private BindingData datacontext = new BindingData();

    public SampleWindow()
    {
        InitializeComponent();
        this.DataContext = this.datacontext;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        MessageBox.Show(this.datacontext.num_text);
    }
}

Binding ソースが変わらない

1 や 9999 を上の SampleControl の TextBox に入力してボタンを押します

このときは普通に 1 や 9999 とそのまま値が表示されています


ですが -1 など値が強制される処理があると 画面は 0 なのにボタンを押して表示される値は -1 などの入力したままです

対策

原因は入力した値が CoerceValueCallback を通すことなく Binding のソース側 (num_text) へそのまま更新されてしまい CoerceValueCallback の値はターゲット側 (text) だけに適用されて Binding のソースとターゲットで値が変わってしまっているからです

前に見つけた方法は PropertyChangedCallback の方で自分自身に代入する方法です
この代入で 通知が起きるので Binding のソース側が変更されます
同じ値を代入してるだけで ここで「変更」は起きないので無限ループにはなりません


コードはこうなります
(d, e) =>
{
    (d as SampleControl).text = (string)e.NewValue;
},

Register 全体は
public static readonly DependencyProperty textProperty =
    DependencyProperty.Register("text", typeof(string), typeof(SampleControl), new FrameworkPropertyMetadata(
        "0",
        (d, e) =>
        {
            (d as SampleControl).text = (string)e.NewValue;
        },
        (d, c) =>
        {
            var text = (string)c;
            int i;
            if (int.TryParse(text, out i))
            {
                return i < 0 ? "0" : i.ToString();
            }
            return "0";
        }));


これで一応解決
でも最終的に PropertyChangedCallback で代入するなら最初から CoerceValueCallback 使わずに PropertyChangedCallback の方で値修正して代入すればいいんじゃない?

というのが前回の結論

新たな問題

前回は気づかなかったのですが 重大な問題がありました

コードはそのままで 一度 -1 を入力してボタンを押します
このときはちゃんと 0 になります

次に 0 から -2 に変更して またボタンを押します

すると -2 と表示されます
画面は修正された 0 になっています


原因は CoerceValueCallback の修正結果が同じだと変更なしとみなされることです

前回の変更時の修正で text は "0" になっています
ここに -2 を入力してフォーカスを変えて 確定すると CoerceValueCallback を通して 値が "0" に修正されます
前回と比較するとどっちも "0" です

なので PropertyChangedCallback は実行されません
これが実行されないと SampleWindow で Binding している num_text は入力した -2 が入ったままです
CoerceValueCallback の結果が伝えられないので ボタンを押すと -2 になるわけです


対策ですが CoerceValueCallback を使わずに PropertyChangedCallback だけを使えばいいです
CoerceValueCallback を使ったらどうにもできないと思います
public static readonly DependencyProperty textProperty =
    DependencyProperty.Register("text", typeof(string), typeof(SampleControl), new FrameworkPropertyMetadata(
        "0",
        (d, e) =>
        {
            var instance = (SampleControl)d;
            var text = instance.text;
            int i;
            if (int.TryParse(text, out i))
            {
                instance.text = i < 0 ? "0" : i.ToString();
            }
            instance.text = "0";
        }));