📜 ⬆️ ⬇️

Nested bindings in WPF

There are three kinds of bindings in WPF: Binding , PriorityBinding and MultiBinding . All three bindings are inherited from the same base class BindingBase . PriorityBinding and MultiBinding allow you to bind several other bindings to one property, for example:

<MultiBinding Converter="{StaticResource JoinStringConverter}" ConverterParameter=" "> <Binding Path="FirstName" /> <Binding Path="MiddleName" /> <Binding Path="LastName" /> </MultiBinding> 

The source code of the JoinStringConverter class
 public class JoinStringConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { var separator = parameter as string ?? " "; return string.Join(separator, values); } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { var separator = parameter as string ?? " "; return (value as string)?.Split(new[] { separator }, StringSplitOptions.None).Cast<object>().ToArray(); } } 


The MultiBinding list of bindings is a collection of type Collection <BindingBase> . It would be logical to assume that another MultiBinding can be used inside the MultiBinding.

 <MultiBinding Converter="{StaticResource JoinStringConverter}" ConverterParameter=" "> <Binding Path="MyProperty1" /> <MultiBinding Converter="{StaticResource JoinStringConverter}" ConverterParameter=", "> <Binding Path="MyProperty2" /> <Binding Path="MyProperty3" /> <Binding Path="MyProperty4" /> </MultiBinding> </MultiBinding> 

But when executing such a code, we catch the exception "The BindingCollection does not support elements of the MultiBinding type. Only the Binding type is allowed. " Why then was it to use Collection <BindingBase> , and not Collection <Binding> ? Because if we use Collection <Binding> , we would catch another exception " Binding cannot be used in the collection" Collection <Binding> "." Binding "can only be set in the DependencyProperty parameter of the DependencyObject object. ".

To solve the problem of nested bindings, the NestedBinding class was written, which allows you to use other Binding and NestedBinding bindings inside you.
NestedBinding class source code
 [ContentProperty(nameof(Bindings))] public class NestedBinding : MarkupExtension { public NestedBinding() { Bindings = new Collection<BindingBase>(); } public Collection<BindingBase> Bindings { get; } public IMultiValueConverter Converter { get; set; } public object ConverterParameter { get; set; } public CultureInfo ConverterCulture { get; set; } public override object ProvideValue(IServiceProvider serviceProvider) { if (!Bindings.Any()) throw new ArgumentNullException(nameof(Bindings)); if (Converter == null) throw new ArgumentNullException(nameof(Converter)); var target = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget)); if (target.TargetObject is Collection<BindingBase>) { var binding = new Binding { Source = this }; return binding; } var multiBinding = new MultiBinding { Mode = BindingMode.OneWay }; var tree = GetNestedBindingsTree(this, multiBinding); var converter = new NestedBindingConverter(tree); multiBinding.Converter = converter; return multiBinding.ProvideValue(serviceProvider); } private static NestedBindingsTree GetNestedBindingsTree(NestedBinding nestedBinding, MultiBinding multiBinding) { var tree = new NestedBindingsTree { Converter = nestedBinding.Converter, ConverterParameter = nestedBinding.ConverterParameter, ConverterCulture = nestedBinding.ConverterCulture }; foreach (var bindingBase in nestedBinding.Bindings) { var binding = bindingBase as Binding; var childNestedBinding = binding?.Source as NestedBinding; if (childNestedBinding != null && binding.Converter == null) { tree.Nodes.Add(GetNestedBindingsTree(childNestedBinding, multiBinding)); continue; } tree.Nodes.Add(new NestedBindingNode(multiBinding.Bindings.Count)); multiBinding.Bindings.Add(bindingBase); } return tree; } } 


Source Code for NestedBindingNode and NestedBindingsTree
 public class NestedBindingNode { public NestedBindingNode(int index) { Index = index; } public int Index { get; } public override string ToString() { return Index.ToString(); } } public class NestedBindingsTree : NestedBindingNode { public NestedBindingsTree() : base(-1) { Nodes = new List<NestedBindingNode>(); } public IMultiValueConverter Converter { get; set; } public object ConverterParameter { get; set; } public CultureInfo ConverterCulture { get; set; } public List<NestedBindingNode> Nodes { get; private set; } } 


NestedBindingConverter class source code
 public class NestedBindingConverter : IMultiValueConverter { public NestedBindingConverter(NestedBindingsTree tree) { Tree = tree; } private NestedBindingsTree Tree { get; } public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { var value = GetTreeValue(Tree, values, targetType, culture); return value; } private object GetTreeValue(NestedBindingsTree tree, object[] values, Type targetType, CultureInfo culture) { var objects = tree.Nodes.Select(x => x is NestedBindingsTree ? GetTreeValue((NestedBindingsTree)x, values, targetType, culture) : values[x.Index]).ToArray(); var value = tree.Converter.Convert(objects, targetType, tree.ConverterParameter, tree.ConverterCulture ?? culture); return value; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } 


Implemented NestedBinding through ordinary MultiBinding. But since MultiBinding cannot accept another MultiBinding, then the tree expands to the Binding list. The position of these Binding s and their converters are saved for further generation of the source tree in the NestedBindingConverter converter.
')


The converter receives the list of values ​​for all the binding references and the source tree structure. Then recursion is made to traverse the tree, and the values ​​of the converters are calculated.

An example of using NestedBinding:
 <TextBlock> <TextBlock.Text> <n:NestedBinding Converter="{StaticResource JoinStringConverter}" ConverterParameter=", "> <Binding Path="A" /> <n:NestedBinding Converter="{StaticResource JoinStringConverter}" ConverterParameter=" "> <Binding Path="B" /> <Binding Path="C" /> <n:NestedBinding Converter="{StaticResource JoinStringConverter}" ConverterParameter=""> <Binding Source="(" /> <Binding Path="D" /> <Binding Path="E" /> <Binding Source=")" /> </n:NestedBinding> </n:NestedBinding> <Binding Path="F" UpdateSourceTrigger="PropertyChanged" /> </n:NestedBinding> </TextBlock.Text> </TextBlock> 

At the output we get the string “A, BC (DE), F”.

Sources are uploaded to the GitHub repository .

Source: https://habr.com/ru/post/277157/


All Articles