Вложенные привязки в WPF

от автора

В WPF существует три вида привязок: Binding, PriorityBinding и MultiBinding. Все три привязки наследуются от одного базового класса BindingBase. PriorityBinding и MultiBinding позволяют к одному свойству привязать несколько других привязок, например:

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

Исходный код класса JoinStringConverter

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();     } } 

Список привязок MultiBinding-а — это коллекция типа Collection<BindingBase>. Логично было бы предположить, что внутри MultiBinding-а можно использовать еще один 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> 

Но при выполнении такого кода ловим исключение "BindingCollection не поддерживает элементы типа MultiBinding. Допускается только тип Binding.". Зачем же было тогда использовать Collection<BindingBase>, а не Collection<Binding>? А потому, что если использовать Collection<Binding>, мы бы поймали другое исключение "Binding нельзя использовать в коллекции «Collection<Binding>». «Binding» можно задать только в параметре DependencyProperty объекта DependencyObject.".

Для решения проблемы вложенных привязок был написан класс NestedBinding, который позволяет использовать внутри себя другие привязки Binding и NestedBinding.

Исходный код класса NestedBinding

[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;     } } 

Исходный код классов NestedBindingNode и 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

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();     } } 

Реализован NestedBinding через обычный MultiBinding. Но т.к. MultiBinding не может принимать другой MultiBinding, то дерево разворачивается в список Binding-ов. Позиция этих Binding-ов и их конвертеры сохраняются для дальнейшей генерации исходного дерева в конвертере NestedBindingConverter.

Конвертер получает список значений всех привязок Binding и структуру исходного дерева. Далее рекурсией производится обход дерева, и вычисляются значения конвертеров.

Пример использования 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> 

На выходе получаем строку «A, B C (DE), F».

ссылка на оригинал статьи https://habrahabr.ru/post/277157/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *