日記 3 BindingContext を子へ伝播したい
日記。
Xamarin.Forms で、CustomRenderer として、このように SementedControl(親)と SegmentedControlOption(子)コントロールを作っていた。
子のバインディング(SementedControl.Text)で失敗するケースがあって、
{Binding Hoge} だとバインディングできず、
{Binding Source={x:Reference aaa}, Path=BindingContext.Hoge}で明示するとバインディンが成功した。
で、デバッグをしてみると、子の SegmentedControlOption の Parent も BindingContext も null だった。
Xamarin.Forms では、一般的に親の BindingContext は子に伝播されると聞く。そして ListView では、ItemSource に指定したコレクションの要素以外を BindingContext に指定したい場合は、{x:Reference aaa} みたいに指定する。
この違いは、何だろう? 何の違いにより、親から子へ伝播する・しない が決まるのだろう?
これを解決したく、調べたり、人に聞いたりした。
私はてっきり、親も子も View を継承しているし、IViewContainer
Layout を継承するとうまくいく。
SegmentedControl にて、View を継承するのではなく、Layout を継承すると、SegmentedControlOption(子)には、Parent も BindingContext もちゃんと設定されていた。
しかし、Layout を継承した場合は、LayoutChildren を override しなければならず、無駄な実装が増えてしまう(return; しておいても一見うまくいったけど)。
OnBindingContextChanged で設定する
Table セクションの実装を参照し、次のように、子の BindingContext に値を設定すれば、バインディングできた。
protected override void OnBindingContextChanged() { base.OnBindingContextChanged(); foreach (SegmentedControlOption child in Children) { SetInheritedBindingContext(child, BindingContext); } }
BindableObject.cs の SetInheritedBindingContext:https://github.com/xamarin/Xamarin.Forms/blob/20e2e12dce2f81b92e8682f128cd81e08469f485/Xamarin.Forms.Core/BindableObject.cs#L94
結局?
Xamarin.Forms のコントロールを見ていると、自分で子の BindingContext を設定しているところが多いように見えた。
okazuki さんの Xamarin 入門を読んでいても、Behavior の BindingContext は自分で設定しないといけない。
つまり、何かを用意しておけば子に伝播する、という仕組みは無く、自分のコントロールは自分でなんとかする(BindingContextをセットする)みたいだ。
でも、Layout.cs の実装の中に、子の BindingContext をなんとかする、という記述は見つけられず、 ObservableCollection
ありがたくも、欲しいコントロールは OSS によくある。が、”こういうプロパティを ViewModel のこんなプロパティとバインディングしたい” となったときは自分でカスタムしなければいけない、ということが多いように思う(まあそうだろうけど…)。
参照した SegmentedControl:
GitHub - alexrainman/SegmentedControl
GitHub - chrispellett/Xamarin-Forms-SegmentedControl: An example for how to implement a cross platform segmented control for iOS and Android using Xamarin Forms