Русский
Русский
English
Статистика
Реклама

Desktop

Electron Разработка Desktop-приложений, используя HTML, CSS и JavaScript

11.01.2021 14:11:20 | Автор: admin
О чем вы узнаете из статьи?
Что такое Electron
Возможности и ограничения при разработке
Как работать с Electron
Плюсы и минусы
Известные проблемы
Вывод

Electron это библиотека, которую можно использовать для написания десктопных приложений с помощью html, css, js. Эти приложения могут быть упакованы под windows, mac, linux.

Возможно некоторые из вас уже использовали Electron даже не подозревая об этом!

Список некоторых приложений, написанных на Electron
Visual Studio Code
Atom
Skype
Discord
Slack
GitHub Desktop
Figma
Zeplin
Postman


Что можно создать?
приложение по работе с файлами
скрытые приложение ( Tray )
приложения для менеджмента ( tracker, pomodoro )
видео/аудио проигрыватели
социальные чаты
календарь

Любые ваши идеи, которые будут полезны вам или компании

Когда вы работаете с Electron вы должны помнить о некоторых ограничениях старые ОС:
а) Windows 7+
b) Mac OS 10.10+
c) Ubuntu 12.04+

Electron состоит из трех компонентов:


Принципы работы
  1. Основной процесс (Main) отвечает за создание и управление окнами и различными событиями приложения (именно этот процесс создает рендеринг процессы)
  2. Процесс рендеринга (Renderer) Отвечает за запуск пользовательского интерфейса вашего приложения (их может быть любое количество)

Прицип работы в схеме


Взаимодействие процессов
Эти процессы полностью изолированы друг от друга и отвечают за различные задачи, но они должны как-то взаимодействовать и с этим нам помогает IPC (inter-process communication) модуль, который позволяет взаимодействовать между этими процессами.

Взаимодействие процессов в схеме


Посмотреть пример взаимодействия можно по ссылке.

В примере показано как событие click, описанное в процессе рендеринга (Renderer) взаимодействует с главным (main) процессом.

Хранение данных: FileSystem
Обычно для хранения каких-либо данных используют БД.
Но в десктоп приложениях у нас есть доступ к файловой системе, поэтому мы можем хранить данные прямо на компьютере пользователя (если их не очень много).

Плюсы Electron приложения
WEB как UI ( HTML, CSS, JS )
Разработка только под Chrome ( Safari, IE )
Chrome Devtools
Скорость разработки
Современный стек ( React, TypeScript, ...)
Кроссплатформенная разработка ( Windows, Mac OS, Linux )

Минусы Electron приложений
Вес проекта
Время старта ( если большое приложение )
Не все платформы имеют одинаковый интерфейс ( Н-р: Tray )
Linux имеет большое количество дистрибутивов

Вывод: достаточно знать html, css, js, чтобы разрабатывать на Desktop.
Подробнее..
Категории: Html , Javascript , Css , Electron , Desktop , Desktop apps

Минимальное PWA

17.03.2021 00:15:17 | Автор: admin

Какие характеристики должны быть у web-приложения, чтобы соответствовать критерию "прогрессивное"? Понятно, что, как и обычные web-приложения, прогрессивные строятся на базе "большой тройки" web-технологий - HTML/CSS/JS. Но что именно делает web-приложения прогрессивными?

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

Принципы отбора характеристик

Я возьму пустой HTML-файл и буду постепенно добавлять к нему артефакты, превращающие его в PWA, пока мой смартфон не решит установить это приложение, а Chrome не перестанет выкидывать предупреждения. Chrome выбран по причине того, что основным "движителем" технологии PWA на данный момент является Google.

HTTPS

Первое, что нужно настроить на сервере - это шифрование трафика. Тут всё делается так же, как и для обычных web-приложений. Лично я использую Apache httpd, а сертификаты для шифрования генерирую через Let's Encrypt.

App Shell

Оболочка приложения - это минимальный набор файлов, позволяющий приложению запускаться в offline-режиме. Я попробую весь необходимый код (HTML/CSS/JS) по максимуму уместить в одном файле - index.html.

<!DOCTYPE html><html lang="en"><head>    <title>PWA</title>    <style>        BODY {            background-color: #FB9902;            color: #342309;            font-size: xx-large;            margin: 0;        }        DIV {            align-content: center;            display: grid;            height: 100vh;            justify-content: center;        }    </style></head><body><div>App Shell</div></body></html>

Manifest

Манифест является непосредственным маркером того, что данная страница является частью прогрессивного web-приложения. Подключается манифест в заголовке HTML-страницы:

<link rel="manifest" href="demo.pwa.json">

Специальных правил для наименования файла с манифестом я не видел, тип содержимого маркируется через значение manifestатрибута rel.

Пустой манифест генерирует такие сообщения об ошибках в Chrome:

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

{  "start_url": "./",  "name": "PWA",  "display": "standalone",  "icons": [    {      "src": "./icon.png",      "sizes": "144x144",      "type": "image/png"    }  ]}

Icon

Как видно из предупреждений Chrome'а, иконка должна быть размера 144x144 минимум и в формате PNG, SVG или WebP. Примерно такую я и сделал:

В общем случае может быть множество иконок различных размеров.

Service Worker

После добавления минимального манифеста и иконки в Chrome остаётся только одно предупреждение - относительно service worker'а.

Service worker подключается через скрипты в оболочке приложения (index.html):

<script>    if ("serviceWorker" in navigator) {        self.addEventListener("load", async () => {            const container = navigator.serviceWorker;            if (container.controller === null) {                const reg = await container.register("sw.js");            }        });    }</script>

Минимальное содержимое файла sw.js:

'use strict';

вызывает вот такое предупреждение в Chrome: Page does not work offline

Stackoverflow сообщает, что предупреждение связано с отсутствием обработчика на событие fetch. Добавляем пустой обработчик:

'use strict';function hndlEventFetch(evt) {}self.addEventListener('fetch', hndlEventFetch);

Теперь предупреждение изменилось на:

Site cannot be installed: Page does not work offline. Starting in Chrome 93, the installability criteria is changing, and this site will not be installable. See https://goo.gle/improved-pwa-offline-detection for more information.

Моя текущая версия Chrome: Version 89.0.4389.72 (Official Build) (64-bit)

Тем не менее, смартфон предлагает установить приложение при заходе на страницу:

То есть, на данный момент service worker может быть номинальным, но в ближайшем будущем (в августе 2021-го) этого будет недостаточно.

Кэширование

Чтобы наше приложение оставалось PWA и после августа 2021-го, нам нужно добавить кэширование файлов, входящих в оболочку приложения. Вот полный код service worker'а:

'use strict';const CACHE_STATIC = 'static-cache-v1';function hndlEventInstall(evt) {    /**     * @returns {Promise<void>}     */    async function cacheStaticFiles() {        const files = [            './',            './demo.pwa.json',            './icon.png',            './index.html',            './sw.js',        ];        const cacheStat = await caches.open(CACHE_STATIC);        await Promise.all(            files.map(function (url) {                return cacheStat.add(url).catch(function (reason) {                    console.log(`'${url}' failed: ${String(reason)}`);                });            })        );    }    //  wait until all static files will be cached    evt.waitUntil(cacheStaticFiles());}function hndlEventFetch(evt) {    async function getFromCache() {        const cache = await self.caches.open(CACHE_STATIC);        const cachedResponse = await cache.match(evt.request);        if (cachedResponse) {            return cachedResponse;        }        // wait until resource will be fetched from server and stored in cache        const resp = await fetch(evt.request);        await cache.put(evt.request, resp.clone());        return resp;    }    evt.respondWith(getFromCache());}self.addEventListener('install', hndlEventInstall);self.addEventListener('fetch', hndlEventFetch);

С таким service worker'ом наше приложение будет считаться прогрессивным и в Chrome 93+. Вот какие файлы легли в кэш:

Ошибка, которая высветилась в консоли - отсутствие файла favicon.ico:

GET https://bwl.local.teqfw.com/favicon.ico 404

Но это уже особенности работы браузера, а не PWA.

Резюме

Чтобы web-приложение считалось прогрессивным (в том числе и после августа 2021-го) оно должно удовлетворять следующим условиям:

  1. Использовать шифрование (HTTPS).

  2. Оболочка приложения (загружает манифест приложения).

  3. Манифест (загружает иконку и service worker).

  4. Иконка.

  5. Service worker.

  6. Кэширование файлов оболочки приложения.

При соблюдении этих условий PWA устанавливается на смартфоне:

Послесловие

На написание этой статьи меня натолкнуло сообщение в Chrome о том, что с версии 93 web-приложения без кэширования оболочки приложения более не будут считаться прогрессивными и им будет отказано в установке. Я вышел на PWA через такое замечательное приложение, как Vue Storefront. Разработчики провели гигантскую работу и привязали к Magento 2 фронт, созданный с использованием современных технологий (положа руку на сердце, оригинальный фронт Magento 2 очень сильно отстал от современных тенденций web-разработки).

Когда я разбирался с тем, как устроен Vue Storefront, я обратил внимание, что приложение написано из расчёта, что соединение с интернетом будет всегда. Что и понятно, e-коммерция без интернета перестаёт быть таковой. И хотя браузеры предоставляют определённые возможности для того, чтобы PWA были максимально похожи на нативные мобильные приложения, современные PWA не спешат их использовать. Всё-таки, они во-первых - web, а прогрессивные - лишь во-вторых. Можно написать web-приложение, можно написать serverless приложение (за исключением App Shell, манифеста и service worker'а, разумеется - их всё равно придётся тянуть с сервера). Но написать приложение, которое бы работало как web-приложение при наличии интернет-соединения, и работало в автономном режиме в его отсутствие - в разы сложнее каждого из этих вариантов.

Именно поэтому разработчики Vue Storefront не стали заморачиваться с автономной функциональностью, ограничившись кэшированием статики и некоторых данных. Именно поэтому для Vue Storefront вылетает сообщение о том, что в Chrome 93+ их приложение более не будет считаться прогрессивным (демо, откройте консоль). Кстати, для собственной PWA-разработки самой Magento ситуация аналогичная.

Другими словами Google ужесточает критерии PWA, сдвигая фокус от web'а в сторону мобильных платформ. Конечно, можно и дальше называть обычные web-приложения прогрессивными, даже если они на 100% предполагают использование на десктопах и в условиях стабильного интернет-соединения. Но общая тенденция говорит о том, что место для PWA - мобильные устройства:

In December 2020, Firefox for desktop abandoned implementation of PWAs (specifically, removed the prototype "site-specific browser" configuration that had been available as an experimental feature). A Firefox architect noted: "The signal I hope we are sending is that PWA support is not coming to desktop Firefox anytime soon." Mozilla still plans to support PWAs on Android.

Разработчики Firefox не собираются поддерживать PWA в десктопной версии своего браузера.

Нельзя сказать, что PWA являются чисто Google'овской технологией:

всё-таки поддержка PWA-технологии различными браузерами на различных платформах достаточно объемлющая. Но дрейф в сторону "мобилизации" технологии я игнорировать не могу.

На мой взгляд PWA - это "нишевое" решение, для мобильных устройств. Да, оно сделано с использованием web-технологий (HTML/CSS/JS) и крутится внутри браузера, но к разработке PWA нужно подходить скорее с точки зрения нативных мобильных приложений, чем с точки зрения web-приложений (сайтов или SPA).

Другими словами, если вам нужно именно web-приложение, то вам не нужно PWA. А вот если вам нужно разработать именно мобильное приложение, то PWA может быть приемлемым вариантом.

Подробнее..

Авалония для самых маленьких

26.11.2020 12:17:06 | Автор: admin
В свежем превью Rider, помимо прочего, появилась поддержка Авалонии. Авалония это самый крупный .NET фреймворк для разработки кроссплатформенного UI, и его поддержка в IDE отличный повод наконец разобраться, как писать десктопные приложения для любых платформ.

В этой статье я на примере простой задачи по реализации калькулятора покажу:

  • как управлять разметкой,
  • как связывать функциональность с компонентами,
  • как управлять стилями.



Подготовка


Для работы я использовал:


Единственным обязательным инструментов в этом списке является сам дотнет. Остальное можете выбирать сами: любимую операционную систему и IDE (например, тот же Rider).
Для инициализации проекта мы воспользуемся шаблонами .NET приложений для Авалонии. Для этого нам потребуется клонировать репозиторий с шаблонами, а затем установить скачанные шаблоны:

git clone https://github.com/AvaloniaUI/avalonia-dotnet-templates.gitdotnet new --install /path/avalonia-dotnet-templates/

Типы проектов Авалонии
Типы проектов

Теперь, когда шаблоны установлены, мы можем создать новый проект на основе MVVM шаблона Авалонии:

dotnet new avalonia.mvvm -o ACalc

Перейдем в директорию проекта и обновим все версии пакетов на самые новые (на момент написания статьи):

dotnet add package Avalonia --version 0.10.0-preview6dotnet add package Avalonia.Desktop --version 0.10.0-preview6dotnet add package Avalonia.ReactiveUI --version 0.10.0-preview6

Давайте внимательнее посмотрим на структуру проекта, сгенерированную шаблоном:

image

  • В папке Assets хранятся ресурсы, используемые нами в данном проекте. На текущий момент там лежит лого Авалонии, использующееся в качестве иконки приложения.
  • В папку Model мы будем складывать все общие модели, используемые в нашем приложении. На текущий момент она пуста.
  • Папка ViewModels предназначена для хранения логики, которая будет использоваться в каждом из окон. Прямо сейчас в этой папке хранится ViewModel главного окна и базовый класс для всех ViewModel.
  • В папке Views хранится разметка окон (а также code behind файл, в который хоть и можно положить логику, но лучше для этих целей использовать ViewModel). На текущий момент у нас есть только главное окно.
  • App.xaml общий конфиг приложения. Несмотря на то, что он и выглядит как еще одно окно, на самом деле, этот файл служит для задания общих настроек приложения.
  • ViewLocator нам в этот раз не пригодится, так как он используется для создания кастомных контролов. Подробнее о нем можно почитать в документации Авалонии.

Запустим наше приложение командой dotnet run.



Теперь все готово для разработки.

Разметка


Начнем с создания базовой разметки. Перейдем в файл Views/MainWindow.xaml там будет храниться разметка главного окна нашего калькулятора.



В данный момент наша разметка состоит из базовых параметров окна (размеров, иконки и заголовка) и одного блока с текстом. Давайте заменим этот блок с текстом на Grid, который будет служить скелетом нашей разметки. Этот контрол разложит все элементы по порядку, один за другим.

Итак, заменим TextBlock на пустой Grid:

<Grid></Grid>

А теперь подготовим основу нашей разметки. Для начала укажем, сколько строк нужно нашему приложению и какой они должны быть высоты:

<Grid>    <Grid.RowDefinitions>        <RowDefinition Height="auto"></RowDefinition>        <RowDefinition Height="auto"></RowDefinition>        <RowDefinition Height="*"></RowDefinition>    </Grid.RowDefinitions></Grid>

Теперь заполним разметку основными компонентами добавим строку меню, базовый экран и вложенный Grid для блока клавиш:

<Grid>    <Grid.RowDefinitions>        <RowDefinition Height="auto"></RowDefinition>        <RowDefinition Height="auto"></RowDefinition>        <RowDefinition Height="*"></RowDefinition>    </Grid.RowDefinitions>    <!--строка меню-->    <Menu>    </Menu>    <!--Импровизированный экран нашего калькулятора-->    <TextBlock>    </TextBlock>    <!--Grid для клавиш-->    <Grid></Grid></Grid>

Отдельно остановимся на расположении клавиш в сетке.
Для начала нужно описать количество строк и столбцов в нашем Grid. А после разложить кнопки по соответствующим им строкам и столбцам, указав их координаты.

<Grid>    <Grid.RowDefinitions>        <RowDefinition></RowDefinition>        <RowDefinition></RowDefinition>        <RowDefinition></RowDefinition>        <RowDefinition></RowDefinition>        <RowDefinition></RowDefinition>    </Grid.RowDefinitions>    <Grid.ColumnDefinitions>        <ColumnDefinition></ColumnDefinition>         <ColumnDefinition></ColumnDefinition>         <ColumnDefinition></ColumnDefinition>         <ColumnDefinition></ColumnDefinition>         <ColumnDefinition></ColumnDefinition>    </Grid.ColumnDefinitions>    <Button Grid.Row="0" Grid.Column="0">1</Button></Grid>

Стоит отметить, что элементы внутри Grid могут занимать несколько ячеек. Для этого используются параметры ColumnSpan и RowSpan:

 <Button Grid.Row="3" Grid.Column="3" Grid.ColumnSpan="2">=</Button>

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

Последнее, что нам осталось сделать это задать параметры окна. Установим стартовые и минимальные размеры окна (они задаются в корневом элементе Window).

MinHeight="300"MinWidth="250"Height="300"Width="250"

После добавления всех элементов разметки наше окно калькулятора будет выглядеть так:



Основной функционал


С разметкой закончили, пора реализовать логику!

Начнем с добавления в папку Models нового Enum, который описывает возможные операции:

public enum Operation{    Add,    Subtract,    Multiply,    Divide,    Result}

Теперь перейдем в класс ViewModel/MainWindowViewModel. Здесь будет храниться основная функциональность нашего приложения.

Добавим в файл несколько приватных полей, с которыми мы будем работать:

private double _firstValue;private double _secondValue;private Operation _operation = Operation.Add;

Теперь реализуем основные методы:

  • AddNumber добавляет новую цифру к числу.
  • ExecuteOperation выполняет одну из операций, описанных в енаме Operation.
  • RemoveLastNumber удаляет последнюю введенную цифру.
  • ClearScreen очищает экран калькулятора.

Не будем останавливаться на реализации этих методов, в этом нет никакой специфики для Авалонии (реализацию вы можете посмотреть в репозитории проекта). Единственное, что нас интересует это то, что помимо перечисленных выше приватных полей, эти методы также оперируют публичным свойством ShownValue. О нем чуть позже.

Связывание


Теперь, когда у нас готовы и разметка, и логика, пора связать их друг с другом.
В Авалонию по умолчанию включен Reactive UI это фреймворк, предназначенный как раз для связывания View и Model при использовании MVVM. Подробнее о нем вы сможете прочитать на официальном сайте и в документации Авалонии. Конкретно сейчас нас интересует возможность фреймворка обновлять View при изменении данных.

Для хранения актуального значения, выводимого на экране, реализуем свойство ShownValue:

public double ShownValue{    get => _secondValue;    set => this.RaiseAndSetIfChanged(ref _secondValue, value);}

Получаемое из этого свойства значение будет выводиться на дисплее нашего калькулятора, а метод RaiseAndSetIfChanged позаботится о вызове уведомления при изменении значения свойства.

Привяжем это свойство к созданному на этапе разметки текстовому полю:

<TextBlock Grid.Row="1" Text="{Binding ShownValue}" />

Благодаря директиве Binding и методу RaiseAndSetIfChanged значение свойства Text в этом поле будет обновляться при каждом изменении значения свойства ShownValue.

Теперь добавим в MainWindowViewModel еще три публичных свойства для команд. Команды являются обертками вокруг функций, которые будут вызываться определенными действиями на UI.

public ReactiveCommand<int, Unit> AddNumberCommand { get; }public ReactiveCommand<Unit, Unit> RemoveLastNumberCommand { get; }public ReactiveCommand<Operation, Unit> ExecuteOperationCommand { get; }

Команды нужно инициализировать в конструкторе класса, связав их с соответствующими методами:

public MainWindowViewModel(){    AddNumberCommand = ReactiveCommand.Create<int>(AddNumber);    ExecuteOperationCommand = ReactiveCommand.Create<Operation>(ExecuteOperation);    RemoveLastNumberCommand = ReactiveCommand.Create(RemoveLastNumber);}

Теперь обновим разметку кнопок. Например, для клавиши Backspace новая разметка будет выглядеть так:

<Button Grid.Row="3" Grid.Column="2" Command="{Binding RemoveLastNumberCommand}"></Button>

Несколько сложнее дела обстоят с номерными кнопками и кнопками операций. Для них мы должны передать в качестве параметра вводимую цифру или операцию. Для этого в корневом теге Window нам нужно добавить пространство имен System:

xmlns:s="clr-namespace:System;assembly=mscorlib"

А затем обновить разметку кнопок, добавив в них связанный метод и параметр:

<Button Grid.Row="0" Grid.Column="0" Command="{Binding AddNumberCommand}">    <Button.CommandParameter>        <s:Int32>1</s:Int32>    </Button.CommandParameter>     1</Button>

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



Стили


Итак, логика нашего калькулятора полностью реализована, но его визуальная сторона оставляет желать лучшего. Самое время поиграться со стилями!

В Авалонии есть три способа управлять стилями:

  • настроить стили внутри компонента,
  • настроить стили в рамках окна,
  • подключить пакет стилей.

Пройдемся по каждому из них.

Начнем с настройки стилей внутри конкретного компонента. Очевидный претендент на точечные изменения это экран нашего калькулятора. Давайте увеличим для него размер шрифта и перенесем текст вправо.

<TextBlock Grid.Row="1" Text="{Binding ShownValue}" TextAlignment="Right" FontSize="30" />

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

<Window.Styles>    <Style Selector="Button">        <Setter Property="Margin" Value="5"></Setter>     </Style></Window.Styles>

Как видите, конкретные компоненты, к которым применяется стиль, можно выбирать при помощи селектора. Больше о селекторах вы можете прочитать в документации Авалонии.

После применения изменений выше наше окно будет выглядеть так



Чтобы упростить себе жизнь, можете воспользоваться готовым пакетом стилей. Давайте, к примеру, подключим для нашего калькулятора стиль Material. Для этого добавим соответствующий nuget пакет:

dotnet add package Material.Avalonia --version 0.10.3

А теперь обновим файл App.xaml и укажем в нем используемый пакет стилей и его параметры.

<Application ...             xmlns:themes="clr-namespace:Material.Styles.Themes;assembly=Material.Styles"             ...>    <Application.Resources>        <themes:BundledTheme BaseTheme="Dark" PrimaryColor="Purple" SecondaryColor="Amber"/>    </Application.Resources>    <Application.Styles>        <StyleInclude Source="avares://Material.Avalonia/Material.Avalonia.Templates.xaml" />    </Application.Styles></Application>

Установленный пакет обновит визуальный стиль нашего приложения, и теперь оно будет выглядеть так:



Такие же пакеты стилей можно создавать самостоятельно их можно использовать внутри вашего проекта или распространять в виде пакета на nuget. Больше информации о стилях и способах управления ими можно найти в документации.

Заключение


В этой статье мы разобрали самый простой пример использования Авалонии, но функционал этого фреймворка куда шире, и он растет с каждым днем. Помимо неоднократно упомянутой мной документации, вы также можете спросить совета в русскоязычном чате, посвященном Авалонии, или прямо здесь в комментариях.
А еще много интересного про Авалонию и .NET UI можно будет послушать на онлайн-митапе от Контура, который пройдет сегодня, в пять по Москве.

Все исходники проекта вы можете найти в репозитории на Github.

На этом все! Оставайтесь на связи, мы вернемся со статьями о более продвинутых возможностях Авалонии.
Подробнее..

От WPF к Авалонии

16.02.2021 12:20:24 | Автор: admin

WPF любимый сообществом фреймворк для десктопной разработки, однако в то время как дотнет и вся его экосистема уже давно кроссплатформенные, WPF работает только под Windows. Сообщество решило эту проблему и теперь у нас есть Авалония фреймворк, во многом очень похожий на WPF, но работающий на разных платформах.

Под катом мы разберем отличия Авалонии от WPF. Что нужно знать людям, переходящим с WPF на Авалонию? В чем преимущества нового фреймворка, а в чем его недостатки по сравнению с WPF?

Стили

На первый взгляд, стили в Авалонии выглядят точно также, как и в WPF они задаются в блоке Styles с помощью селекторов и сеттеров. Первые выбирают набор блоков, к которым применяются стили, вторые задают непосредственно стили. Давайте сравним два одинаковых стиля в WPF и Авалонии:

<Style TargetType="TextBlock">  <Setter Property="HorizontalAlignment" Value="Center" />  <Setter Property="FontSize" Value="24"/></Style>
<Style Selector="TextBlock"><Setter Property="HorizontalAlignment" Value="Center" /><Setter Property="FontSize" Value="24"/></Style>

Как видите, в данном фрагменте различается только объявление тега Style в WPF для выбора целевого блока используется параметр TargetType, а в Авалонии - Selector. Однако селекторы в Авалонии куда мощнее, чем TargetType в WPF. Больше всего они напоминают селекторы из CSS с классами, псевдоклассами и кастомными обращениями.

Например, вот так мы можем задать размер шрифта для всех текстовых блоков с классом h1

<Styles>  <Style Selector="TextBlock.h1">    <Setter Property="FontSize" Value="24"/>  </Style></Styles><TextBlock Classes="h1">Header</TextBlock>

Псевдоклассы позволяют нам выбирать элементы в определенном состоянии когда они в фокусе, когда на них наведена мышь, когда чекбокс выбран и так далее. В WPF подобное реализовывалось через триггеры (стиль изменялся, когда активировалось некоторое событие), однако подход через псевдоклассы выглядит более удобным и современным.

<Styles>  <Style Selector="Button:pointerover">    <Setter Property="Button.Foreground" Value="Red"/>  </Style></Styles><Button>I will have red text when hovered.</Button>

И, конечно же, селекторы в Авалонии позволяют гибко выбирать целевые контролы для стилей через цепочки дочерних элементов, по совпадению нескольких классов, по шаблонам и по значениям определенных свойств. Опять же, это очень похоже на селекторы в CSS. Например, вот так мы можем выбрать кнопку, являющуюся прямым наследником элемента с классом block, имеющую значение свойства IsDefault = true:

.block > Button[IsDefault=true]

Полный список доступных селекторов и их описания вы можете найти в документации Авалонии.

Обновленный синтаксис XAML

Как и в случае со стилями, синтаксис XAML не слишком сильно отличается от WPF. Объявление контролов, параметры, биндинги все выглядит по-прежнему. Однако некоторые отличия все же есть синтаксис стал более емким и понятным, а где-то добавились новые возможности. Посмотрим на изменения по порядку.

Начнем с упрощений в синтаксисе. Самый простой пример такого упрощения это объявление строк и столбцов в Grid. Классическое, привычное с WPF объявление будет выглядеть следующим образом:

<Grid>  <Grid.RowDefinitions>    <RowDefinition Height="*"></RowDefinition>    <RowDefinition Height="Auto"></RowDefinition>    <RowDefinition Height="32"></RowDefinition>  </Grid.RowDefinitions></Grid>

Этот код будет отлично работать и в Авалонии, однако, помимо полного варианта объявления, добавился и сокращенный.

<Grid RowDefinitions="*,Auto,32,"/>

Упростилось и подключение зависимостей в XAML файлах. Теперь clr-namespace можно заменить на using. Такое изменение позволяет сделать подключение сторонних библиотек короче и читаемее.

Было: xmlns:styles="clr-namespace:Material.Styles;assembly=Material.Styles"

Стало: xmlns:styles="using=Material.Styles"

Другое любопытное изменение это вынесение DataTemplates и Styles в отдельные теги. Раньше они размещались внутри Resources.

<UserControl xmlns:viewmodels="clr-namespace:MyApp.ViewModels;assembly=MyApp">  <UserControl.DataTemplates>    <DataTemplate DataType="viewmodels:FooViewModel">      <Border Background="Red" CornerRadius="8">        <TextBox Text="{Binding Name}"/>      </Border>    </DataTemplate>  </UserControl.DataTemplates>  <UserControl.Styles>    <Style Selector="ContentControl.Red">      <Setter Property="Background" Value="Red"/>    </Style>  </UserControl.Styles><UserControl>

Важные изменения произошли и в биндингах. Авалония позволяет связывать между собой элементы разметки, прибегая только к свойствам XAML. Достаточно обратиться к источнику зависимости, используя # и имя элемента. Например, вот такой код привяжет значение поля other к значению поля source.

<TextBox Name="source"/><!-- Binds to the Text property of the "source" control --><TextBlock Name=other Text="{Binding #source.Text}"/>

Конструкция $parent позволяет обращаться к родительским компонентам.

<Border Tag="Hello World!">  <TextBlock Text="{Binding $parent.Tag}"/></Border>

Кстати, такое обращение поддерживает индексирование. Иначе говоря, конструкция $parent[1] позволит вам обратиться к родителю родителя вашего компонента. А конструкция $parent[0] эквивалентна $parent.

Помимо индексов здесь также можно использовать обращение по типу. $parent[Border] позволит вам обратиться к первому предку с типом Border. А еще такое обращение можно совместить с индексированием.

<Border Tag="Hello World!">  <Border>    <Decorator>      <TextBlock Text="{Binding $parent[Border;1].Tag}"/>    </Decorator>  </Border></Border>

В WPF аналогичную функцию выполняет свойство RelativeSource, однако RelativeSource завязано на визуальном дереве элементов, а не на логическом. Подробнее почитать о разнице вы можете в этой статье.

Небольшие изменения также коснулись конвертеров. Один из самых популярных конвертеров, логическое отрицание, был реализован в виде синтаксиса в XAML коде.

<StackPanel>  <TextBox Name="input" IsEnabled="{Binding AllowInput}"/>  <TextBlock IsVisible="{Binding !AllowInput}">Sorry, no can do!</TextBlock></StackPanel>

Кстати, этот конвертер использует метод Convert.ToBoolean для преобразования значений, что позволяет писать код такого вида:

<Panel>  <ListBox Items="{Binding Items}"/>  <TextBlock IsVisible="{Binding !Items.Count}">No results found</TextBlock></Panel>

А еще конвертер поддерживает множественное использование, что позволяет преобразовывать не-булевские значения в boolean с помощью двойного отрицания.

<Panel>  <ListBox Items="{Binding Items}" IsVisible="{Binding !!Items.Count}"/></Panel>

Я бы не рекомендовал использовать такой трюк, однако множественное применение конвертеров может пригодиться, когда в Авалонии реализуют синтаксическую поддержку для других конвертеров. Теоретически вы можете сделать это сами, но на практике это достаточно сложно и никак не документировано.

Помимо синтаксической поддержки, Авалония дает пользователю несколько дефолтных конвертеров. Для них не реализован никакой специфический XAML синтаксис, так что они используются как обычные конвертеры.

<TextBlock Text="{Binding MyText}" IsVisible="{Binding MyText, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>

И последняя возможность, которую хочется упомянуть, относится к проблемам кроссплатформенности. Видели вот это меню в macOS?

Подобные элементы это вечная головная боль кроссплатформенных приложений. На маках такое меню есть, а на винде нет. А в Linux есть, но не везде, только в некоторых оконных менеджерах.

Для работы с такими меню в девятой версии Авалонии реализовали компонент NativeMenu. По умолчанию, даже без добавления NativeMenu в приложение, Авалония будет генерировать меню с базовыми пунктами как раз они и представлены на скриншоте. А для добавления новых пунктов как раз и используется NativeMenu.

<Application>  <NativeMenu.Menu>    <NativeMenu>      <NativeMenuItem Header="About MyApp" Command="{Binding AboutCommand}" />    </NativeMenu>  </NativeMenu.Menu></Application>

Декларативный UI via F#

Несмотря на все улучшения и упрощения для XAML, многим он кажется громоздким и избыточным, поэтому все большую популярность набирают разнообразные декларативные DSL.

Сообщество Авалонии разработало отличную библиотеку Avalonia.FuncUI, позволяющую вам писать UI на Авалонии в декларативном стиле. Получающийся код напоминает реализацию UI с помощью Elm или Jetpack Compose.

module Counter =    type CounterState = {    count : int  }  let init = {    count = 0  }  type Msg =  | Increment  | Decrement      let update (msg: Msg) (state: CounterState) : CounterState =   match msg with    | Increment -> { state with count =  state.count + 1 }    | Decrement -> { state with count =  state.count - 1 }  let view (state: CounterState) (dispatch): IView =    DockPanel.create [      DockPanel.children [        Button.create [          Button.onClick (fun _ -> dispatch Increment)          Button.content "click to increment"        ]        Button.create [          Button.onClick (fun _ -> dispatch Decrement)          Button.content "click to decrement"         ]        TextBlock.create [          TextBlock.dock Dock.Top          TextBlock.text (sprintf "the count is %i" state.count)        ]      ]    ]

Такая стилистика пока не стала чем-то общепринятым кому-то привычнее XAML, а кто-то считает FuncUI громоздким по сравнению с конкурентами. В любом случае, это интересная опция, которая для многих может быть более удобной и понятной.

Недостатки

Перейдем к самому интересному а что же не так с Авалонией в сравнении с WPF?

Если смотреть на функциональность, то Авалония не только приобрела новые фичи, но и потеряла некоторые возможности WPF. Например, в Авалонии из коробки отсутствуют триггеры (обсуждение этой фичи можно найти в репозитории Авалонии, а пока триггеры можно использовать с помощью пакета AvaloniaBehaviors), не работают биндинги через стили и так далее.

Другая проблема это экосистема. Так как Авалония куда моложе WPF и имеет значительно меньшую аудиторию, вокруг Авалонии пока не выстроилась богатая экосистема со множеством стилей и кастомных контролов. Да и гигантских платных UI фреймворков вроде DevExpress пока не появилось. Аналогичные сложности и с инструментами разработки они есть, и поддержка становится все лучше, однако пока что отстает по качеству от WPF.

Ну и самая заметная проблема, о которой говорят многие это отсутствие гарантий. Авалония это инструмент, создаваемый исключительно сообществом, что не слишком-то привычно для .NET разработчиков. За ним не стоит Microsoft или какая-то другая крупная компания. В этих условиях многим не хочется рисковать, надеясь на open source продукт кто знает, вдруг завтра мейнтейнер потеряет интерес, и разработка встанет? Однако это же дает вам возможность заметно влиять на развитие Авалонии, исправляя существующие проблемы и предлагая новые фичи.

Заключение

Подводя итог можно сказать, что Авалония это осовремененная версия WPF. Синтаксис чуть полегче, стили современнее, да и в целом фреймворк подталкивает к использованию современных подходов вроде реактивного программирования и декларативного DSL.

Компенсация за это типичный набор проблем молодого опенсорсного инструмента. Кое-какие фичи не реализованы, экосистема бедновата, да и качественная поддержка в будущем не гарантирована. Так или иначе, это несет и преимущества в виде возможности заметно влиять на будущее Авалонии.

Если вам нравится этот фреймворк не стесняйтесь активно участвовать в его разработке. Пишите предложения в репозитории, участвуйте в дискуссиях, правьте найденные баги и общайтесь с сообществом в телеграме Авалонии.

И, конечно же, подписывайтесь на наш блог на Хабре мы уже готовим следующую статью про Авалонию. А на сегодня все, оставайтесь на связи.

Подробнее..

Портирование приложений с QWidget на QML под Desktop

07.07.2020 14:13:48 | Автор: admin
Привет, Хабровчане!

Последнее время часто встречаю проекты для desktop, которые необходимо портировать с QWidget на QML. Кто-то хочет написать с нуля, кто-то перенести старые наработки. В любом из сценариев это популяризация QML, чему я очень рад. Я решил тоже побыть полезным и поделиться своими наработками и ситуациями из личной практики. Возможно, некоторые из них покажутся вам простыми и понятными, однако если я что-то упустил, буду рад почитать об этом в комментариях.

Кратко о QML и QWidget


QML это декларативный язык программирования. Он позволяет разработчикам и дизайнерам создавать удобные, плавно анимированные и визуально привлекательные приложения. QML предлагает читаемый декларативный JSON-подобный синтаксис с поддержкой императивных выражений JavaScript в сочетании с динамическими привязками свойств.

QWidget это предшествующая QML технология для создания пользовательского интерфейса, основанная на использовании заранее созданных desktop-style компонентов и предоставляющая API для взаимодействия с ними.

Пользовательский опыт


Около года назад я нашел прекрасный инструмент для работы с гитом gitAhead и в целом я доволен большей частью функционала и он мне часто помогает. Но на этом примере прекрасно видно, что виджетам не хватает гибкости и функционала. Из-за этого невозможно покрыть больше пользовательских сценариев и сделать более дружелюбный интерфейс. В итоге вместо отличного дерева коммитов и дифов, мы имеем огромное количество ограничений.

Стандартное окно(кликабельно)


Дизайн приложения и его влияние на портирование


Классически Qt нам советует придерживаться паттернов MVC\MVP, где всё взаимодействие с UI вынесено в отдельные сущности, что позволяет нам изменять их, не затрагивая остальные части приложений и бизнес логику. Абстрагирование элементов и возможность встраивать QML-компонент в стандартный QWidget позволяет гибко подойти к плану дальнейшей работы. Если вы сейчас на этом пункте, то я бы хотел вам посоветовать продумать требования и функциональность будущего приложения, нарисовать его макет, посоветоваться с дизайнерами, опросить пользователей или заказчика. В целом решить как вы будете жить ближайшее время, и какие вопросы будете решать. И выбрать один из двух путей, предложенных ниже.

План А С чистого листа


Мы убираем весь старый UI, виджеты, зависимости, legacy код и оставляем только требования и функциональность, которые нам необходимо будет восстановить. Хорошая возможность улучшить архитектуру, подготовить почву для новых изменений. При подготовке необходимо учитывать следующие вещи, которые изменяются при переходе от одной библиотеке к другой и лучше продумать их заранее:

  1. Библиотека элементов. Создайте свою начальную базу элементов, где пропишите базовые настройки для элементов, их вид и состояния по умолчанию. Если есть возможность, оберните их тестами. В одной из конфигураций проекта сделайте их копируемыми в папку проекта, а не компилируемыми в бинарный*.qrc, это позволит вам править эти сборки в runtime без дополнительных компиляций и подключать qmllive и его аналоги.
  2. Отсутствие QAction. В приложениях на QWidget, частым гостем является и QAction, расширяющий возможности перехвата действий пользователя как по нажатию кнопки на экране, так и перехватом горячих клавиш. К сожалению в QML QAction переехал в несколько ином виде и позволяет только из QML обертки перехватывать заранее определенные горячие клавиши, что накладывает ограничения на использование его внутри C++ или создавать динамически. Здесь вы можете написать свою реализацию, использовать event-filter от qobject или использовать сторонние проекты, такие как QHotkey .

В остальном здесь всё типично для приложения на C++ и QML.

План Б У нас типо agile


Так как для Agile одной из главной ценностью является работающий продукт, то мы должны делать переезд поэтапно, без регресса функционала. Для этого мы делим весь UI на логические сегменты с минимальным количеством взаимозависимостей и реализуем их отдельно. На примере gitAhead можно рассмотреть следующий вариант(выделил зеленым):

Разделение UI на сегменты(кликабельно)


Для каждой из областей мы создадим родительский виджет, в который и положим корневой QML элемент. Это можно сделать двумя способами.

Создать QQuickWidget

QQuickWidget *view = new QQuickWidget;view->setSource(QUrl::fromLocalFile("myqmlfile.qml"));view->show();

Или вызывать метод QWidget::createWindowContainer(...)

QQuickView *view = new QQuickView();...QWidget *container = QWidget::createWindowContainer(view);container->setMinimumSize(...);container->setMaximumSize(...);...widgetLayout->addWidget(container);

Разница между предложенными способами в том что QQuickWidget является более гибкой альтернативой чем QWidget::createWindowContainer(...), ведя себя больше как обычный QWidget. При этом большая функциональность достигается из-за незначительных потерь в производительности.

Дальнейшая работа производится как с обычным QQuickView, за исключением следующих особенностей:

  1. Ограниченный контекст. У каждого родительского QWidget будет свой QML-контекст, что в свое очередь накладывает ограничения на область видимости. Свои property, функции, объекты и т.д. Каждый widget по сути становится независимой песочницей
  2. Ограничение по размеру виджета. В ходе создания виджета, можно создать правило чтобы размеры виджета следовали за размерами контента внутри и по сути это правильно. Но это распространяется только на корневые элементы внутри этого QML, а все временные объекты с размерами превышающими виджет, будут обрезаться границами. Хорошим примером тут может служить всплывающая подсказка для кнопок, если кнопки маленькие, при это есть большое описание, то часть подсказки скроется за границей виджета. Если размеры виджета-контейнера поставлены не по размеру контента, то это так же может привести к проблемам при масштабировании интерфейса. Частично эта проблема решается элементами из lab.

    Пример выхода за границы и неправильной верстки
    image

  3. 3D графика. Здесь всё усложнено и полно ограничений, рекомендуется использовать QQuickWidget и есть не маленькая вероятность что именно ваш сценарий либо потребует костылей, либо больших трудовложений, поэтому ознакомьтесь с ограничениями заранее, либо сразу реализуйте под использование в QML

Стоит напомнить что приведенные выше ограничения распространяются только на тот период, пока вы переносите свою элементную базу. Описанные же в плане А ограничения распространяются на всё приложение. Последним элементом будет являться базовое окно приложения, где необходимо будет всё расположить в соответствии с дизайном, работой фокуса и др. требованиями. Останется удалить последние зависимости приложения от QWidget библиотеки, убрать ее из поставки и всё, работа закончена. Вы молодцы!

Итоги


Над gitAhead мне предстоит еще много работы, и не только по UI, но приблизительно как это происходит я описал. Если вам понравилась идея и вам хочется поучаствовать, то буду рад сотрудничеству. Мой репозиторий будет тут.
Подробнее..

UbuntuDDE замечательный гибрид

23.01.2021 14:23:52 | Автор: admin

Привет, %username%. Вот уже на протяжении 15 лет провожу над собой забавный эксперимент. Заключается он в том, чтобы пробовать разные дистрибутивы Linux и делать однозначный вывод о возможности использования в качестве основной десктопной ОС. Времена, когда Linux был не слишком дружелюбен к обычному пользователю, давно прошли, и сейчас установить его не представляет труда.

Одним из первых дистрибутивов, с которым я познакомился и с которого начался мой путь в Linux, была ОС Ubuntu 6.06 LTS. Скачивание образов было непомерно дорогой роскошью, так что я отправил запрос в Canonical, оставил домашний адрес и спустя месяц получил вожделенный набор дисков (Ubuntu / Kubuntu / Edubuntu). Не могу сказать, что был в восторге от ОС, но тем не менее это был интересный опыт.

С тех пор я перебрал множество разных дистрибутивов, но в итоге самым удобным для меня стал OpenSUSE Tumbleweed. Вместе с KDE он оказался самым стабильным и универсальным. Разумеется, я не остановился на этом и пару лет назад нашел еще весьма интересный китайский дистрибутив под названием deepin (он же Hiwix, он же Hiweed Linux, он же Linux Deepin).

Рабочий стол Deepin 20.1 Community Edition
Вначале это была связка из Morphix и IceWM, затем разработчики стали использовать в качестве базовой системы Debian с Xfce. Результат им, видимо, понравился, но в итоге они ушли на связку из Ubuntu и LXDE, а затем Ubuntu и Gnome (вначале версии 2, затем 3). И оставался бы deepin рядовым клоном Ubuntu, если бы с 2013 года не началась разработка собственного окружения рабочего стола дистрибутива, которое получило название DDE Deepin Desktop Environment.

Установив deepin, я был приятно удивлен качеством и продуманностью рабочего стола, расположением элементов и прочими визуальными мелочами. Но, несмотря на кажущееся удобство, у китайского дистрибутива была и масса минусов, навскидку:

  • крайне тормозные репозитории, из-за чего процедура обновления затягивалась на несколько часов;
  • директории с именами на китайском языке, вылезающими в разных местах;
  • скандал с наличием в дистрибутиве шпионского ПО;
  • ...

Мне пришлось вернуться к OpenSUSE. DDE есть для дистрибутивов Arch и Manjaro, но устанавливать их не стал и оставил идею полноценного использования новой среды окружения. Лишь спустя два года мне на глаза попалась новость о выходе UbuntuDDE на базе версии Ubuntu 20.04. В дальнейшем UbuntuDDE вошел в список дистрибутивов, поддерживаемых сообществом.

И вот, в очередной раз сменив дистрибутив, мне захотелось рассказать о том, с какими сложностями и удобствами довелось столкнуться. Для начала обратим внимание на визуальную составляющую. DDE имеет два режима отображения. В русскоязычной версии они носят названия Эффективный и Современный.

Эффективный режим DDE схож с Windows 10
Насколько можно судить по первому взгляду на эффективный режим DDE, разработчики старались сделать максимально похожий на Windows 10 стиль. Главное меню тоже частично копирует расположение и логику элементов из операционной системы компании Microsoft.

Главное меню в Эффективном режиме
Пользователей MacOS тоже не забыли. Чтобы сделать работу в DDE привычнее для людей, использующих Apple, достаточно переключиться в Современный режим. Тут же появляется док по центру, куда можно помещать нужные приложения.

Док DDE весьма сильно напоминает док MacOS (Современный режим)
Меню тоже можно сделать похожим на лаунчпад из MacOS:

Главное меню DDE имеет схожесть с лаунчпадом MacOS
Все железо определилось и заработало корректно, за исключением трекбола Logitech Marble Trackman. Из-за отсутствия у последнего отдельного колеса прокрутки приходится использовать программное решение, детально расписанное в небольшой статье Настраиваем трекбол Trackman Marble пользователя @norguhtar (за что ему отдельное спасибо!). Но, в отличие от OpenSUSE, для UbuntuDDE этот трюк почему-то не сработал, и пришлось использовать альтернативный вариант с двумя командами:

xinput --set-prop "Logitech USB Trackball" "libinput Scroll Method Enabled" 0 0 1xinput --set-prop "Logitech USB Trackball" "libinput Button Scrolling Button" 8

Также никаких проблем не возникло при установке и работе с гипервизором Oracle VM VirtualBox. Единственный нюанс: расширение Oracle VM VirtualBox Extension pack пришлось ставить из интерфейса программы (файловый менеджер не смог корректно передать команду на установку). Отмечен также забавный глюк, заключающийся в том, что в главном меню VirtualBox автоматически был помещен в раздел Игры, а не в раздел Другое.

В качестве офисного пакета используется привычный LibreOffice версии 7, также присутствуют два специфичных для Deepin приложения: Просмотрщик документов Deepin и Текстовый редактор Deepin. Еще из полезного можно отметить утилиту Zathura (просмотрщик PDF, DjVu, PostScript). Для работы с графикой установлен GIMP, приложение для сканирования документов и просмотрщик изображений. Еще есть очень удобная утилита записи с экрана, позволяющая не только сделать скриншот выделенной области, но и снять видео этой области сразу в GIF.

Все часто используемые мной программы заработали корректно, например ksnip для снятия скриншотов экрана или Telegram Desktop. Некоторые приложения, которые предназначены только для Windows корректно работают через Wine. В UbuntuDDE 20.10 используется стабильная версия wine-5.0. Ее возможностей вполне достаточно для запуска нужных мне приложений, таких как:

  • WinBox (для конфигурирования оборудования MikroTik),
  • PIPP (препроцессор для обработки астрофотографий),
  • AutoStakkert! (утилита обработки астрофотографий).

Вместо заключения хочется отметить, что UbuntuDDE мне понравился. Разработчики явно постарались создать идеальный компромиссный гибрид, взяв лучшее из двух популярных дистрибутивов. И надо признать, им удалось. Это хороший вариант для тех, кто надумал переезжать на Linux, но не готов использовать привычные оболочки рабочего стола, такие как GNOME, KDE, Xfce, LXDE. Узнать больше об UbuntuDDE можно на официальном сайте проекта. Там же можно скачать актуальную версию ОС в виде ISO-образа.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru