Aero Framework — новое дыхание WPF. Поднимаемся выше MVVM


Aero Framework — передовая библиотека для промышленной и индивидуальной разработки кросс-платформенных XAML-ориентированных приложений с применением концепций MVVM-проектирования. Её основные достоинства — интуитивная ясность, предельная лаконичность, минималистичность и высокое быстродействие.

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

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

image

Первым делом нужно загрузить библиотеку с официального сайта или альтернативных источников:
сайт,
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. По ссылке немного устаревшие, но всё ещё актуальные материалы
Вызвала ли у вас интерес библиотека?

Проголосовал 101 человек. Воздержалось 42 человека.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.




К сожалению, не доступен сервер mySQL