Релиз кросс-платформенного XAML UI-фреймворка AvaloniaUI 0.5

    Состоялся релиз версии 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.

    Поделиться публикацией

    Комментарии 56

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

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


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

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

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

          +1

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


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

            +1

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


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

              0

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


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

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

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

                    0
                    — Где-нибудь как-нибудь устанавливаете 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.: Да, я знаю, что код ужасен. Именно поэтому я жалею об отсутствии препроцессора: он помог бы обернуть бойлерплэйт во что-нибудь вменяемое.
                      0

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


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

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

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

                    0

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

                      0

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

                  0

                  Кажется, я понял что объяснил слишком непонятно. Попробую подробнее. //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 нетрудно — но при этом теряется ленивость). Для того, чтобы это сделать, нужна возможность оборачивать код биндинга в вызов функции из моей библиотеки.

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

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

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

                      0

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

                    0
                    Можно по-подробнее про реализациею соответствующего MarkupExtension? На WPF я уже второй раз натыкаюсь на невозможность подписаться на изменение свойства у объекта. Хотя бы на изменение DataContext.

                    На AvaloniaUI такая возможность есть?
                      0

                      В авалонии свойство DataContext является обычным StyledProperty (аналог DependencyProperty), так что на него можно спокойно биндиться.


                      Подписки из коробки работают на все свойства зарегистрированные через AvaloniaProperty и на свойства, уведомляющие о своём изменении через интерфейс INotifyPropertyChanged.


                      Если хотите вот прям свою систему, то наследуетесь от MarkupExtension и из ProvideValue возвращаете что-то реализующее интерфейс IBinding.

                        0
                          0
                          Это же вроде UWP, а не WPF. В классе System.Windows.DependencyObject я такого метода не нашел.
                            0
                            Черт, к хорошему быстро привыкаешь. Но кажется это через AddOwner можно было сделать
                              0
                              Разве можно добавить доп. метаданные через AddOwner для того типа, в котором DependencyProperty уже определено?
                                0
                                ну так я ж ссылку привел, есть перегрузка с параметром метаданных.
                                Собственно по ссылке:
                                The supplied metadata is merged with the property metadata for the dependency property as it exists on the base owner. Any characteristics that were specified in the original base metadata will persist. Only those characteristics that were specifically changed in the new metadata will override the characteristics of the base metadata. Some characteristics, such as DefaultValue, are replaced if they are specified in the new metadata. Others, such as PropertyChangedCallback, are combined.
                    +1
                    интересный проект, но боюсь что будущее за Xamarin.Forms

                    уже сейчас поддерживаются Mac, UWP, Android и iOS
                      +1
                      А в Avalonia поддерживаются Windows 7-8.1, где нет UWP, а также Linux.
                        0
                        Для XF есть пулл-реквест с поддержкой WPF
                        +1

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


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

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

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

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


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

                        +1

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

                          +1

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

                          +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

                            Что-то с 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

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

                                +1

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

                                  0

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

                                    0

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

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

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

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

                                        :)
                                        поддержки MP3\4 не было. Сейчас скомпилирую еще раз, посмотрю…
                              0

                              Починено в 0.5.1-build3133-alpha. Брать на фиде с ночными сборками.

                                0

                                Спасибо. Достаточно оперативно. В настоящий момент пытаюсь подружить мою сборку хромиума с AvaloniaUI и CEFGlue. :) В принципе понятно, что перспективы есть. Насколько хорошие — выясняю. Еще раз спасибо.

                              0

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

                                0

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

                                  0

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

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

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

                                  0
                                  А что с поддержкой Standard 2.0? Публичная версия 0.5 работает с стандартом 1.1. Планируется ли поддержка 2.0?
                                    0

                                    Стандарты же обратно совместимы. Переход на новую версию стандарта — это не расширение поддержки, а сужение.

                                      0
                                      Если я сделаю проект netstandard20, то добавить его в зависимости проекта авалонии не получится. Приходится создавать проект стандарта 1.1.
                                        +1

                                        Вам кто такую глупость сказал?

                                          0
                                          Студия 2017.4 так сказала. Я указал в настройках проекта версию стандарта 1.1, проект добавился. Версия авалонии 0.5.0.2, вроде (установлена через дополнение, о которой говорилось в статье выше).
                                            0

                                            Студия плохо умеет работать с нугетом и собственной системой проектов, нугет плохо умеет работать с собственной системой зависимостей. Так что надо победить тупость нугета и его политики nearest-wins указанием нужных версий зависимостей прямо у себя, тогда всё нормально ставится.

                                              0
                                              вот тут напрашивается конкретная инструкция и/или пример на github
                                                0

                                                Делаете


                                                dotnet restore|grep Detected|sed 's/.*downgrade../<PackageReference Include="/'|sed 's/ from /" Version="/'|sed 's/ to.*/"\/>/'

                                                получаете на выходе что-то типа:


                                                <PackageReference Include="System.Collections" Version="4.3.0"/>
                                                <PackageReference Include="System.Diagnostics.Debug" Version="4.3.0"/>
                                                <PackageReference Include="System.IO" Version="4.3.0"/>
                                                <PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0"/>
                                                <PackageReference Include="System.Runtime.Extensions" Version="4.3.0"/>
                                                <PackageReference Include="System.Runtime.InteropServices" Version="4.3.0"/>
                                                <PackageReference Include="System.Text.Encoding" Version="4.3.0"/>
                                                <PackageReference Include="System.Threading.Tasks" Version="4.3.0"/>
                                                <PackageReference Include="System.Collections" Version="4.3.0"/>
                                                <PackageReference Include="System.Diagnostics.Debug" Version="4.3.0"/>
                                                <PackageReference Include="System.IO" Version="4.3.0"/>
                                                <PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0"/>
                                                <PackageReference Include="System.Runtime.Extensions" Version="4.3.0"/>
                                                <PackageReference Include="System.Runtime.InteropServices" Version="4.3.0"/>
                                                <PackageReference Include="System.Text.Encoding" Version="4.3.0"/>
                                                <PackageReference Include="System.Threading.Tasks" Version="4.3.0"/>
                                                <PackageReference Include="System.Collections" Version="4.3.0"/>
                                                <PackageReference Include="System.Diagnostics.Debug" Version="4.3.0"/>
                                                <PackageReference Include="System.IO" Version="4.3.0"/>
                                                <PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0"/>
                                                <PackageReference Include="System.Runtime.Extensions" Version="4.3.0"/>
                                                <PackageReference Include="System.Runtime.InteropServices" Version="4.3.0"/>
                                                <PackageReference Include="System.Text.Encoding" Version="4.3.0"/>
                                                <PackageReference Include="System.Threading.Tasks" Version="4.3.0"/>
                                                <PackageReference Include="System.Collections" Version="4.3.0"/>
                                                <PackageReference Include="System.Diagnostics.Debug" Version="4.3.0"/>
                                                <PackageReference Include="System.IO" Version="4.3.0"/>
                                                <PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0"/>
                                                <PackageReference Include="System.Runtime.Extensions" Version="4.3.0"/>
                                                <PackageReference Include="System.Runtime.InteropServices" Version="4.3.0"/>
                                                <PackageReference Include="System.Text.Encoding" Version="4.3.0"/>
                                                <PackageReference Include="System.Threading.Tasks" Version="4.3.0"/>
                                                

                                                Вставляете это в файл проекта, у нугета внезапно случается просветление и он начинает нормально ставить зависимости.

                                      0

                                      В ночных сборках используется netstandard2.0 ещё с августа.

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

                                    Самое читаемое