Comments 61
Никаких конвертеров вам больше не надо.
bool IsButtonDisabled
, другое Visibility TextVisibility
и не забывать их оба обновлять. На примерах из реального мира таких свойств будет еще больше.Сам смысл моделей в отделении логики от интерфейса, и использовать там UI-типы — это очень, очень грязно.
Захотели добавить ещё одно условие к видимости контрола — дописали строчку кода, в разметку лезть не надо.
Логику поведения (команда X не разрешена для выполнения, если выполняется в данный момент), а не отображения (кнопка для вызова команды X должна быть невидимой).
Специфическим для View типам во ViewModel места нет по построению.
Да, вьюмодели описывают состояние View. Но от View они не должны зависить, в том числе от перечесления System.Windows.Visability. Сегодня вы используете в качестве View WPF, завтра UWP, где это перечисление Windows.UI.Xaml.Visibility, а послезавтра решите подключить Xamarin, где вообще нет аналога для этого перечисления.
Но кроме Xamarin есть еще и разница между Winphone Silverlight и WinRt/UWP, для них во времена win8 как раз и придумали SharedProject, чтоб вьюмодели там лежали.
Да, мне очень помог Caliburn.Micro, позволивший не пробрасывать из V во VM *EventArg и не городить лишних зависимостей. А там где нужно было завязаться на специфические API — все решалось через DI.
Годится такой пример?
Согласен, для этого сущевстуют маппинги
Это совсем не рабочий вариант.
ViewModel не должна ничего знать о визуализации, ее ответственность — давать необходимую информацию и доступ к командам.
В любом случае нужен взвешенный подход. Нельзя однозначно отказаться от конвертеров, ровно как и от подхода создания дополнительных свойств. Наверное, только с опытом придет понимания, как лучше поступить в том или ином случае. А универсальных и строго формализованных правил на этот счет сформулировать трудно.
Чем больше гранулярность, тем сложнее поддерживать приложение. Когда новый программист взглянет на код и увидит отдельно свойства
TextVisibility
и IsButtonDisabled
, ему будет абсолютно неочевидно, что их всегда нужно использовать вместе. Так в проект могут проникнуть трудноуловимые логические баги.Не должно быть в интерфейсе ViewModel понятий "видимость текста" и "доступность кнопки"
По определению паттерна MVVM.
- View и никто другой отвечает за визуализацию
- ViewModel отвечает за поведение интерфейса пользователя (индикаторы, команды, поток UI), но не за то, как это будет отображаться.
- Model отвечает за API
ViewModel отвечает за поведение интерфейса пользователя (индикаторы, команды, поток UI), но не за то, как это будет отображаться.
Другими словами, за состояние UI. включать или не включать отображение конкретного текста в бизнесс-логику решается к каждом конкретном случае. Не вижу причин принципиально этого не делать. Особенно если состояние кнопки и надписи независимы.
То будет ли текст отображаться, и является ли кнопка активной это не то, как рисуется View. View каждое из этих состояний может трактовать десятком различный способов, определять анимацию переходов из одного состояния в другое. Но выносить логику показа надписи в конвертер — это головная боль в будущем.
видимость текстаСоглашусь.
доступность кнопкиВозражу — доступность кнопки может быть обусловлена бизнес-правилами, а не UI. Если рассуждать не в контексте Кнопка, Доступность Кнопки, а в категориях Команда (отправка данных на сервер), Доступность Команды (не все поля заполнены), то вполне разумно в VM добавить такую функциональность, которую, к тому же, легко проверить тестами.
ПС.
вообще я не уверен что MethodInfo.Invoke() работает так же быстро, как вызов метода напрямуюРефлексия работает очень медленно, но в данном случае она вызывается редко.
ППС. Я буду обновлять комментарии
Reflection
— по-моему, это стрельба по воробьям даже не из пушки, а из BFG9000 с орбиты.Больше всего меня смущает использование строк для описания имени метода. Например, R# умеет статически проверять синтаксис биндингов и предупредит, если в имени конвертера опечатка. Здесь же мы об этом узнаем только в рантайме.
public abstract class ConverterBase<T> : MarkupExtension, IValueConverter where T: class, new()
{
private static T instance;
static ConverterBase()
{
ConverterBase<T>.instance = Activator.CreateInstance<T>();
}
protected ConverterBase()
{
}
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();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (ConverterBase<T>.instance == null)
{
ConverterBase<T>.instance = Activator.CreateInstance<T>();
}
return ConverterBase<T>.instance;
}
public static T Instance
{
get
{
return ConverterBase<T>.instance;
}
}
}
internal class DelayToTimeSpanConverter : ConverterBase<DelayToTimeSpanConverter>
{
public override object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
if (ReferenceEquals(value, null))
{
return DependencyProperty.UnsetValue;
}
return TimeSpan.FromMilliseconds(System.Convert.ToDouble(value));
}
}
<TextBlock Text="{Binding Path=Span, Converter={conv:DelayToTimeSpanConverter}, StringFormat=hh\\:mm\\:ss}" />
Есть один пакет, имхо, как раз для удобства создания конвертеров, там всего 2 женерик абстрактных класса, но с ними гораздо удобнее, чем IValueConverter каждый раз реализовывать: https://www.nuget.org/packages/AgentFire.Wpf.ValueConverters/
Конвертеры же либо качуют из проекта в проект, либо пишутся какие-то специфические. В первом случае они вообще никогда не редактируются. А во втором… Зачем их тоже редактировать-то часто?
А почему используется слово «биндинг»?
Я всегда читал на английский манер — «байндинг».
Да и на dictionary.com указано так же
"биндинг" быстрее
А потому, что "произносить на английский манер" — реализация негодной цели негодными средствами.
Общаетесь на русском — говорите по-русски, на английском — по-английски.
Пародия на английское произношение в русском — типичный антипаттерн, носителям русккого неудобно, носителям английского вообще фиолетово.
Другое дело, когда термин перешел в профессиональный жаргон. Тогда, конечно, source code становится сорцами, а view — вьюхой.
Думаю тут именно второй вариант.
На мой взгляд, если используется термин из английского языка, то и произносить его следует так, как носители этого языка.
Такой взгляд — антипаттерн. Правильное произношение носителям другого языка скорее вредно, чем бесполезно, и в большинстве случаев недоступно. Заимствование иностранных слов по построению включает в себя произношение, характерное для родного.
Внесение в русский пародии на инглиш приведет только к затруднениям в общении с носителями русского.
Общаетесь на русском — говорите по-русски, на английском — по-английски.Следуя этой логике нужно сказать не биндинг, а привязка.
А вместо "клизма" — "задослаб"
Заимствование обычно полезно, когда позволяет сократить наименование или сузить смысл понятия.
Использование слова "биндинг" может быть оправдано в контекте разговора о WPF так как подразумевает не привязку "вообще", а конкретную ее реализацию. В результате заимствованное слово получается точнее, чем оба оригинальных ("привязка" и "binding")
namespace Converters
{
public abstract class BaseConverter : IValueConverter
{
protected abstract object Convert(object value);
protected virtual object ConvertBack(object value)
{
return value;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return Convert(value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return ConvertBack(value);
}
}
public abstract class BaseConverter<T> : BaseConverter
{
protected abstract object Convert(T value);
protected override object Convert(object value)
{
return Convert(value is T ? (T)value : default(T));
}
}
public abstract class BaseConverter<T1, T2> : BaseConverter
{
protected abstract T2 Convert(T1 value);
protected override object Convert(object value)
{
return Convert(value is T1 ? (T1)value : default(T1));
}
}
}
Может и не лучшая реализация, но для моих 90% случаев она подходит.
Главное — это понимать, что конвертеры — это некоторые «хелперы» слоя вьюшки, и они должны быть максимально отделены от других слоев. Т.е. когда у меня возникает соблазн или необходимость конвертировать какой либо класс из вьюмодел или модел, то я сперва сильно задумаюсь. а нельзя ли это сделать каким либо другим архитектурным решением. Таким образом довольно быстро формируется набор «стандартных» конвертов, которые живут в своей библиотечке.
В качестве уменьшения кода, вдохновившись статьёй, быстро сообразил свой велосипед, который состоит из одной реализации интерфейса IValueConverter (мультиконверторы и мультибиндинг я стараюсь не использовать, но добавить будет не сложно). Если это уже кем-то реализовано, то прошу прощения.
namespace Converters
{
public delegate object ConvertHandler(object value, Type targetType, object parameter, CultureInfo culture);
public class ValueConverter : IValueConverter
{
public ConvertHandler ConvertHandler { get; set; }
public ConvertHandler ConvertBackHandler { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ConvertHandler?.Invoke(value, targetType, parameter, culture) ?? value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return ConvertBackHandler?.Invoke(value, targetType, parameter, culture) ?? value;
}
}
public class ConvertersFactory
{
public static string NullableValue { get; set; }
public static ConvertHandler IntToString => (value, type, parameter, culture) => value.ToString();
public static ConvertHandler NullableIntToString => (value, type, parameter, culture) => value is int ? value.ToString() : NullableValue;
}
}
<UserControl x:Class="ConverterControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Converters">
<UserControl.Resources>
<local:ValueConverter x:Key="IntToStringConverter"
ConvertHandler="{x:Static local:ConvertersFactory.NullableIntToString}" />
</UserControl.Resources>
<Grid>
<TextBlock Text="{Binding Converter={StaticResource IntToStringConverter}}"/>
</Grid>
</UserControl>
Может не очень красиво, но кода получается немного меньше, есть некая гибкость, возможность запихнуть все конвертеры в один класс, который вообще не зависит от wpf. Минус — статические классы, по-быстрому не смог сообразить, почему кзамл не хочет динамически биндить делегаты.
Но самое главное, для стороннего WPF-разработчика код выглядит более-менее стандартно.
Детали тут:
habr.com/ru/post/526450
Упрощаем конвертеры для WPF