WPF のバインディングは便利で強力ですが、ちょっとした設計の違いで後から修正がとても辛くなることがあります。
私自身も最初の頃、ItemsControl 内の DataTemplate で親 ViewModel のコマンドを呼び出す という単純な実装で、後から UI をリファクタリングしたときに何時間もハマった経験があります。
「便利だからこう書いたけど、後で困った…」というパターン、意外と多いんです。
この記事では、実務でよく遭遇する典型的なハマりポイント3つを整理し、それぞれの回避策も紹介します。
典型パターン1:DataContext が深すぎる階層でのバインディング
ポイント
- ItemsControl / DataTemplate 内で親 ViewModel のプロパティを参照すると辛い
- RelativeSource や ElementName が多用され、修正が大変
コード例
<ItemsControl ItemsSource="{Binding Orders}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding CustomerName}"/>
<Button Content="キャンセル"
Command="{Binding DataContext.CancelCommand,
RelativeSource={RelativeSource AncestorType=Window}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
回避策
ViewModel 側で子アイテムに必要なコマンドをラップして渡す
DataContext を深くしない設計を意識する
public class OrderItemViewModel
{
public string CustomerName { get; set; }
public ICommand CancelCommand { get; set; }
}
典型パターン2:TwoWay Binding なのに更新タイミングが合わない
ポイント
- TextBox.Text のデフォルト更新タイミングは LostFocus
- Command や Validation と衝突することがある
コード例
<TextBox Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
回避策
- UpdateSourceTrigger=PropertyChanged で即時反映を指定
- 文字入力中に ViewModel が最新状態になるように設計
典型パターン3:コレクションと選択状態の同期ミス
ポイント
- ObservableCollection の差し替えで SelectedItem が null になる
- ListView / ListBox でありがちなパターン
コード例
SelectedUser = null;
Users.Clear();
Users.Add(newUser1);
Users.Add(newUser2);
回避策
- Collection を差し替えず、Clear/Add で更新
- SelectedItem は明示的にリセット/設定
図解:典型パターンまとめ
深すぎるDataContext、更新タイミングのズレ、選択状態同期ミスの例

まとめ
- DataContext は浅く保つ:深い階層の RelativeSource は後から辛い
- TwoWay Binding の更新タイミングに注意:TextBox なら UpdateSourceTrigger を確認
- コレクションと選択状態の同期は慎重に:SelectedItem の扱いを意識する
関連記事(内部リンク)
WPF開発で最初に知っておきたかったこと
MVVMを意識していたのに、ViewModelが壊れ始めた話
WPFでよくあるCommandハマりポイント3選+回避策【初心者でも安心】