Search
Write a publication
Pull to refresh
10
0
Эдуард Щавелев @edward_nsk

Разработчик: .NET, SQL, Typescript

Send message

Версия тестового приложения для WPF немного отличается определением стиля и его использованием.

Чтобы получить работающее тестовое приложение для WPF, выполните в терминале команду:

dotnet new wpf -n TryWpf

Затем замените текст MainWindow.xaml:

Скрытый текст
<Window x:Class="TryWpf.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TryWpf"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="300">
  <Window.Resources>
    <DrawingGroup x:Key="Geometry/Record2">
      <GeometryDrawing Brush="Red" Geometry="M8 12a4 4 0 1 1 0-8 4 4 0 0 1 0 8m0 1A5 5 0 1 0 8 3a5 5 0 0 0 0 10" />
      <GeometryDrawing Brush="Blue" Geometry="M10 8a2 2 0 1 1-4 0 2 2 0 0 1 4 0" />
    </DrawingGroup>
    <DrawingImage x:Key="Image/Record2" Drawing="{StaticResource Geometry/Record2}" />
    <Style TargetType="Image" x:Key="IconImageStyle">
      <Setter Property="Width" Value="16" />
      <Setter Property="Height" Value="16" />
      <Setter Property="Stretch" Value="None" />
      <Style.Triggers>
        <Trigger Property="UIElement.IsEnabled" Value="False">
          <Setter Property="Opacity" Value="0.25" />
        </Trigger>
      </Style.Triggers>
    </Style>
  </Window.Resources>
  <StackPanel>
    <Button Margin="2">
      <Viewbox Width="32" Height="32" Stretch="Uniform">
        <Image Source="{StaticResource Image/Record2}" Style="{StaticResource IconImageStyle}" />
      </Viewbox>
    </Button>
    <Button Margin="2" IsEnabled="False">
      <Viewbox Width="32" Height="32" Stretch="Uniform">
        <Image Source="{StaticResource Image/Record2}" Style="{StaticResource IconImageStyle}" />
      </Viewbox>
    </Button>
  </StackPanel>
</Window>

Если требуются изменять размеры иконок, то <Image> надо обернуть во <Viewbox>. Также для удобства ввел класс для стилизации bi (bootstrap icon).

Чтобы получить работающее тестовое приложение для Avalonia, выполните в терминале команду:

dotnet new avalonia.app -n TryAvalonia

Затем замените текст MainWindow.axaml:

Скрытый текст
<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="TryAvalonia.MainWindow"
        Title="TryAvalonia">
  <Window.Resources>
    <DrawingGroup x:Key="Geometry/Record2">
      <GeometryDrawing Brush="Red" Geometry="M8 12a4 4 0 1 1 0-8 4 4 0 0 1 0 8m0 1A5 5 0 1 0 8 3a5 5 0 0 0 0 10" />
      <GeometryDrawing Brush="Blue" Geometry="M10 8a2 2 0 1 1-4 0 2 2 0 0 1 4 0" />
    </DrawingGroup>
    <DrawingImage x:Key="Image/Record2" Drawing="{StaticResource Geometry/Record2}" />
  </Window.Resources>
  <Window.Styles>
    <Style Selector="Image.bi">
      <Setter Property="Width" Value="16" />
      <Setter Property="Height" Value="16" />
      <Setter Property="Stretch" Value="None" />
      <Style Selector="^:disabled">
        <Setter Property="Opacity" Value="0.25" />
      </Style>
    </Style>
  </Window.Styles>
  <StackPanel>
    <Button>
      <Viewbox Width="32" Height="32" Child="{StaticResource IconVer1}" Stretch="Uniform"/>
    </Button>
    <Button>
      <Viewbox Width="32" Height="32">
        <Image Classes="bi" Source="{StaticResource Image/Record2}"/>
      </Viewbox>
    </Button>
    <Button IsEnabled="False" >
      <Viewbox Width="32" Height="32">
        <Image Classes="bi" Source="{StaticResource Image/Record2}"/>
      </Viewbox>
    </Button>
  </StackPanel>
</Window>

В простом пролетарском WPF определения Style помещают в Window.Resources. Авалония стили отделила от ресурсов. А вот селекторы в стилях - это уже чисто на авалонском. Для единообразия попробуйте вместо них использовать <Style.Triggers>

Забыл добавить Stretch="None"

<Image Width="16" Height="16" Source="{StaticResource Image/Record2}" Stretch="None"/>

Да, конечно. Это тоже вполне работающий вариант, хотя и не привычный.

По моему, при таком подходе (определение иконок в ресурсах) вместо <Canvas> предпочтительнее использовать <Image>. Как минимум, для тех кто ранее использовал растровые иконки это будет привычнее (для них тоже принято использовать <Image>).

Попробуйте такой вариант определения иконки в ресурсах (на примере «record2.svg»):

  <Window.Resources>
    <DrawingGroup x:Key="Geometry/Record2">
      <GeometryDrawing Brush="Red" Geometry="M8 12a4 4 0 1 1 0-8 4 4 0 0 1 0 8m0 1A5 5 0 1 0 8 3a5 5 0 0 0 0 10" />
      <GeometryDrawing Brush="Blue" Geometry="M10 8a2 2 0 1 1-4 0 2 2 0 0 1 4 0" />
    </DrawingGroup>
    <DrawingImage x:Key="Image/Record2" Drawing="{StaticResource Geometry/Record2}" />
  </Window.Resources>

Чтобы отличать запрещенную кнопку, возможно, такой вариант изменения Opacity будет проще:

  <Window.Styles>
    <Style Selector="Button:disabled Image">
      <Setter Property="Opacity" Value="0.25" />
    </Style>
  </Window.Styles>

В таком случае ваш пример использования иконки можно превратить в нечто подобное:

  <StackPanel>
    <Button>
      <Image Width="50" Source="{StaticResource Image/Record2}" />
    </Button>
    <Button IsEnabled="False" >
      <Image Width="50" Source="{StaticResource Image/Record2}"/>
    </Button>
  </StackPanel>

Исправленный вариант (кроме ширины у "align-top" ещё смещение вверх было):

{
  "align-bottom": [
    "M7 1h2a1 1 0 011 1v10a1 1 0 01-1 1H7a1 1 0 01-1-1V2a1 1 0 011-1z",
    "M1.5 14a.5.5 0 0 0 0 1zm13 1a.5.5 0 0 0 0-1zm-13 0h13v-1h-13z"
  ],
  "align-top": [
    "M7 3h2a1 1 0 011 1v10a1 1 0 01-1 1H7a1 1 0 01-1-1V4a1 1 0 011-1z",
    "M1.5 2a.5.5 0 0 1 0-1zm13-1a.5.5 0 0 1 0 1zm-13 0h13v1h-13z"
  ],
}

Спасибо, поправлю. Мне его ИИ перерисовывал. Надо было проверять тщательнее.

Спасибо, правильный вопрос. Я даже это объяснял в статье, но потом вырезал (и так объемно получилось).

Основной мотив: не хотелось просаживать рендеринг (на сотнях элементов становится заметно). Кроме того, из личного опыта - редко требовалось более 2-х цветов (для иконок в меню, тулбале, сайдбаре, гриде). Речь не идет о многоцветных логотипах, для этого можно использовать, например, сложную геометрию в XAML-ресурсе.

Обычно один цвет нужен для привлечения внимания (красный, желтый) и повышения узнаваемости (брэнд-колор).

Второй цвет полезен, чтобы меньше промахивались: по иконкам 16x16 можно не отличить команды "Удалить запись" от "Добавить запись" (наличие красного дает больше шансов).

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="Try.MainWindow"       
        Title="Try">
  <Window.Resources>
    <DrawingGroup x:Key="Geometry/DatabaseDash">
      <GeometryDrawing Brush="#dc3545" Geometry="M12.5 16a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7M11 12h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1 0-1" />
      <GeometryDrawing Brush="#7c3aed" Geometry="M12.096 6.223A5 5 0 0 0 13 5.698V7c0 .289-.213.654-.753 1.007a4.5 4.5 0 0 1 1.753.25V4c0-1.007-.875-1.755-1.904-2.223C11.022 1.289 9.573 1 8 1s-3.022.289-4.096.777C2.875 2.245 2 2.993 2 4v9c0 1.007.875 1.755 1.904 2.223C4.978 15.71 6.427 16 8 16c.536 0 1.058-.034 1.555-.097a4.5 4.5 0 0 1-.813-.927Q8.378 15 8 15c-1.464 0-2.766-.27-3.682-.687C3.356 13.875 3 13.373 3 13v-1.302c.271.202.58.378.904.525C4.978 12.71 6.427 13 8 13h.027a4.6 4.6 0 0 1 0-1H8c-1.464 0-2.766-.27-3.682-.687C3.356 10.875 3 10.373 3 10V8.698c.271.202.58.378.904.525C4.978 9.71 6.427 10 8 10q.393 0 .774-.024a4.5 4.5 0 0 1 1.102-1.132C9.298 8.944 8.666 9 8 9c-1.464 0-2.766-.27-3.682-.687C3.356 7.875 3 7.373 3 7V5.698c.271.202.58.378.904.525C4.978 6.711 6.427 7 8 7s3.022-.289 4.096-.777M3 4c0-.374.356-.875 1.318-1.313C5.234 2.271 6.536 2 8 2s2.766.27 3.682.687C12.644 3.125 13 3.627 13 4c0 .374-.356.875-1.318 1.313C10.766 5.729 9.464 6 8 6s-2.766-.27-3.682-.687C3.356 4.875 3 4.373 3 4" />
    </DrawingGroup>
    <DrawingImage x:Key="Image/DatabaseDash" Drawing="{StaticResource Geometry/DatabaseDash}"/>
  </Window.Resources>

  <Button>
    <Image Width="24" Source="{StaticResource Image/DatabaseDash}" />
  </Button>
</Window>

Попробуйте такой вариант (конвертация SVG в XAML). Мне кажется у него есть некоторые преимущества. Для простоты здесь "DrawingImage" помещен в локальные ресурсы, но на практике их принято держать в отдельном файле, например, "Resources/Icons.axaml".

Спасибо. Хороший пример для тех, кто собирается научится использовать эту библиотеку.

Но ведь название статьи "Создание контрола Avalonia/WPF для двухцветных векторных Bootstrap Icons".

Статья имеет тип "Туториал", название начинается с "Создание контрола". Т.е., по определению, статья ориентирована на тех, кто учится или уже создает "свои велосипеды" (собственные контролы).

Я не очень понял зачем это все?

Нет, ответа там нет, потому я и задал его.

В пункте "Почему именно Bootstrap Icons?" я постарался обосновать, что не надо "изобретать велосипед" и рисовать свои иконки.
Извините, если у меня это не получилось.

Bootstrap (и позже появившийся Bootstrap Icons) являются для веб-разработчиков стандартом де-факто. Мне кажется, попробовать перетащить Bootstrap Icons из мира веб в Avalonia будет не лишним.

ПыСы Извините, немного вопрос не по окладу: а почему "айконка", а не "иконка"?

Чего придираетесь? Еду как могу...

ПыСы: Обновил текст статьи, сделал замену: "айкон" -> "икон".

Я не очень понял зачем это все?

Ответ на ваш вопрос есть в первых двух разделах статьи. Из наиболее существенного для предлагаемого нами подхода:

  • за нас команда Bootstrap почти всё нарисовала, можно обойтись без дизайнера;

  • можно относительно легко добавить немного цвета, что-то подправить, дополнить;

  • увеличение затрат на рендеринг минимальное (штатные <Path> + <Viewbox> работают прекрасно для Avalonia и WPF);

  • альтернатива хранения ресурсов в XAML/AXAML (нет зависимости от Avalonia/WPF).

Есть же прекрасный бесплатный пакет Avalonia.Svg

This package has been deprecated.

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

В любом случае: Зачем нам лишняя зависимость от внешней библиотеки в production?

Кроме того, увеличиваем углеродный след приложения (конвертация SVG в runtime - самый энергозатратный и тормозной для UI вариант).

Svg.Css - это attached inherited свойство. Работает на поддерево.

Спасибо, посмотрю как они это реализовали. Только для нашего случая, наверно, будет тяжеловато. И по рендерингу, и по программной реализации.

Абсолютно точно, именно эти 4 штуки мне кровь портили.
Поэтому я в своем генераторе их переопределяю.

{
  "align-bottom": [
    "M6 1h4a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1z",
    "M1.5 14a.5.5 0 0 0 0 1zm13 1a.5.5 0 0 0 0-1zm-13 0h13v-1h-13z"
  ],
  "align-top": [
    "M6 15h4a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H6a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1z",
    "M1.5 2a.5.5 0 0 1 0-1zm13-1a.5.5 0 0 1 0 1zm-13 0h13v1h-13z"
  ],
  "circle-fill": [
    "M16 8A8 8 0 1 1 0 8A8 8 0 1 1 16 8Z"
  ],
  "dice-1": [
    "M9.5 8a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z",
    "M13 1a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2zM3 0a3 3 0 0 0-3 3v10a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V3a3 3 0 0 0-3-3z"
  ]
}

Результат работы генератора:

> path-icons@0.0.13 update-bi D:\Git\path-icons
> node build/update-bi-json.mjs --verbose

Starting Bootstrap Icons JSON build...
Found 2078 SVG file(s) to process from D:\Git\path-icons\node_modules\bootstrap-icons\icons
  WARN: Skipping align-bottom.svg. Contains disallowed element type: <rect>.
  WARN: Skipping align-top.svg. Contains disallowed element type: <rect>.
  WARN: Skipping circle-fill.svg. Contains disallowed element type: <circle>.
  WARN: Skipping dice-1.svg. Contains disallowed element type: <circle>.

--- Build Summary ---
Total SVG files found: 2078
- 2074 successfully processed and included
- 4 skipped (invalid size/content/no paths/error)

Path Count Distribution (for processed icons):
- 1217 icon(s) with 1 path
- 753 icon(s) with 2 paths
- 92 icon(s) with 3 paths
- 9 icon(s) with 4 paths
- 3 icon(s) with 5 paths

Output JSON written to: D:\Git\path-icons\src\bi.json
Build completed: 112.916ms

PS: простите за мой француcкий, проверявший меня на ошибки и опечатки ИИ не ругался.

для раскраски иконок используем css

А это как сделать в Avalonia?

Пример подсказок Visual Studio
Пример подсказок Visual Studio

Согласен, при помощи класса тоже хороший вариант. Для моей реализации подсказки в дизайнере Visual Studio тоже работают, причем без {x:Static} и, что удивительно, даже namespace не надо указывать.

upd: Компилятор тоже не даст указать несуществующую айконку.

Это один из вариантов хранения в C#-коде, когда всё в одном месте (обозримость, легкая правка). Для меня было главное - уйти от ресурсов в XAML и как-то автоматизировать включение фрагментов SVG-файла в свой код. Возможно, не самый оптимальный вариант. Что можете предложить взамен?

Спасибо за пример. Хорошая заготовка.
Приложение даже запустилось без проблем.
Только версии NPM-пакетов очень старые.
Подтягивание dot.net core до версии 2.1 прошло нормально.
Но перейти на свежий webpack не получилось — не хватает образования.
Попытка подтянуть версию Vue.js тоже сходу не проходит (ошибки компиляции TypeScript).
Официальный стайл-гайд требует, чтобы для фреймворка явно описывались типы свойств компонента. У вас — массив. Исправляем, делаем явно — и получаем дубликат описаний, то есть еще хуже.

Массив использовать опасно ещё по одной причине — у компилятора TypeScript частично отключается контроль типов.


Теоретически оба варианта определения props, которые приведены ниже, должны работать одинаково. И это действительно так. Даже получаемый на выходе JavaScript код одинаковый.


...
export default Vue.extend({
    template: '#demo-grid-template',
    props: ['rows', 'columns', 'filterKey'],
    //props: { rows: Array, columns: Array, filterKey: String },
    ...
});

Но обнаруживается неприятный баг-фича в тайпинге Vue.js.


Если props определены как массив, то для компилятора TypeScript перечисленные свойства становятся легальными.
Например, компилятор спокойно скушает использование this.filterKey.
Перечисленные в props свойства даже в IntelliSense появляются при использовании VS2017.


А если определить props явно с указанием типов, то компилятор будет материться на тот же this.filterKey.


Вот такой баг-фича.

Information

Rating
2,719-th
Location
Новосибирск, Новосибирская обл., Россия
Registered
Activity