Я видел онлайн два разных подхода к расширению IValueConverter. Один из них расширил ValueConverter от MarkupExtension, другой - от DependencyObject. Я не могу распространяться от обоих, поэтому мне интересно, лучше ли кто-нибудь другой?
Улучшен IValueConverter - MarkupExtension или DependencyObject?
Ответ 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
(или его простой производной), если вы действительно не думаете, что вам понадобится виртуальное ветвление.