В этой статье расскажем, что такое Avalonia UI, для чего нужен этот фреймворк и как начать им пользоваться. А еще напишем парочку несложных приложений.
Что такое Avalonia UI и как начать использовать этот фреймворк
В настоящее время существует множество фреймворков, с помощью которых можно создавать приложения с графическим интерфейсом пользователя (GUI), используя популярные языки программирования. Среди этих фреймворков есть те, которые позволяют создавать приложения, работающие на всех известных платформах. Такие приложения называются кроссплатформенными. Avalonia UI как раз позволяет создавать такие приложения.
Avalonia UI — это современный и открытый кроссплатформенный UI-фреймворк для .NET, позволяющий создавать графические интерфейсы для Windows, macOS, Linux, iOS, Android и WebAssembly с единой кодовой базой. Он использует язык разметки интерфейса XAML и C#, на котором пишется логика приложения.
Чтобы начать использовать этот фреймворк, сначала нужно установить .NET и необходимые шаблоны. Мы будем использовать дистрибутив Linux Mint для разработки приложений на Avalonia. Открываем консоль и вставляем в нее следующую команду:
sudo apt install dotnet10
В результате выполнения этой команды будут установлены необходимые средства разработки (SDK) и среда выполнения (Runtime). Более подробно про установку .NET в различных дистрибутивах Linux можно прочитать на этой странице платформы Microsoft Learn. Про установку в Windows и macOS найти информацию можно соответственно здесь и здесь.
Далее нужно установить шаблоны. Командуем:
dotnet new install Avalonia.Templates
Проверить, что шаблоны установились можно такой командой:
dotnet new list
Вывод в консоли должен быть примерно такой:

Контролы в Avalonia UI
Также как и во многих других фреймворках, в Avalonia UI присутствуют все необходимые компоненты для построения графических интерфейсов. Это различные кнопки, поля ввода, текстовые метки, переключатели, выпадающие списки и прочее. В Avalonia UI эти компоненты называются контролами.
Кнопок, меток и полей и прочего недостаточно для создания GUI. Все эти элементы надо в чем-то размещать. Для этого существуют панели. Панели — это такие компоненты (контейнеры), в которых можно размещать другие компоненты. Панель управляет размером, позицией и расположением дочерних элементов компонента.
Например, чтобы расположить 3 кнопки друг под другом можно использовать StackPanel. На XAML это будет выглядеть так:
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Top"
Spacing="25">
<Button Content="Button 1"/>
<Button Content="Button 2"/>
<Button Content="Button 3"/>
</StackPanel>
XAML — это декларативный язык разметки, основанный на XML, созданный корпорацией Microsoft специально для описания пользовательских интерфейсов. В нашем случае описывается панель, которая отцентрована по горизонтали (HorizontalAlignment=»Center»), а по вертикали вынесена вверх (VerticalAlignment=»Top»). Также задан отступ между кнопками (Spacing=»25″). Выглядит это так:

По умолчанию панель StackPanel располагает контролы по вертикали. Чтобы контролы располагались по горизонтали, нужно задать для атрибута Orientation значение Horizontal:
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Spacing="25">
<Button Content="Button 1"/>
<Button Content="Button 2"/>
<Button Content="Button 3"/>
</StackPanel>
Получим такой вид:

Иногда необходимо расставить контролы в виде сетки. Для этого служит контейнер Grid. Вот так, например, можно его использовать для расстановки двух текстовых меток TextBlock и двух полей TextBox:
<Grid ShowGridLines="False" Margin="5"
ColumnDefinitions="120, 100"
RowDefinitions="Auto, Auto">
<TextBlock Grid.Row="0" Grid.Column="0" Margin="10">Первое число:</TextBlock>
<TextBox Grid.Row="0" Grid.Column="1" Margin="0 5"/>
<TextBlock Grid.Row="1" Grid.Column="0" Margin="10">Второе число:</TextBlock>
<TextBox Grid.Row="1" Grid.Column="1" Margin="0 5"/>
</Grid>
Для каждого элемента Grid с помощью атрибутов Grid.Row (строка) и Grid.Column (столбец) указывается его местоположение в ячейках сетки. Выглядит этот Grid следующим образом:

Кроме Grid есть еще UniformGrid. Ниже приведен код разметки, который создает сетку размером 3 на 3 из разноцветных кругов и квадратов:
<UniformGrid Rows="3" Columns="3">
<Ellipse Width="50" Height="50" Fill="#330000"/>
<Rectangle Width="50" Height="50" Fill="#660000"/>
<Ellipse Width="50" Height="50" Fill="#990000"/>
<Rectangle Width="50" Height="50" Fill="#CC0000"/>
<Ellipse Width="50" Height="50" Fill="#FF0000"/>
<Rectangle Width="50" Height="50" Fill="#FF3300"/>
<Ellipse Width="50" Height="50" Fill="#FF6600"/>
<Rectangle Width="50" Height="50" Fill="#FF9900"/>
<Ellipse Width="50" Height="50" Fill="#FFCC00"/>
</UniformGrid>
Вид:

С полным списком всех встроенных элементов управления (контролов) можно ознакомиться на этой странице официальной документации по Avalonia UI. Там же есть возможность получить о контролах более подробную информацию и изучить примеры их использования.
Проект по умолчанию из шаблона Avalonia .NET App
Для создания проектов мы воспользуемся шаблоном Avalonia .NET App. Это самый простой шаблон и чтобы создать проект на его основе, нужно выполнить следующую команду в консоли:
dotnet new avalonia.app -o AvaloniaApp
В результате выполнения этой команды в домашней директории появится папка с именем AvaloniaApp. В этой папке находятся несколько файлов с исходным кодом. Из них нас будут интересовать только 2 файла. Это файл 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="AvaloniaApp.MainWindow"
Title="AvaloniaApp">
Welcome to Avalonia!
</Window>
И файл MainWindow.axaml.cs, где содержится логика приложения. Его содержимое:
using Avalonia.Controls;
namespace AvaloniaApp;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
Чтобы запустить весь этот код, нужно, находясь в папке проекта, скомандовать в консоли:
dotnet run
Приложение должно запуститься:

Теперь перейдем к созданию нашего первого приложения на Avalonia UI.
Конвертер температуры
Сделаем простой конвертер температуры, который сможет конвертировать градусы Цельсия в градусы Фаренгейта и наоборот. Сначала создадим папку проекта:
dotnet new avalonia.app -o TemperatureConverter
Далее сделаем интерфейс конвертера. Конвертер у нас будет работать в двух режимах. В одном режиме он превращает Цельсии в Фаренгейты, а в другом — Фаренгейты в Цельсии. Значит, для выбора режима идеально подойдет выпадающий список ComboBox. Для ввода значения температуры, которую нужно конвертировать используем обычный TextBox, а для вывода результата — TextBlock. Ну, а для запуска процесса конвертации ничего лучше кнопки Button не найти.
Поле для ввода и выпадающий список расположим в один ряд, то есть они будут содержаться в панели StackPanel с горизонтальной ориентацией. Остальные контролы выстроим ниже, друг под другом: сначала кнопку, а после нее текстовый блок. Иными словами у нас получается StackPanel с вертикальной ориентацией, в который вложен StackPanel с горизонтальной ориентацией вместе с кнопкой и текстовым блоком. В коде это будет выглядеть следующим образом:
<StackPanel HorizontalAlignment="Center" Margin="20">
<StackPanel Orientation="Horizontal">
<TextBox Name="TemperatureInput" Width="250" Margin="0 0 5 15" Watermark="Введите значение температуры"/>
<ComboBox Name="ConversionType" SelectedIndex="0" Margin="0 0 0 15">
<ComboBoxItem Content="°C в °F"/>
<ComboBoxItem Content="°F в °C"/>
</ComboBox>
</StackPanel>
<Button HorizontalAlignment="Center" Content="Конвертировать" Click="ConvertTemperature" Margin="0 0 0 15"/>
<TextBlock Name="ResultOutput" HorizontalAlignment="Center"/>
</StackPanel>
Этот код следует вставить (при помощи любого удобного текстового редактора) в файл MainWindow.axaml вместо присутствующей там надписи «Welcome to Avalonia!». Но запускать код пока рано. У нас еще нет логики приложения. Атрибуты Name (имя) в разметке как раз нужны для связывания логики и интерфейса. По этим именам можно обращаться к контролам из кода на C#. Атрибут Click у кнопки задает имя метода, который будет выполняться при нажатии на кнопку.
Напишем этот самый метод. Сначала получим значение введенной температуры при помощи TryParse. А потом выберем нужную формулу для расчетов посредством оператора switch – case. Для конвертации из градусов Цельсия в Фаренгейты применим формулу:

Для обратной конвертации формула будет такая:

Код метода получается следующий:
private void ConvertTemperature(object? sender, RoutedEventArgs e)
{
// Пытаемся разобрать данные из поля ввода и преобразовать их в число
if (!double.TryParse(TemperatureInput.Text, out double temperature))
{
ResultOutput.Text = "Введите число!";
return;
}
double result = 0;
string unit = "";
// Выбираем формулу
switch (ConversionType.SelectedIndex)
{
case 0:
result = (temperature * 9 / 5) + 32;
unit = "°F";
break;
case 1:
result = (temperature - 32) * 5 / 9;
unit = "°C";
break;
default:
return;
}
// Выводим результат
ResultOutput.Text = $"{result:F2} {unit}";
}
Вставляем этот код в файл MainWindow.axaml.cs между двух последних фигурных скобок. Но это еще не все. Чтобы сработал вставленный метод, необходимо дополнить секцию using в самом начале файла. Следует добавить туда эту строчку:
using Avalonia.Interactivity;
Дополнительно еще можно изменить размеры окна приложения, так как оно по умолчанию довольно большое. В файле разметки между атрибутом Title и внешним StackPanel добавим атрибуты Width и Height для Window. В качестве значений зададим этим атрибутам желаемые размеры окна. Должно получиться примерно так:
Title="TemperatureConverter"
Width="450"
Height="250">
<StackPanel HorizontalAlignment="Center" Margin="20">
Вот теперь можно попробовать запустить наш конвертер. Открываем консоль из папки проекта и командуем:
dotnet run
Конвертер запустился:

Проверим его в работе:

Переключим режим конвертации:

Отлично! Все работает!
Простой калькулятор
Напишем простой калькулятор, который берет 2 введенных пользователем числа и выводит в качестве результата их сумму, разность, произведение и частное. Создаем папку проекта:
dotnet new avalonia.app -o Calculator
Переходим к интерфейсу. Так как нам понадобится 2 поля для ввода чисел, то для нашего приложения отлично подойдет тот Grid, который мы рассматривали в разделе про контролы. Остается только добавить кнопку и текстовые блоки для вывода результатов. Блоки для результатов упаковываем в StackPanel с вертикальной ориентацией. Всего будет 4 отдельных блока для каждого вида результата, а именно: суммы, разности, произведения и частного. Grid, кнопку и панель с текстовыми блоками также помещаем в вертикальный StackPanel. Получается такой код:
<StackPanel HorizontalAlignment="Center" Margin="20">
<Grid ShowGridLines="False" ColumnDefinitions="120, 150" Margin="20"
RowDefinitions="Auto, Auto">
<TextBlock Grid.Row="0" Grid.Column="0" Margin="10">Первое число:</TextBlock>
<TextBox Name="FirstNumber" Watermark="Введите число" Grid.Row="0" Grid.Column="1" Margin="0 5"/>
<TextBlock Grid.Row="1" Grid.Column="0" Margin="10">Второе число:</TextBlock>
<TextBox Name="SecondNumber" Watermark="Введите число" Grid.Row="1" Grid.Column="1" Margin="0 5"/>
</Grid>
<Button HorizontalAlignment="Center" Content="Вычислить" Click="OnCalculate" Margin="0,0,0,10"/>
<StackPanel HorizontalAlignment="Center" Margin="0,10,0,0">
<TextBlock Name="SumResult"/>
<TextBlock Name="DiffResult"/>
<TextBlock Name="ProdResult"/>
<TextBlock Name="DivResult"/>
</StackPanel>
</StackPanel>
Внешний StackPanel имеет атрибут HorizontalAlignment со значением Center для горизонтального выравнивания по центру. Для элементов TextBox в Grid добавлен атрибут Name, чтобы к этому элементу можно было обращаться из кода. Также у TextBox теперь есть атрибут Watermark, служащий для отображения подсказки. К кнопке и панели с текстовыми блоками также как и к внешней панели применено горизонтальное выравнивание по центру.
Логика у калькулятора довольно простая. Сначала нужно получить числа из полей, произвести над ними необходимые операции и вывести результат. Получать числа будем с помощью TryParse, как и в случае с температурным конвертером. При вычислении результатов надо не забывать случай деления на ноль. Этот случай мы обработаем при помощи тернарных операторов (тернаров). Эти операторы имеет такой синтаксис:
условие ? значение_если_условие_истина : значение_если_условие_ложь;
То есть, по сути, это сокращенная запись условной конструкции if – else в одну строку:
if (условие)
{
значение_если_условие_истина;
}
else
{
значение_если_условие_ложь;
}
Код метода OnCalculate получается следующий:
private void OnCalculate(object? sender, RoutedEventArgs e)
{
// Пытаемся разобрать данные из полей ввода и преобразовать их в числа
if (!double.TryParse(FirstNumber.Text, out double a) ||
!double.TryParse(SecondNumber.Text, out double b))
{
SumResult.Text = "Введите числа в оба поля!";
DiffResult.Text = "";
ProdResult.Text = "";
DivResult.Text = "";
return;
}
// Вычисляем результаты
double sum = a + b;
double diff = a - b;
double prod = a * b;
double div = b != 0 ? a / b : double.NaN;
// Выводим результаты
SumResult.Text = $"Сумма: {sum:G}";
DiffResult.Text = $"Разность: {diff:G}";
ProdResult.Text = $"Произведение: {prod:G}";
DivResult.Text = b == 0 ? "Частное: деление на ноль!" : $"Частное: {div:G}";
}
Первый тернар производит деление a на b, если делитель не равен нулю. Если же делитель равен нулю, то возвращается значение double.NaN, которое означает, что результат не является числом (NaN — Not-a-Number — Не число). Второй тернар выводит сообщение о делении на ноль в случае, если делитель равен нулю. В противном случае выводится результат деления.
Вставляем разметку и логику в соответствующие места файлов MainWindow.axaml и MainWindow.axaml.cs. Осталось только добавить уже упоминавшуюся в прошлом разделе строку в секцию using и при желании изменить размер окна, добавив нужные атрибуты, например, вот с такими значениями:
Width="400" Height="300"
Теперь можно попробовать запустить наш калькулятор. Вводим команду dotnet run в консоли, нажимаем на клавишу Enter и калькулятор запускается:

Проверим его в работе:

Попробуем поделить на ноль:

Все прекрасно работает.
Полезные советы и ссылки
- Не всегда целевая версия .NET проекта согласуется с версией .NET установленного в системе. Если нет возможности установить нужную версию .NET, то можно настроить проект на ту, которая есть в наличии. Для этого надо открыть файл ProjectName.csproj и найти там вот такую строчку:
<TargetFramework>net10.0</TargetFramework>
Вместо net10.0 прописываем нужную версию, например, net8.0, сохраняем файл и запускаем проект.
- Для разработки программ на Avalonia UI можно использовать редактор Visual Studio Code с расширением Avalonia for VSCode. Но надо иметь в виду, что в этом редакторе расширения иногда могут не устанавливаться или же устанавливаться с большой задержкой. Также можно использовать Rider от JetBrains. Компания предоставляет его бесплатно для некоммерческого использования. Правда могут быть проблемы с загрузкой и установкой IDE для пользователей находящихся в РФ, так как официально продукты JetBrains недоступны для использования в России.
- Для конструирования пользовательского интерфейса можно пользоваться визуальными редакторами. Есть вот такой вариант редактора. Еще этот же разработчик развивает целую интегрированную среду разработки для C#.
- Для предварительного просмотра графического интерфейса, что может быть полезно во время его создания, можно пользоваться сервисом Avalonia Play. Там все просто: в левой части вносите изменения в код и они тут же отображаются в правой части в виде GUI.
- Avalonia Docs — официальная документация по фреймворку.
- Awesome-Avalonia — коллекция интересных инструментов и библиотек для проектов на Avalonia UI.
Задание для самостоятельного выполнения
Упростите код калькулятора таким образом, чтобы в коде был только один тернарный оператор, а для отображения результатов использовался лишь один текстовый блок TextBlock.
С разметкой здесь все понятно: меняем внутренний StackPanel на единственный TextBlock:
<TextBlock Name="Result" HorizontalAlignment="Center" Margin="0,10,0,0"/>
Что касается логики, то и здесь нет ничего сложного. Так как у нас теперь только один текстовый блок, то и вывод сообщения с просьбой ввести числа в оба поля будет выглядеть так:
Result.Text = "Введите числа в оба поля!";
У переменной div поменяем тип с double на string. Теперь в ней будут храниться строковые значения и тернарный оператор примет следующий вид:
string div = b == 0 ? "деление на ноль!" : (a / b).ToString("G");
Контрол TextBlock может отображать текст в многострочном виде. Соответственно, вывод результатов будет таким:
Result.Text = $@"Сумма: {sum:G}
Разность: {diff:G}
Произведение: {prod:G}
Частное: {div}";
Весь код метода:
private void OnCalculate(object? sender, RoutedEventArgs e)
{
if (!double.TryParse(FirstNumber.Text, out double a) ||
!double.TryParse(SecondNumber.Text, out double b))
{
Result.Text = "Введите числа в оба поля!";
return;
}
double sum = a + b;
double diff = a - b;
double prod = a * b;
string div = b == 0 ? "деление на ноль!" : (a / b).ToString("G");
Result.Text = $@"Сумма: {sum:G}
Разность: {diff:G}
Произведение: {prod:G}
Частное: {div}";
}
Разметка панели:
<StackPanel HorizontalAlignment="Center" Margin="20"> <Grid ShowGridLines="False" ColumnDefinitions="120, 150" Margin="20" RowDefinitions="Auto, Auto"> <TextBlock Grid.Row="0" Grid.Column="0" Margin="10">Первое число:</TextBlock> <TextBox Name="FirstNumber" Watermark="Введите число" Grid.Row="0" Grid.Column="1" Margin="0 5"/> <TextBlock Grid.Row="1" Grid.Column="0" Margin="10">Второе число:</TextBlock> <TextBox Name="SecondNumber" Watermark="Введите число" Grid.Row="1" Grid.Column="1" Margin="0 5"/> </Grid> <Button HorizontalAlignment="Center" Content="Вычислить" Click="OnCalculate" Margin="0,0,0,10"/> <TextBlock Name="Result" HorizontalAlignment="Center" Margin="0,10,0,0"/> </StackPanel>
