添付プロパティとBindingMode

2024年12月30日月曜日

C# WPF

記事のカテゴリー: C#、.NET 9、WPF

自作の添付プロパティを使用するとき、XAMLで指定するBindingModeによって変わる挙動についてまとめてみました。

サンプル

例として、TextBoxのSelectedTextプロパティ(選択範囲の文字を取得、設定するプロパティ)にバインドできるようにする添付ビヘイビアを用意します。OnBindableSelectedTextChangedメソッドでBindingModeを判断して必要な処理だけをする、という内容になっています。実際に動かしてみると、BindingModeがOneWayToSourceの場合は思った通りに動いてくれません。

C#:

  1. public static class TextBoxExtensions
  2. {
  3. // バインドできる SelectedText の添付プロパティ
  4. public static readonly DependencyProperty BindableSelectedTextProperty = DependencyProperty.RegisterAttached(
  5. "BindableSelectedText",
  6. typeof(string),
  7. typeof(TextBoxExtensions),
  8. new FrameworkPropertyMetadata(string.Empty, OnBindableSelectedTextChanged));
  9.  
  10. public static string GetBindableSelectedText(DependencyObject obj)
  11. {
  12. return (string)obj.GetValue(BindableSelectedTextProperty);
  13. }
  14.  
  15. public static void SetBindableSelectedText(DependencyObject obj, string value)
  16. {
  17. obj.SetValue(BindableSelectedTextProperty, value);
  18. }
  19.  
  20. private static void OnBindableSelectedTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  21. {
  22. if (d is not TextBox textBox)
  23. {
  24. return;
  25. }
  26.  
  27. BindingMode mode = BindingOperations.GetBinding(d, BindableSelectedTextProperty).Mode;
  28.  
  29. // モードが TwoWay か OneWayToSource なら TextBox のイベントを購読する (ターゲット -> ソース)
  30. if ((mode == BindingMode.TwoWay) || (mode == BindingMode.OneWayToSource))
  31. {
  32. WeakEventManager<TextBox, RoutedEventArgs>.AddHandler(
  33. textBox, nameof(textBox.SelectionChanged), TextBox_SelectionChanged);
  34. }
  35.  
  36. // モードが OneWayToSource 以外なら TextBox に反映する (ソース -> ターゲット)
  37. if (mode != BindingMode.OneWayToSource)
  38. {
  39. if ((e.NewValue is string text) && (text != textBox.SelectedText))
  40. {
  41. textBox.SelectedText = text;
  42. }
  43. }
  44. }
  45.  
  46. private static void TextBox_SelectionChanged(object? sender, RoutedEventArgs e)
  47. {
  48. if (sender is not TextBox textBox)
  49. {
  50. return;
  51. }
  52.  
  53. if (textBox.SelectedText != GetBindableSelectedText(textBox))
  54. {
  55. SetBindableSelectedText(textBox, textBox.SelectedText);
  56. }
  57. }
  58. }

XAML:

  1. <TextBox local:TextBoxExtensions.BindableSelectedText="{Binding SelectedText, Mode=TwoWay}" />

実際の挙動

添付ビヘイビアのOnBindableSelectedTextChangedメソッドをA、TextBox_SelectionChangedメソッドをBとしたとき、BindingModeによって挙動は次のように変わりました。

OneWayToSourceの場合は思った通りの挙動をしません。回避策としては、BindableSelectedText添付プロパティとは別にTextBoxのイベントを購読するための添付プロパティを用意して、そちらからBindableSelectedText添付プロパティの値を変更します(参照先にコードがあります)。

  • TwoWay: 思った通りの挙動をする
    • Aは呼ばれる
    • Bは呼ばれる
  • OneWay: 思った通りの挙動をする
    • Aは呼ばれる
    • Bは呼ばれない(未購読なので当然)
  • OneTime: 思った通りの挙動をする
    • Aは1回だけ呼ばれる
    • Bは呼ばれない(未購読なので当然)
  • OneWayToSource: 思った通りの挙動をしない
    • Aは呼ばれない
    • Bは呼ばれない(未購読なので当然)

参照