Состоялся релиз версии 0.5 кросслплатформенного XAML UI фреймворка AvaloniaUI (раннее назывался Perspex). Фреймворк сделан по тем же принципам, что и WPF/UWP, т. е. используется XAML, биндинги и шаблонизированные элементы управления. На текущий момент это единственный способ сделать UI на настоящем XAML, который будет работать на Windows, OS X и Linux (так же имеется экспериментальная поддержка iOS и Android).


КПДВ


> Каталог встроенных контролов (gif 3MB)


Начать работать с фреймворком можно скачав дополнение для Visual Studio 2017 и создав проект из шаблона. Так же стоит ознакомиться с документацией на wiki.


В этом релизе: Поддержка .NET Core, переход на GTK3 для *nix-систем, поддержка вывода через Linux fbdev, система расширений, исправлено множество ошибок.


.NET Core


.NET Core поддерживается в качестве цели для сборки (для поддержки превьювера в Visual Studio всё ещё необходимо добавлять net461 в TargetFrameworks) и работает на всех трёх десктопных платформах. Так же произведён переход на netstandard1.1 для неплатформоспецифичных библиотек и на netstandard1.3 для бэкэндов Win32, GTK3 и Skia. Шаблоны для dotnet new брать здесь.


GTK3 на *nix-платформах


Мы больше не используем биндинги из состава GTK#, которые требуют сборки нативных бинарников для каждой платформы и привязаны к Mono. Вместо этого взаимодействие с GTK происходит напрямую через P/Invoke, что дало возможность сделать бэкэнд совместимым с netstandard1.3. В качестве бонуса на *nix-системах заработала плавная прокрутка. Если требуется поддержка дистрибутивов без GTK3, всё ещё можно переключиться на старый GTK2-бэкэнд.


На OS X требуется установить GTK3 через brew install gtk+3. В дальнейшем эта зависимость на OSX будет устранена, т. к. удалось завести MonoMac поверх netstandard2.0


Улучшения в поддержке мобильных платформ


Мы больше не пытаемся эмулировать наличие десктопных окон там, где их на самом деле нет. Поэтому на мобильных платформах больше не доступно использование класса Window. Вместо этого поставляются нативные для каждой платформы классы AvaloniaView со свойством Content, в котором и следует размещать XAML-контролы. Для удобства так же предоставляются AvaloniaActivity для Android и AvaloniaWindow (UIWindow с встроенным контроллером) для iOS. Таким образом инициализация теперь выглядит примерно так:


public override bool FinishedLaunching(UIApplication uiapp, NSDictionary options)
{
    AppBuilder.Configure<App>()
        .UseiOS()
        .UseSkia().SetupWithoutStarting();
    Window = new AvaloniaWindow() {Content = new YOUR_CONTROL_HERE(){DataContext = ...}};
    Window.MakeKeyAndVisible();
    return true;
}

public class MainActivity : AvaloniaActivity
{
    protected override void OnCreate(Bundle savedInstanceState)
    {
        if (Avalonia.Application.Current == null)           
        {
            AppBuilder.Configure(new App())
                .UseAndroid()
                .SetupWithoutStarting();
        }
        Content = YOUR_CONTROL_HERE(){DataContext = ...};
        base.OnCreate(savedInstanceState);
    }
}

Linux fbdev


Добавлена начальная поддержка для вывода через fbdev и ввода через libevdev2. Это позволит в дальнейшем использовать фреймворк на встраиваемых Linux-устройствах без X-сервера.


Встройка в WPF/Winforms/GTK2


Теперь поставляются родные для этих платформ контролы, которые могут отображать контролы AvaloniaUI. Устроены по аналогии с мобильными платформами.


Система расширяемости


Улучшенный авто-детект платформ с системой приоритетов. Пока работает только на полном .NET. Так же в комплекте средства для авторегистрации разного рода зависимостей для сторонних библиотек. См. пул-реквест с описанием.


Оператор ^ в биндингах


Ранее система пыталась автоматически подписываться на все IObservable и Task, которые видела в цепочке свойств. Это приводило к проблемам при попытке биндинга к свойствам классов, реализующих IObservable. Чтобы избежать неоднозначности был введён оператор ^. Пользоваться им можно так:


<!-- Bind to the values produced by Content.Observable -->
<TextBlock Text="{Binding Content.Observable^}"/>

<!-- Bind to the Name property on the values produced by Content.Observable -->
<TextBlock Text="{Binding Content.Observable^.Name}"/>

Оператор поддерживает расширяемость, нужно реализовать IStreamPlugin


3rd-party библиотеки


Со времени предыдущего релиза были портированы и в какой-то степени работают:



Библиотеки пока не обновлены для поддержки последней версии, ибо релиз опубликован на nuget только позавчера.


Исправлено множество ошибок


Закрыто 133 issue/PR на гитхабе. Не все из них были ошибками. Полный список можно посмотреть тут.


Мы всегда рады контрьбьюторам. Если вы считаете, что можете помочь или у вас просто есть вопрос, стучитесь в наш чат в gitter.

Поделиться с друзьями
-->

Комментарии (38)


  1. MonkAlex
    15.05.2017 12:59
    +1

    Грац.
    Пробовал простые формы накидывать, получается довольно удобно.
    Есть какие то вещи, которые ещё явно нужны и будут в ближайшем релизе?


    1. kekekeks
      15.05.2017 13:04
      +3

      Смотря для чего нужны. На десктопе сейчас разве что API для работы со шрифтами не хватает.


      У нас пока в планах на 0.6 доделать deferred-рендеринг (который в отдельном потоке), переехать на Portable.Xaml (OmniXAML тормозной очень и хуже поддерживается), сделать нормальный превьювер для *nix-платформ и воткнуть его хотя бы в MonoDevelop, сделать уже наконец генерацию линуксовых пакетов и бандлов для OSX в один клик, сделать бакэнд на базе MonoMac.


  1. SonicGD
    15.05.2017 13:23
    +2

    Проект выглядит довольно интересно. А какие у вас мысли насчёт XAML Standard?


    1. kekekeks
      15.05.2017 13:28
      +6

      Будем его поддерживать. Он же по сути определяет перечень контролов и свойств, которые они должны иметь, не более того.


  1. mayorovp
    15.05.2017 14:10
    +1

    Насколько сложно прикрутить туда альтернативный механизм биндингов? Моя попытка покопаться в дебрях биндингов WPF закончилась кучей внутренних методов которые непонятно что делают...


    В первую очередь интересует механизм подписки на изменения когда операция подписки выполняется не вызывающим кодом, а вызываемым.


    1. kekekeks
      15.05.2017 14:17
      +1

      Наша встроенная система работает на базе ReactiveExtensions. Альтернативный механизм биндингов прикручивается примерно так же как и к WPF-написанием этого самого механизма и реализацией соответствующего MarkupExtension.


      Что именно имеется ввиду под "подпиской на изменения вызываемым кодом"?


      1. mayorovp
        15.05.2017 14:30

        Механизм, при котором вызывающий код устанавливает в статическом свойстве обработчик, который будет запомнен теми объектами, к которым он обращается и вызван при изменении любой зависимости. Еще называется "динамическое определение зависимостей".


        Для биндинга он, возможно, несколько избыточен — но реализовывать вью-модель с ним довольно удобно.


        1. Krypt
          16.05.2017 12:31

          В стандартной разметке это вполне можно сделать через Dependity Property, на который забинден объект. Другой вопрос, что этот код не отличается вменяемостью (место, в котором я ДЕЙСТВИТЕЛЬНО жалею об отсутствии препроцессора в C#)


          1. mayorovp
            16.05.2017 12:40

            Нельзя ли по-подробнее? Я не вполне понимаю что такое "Dependity Property, на который забинден объект".


            1. Krypt
              16.05.2017 13:17

              — Где-нибудь как-нибудь устанавливаете DataContext элемента управления на экземпляр класса (пусть будет A)
              — Добавляете DependencyProperty «Prop» типа B в класс A вот так:

              public static readonly DependencyProperty PropProperty =
                          DependencyProperty.Register("Prop", typeof(<B>),
                          typeof(ActionPanel), new FrameworkPropertyMetadata(null, (DependencyObject d, DependencyPropertyChangedEventArgs e) => {
              // Вот тут ваш callback
               }));
              
              public B Prop 
              {
              	set { SetValue(PropProperty, value); }
                  get { return (B)GetValue(PropProperty); }
              }
              
              

              — Биндите свойство в Xaml как вам нужно (если свойство меняется из интерфейса — скорее всего понадобится TwoWay binding) ( <local:A Prop="{Binding}" />)

              P.S.: Да, я знаю, что код ужасен. Именно поэтому я жалею об отсутствии препроцессора: он помог бы обернуть бойлерплэйт во что-нибудь вменяемое.


              1. mayorovp
                16.05.2017 13:31

                И какое это имеет отношение к тому, что я писал?


                Как это решение обобщить на случай произвольного числа зависимостей? Например, как вы предлагаете "забиндиться" на сумму элементов коллекции?


                1. Krypt
                  16.05.2017 13:45

                  Всё, понял. Что неправильно понял. Можете подробнее описать, что бы хотите сделать? Вы имеете ввиду Dependency Injection?

                  Код, что я привёл, по сути кастомный сеттер свойства. А-ля public B Prop { set; get; }, но с поддержкой XAML, на случай, если вы хотите мгновенно реагировать на изменения в интерфейсе (например, обновлять по мере ввода текста результаты поиска)


          1. kekekeks
            16.05.2017 12:40

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


            1. mayorovp
              16.05.2017 12:42

              Нее, это совсем не то. Хотя и лучше чем ситуация в WPF.


      1. mayorovp
        16.05.2017 13:45

        Кажется, я понял что объяснил слишком непонятно. Попробую подробнее. //cc: Krypt


        Вот у меня есть библиотека, которая умеет делать вот так (писал ее под впечатлением от MobX):


        private IEnumerable<Foo> _foos;
        public IEnumerable<Foo> Foos 
        {
            get { return GetProperty(_foos); }
            set { SetProperty(ref _foos, value); }
        }
        
        public double Avg => Computed(() =>
        {
            return Foos.Average(s => s.X);
        });

        Свойство здесь Avg будет пересчитываться при изменении коллекции Foo или изменении свойства X у любого объекта, который входит в эту коллекцию — при условии что коллекция и объект написаны аналогичным способом.


        Интересует возможность прикрутить ее куда-нибудь напрямую без реализации INPC (реализовать INPC нетрудно — но при этом теряется ленивость). Для того, чтобы это сделать, нужна возможность оборачивать код биндинга в вызов функции из моей библиотеки.


        1. Krypt
          16.05.2017 15:03

          Для использования в качестве элементов списка вы можете использовать такой код:
          List.ItemsSource = Foos

          Если вы хотите обновлять элементы из кода, то обновляемые поля должны быть INotifyPropertyChanged или ObservableCollection. Либо дёргать обновление вручную из кода, что наверняка возможно, но я не знаю как. Ибо никогда не использовал.

          Но, впрочем, стоит уточнить, что C#/WPF не являются моей основной платформой уже лет 5 как. Так что я не последняя инстанция.


          1. mayorovp
            16.05.2017 15:25

            Эти INotifyPropertyChanged или ObservableCollection — это, по сути, и есть дерганье обновлений из кода вручную. Которое надоело.


  1. Don_Eric
    15.05.2017 17:04
    +1

    интересный проект, но боюсь что будущее за Xamarin.Forms

    уже сейчас поддерживаются Mac, UWP, Android и iOS


    1. denismaster
      15.05.2017 17:09
      +1

      А в Avalonia поддерживаются Windows 7-8.1, где нет UWP, а также Linux.


      1. ad1Dima
        16.05.2017 06:58

        Для XF есть пулл-реквест с поддержкой WPF


    1. kekekeks
      15.05.2017 17:41
      +1

      Авалония и формы — вещи ортогональные. В том плане, что у нас подход "рисуем всё сами и одинаково" (минус — гуй выглядит не "нативно", частично решается мимикрией через темы), а forms оборачивает нативные контролы (минусы — удачи с вёрсткой макета от дизайнера и весельем с реализацией хоть чего-то сложного или нестандартного для каждой из платформ в отдельности).


      Каждый из подходов хорош для своих применений. Так что тут скорее будет интеграция одного с другим, а не наоборот. Причём на Android/iOS уже сейчас есть принципиальная возможность встраивать куски интерфейса на авалонии в forms, нужно только рендереры соответствующие написать. На досуге надо будет заняться


      1. Don_Eric
        16.05.2017 13:12

        да, эта разница принципиальная.

        а насколько производительность рисования самим отличается от WebView/Browser и электрона?


        1. kekekeks
          16.05.2017 13:54

          С браузером не сравнивали, но уже сейчас быстрее чем WPF на Windows на определённых сценариях (авалония поверх Direct2D начинается с 1:30).


          И такой немаловажный момент. У нас (как во всех нормальных UI-тулкитах) есть виртуализация списков. В браузеры её не завезли и приходится изобретать криво работающие костыли. Соответственно на задачах "показать список на несколько тысяч элементов" работать будет шустрее, а памяти кушать на порядки меньше.


  1. Skykharkov
    15.05.2017 18:35
    +1

    Смотрится неплохо. Попытаюсь поюзать для одного некоммерческого проекта.
    Первый глюк. Что-то не так с canvas для button.


    1. kekekeks
      15.05.2017 18:36
      +1

      А весь XAML можно привести? Текстом.


  1. Skykharkov
    15.05.2017 18:41
    +1

    <Window xmlns="https://github.com/avaloniaui" Title="Test app" WindowState="Maximized" MinWidth="500" MinHeight="300">
        <Grid Name="MainGrid">
            <Button Content="Button text" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="40,40,0,0" Width="201" Height="65"/>   
        </Grid> 
    </Window>

    Без Alignment работает как ожидается. Судя по всему канва отрисовки текста не понимает Aligment и считает что их нет.


    1. kekekeks
      15.05.2017 19:06
      +1

      Что-то с Layout-ом перемудрили. Если сделать вот так, то работает.


      <Window xmlns="https://github.com/avaloniaui" Title="Test app" WindowState="Maximized" MinWidth="500" MinHeight="300">
          <Grid Name="MainGrid">
              <Button Content="Button text" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="40,65,0,0" >
                  <Border Width="201" Height="65">
                      <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">Button Text</TextBlock>
                  </Border>
              </Button>
          </Grid>
      </Window>

      Пойму, что там именно происходит и заведу issue.


      1. Skykharkov
        15.05.2017 19:13
        +1

        Да, работает. Спасибо. Чуть больше кода, но работает. В UWP моя разметка работает корректно.


        1. kekekeks
          15.05.2017 19:16
          +1

          Да, это явный баг с layout-ом у нас.


          1. kekekeks
            15.05.2017 19:54

            #984. Когда определимся с как правильно и смержим — будет доступно в nuget-фиде с ночными сборками.


            1. Skykharkov
              15.05.2017 20:03

              Спасибо. Еще вопрос — CEFGlue компилит Chromium с поддержкой MP3\4? Или все равно приходится компилить Chromium с proprientary_codecs=true?


              1. Varim
                15.05.2017 20:11

                между прочим, мне или приснилось или MP3 больше не требует лицензию с 23 апреля 2017 в вики так написано


                1. Skykharkov
                  15.05.2017 20:16

                  Это надо гуглу сказать. :) Хотя возможно вы и правы. Около трех недель назад, когда я собирал Chromium "крайний раз"
                  (

                  Заголовок спойлера
                  да я в курсе, про "крайний", но избавиться не могу


  1. Skykharkov
    15.05.2017 18:56

    Насколько плохо (в какой-то степени :) работает CEFGlue? В текущем проекте в WinForms версии использую CefSharp3. Понемногу переношу проект в .NET core и браузер — критичный элемент.


    1. kekekeks
      15.05.2017 19:09

      Я оригинальный CEFGlue использовал в WinForms и в целом был доволен. Порт для авалонии, насколько я знаю, делался по-возможности построчным копированием с WPF. Имеет смысл попробовать его погонять и попинать автора порта, если есть какие-то проблемы. Ну и следует понимать, что сейчас это не коробочное решение, а в какой-то мере конструктор "собери сам". Порт стал возможен только недавно после появления инфраструктуры WritableBitmap.


      1. Skykharkov
        15.05.2017 19:15

        Будем пробовать. Проект выглядит интересно. Спасибо большое. Теперь буду в issue писать. :) Ваш подход выглядит изящно и жизнеспособно (на первый взгляд ;).


  1. QtRoS
    15.05.2017 22:34

    1. А с темами как обстоят дела? Внешний вид кастомизируется? Встроенные системные темы не подхватываются?
    2. Какие сценарии использования видите лично Вы? Т.е. если бы это был бы платный продукт, какая бы у вас была «бизнес модель» и «целевой потребитель» (кому бы продавали)?


    1. kekekeks
      15.05.2017 22:45
      +1

      1) Есть штатная тема, которую можно заменить на свою и в которой можно заменить цвета. Мимикрию под конкретные ОС пока не делали и наврятли будем делать до версии 1.0, если, конечно, не найдётся желающих этим делом заняться.
      2) В качестве платного продукта оно бы неплохо зашло на всякого рода встраиваемых устройствах (Avalonia+Linux кушают порядка 100 мегабайт (x86, fbdev, наш каталог контролов в качестве объекта замера) против полугигабайтного аппетита Windows for IoT), а так же в качестве UI для игр. Там наблюдается некоторый голод в плане хорошего UI, так что можно было бы продавать за хорошую цену.