С версии 2.0 она стала платной для коммерческого использования, но остаётся свободной для учебных целей и проектов с открытым исходным кодом. Стоимость лицензии составляет 30$ на одного разработчика.
Вложив небольшую сумму денег и день-два в углубленное изучение её особенностей, вы сохраните недели и месяцы труда на реальных проектах. Более того, даже если вы опытный разработчик, значительно повысите свой технический уровень, архитектурные навыки и системность мышления. В данный момент всё зависит лишь от вашей усидчивости и трудолюбия. Гарантирую, что оно того стоит.

Первым делом нужно загрузить библиотеку с официального сайта или альтернативных источников:
сайт,
dropbox,
onedrive,
google.drive, — после чего распаковать архив и открыть тестовые проекты HelloAero, BankOnline, Sparrow, — они помогут быстрее войти в курс дела и послужат живыми примерами.
Следует отметить, что библиотека достаточно легко внедряется в уже существующие решения, поэтому буквально сразу может быть использована в деле. Итак, рассмотрим что же в ней примечательного на первый взгляд.
1) Эвокаторы Свойств и Команд (Property and Command Evocators)
Чтобы подписаться на событие уведомления об изменении значения свойства вместо классической конструкции
PropertyChanged += (o, args) => { if (args.PropertyName == "Text") { ... }; }
достаточно использовать более лаконичный вариант на основе лямбда выражений
this[() => Text].PropertyChanged += (o, args) => { ... };
Парсинг выражения выполняется лишь единожды при инициализации, поэтому на производительность это не оказывает существенного влияния, но делает код намного более линейным и читаемым.
Обработчики для маршрутизируемых и контекстных команд задаются схожим образом.
this[MediaCommands.Play].CanExecute += (o, args) => args.CanExecute = /* conditions */; this[MediaCommands.Play].Executed += (o, args) => { ... }; this[Context.Make].CanExecute += (o, args) => args.CanExecute = /* conditions */; this[Context.Make].Executed += (o, args) => { ... };
Контекстные команды — кросс-платформенная и безопасная от утечек памяти реализация команд, отдалённо напоминающая логику работы маршрутизируемых команд в WPF.
2) Равноправные Инжекции путём Экспанирования (Equitable Injections via Exposable Way)
Классические инжекции зависимостей в конструктор вью-моделей не самая лучшая практика. Да, существует множество контейнеров, реализующих такую функциональность и распространён такой подход повсеместно, но он обладает целым рядом недостатков и накладывает большие ограничения на проектируемую систему. Об этом можно долго говорить, поэтому сейчас обозначим лишь суть проблемы: инжекции в конструктор создают иерархические зависимости, то есть при возникновении необходимости добраться из низкоуровневой вью-модели к высокоуровневой без сомнительных решений нельзя. Однако существует способ прямых инжекции вью-моделей без создания иерархических зависимостей, сохраняющий их равноправность! Это очень важное свойство для сериализации и десереализации замкнутых графов объектов, что оставляет возможность для полного сохранения и восстановления логического и визуалного состояний всего приложения при его перезагрузке.
Реализуется подобное очень просто и естественно. Посмотрите пример кода в проекте HelloAero в разделе Exposable.
[DataContract] public class GuyViewModel : ContextObject, IExposable { [DataMember] public int Kisses { get { return Get(() => Kisses); } set { Set(() => Kisses, value); } } public void Expose() { var girlViewModel = Store.Get<GirlViewModel>(); this[() => Kisses].PropertyChanged += (sender, args) => { Context.Get("KissGirl").RaiseCanExecuteChanged(); Context.Get("KissGuy").RaiseCanExecuteChanged(); }; this[Context.Get("KissGirl")].CanExecute += (sender, args) => args.CanExecute = Kisses > girlViewModel.Kisses - 2; this[Context.Get("KissGirl")].Executed += (sender, args) => girlViewModel.Kisses++; } } [DataContract] public class GirlViewModel : ContextObject, IExposable { [DataMember] public int Kisses { get { return Get(() => Kisses); } set { Set(() => Kisses, value); } } public void Expose() { var guyViewModel = Store.Get<GuyViewModel>(); this[() => Kisses].PropertyChanged += (sender, args) => { Context.Get("KissGirl").RaiseCanExecuteChanged(); Context.Get("KissGuy").RaiseCanExecuteChanged(); }; this[Context.Get("KissGuy")].CanExecute += (sender, args) => args.CanExecute = Kisses > guyViewModel.Kisses - 3; this[Context.Get("KissGuy")].Executed += (sender, args) => guyViewModel.Kisses++; } }
3) Умное Состояние и Умные Свойства (Smart State & Smart Properties)
В языке JavaScript реализована очень мощная концепция слаботипизированных свойств, а также встроенные возможности простой сериализации и десериализации объектов, что делает его незаменимым при разработке сложных динамических интерфейсов. Отчасти эти концепции нашли отражение в механизмах Smart State и Smart Properties.
Взгляните, насколько легко и естественно сохраняются размеры и положения окон в примерах проектов к библиотеке, при этом вью-модели кросс-платформенны и полностью чисты от интерфейсной логики.
Обратите внимание на следующие конструкции
WindowStyle="{Smart 'WindowStyle, SingleBorderWindow'}" ResizeMode="{Smart 'ResizeMode, CanResizeWithGrip'}" Height="{Smart 'Height, 600'}" Width="{Smart 'Width, 800'}"
4) Встроенные и Композитные Конвертеры (Inline & Composite Converters)
Порой встречается необходимость доступа из конвертера к представлению или его контексту данных. Для этих целей можно применить паттерн IInlineConverter
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; }
Суть метода в том, что вместо создания нового класса для конвертера мы просто перемещаем методы Convert и ConvertBack в Code Behind представления в качестве обработчиков события Converting и ConvertingBack. Это даёт полный доступ к представлению, где применяется данный экземпляр конвертера. Пример использования легко отыскать в исходных кодах.
Паттерн ICompositeConverter позволяет соединять конвертеры в цепочки, что помогает иногда избежать создания новых классов-конвертеров. Как его применять, следует посмотреть в исходных кодах.
public interface ICompositeConverter : IValueConverter { IValueConverter PostConverter { get; set; } object PostConverterParameter { get; set; } }
5) Стеллажи для Grid (Rack for Grid)
Здесь достаточно взглянуть на код, чтобы всё стало понятно.
<Grid Rack.Rows="* 20\Auto * 2* */100 * *" Rack.Columns="* 50\*/100 *"> <!--...--> </Grid> equals <Grid> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition MinHeight="20" Height="Auto"/> <RowDefinition Height="*"/> <RowDefinition Height="2*"/> <RowDefinition Height="*" MaxHeight="100"/> <RowDefinition Height="*"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition MinWidth="100" Width="*" MaxWidth="300"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <!--...--> </Grid>
Пожалуй, для данной статьи достаточно, она носит ознакомительный обзорный характер. Мы рассмотрели далеко не все возможности библиотеки, за кадром остались локализация, валидация данных, расширения привязки, другие типы конвертеров, синтаксический сахар и много других мелочей. При наличии интереса к материалам будут и другие статьи, поэтому не оставайтесь равнодушны, вливайтесь в обсуждения, выражайте свои отзывы в комментариях или по е-мэил, делитесь с другими.
P.S. По ссылке немного устаревшие, но всё ещё актуальные материалы

