Наверно, некоторые разработчики сталкивались с той проблемой, что при использовании конвертеров в параметры конвертера не передаётся информации о представлении, его контексте данных либо самом визуальном элементе, к которому осуществлена привязка. С одной стороны это хорошо, получается некоторая защита и разделение логики, не слишком правильно в конвертере напрямую работать с контролом, с другой же стороны в редких случаях именно из-за такого ограничения приходится идти на различные ухищрения.
Старая добрая событийная модель по-прежнему не утратила своей актуальности даже несмотря на то, что получил развитие мощный и эффективный механизм привязки данных (Data Binding). Конечно, не стоит использовать события в ущерб прогрессивным средствам разработки, но иногда их применение получается удобным и естественным.
Почему бы не скомбинировать оба этих способа? К примеру, таким образом
using System.Windows.Data;
namespace Aero.Converters.Patterns
{
public interface ICompositeConverter : IValueConverter
{
IValueConverter PostConverter { get; set; }
object PostConverterParameter { get; set; }
}
}
using System;
using System.Globalization;
using System.Windows.Data;
namespace Aero.Converters.Patterns
{
public class ConverterEventArgs : EventArgs
{
public object ConvertedValue { get; set; }
public object Value { get; private set; }
public Type TargetType { get; private set; }
public object Parameter { get; private set; }
public CultureInfo Culture { get; private set; }
public ConverterEventArgs(object value, Type targetType, object parameter, CultureInfo culture)
{
TargetType = targetType;
Parameter = parameter;
Culture = culture;
Value = value;
}
}
public interface IInlineConverter : IValueConverter
{
event EventHandler<ConverterEventArgs> Converting;
event EventHandler<ConverterEventArgs> ConvertingBack;
}
//public interface IInlineConverter : IValueConverter
//{
// event Func<object, Type, object, CultureInfo, object> Converting;
// event Func<object, Type, object, CultureInfo, object> ConvertingBack;
//}
}
using System;
using System.Globalization;
using System.Windows.Data;
using Aero.Converters.Patterns;
namespace Aero.Converters
{
public class InlineConverter : IInlineConverter, ICompositeConverter
{
public IValueConverter PostConverter { get; set; }
public object PostConverterParameter { get; set; }
public event EventHandler<ConverterEventArgs> Converting;
public event EventHandler<ConverterEventArgs> ConvertingBack;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var args = new ConverterEventArgs(value, targetType, parameter, culture);
var handler = Converting;
if (handler != null) handler(this, args);
return PostConverter == null
? args.ConvertedValue
: PostConverter.Convert(args.ConvertedValue, targetType, PostConverterParameter, culture);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var args = new ConverterEventArgs(value, targetType, parameter, culture);
var handler = ConvertingBack;
if (handler != null) handler(this, args);
return PostConverter == null
? args.ConvertedValue
: PostConverter.ConvertBack(args.ConvertedValue, targetType, PostConverterParameter, culture);
}
}
}
Что мы получаем? Экземпляр конвертера нам нужно встроить в ресурсы контрола или представления, а в бехаин коде (Code Behind) представления сделать нужные обработчики для событий Converting и ConvertingBack, после чего эти события станут вызываться во время срабатывания привязки, а в обработчиках через указатель this будет доступно как само представление c визуальным деревом, так и контекст данных! Неожиданно получилась большая свобода действий, к тому же всё осталось идеологически верно, ведь в сам конвертер не попало интерфейсной логики, а она осталась лишь в бехаин коде.
Вот простой пример использования этого подхода
<Grid>
<Grid.Resources>
<InlineConverter x:Key="InlineConverter" Converting="InlineConverter_OnConverting"/>
</Grid.Resources>
<TextBlock Text="{Binding Number, Converter={StaticResource InlineConverter}}"/>
</Grid>
private void InlineConverter_OnConverting(object sender, ConverterEventArgs e)
{
e.ConvertedValue =
string.Format("Title: {0} \nDataContext:\n{1} \nConverter Value: {2}",
Title,
DataContext,
e.Value);
}
Дополнительное использование паттерна Composite Converter [ICompositeConverter ] позволяет объединять различные конвертеры в цепочки, модифицируя логику, без необходимости создания новых классов.
Увидеть в действии Inline Converter и некоторые другие можно в демонстрационном проекте HelloAero библиотеки Aero Framework [резервная ссылка].
Благодарю за внимание!
P.S. Предыдущая статья о динамическом Grid
Update
Применение
Необходимость в представленном методе на практике возникает нечасто, однако в то же время он может крайне облегчить жизнь в «неудобных» ситуациях. К примеру, таким образом можно заместить MultiBinding при портировании кода с WPF на Windows Phone, поскольку на мобильной платформе множественная привязка не поддерживается. Просто помните о встраиваемых конвертерах и в нужный момент вы найдёте для них применение.
Комментарии (6)
Makeman
29.01.2016 20:30Согласен, что такие ситуации скорее исключение из правил, но в то же время они очень «неудобные».
В примере свойство Text привязывается к свойству Number вью-модели через конвертер, но по условию к этому числу нужно добавить заголовок окна приложения.
Если мы будем делать дополнительное свойство Title во вью-модели, то она будет загрязняться интерфейсным кодом, а также придётся использовать что-то вроде MultiBinding, который, кстати, не на всех платформах поддерживается, плюс создавать IMultiValueConverter. Всё довольно усложняется несмотря на тривиальность задачи. С Inline Converter выглядит просто и понятно.
Также может возникнуть и другая ситуация: основная привязка свойства Text должна идти к свойству Number во вью-модели, но, например, в зависимости от нескольких других различных значений свойств этой вью модели, сама строка должна декорироваться тем или иным образом. Поскольку в обычном конвертере у нас нет доступа к самой вью-модели, приходится искать обходные пути, не всегда красивые. В нашем же случае есть прямой доступ к контесту данных представления, нужной нам вью-модели и всем её свойствам.bstdman
30.01.2016 16:05Описанный вами случай довольно просто решается через MultiBinding и RelativeSource, без использования конвертеров вообще. Другой вопрос мультиплатформенность.
Но опять же code behind — это лютое зло, которое проблематично тестировать. Для WPF при использовании паттерна MVVM рекомендуется его использование только для изменения состояния представления.Makeman
31.01.2016 19:33Каким бы ни было ужасным явление Code Behind, стоит признать, что в некоторых ситуациях его применение уместно. Конечно, тут важно не переусердствовать, поэтому ответственность лежит на разработчике.
Представленный в статье «трюк» позволяет обойти некоторые ограничения при выполнении привязок. Безусловно, когда мы получаем больше свободы стоит помнить о безопасности, однако и возможности наши возратают.
IL_Agent
31.01.2016 00:01В примере свойство Text привязывается к свойству Number вью-модели через конвертер, но по условию к этому числу нужно добавить заголовок окна приложения.
И вы предлагаете часть конвертации выполнять в самом конвертере, а другую часть — code behind. Это не хорошо. Вся логика должна быть в конвертере.Makeman
31.01.2016 19:40Не спорю, здесь есть к чему придраться за нарушение «правильной» идеологии :)
Но, на мой взгляд, лучше использовать универсальный способ для решения обширного круга проблемных ситуаций, чем каждый раз изобретать новый хак.
impwx
На первый взгляд выглядит как костыль, подпирающий архитектурную ошибку. Поэтому очень хочется реальный пример ситуации, когда конвертеру нужен контекст.