Подтвердить что ты не робот

Улучшен IValueConverter - MarkupExtension или DependencyObject?

Я видел онлайн два разных подхода к расширению IValueConverter. Один из них расширил ValueConverter от MarkupExtension, другой - от DependencyObject. Я не могу распространяться от обоих, поэтому мне интересно, лучше ли кто-нибудь другой?

4b9b3361

Ответ 1

Получение от каждого дает вам различную мощность и гибкость:

  • Вывод из MarkupExtension позволяет использовать конвертер значений без статического ресурса, как описано ниже:

    public class DoubleMe : MarkupExtension, IValueConverter
    {
       public override object ProvideValue(IServiceProvider serviceProvider)
       {
          return this;
       }
       public object Convert(object value, /*rest of parameters*/ )
       {
          if ( value is int )
             return (int)(value) * 2; //double it
          else
             return value.ToString() + value.ToString();
       }
      //...
    }
    

    В XAML вы можете напрямую использовать его без создания StaticResource:

    <TextBlock Text="{Binding Name, Converter={local:DoubleMe}}"/>
    <TextBlock Text="{Binding Age, Converter={local:DoubleMe}}"/>
    

    Такой код очень удобен при отладке, поскольку вы можете просто написать local:DebugMe, а затем отладить DataContext элемента управления, на котором вы его используете.

  • Вывод из DependencyObject позволяет вам сконфигурировать конвертер значений с некоторыми настройками более выразительным образом, как описано ниже:

    public class TruncateMe : DependencyObject, IValueConverter
    {
         public static readonly DependencyProperty MaxLengthProperty =
             DependencyProperty.Register("MaxLength",
                                          typeof(int),
                                          typeof(TruncateMe),
                                          new PropertyMetadata(100));
         public int MaxLength
         {
             get { return (int) this.GetValue(MaxLengthProperty); }
             set { this.SetValue(MaxLengthProperty, value); }
         }
    
         public object Convert(object value, /*rest of parameters*/ )
         {
            string s = value.ToString();
            if ( s.Length > MaxLength)
              return s.Substring(0, MaxLength) + "...";
          else
              return s;
         }
         //...
    }
    

    В XAML вы можете напрямую использовать его как:

    <TextBlock>
       <TextBlock.Text>
           <Binding Path="FullDescription">
               <Binding.Converter>
                 <local:TruncateMe MaxLength="50"/>
               </Binding.Converter>
           </Binding>
       </TextBlock.Text> 
    

    Что он делает? Он обрезает строку FullDescription, если она больше, чем 50 символов!

@crazyarabian прокомментировал, что:

Ваше утверждение "Получение из DependencyObject позволяет вам сконфигурировать конвертер значений с некоторыми предпочтениями более выразительным образом" не является исключительным для DependencyObject, поскольку вы можете создать одно и то же свойство MaxLength в MarkupExtension, что приведет к <TextBlock Text="Binding Age, Converter={local:DoubleMe, MaxLength=50}}"/>. Я бы сказал, что MarkupExtension является более выразительным и менее подробным.

Это верно. Но тогда это не связано; то есть, когда вы выходите из MarkupExtension, вы не можете делать:

MaxLength="{Binding TextLength}"

Но если вы извлечете конвертер из DependencyObject, вы можете сделать это. В этом смысле он более выразителен по сравнению с MarkupExtension.

Обратите внимание, что для свойства target должно быть DependencyProperty для Binding. MSDN говорит,

  • Каждая привязка обычно имеет эти четыре компонента: цель привязки объект, свойство target, привязка источник и путь к значению в источник связывания для использования. Например, если вы хотите связать содержимое TextBox для свойства Name Объект Employee, ваш целевой объект TextBox, целевое свойство свойство Text, используемое значение Имя, а исходным объектом является Объект сотрудника.

  • Свойство target должно быть зависимым.

Ответ 2

Поскольку моя библиотека вы цитируете в качестве примера конвертеров, расширяющих DependencyObject, я думаю, что это подходит, чтобы объяснить себя.

Я действительно начал с простого внедрения IValueConverter с Object в качестве базового класса. Единственная причина, по которой я переключился на расширение DependencyObject, состояла в том, чтобы разрешить технику - впервые предложенную Джошем Смитом - называемую виртуальную ветвь. Вы можете прочитать об этой технике здесь.

Предположим, вы хотите сделать что-то вроде этого:

<UserControl.Resources>
    <con:CaseConverter Casing="{Binding SomeProperty}"/>
</UserControl.Resources>

Это не будет работать, потому что ресурсы не являются частью визуального дерева, и поэтому привязка не удастся. Виртуальная ветка взламывает эту маленькую дилемму, позволяя вам выполнять такую ​​привязку. Тем не менее, он по-прежнему полагается - как и любая другая привязка WPF - на целевом объекте есть DependencyObject. Таким образом, если бы я просто реализовал IValueConverter без расширения DependencyObject, вам было бы запрещено использовать виртуальные ветки.

Теперь, если я полностью честен, я не уверен, что все равно это сделаю, если у меня снова будет время. Мне никогда не приходилось использовать виртуальную ветку - я просто хотел включить сценарий. Я даже могу изменить это в будущей версии моей библиотеки. Поэтому мой совет будет придерживаться базового класса Object (или его простой производной), если вы действительно не думаете, что вам понадобится виртуальное ветвление.