Я работаю над приложением WPF с представлениями, которые требуют многочисленных преобразований значений. Изначально моя философия (Вдохновленный частично этой оживленной дискуссии по XAML Disciples ) было то , что я должен сделать модель представления строго о поддержке данных требований зрения. Это означало, что любые преобразования значений, необходимые для преобразования данных в такие вещи, как видимости, кисти, размеры и т. Д., Будут обрабатываться с помощью преобразователей значений и многозначных преобразователей. Концептуально это казалось довольно элегантным. Модель представления и представление имели бы различную цель и были бы хорошо отделены. Будет проведена четкая грань между «данными» и «внешним видом».
Что ж, после того, как я попробовал эту стратегию "попытаться в старом колледже", у меня возникли сомнения, хочу ли я продолжать развиваться таким образом. Я на самом деле настоятельно рекомендую сбросить преобразователи значений и возложить ответственность за (почти) все преобразования значений прямо на руки модели представления.
Реальность использования преобразователей значений просто не соответствует очевидной ценности четко разделенных задач. Моя самая большая проблема с конвертерами значений заключается в том, что их утомительно использовать. Вы должны создать новый класс, реализовать IValueConverter
или преобразовать IMultiValueConverter
значение или значения object
в правильный тип, проверить DependencyProperty.Unset
(по крайней мере, для многозначных преобразователей), написать логику преобразования, зарегистрировать преобразователь в словаре ресурсов [см. Обновление ниже ] и, наконец, подключите конвертер, используя довольно многословный XAML (который требует использования магических строк как для привязки (ей), так и для имени конвертера[см. обновление ниже]). Процесс отладки тоже не пикник, поскольку сообщения об ошибках часто бывают загадочными, особенно в режиме разработки Visual Studio / Expression Blend.
Нельзя сказать, что альтернатива - сделать модель представления ответственной за все преобразования значений - это улучшение. Это вполне может быть связано с тем, что трава с другой стороны зеленее. Помимо потери элегантного разделения интересов, вы должны написать кучу производных свойств и убедиться, что вы добросовестно вызываете их RaisePropertyChanged(() => DerivedProperty)
при настройке базовых свойств, что может оказаться неприятной проблемой обслуживания.
Ниже приведен первоначальный список плюсов и минусов, которые позволяют моделям представления обрабатывать логику преобразования и покончить с преобразователями значений:
- Плюсы:
- Меньше общего количества привязок, так как устранены мульти-конвертеры
- Меньше волшебных строк (пути привязки
+ имена ресурсов конвертера) Больше не нужно регистрировать каждый конвертер (плюс ведение этого списка)- Меньше работы по написанию каждого конвертера (не требуется реализации интерфейсов или приведения)
- Может легко вводить зависимости, чтобы помочь с преобразованиями (например, таблицы цветов)
- Разметка XAML менее многословна и ее легче читать
- Повторное использование конвертера все еще возможно (хотя требуется некоторое планирование)
- Никаких загадочных проблем с DependencyProperty.Unset (проблема, которую я заметил с многозначными преобразователями)
* Зачеркивания указывают на преимущества, которые исчезают, если вы используете расширения разметки (см. Обновление ниже)
- Минусы:
- Более сильная связь между моделью представления и представлением (например, свойства должны иметь дело с такими понятиями, как видимость и кисти)
- Больше общих свойств, позволяющих прямое сопоставление для каждой привязки в представлении
(см. обновление 2 ниже)RaisePropertyChanged
должен вызываться для каждого производного свойства- Необходимо по-прежнему полагаться на преобразователи, если преобразование основано на свойстве элемента пользовательского интерфейса
Итак, как вы, вероятно, можете сказать, у меня изжога по этому вопросу. Я очень не решаюсь идти по пути рефакторинга, только чтобы понять, что процесс кодирования столь же неэффективен и утомителен, использую ли я преобразователи значений или выставляю многочисленные свойства преобразования значений в моей модели представления.
Я пропускаю какие-либо плюсы / минусы? Для тех, кто попробовал оба способа преобразования стоимости, какой из них, по вашему мнению, работал лучше для вас и почему? Есть ли другие альтернативы? (Ученики упомянули кое-что о поставщиках дескрипторов типов, но я не мог понять, о чем они говорили. Любое понимание этого будет оценено.)
Обновить
Сегодня я обнаружил, что можно использовать то, что называется «расширением разметки», чтобы исключить необходимость регистрации преобразователей значений. Фактически, это не только устраняет необходимость их регистрации, но и фактически обеспечивает интеллектуальный смысл для выбора конвертера при вводе Converter=
. Вот статья, с которой я начал: http://www.wpftutorial.net/ValueConverters.html .
Возможность использовать расширение разметки несколько меняет баланс в моем списке плюсов и минусов и обсуждении выше (см. Зачеркивание).
В результате этого откровения я экспериментирую с гибридной системой, в которой я использую конвертеры для BoolToVisibility
того, что я называю, MatchToVisibility
и модель представления для всех других конверсий. MatchToVisibility - это в основном конвертер, который позволяет мне проверить, соответствует ли связанное значение (обычно перечисление) одному или нескольким значениям, указанным в XAML.
Пример:
Visibility="{Binding Status, Converter={vc:MatchToVisibility
IfTrue=Visible, IfFalse=Hidden, Value1=Finished, Value2=Canceled}}"
По сути, это проверка, является ли статус «Завершено» или «Отменено». Если это так, то видимость получает значение «Видимый». В противном случае он получает наборы «Скрытые». Это оказалось очень распространенным сценарием, и этот конвертер спас мне около 15 свойств в моей модели представления (плюс соответствующие операторы RaisePropertyChanged). Обратите внимание, что при Converter={vc:
вводе «MatchToVisibility» отображается в меню intellisense. Это заметно снижает вероятность ошибок и делает использование преобразователей значений менее утомительным (вам не нужно запоминать или искать нужное имя преобразователя значений).
Если вам интересно, я вставлю код ниже. Одной из важных особенностей данной реализации MatchToVisibility
является то , что он проверяет, если оценка стоимости является enum
, и , если он есть, он проверяет , чтобы убедиться Value1
, Value2
и т.д., также перечислений одного и того же типа. Это обеспечивает проверку времени разработки и выполнения того, были ли какие-либо из значений перечисления опечатаны. Чтобы улучшить это до проверки во время компиляции, вы можете использовать следующее (я набрал это вручную, поэтому, пожалуйста, простите меня, если я сделал какие-либо ошибки):
Visibility="{Binding Status, Converter={vc:MatchToVisibility
IfTrue={x:Type {win:Visibility.Visible}},
IfFalse={x:Type {win:Visibility.Hidden}},
Value1={x:Type {enum:Status.Finished}},
Value2={x:Type {enum:Status.Canceled}}"
Хотя это безопаснее, это слишком многословно, чтобы стоить для меня. Я мог бы также просто использовать свойство в модели представления, если я собираюсь сделать это. Во всяком случае, я обнаружил, что проверка во время разработки вполне подходит для сценариев, которые я пробовал до сих пор.
Вот код для MatchToVisibility
[ValueConversion(typeof(object), typeof(Visibility))]
public class MatchToVisibility : BaseValueConverter
{
[ConstructorArgument("ifTrue")]
public object IfTrue { get; set; }
[ConstructorArgument("ifFalse")]
public object IfFalse { get; set; }
[ConstructorArgument("value1")]
public object Value1 { get; set; }
[ConstructorArgument("value2")]
public object Value2 { get; set; }
[ConstructorArgument("value3")]
public object Value3 { get; set; }
[ConstructorArgument("value4")]
public object Value4 { get; set; }
[ConstructorArgument("value5")]
public object Value5 { get; set; }
public MatchToVisibility() { }
public MatchToVisibility(
object ifTrue, object ifFalse,
object value1, object value2 = null, object value3 = null,
object value4 = null, object value5 = null)
{
IfTrue = ifTrue;
IfFalse = ifFalse;
Value1 = value1;
Value2 = value2;
Value3 = value3;
Value4 = value4;
Value5 = value5;
}
public override object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
var ifTrue = IfTrue.ToString().ToEnum<Visibility>();
var ifFalse = IfFalse.ToString().ToEnum<Visibility>();
var values = new[] { Value1, Value2, Value3, Value4, Value5 };
var valueStrings = values.Cast<string>();
bool isMatch;
if (Enum.IsDefined(value.GetType(), value))
{
var valueEnums = valueStrings.Select(vs => vs == null ? null : Enum.Parse(value.GetType(), vs));
isMatch = valueEnums.ToList().Contains(value);
}
else
isMatch = valueStrings.Contains(value.ToString());
return isMatch ? ifTrue : ifFalse;
}
}
Вот код для BaseValueConverter
// this is how the markup extension capability gets wired up
public abstract class BaseValueConverter : MarkupExtension, IValueConverter
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public abstract object Convert(
object value, Type targetType, object parameter, CultureInfo culture);
public virtual object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Вот метод расширения ToEnum
public static TEnum ToEnum<TEnum>(this string text)
{
return (TEnum)Enum.Parse(typeof(TEnum), text);
}
Обновление 2
С тех пор как я разместил этот вопрос, я натолкнулся на проект с открытым исходным кодом, который использует «IL-ткачество» для внедрения кода NotifyPropertyChanged для свойств и зависимых свойств. Это делает реализацию видения Джоша Смита модели представления как «преобразователя стоимости на стероидах» абсолютным бризом. Вы можете просто использовать «Автоматически реализованные свойства», а ткач сделает все остальное.
Пример:
Если я введу этот код:
public string GivenName { get; set; }
public string FamilyName { get; set; }
public string FullName
{
get
{
return string.Format("{0} {1}", GivenName, FamilyName);
}
}
... это то, что компилируется:
string givenNames;
public string GivenNames
{
get { return givenName; }
set
{
if (value != givenName)
{
givenNames = value;
OnPropertyChanged("GivenName");
OnPropertyChanged("FullName");
}
}
}
string familyName;
public string FamilyName
{
get { return familyName; }
set
{
if (value != familyName)
{
familyName = value;
OnPropertyChanged("FamilyName");
OnPropertyChanged("FullName");
}
}
}
public string FullName
{
get
{
return string.Format("{0} {1}", GivenName, FamilyName);
}
}
Это огромная экономия в объеме кода, который вы должны набирать, читать, прокручивать и т. Д. Однако, что более важно, это избавляет вас от необходимости выяснять, каковы ваши зависимости. Вы можете добавлять новые свойства, например, FullName
без необходимости тщательно продвигаться по цепочке зависимостей для добавления в RaisePropertyChanged()
вызовы.
Как называется этот проект с открытым исходным кодом? Первоначальная версия называется «NotifyPropertyWeaver», но владелец (Саймон Поттер) с тех пор создал платформу «Fody» для размещения целой серии ткачей IL. Эквивалент NotifyPropertyWeaver под этой новой платформой называется PropertyChanged.Fody.
- Ложные инструкции по установке: http://code.google.com/p/fody/wiki/SampleUsage (замените «Virtuosity» на «PropertyChanged»)
- Сайт проекта PropertyChanged.Fody: http://code.google.com/p/propertychanged/
Если вы предпочитаете использовать NotifyPropertyWeaver (который немного проще в установке, но не обязательно будет обновляться в будущем после исправления ошибок), вот сайт проекта: http://code.google.com/p/ notifypropertyweaver /
В любом случае, эти решения для IL-ткача полностью меняют исчисление в споре между моделью представления о стероидах и преобразователях стоимости.
MatchToVisibility
казалось удобным способом включить некоторые простые переключатели режимов (у меня есть одно представление, в частности, с кучей деталей, которые можно включать и выключать. В большинстве случаев участки вида даже помечаются (с x:Name
), чтобы соответствовать режиму они соответствуют.) Мне действительно не приходило в голову, что это «бизнес-логика», но я подумаю над вашим комментарием.
BooleanToVisibility
принимает одно значение, которое связано с видимостью (true / false), и переводит его в другое. Это похоже на идеальное использованиеValueConverter
. С другой стороны,MatchToVisibility
кодирует бизнес-логику вView
(какие типы элементов должны быть видны). По моему мнению, эта логика должна быть направлена внизViewModel
или даже дальше в то, что я называюEditModel
. То, что может видеть пользователь, должно быть проверено.