Avalonia – это кроссплатформенный XAML фреймворк для платформы .NET. Для многих разработчиков на WPF/UWP/Xamarin данный фреймворк будет интуитивно понятен и прост в освоении. Avalonia поддерживает Windows, Linux, macOS, также заявлена экспериментальная поддержка Android и iOS. Развивается проект при поддержке сообщества и является open-source.  Подробнее про фреймворк можно почитать по следующим ссылкам: Автору нравится разработка с помощью WPF/UWP и есть опыт использования в реальных проектах. У данных платформ есть свои плюсы и минусы, но самым главным недостатком было то, что они не являются кроссплатформенными. Долгое время, сосредоточенность компании Microsoft на Windows среде, даже с наличием Mono и выходом Net Core, для многих разработчиков вне мира .net – сохранило стереотип, что C# это только для Windows. И если для back-end – платформа .Net Core, стала действительно решением, то с точки зрения кроссплатформенного десктопа изменений не было. Портирование WinForms и WPF для запуска с под .Net Core, является оптимизацией C# кода, но с точки зрения поддержки Linux/MacOS изменений не было. Как только появилось свободное время решил попробовать разобраться в фреймворке. Конкретно поводом для статьи стал issue к официальной документации Авалонии на Github.  Сейчас в документации пример сделан непосредственно с помощью библиотеки ReactiveUI и в issue поднимается вопрос о необходимости реализации MVVM паттерна на чистом шаблоне проекта без подключения сторонних библиотек. Именно данную цель преследует эта статья. Про данный шаблон написано достаточно на Хабре (1,2) и в документации Xamarin.Forms, Avalonia, поэтому данный раздел вынесен в спойлер. MVVM (Model-View-ViewModel) – шаблон проектирования, концентрирующий внимание на разделении бизнес-логики и интерфейса программы. Данный шаблон широко используется в приложениях на платформах WPF/UWP/Xamarin. При использовании вышеприведенных фреймворков, приложение делится на два слоя: Именно эти два слоя и призван разделить паттерн с помощью добавления еще одного – модель представления (ViewModel). ViewModel — связывающий слой между Model и View с помощью технологии привязки (Binding). Для понятия Binding, введем понятие свойства (Property) – изменяемое поле данных во ViewModel. Простыми словами, с помощью binding, все property, описанные в ViewModel доступны для View. Важным, также является изменяемость property – под этим следует, что любые изменения во View или Model о которых "узнает" ViewModel будут автоматически изменены в зависимости от того, откуда пришли изменение (ввод текстового поля, получение ответа от API и т.п)
Классическая схема выглядит следующим образом:   Примечание: в дальнейшем, в статье будут использоваться английские значения (View. ViewModel, Model, binding, property). Субъективное мнение автора, о том, что так удобнее, сформировано привычкой использования и коммуникацией с другими разработчиками. Для создания независимых частей приложения. Используя MVVM, вы гарантируете, что слой Model ничего не знает о View, это же применимо для ViewModel. В свою очередь это дает следующие преимущества:  За реализацию приложения возьмем идею отображения популярных фильмов. Сформируем следующие задачи:  Задачи выбраны достаточно простыми, чтобы разработчикам не знакомым с WPF/UWP/Xamarin было просто попробовать и запустить приложение. В первом варианте задумки было намного больше функциональности, но каждая из них добавляла новые понятие и охватывало куда больше нюансов и подходов, и цель статьи размывалась.  Для разработки Вам понадобится Visual Studio и плагин Avalonia для нее.
По умолчанию, согласно документации, Avalonia поддерживает .Net Framework и Net Core 2.0+. Для кроссплатформенной разработки нужно выбрать Net Core. Для написания статьи плагин был установлен в Visual Studio 2019, платформой запуска является Net Core 3.0. Выбор проекта Avalonia в Visual Studio 2019:  Вот так выглядит проект Avalonia сразу после его создания:  Program.cs – в нем находится функция Main и описываются конфигурации для запуска проекта. В нем указывается непосредственно Application класс App. Данный класс имплементируется в двух файлах:
App.xaml в котором описываются ресурсы доступные всему проекту – формируют класс Application.
App.xaml.cs – code-behind файл, в котором происходит инициализация xaml компонентов и пользовательского интерфейса, с него вызывается MainWindow.
MainWindow представляет собой реализацию класса Window, в code-behind файле можно добавлять обработчики событий (клики на кнопки, выбор из списков и т.п) на элементы интерфейса описанных в MainWindow.xaml. При запуске приложения, у нас должно появится окно с текстовым сообщение “Welcome to Avalonia”. В статье описание самих стилей опускается, но чтобы выглядело красиво, нужно добавить библиотеку (авторская) в сборку и подключить в проект Авалонии. Для использования в самом проекте, нужно указать стили в файле MainWindow.xaml вот таким образом: Для проверки стилей, создадим хедер приложения: Вот так оно должно выглядеть после запуска:  Данные подготовлены заранее и скачать их можно с репозитория на GitHub. Дальше нужно скопировать в папку проекта Moviewer и в файле Moviewer.csproj добавить следующее: Это необходимо, чтобы при сборке данные добавлялись в исполняемую папку. Проверить себя, можно собрав проект и после найти папку в сборке по такому пути: project-folder/bin/netcoreapp3.0/Data.   Формат данных выглядит так:
 Добавим папку ViewModels и классы ViewModelBase, MainWindowViewModel.cs Для реализации ViewModel необходимо реализовать интерфейс INotifyPropertyChanged. Данный интерфейс используется для отслеживания изменений в Property, определенных в ViewModel. В приложении может быть множество ViewModel, для различных элементов интерфейса, поэтому удобно реализовать интерфейс в классе ViewModelBase, а в ViewModel использовать имплементацию ViewModelBase. Примечание: существует достаточное количество библиотек (MVVMLight, Prism, ReactiveUI, MVVM Cross), где шаги приведенные выше уже реализованы. В данной статье описывается реализация паттерна в целом. Для Авалонии существует шаблон с использованием ReactiveUI. В MainWindowViewModel.cs создадим текстовое поле, чтобы использовать binding и проверить, связь между View и ViewModel. Для отображения сообщения, в MainWindow.xaml добавим следующую разметку перед закрывающимся тэгом Grid. Последний шаг, связываем нашу форму с ViewModel. В файле App.xaml.cs, указываем DataContext для MainWindow. DataContext – используется для реализации привязки данных, источником данных выступает MainWindowViewModel. В упрощенном понимании, можно сформулировать так: “property описанные в классе MainWindowViewModel, доступны для привязки в MainWindow.xaml”
В MainWindow.xaml с помощью binding мы можем указать текстовому полю наше свойство из MainWindowViewModel. Запуская приложение, вы увидите “Welcome to Avalonia” в левом верхнем углу.
О преимуществах данного подхода можно прочесть в спойлере выше, а о DataContext можно прочитать в следующем блоге. На уровне модели мы реализуем: класс фильма Сервис для получения и обработки данных о фильмах. Создадим папку Services и реализуем загрузку данных. Для работы с json используется Newtonsoft.Json  В GetMovies загружаются данные о фильмах, для загрузки картинок используется GetPoster. На этом логика завершена, дальше обновим ViewModel и View и покажем список фильмов. GetMovie возвращает Task, поэтому будем NotifyTaskCompletion из Nito.AsyncEx 3.0.1 (Важно использовать именно версию 3.0.1). О том как правильно реализовывать загрузку в ViewModel описано детально в блоге Стивена Клери.
Логика загрузки весьма проста: пока грузятся данные показываем ProgressBar, после завершения загрузки выводим на экран.
Примечание: поскольку данные загружаются локально, то задержки почти нет, поэтому поставим ее сами (на 1 секунду) с помощью Task.Delay(1000). Выведем на экран фильмы, заменив StackPanel в MainWindows.xaml на следующую разметку: После этого, при запуске приложения в начале появится ProgressBar, а затем набор фильмов.  В ViewModel нам нужно добавить параметр страницы Page, который выведем вверху страницы. Интерфейс подписывается на событие PropertyChange и “прослушивает” изменения впоследствии обновляя нужный property: Примечание: в классическом виде выглядит достаточно громоздко, с помощью различный библиотек (Fody, ReactiveUI) можно выразить это лаконичнее. Для обработки переключения: вперед, назад необходимо добавить две функции. В них мы будем менять счетчик и вызывать GetMovie с новым его значением. Логика будет идентично той, что и при загрузке, только добавится вычисление страницы. Для удобства вынесем загрузку в отдельный метод, который будет принимать параметр страницы. Инициализация ViewModel преобразуется в следующий вид: Сами функции будут выглядеть так: Условия в начале каждой из функций зацикливает переключение. Дойдя до последней страницы, следующей будет 1 и в обратную сторону. Нажимая на кнопку Prev на первой, мы перейдем на последнюю. Стоит отметить, что в классическом варианте MVVM для обработки действий используются команды (Commands). В Авалонии в отличии от других XAML фреймворков есть возможность привязки не только к командам, но и к методам (как в нашем случае), что порой удобнее, чем создание команд.  Получение новой коллекции остается асинхронной операцией, поэтому мы используем тот же подход, что при инициализации ViewModel, а именно NotifyTaskCompletion. Однако для того, чтобы ProgressBar появлялся при переключении и автоматически обновлялось поле, необходимо InitializationNotifier сделать property, добавив OnPropertyChange(): Для позиционирования элементов используем Grid с 2-мя строками 3-мя колонками. После Grid.Row = “1” добавьте следующее (Важно, чтобы закрывающий тэг > был после новых строчек): Расположим ProgressBar по центру  В MainWindow выведем номер страницы и свяжем кнопки и функциями.  Приложение готово! Автором приложение запускалось на Windows 10, Mint.    Достаточно установить Net Core, скопировать собранный проект на систему и внутри папки вызвать из консоли dotnet Moviewer.dll (или то название, которое Вы укажете в создании проекта) Во время реализации проекта было небольшое опасение, что запуск на разных платформах будет «особенным» и что-то «поедет, сломается». Необходимость разных вариантов сборок, имплементации разных форматов интерфейса ну и слегка стереотипное “ UI разработка на Net только технология Windows”. Приятным удивлением стал запуск приложения в два шага и в том, что абсолютно ничего не нужно было менять. Возможно, если усложнить проект и добавить больше функциональности – разница будет заметна: придется придумывать велосипеды, обходные пути, а с некоторыми проблемами Вы можете столкнутся первыми, но в данном приложении все было гладко.  Отдельной особенностью Avalonia является реализация стилей подобных css. В статье данный аспект не рассматривался. В действительности, это очень удобная реализация для стилей приложение в сравнении с классической в WPF/UWP. Если интересно больше прочитать по стили можно обратится к следующим статьям: Из минусов можно выделить, что на сегодняшний момент полноценная разработка удобнее с плагином для Visual Studio, так как в ней доступен превьюер. Вы также можете разрабатывать на Linux, но без него. Сейчас усилиями сообщества (ForNeVeR) к Rider ведется разработка плагина к которой может присоединиться любой желающий. На этом все, надеюсь данная статьи будем Вам полезна, и Вы найдете что-то новое для себя в ней. В конце хочется выразить благодарности: .Net Developer