Как выбрать нужный Converter в XAML, в зависимости от значения свойства во ViewModel?
У меня в приложении есть таблица цен, значения которой фактически представляют собой тип long, но для отображения в UI, необходимо делить эти значения на 100 с помощью конвертера:
class PenniesToRubleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int multiplier = 100;
return (double) Math.Round(System.Convert.ToDouble(value), 0) / multiplier;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
int multiplier = 100;
try
{
var newValue = (value ?? 0f).ToString().Replace(".", ",");
return System.Convert.ToSingle(newValue) * multiplier;
}
catch (Exception)
{
return string.Empty;
}
}
}
Код XAML из View, описывающий применение конвертера к конкретной колонке с ценами:
<DataGridTextColumn Binding="{Binding Path=OrderMaxPrice, Converter={StaticResource penniesToRubleConverter}}" IsReadOnly="True" Width="0.5*">
<DataGridTextColumn.Header>
<Label Foreground="{DynamicResource White}" Content="Макс. ордер"/>
</DataGridTextColumn.Header>
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
В чем суть проблемы:
В приложение добавилась поддержка валюты USD, значения которой имеют другую разрядность (тот же long, но на порядок больше - до десятых долей центов), и конвертер PenniesToRubleConverter не подходит для конвертации в подходящий для View формат. Делить теперь нужно на 1000.
Пробовал подставить ConverterParameter, но это не работает. Первая проблема этого подхода - ConverterParameter не принимает Binding в качестве параметра, и я не могу использовать выбранную валюту во ViewModel для передачи в конвертер.
Может есть способ назначать сам конвертер в зависимости от выбранной валюты во ViewModel? Например, если валюта окна - USD, то использовать CentsToDollarConverter, с делением на 1000, а если RUB - использовать существующий PenniesToRubleConverter?
Либо может есть другой способ обойти ограничение на привязку Binding к ConverterParameter?
Ответы (1 шт):
Нашёл решение, которое может подойти в некоторых случаях лучше, чем MultiBinding
и IMultiValueConverter
(Спасибо @EvgeniyZ за помощь в поиске информации по вопросу).
Я решил проблему через proxy-объект, выступающий в качестве DataContext
.
В VM я создал два свойства bool RubMode
и bool UsdMode
, сделал в XAML по две копии всех колонок, и отображаю их в зависимости от того, какая валюта задана в VM.
<DataGridTextColumn Visibility="{Binding Data.RubMode, Converter={StaticResource BooleanToVisible}, Source={StaticResource proxy}}" Binding="{Binding Path=MinPrice, Converter={StaticResource penniesToRubleConverter}}">
<DataGridTextColumn Visibility="{Binding Data.UsdMode, Converter={StaticResource BooleanToVisible}, Source={StaticResource proxy}}" Binding="{Binding Path=MinPrice, Converter={StaticResource centsToDollarsConverter}}">
В моей задаче никак не избежать нарушения принципа DRY. Либо мы сильно раздуваем разметку, описывая Multibinding
, и одинакового кода у нас добавляется больше в 7-10 местах, и при описании конвертера нужно очень серьезно поработать над обратной конвертацией, т.к. в методе ConvertBack
конвертера мы принимаем на входе один объект, который на входе не может хранить данные о валюте, только её "сконвертированное" значение.
Можно, конечно, распарсить string
-представление числа 741.012
, чтобы понять, что это USD цена, но когда число будет 741.00
, начнутся проблемы. К тому же точность у USD и EUR валют одинаковая.
Минус моего решения говорит сам за себя - на деле у нас две копии колонок, но в данном конкретном примере я его считаю несущественным, можно отрефакторить при наличии времени и желания (вплоть до изменения используемой визуальной технологии вместо XAML).