WPFでTextBoxにフォーカスが当たっている間更新を止めるにはOneTimeが適当

# やりたいこと

`TextBox`の値を入力している間は値更新を無効にしたい。`TextBox`のデフォルトの動作では、入力の途中でも更新される。

![やりたいこと.png](https://qiita-image-store.s3.amazonaws.com/0/12550/4e184284-071d-b8ce-7906-fd101db07eda.png)

# Binding Mode

`Binding`する際に `Mode`属性でデータフローを定義できる

```xml
<TextBox Text={Bidning Path=Value, Mode=Twoway} />
```

| 属性値 | 方向 | 内容 |
|:------|------|:-----|
| Two Way | TextBox <---> Value | TextBoxからの値をValueに反映するし、その逆もする |
| One Way | TextBox <---  Value | Valueの値のみを反映。TextBoxからは無視される |
| One Time | TextBox <---  Value | One Wayの動作に加えて、起動時かDataContext変更時一発のみ更新 |
| OneWayToSource | TextBox  ---> Value | TextBoxからの値更新のみ反映 |
| Default | *** | そのオブジェクトにデフォルトで設定されているModeが使用される。TextBoxの場合は Two Way |

上記の表を見る限り、`Value`の更新通知が来ないのは`OneWayToSource`のようなので、フォーカスが`TextBox`に移った時にこのプロパティに変更することで値更新を一時的に無効にできるのではないかと考えた。

# TextBoxのフォーカスが移った際にBinding ModeをOneWayToSourceに変更する

こんな感じにする。

```csharp
        private void value_GotFocus(object sender, RoutedEventArgs e)
        {
            var binding = new Binding("Value");
            binding.Mode = BindingMode.OneWayToSource;
            ((TextBox)sender).SetBinding(TextBox.TextProperty, binding);
        }
```

実行して、フォーカスを当ててみる。

![やりたいこと.png](https://qiita-image-store.s3.amazonaws.com/0/12550/6e85fb2b-4840-3691-e37b-2d2f6f0a78d1.png)

値が消えた。。。

# 値の更新タイミング

上記では、フォーカスイベントの中で`Binding`を作り直しているため、前回の`Binding`で取得していた `Value`プロパティの値が消えてしまった。`OneWayToSource`は`Value`の値更新を一切取得しないので、初期値が表示できず、消えてしまう。

では、作り直すのをやめてモードだけ変えるとどうだろうか。やってみたが例外が発生して変えることができない。これはC#の仕様のようで、一度バインドした`Binding`オブジェクトのモードは変更することはできない。よって、新たに`Binding`オブジェクトを作成するのは必須。ということは別のモードを使う必要がある。

# OneTimeを使う

`OneWayToSource`は`Value`プロパティの値を一切更新しないことが問題だった。では、フォーカスが移った際に初期時に一度だけ取得できればよいわけだ。それができるのが`OneTime`だ。上記コードの `OneWayToSource` を `OneTime` にし再度実行。

![やりたいこと.png](https://qiita-image-store.s3.amazonaws.com/0/12550/5a943aba-06e6-5a2a-a340-883b8eeaa8a7.png)

ちゃんと動いたようだ。

# まとめ

値の更新を止めたい場合は、フォーカスが移動したタイミングで`Binding`オブジェクトを生成し、`OneTime`モードでバインドすること

# 追記(2017/11/28)

DataGrid の DataGridTemplateColumn プロパティの場合だと、OneTime では、書き込んだときに View Model 側に通知が行かないことが判明。その場合は、DataGridTemplateColumn.CellEditingTemplate と DataGridTemplateCOlumn.CellTemplate で別のプロパティをバインドしておいて、書込み側では PropertyChanged イベントを発行しないような仕組みが必要。

以下、ソースの一部分。結構関係ないコードも入っちゃってるのでアレ。

```xml:test.xaml
        <DataGrid ItemsSource="{Binding Path=DataGridCollectionView}" SelectionChanged="DataGrid_SelectionChanged" AutoGenerateColumns="False" BeginningEdit="DataGrid_BeginningEdit">
            <DataGrid.Columns>
                <DataGridTemplateColumn Header="Type">
                    <DataGridTemplateColumn.CellEditingTemplate>
                                    <DataTemplate DataType="{x:Type local:Person}">
                                        <StackPanel>
<!-- Edit側は 通知の来ないAny2 をバインド -->
                                            <TextBox Text="{Binding Path=Any2, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" />
                                        </StackPanel>
                                    </DataTemplate>
                    </DataGridTemplateColumn.CellEditingTemplate>

                    <DataGridTemplateColumn.CellTemplate>
                                    <DataTemplate DataType="{x:Type local:Person}">
<!-- View側は 通知のくるAny をバインド -->
                                        <StackPanel>
                                            <TextBlock Text="{Binding Path=Any, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" />
                                        </StackPanel>
                                    </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
```

```csharp:test.cs
// VirtualPerson で INotifyPropertyChanged を実装していると仮定
    public class Person : VirtualPerson 
    {
        private string any;
        public string Any
        {
            get {return this.any;}

            set
            {
// こっちは呼ぶけど
                this.SetProperty(ref this.any, value);
            }
        }

        public string Any2
        {
            get {return this.any;}

            set
            {
// こっちは呼ばない
                this.any = value;
            }
        }
    }
```