Бытует мнение, что язык C# годится только для разработки приложений под Windows. Между тем, это все было давно и неправда — даже дважды неправда с момента появления .NET Core. Сейчас на C# можно писать и десктопные приложения, и бэк, и мобильные приложения, и системные утилиты. Язык не стоит на месте и покрывает всё больше направлений разработки.

.NET-ран­тайм обес­печива­ет перено­симость, и один и тот же код нор­маль­но запус­кает­ся на поч­ти любой плат­форме. И если с бэкэн­дом и кон­соль­ными ути­лита­ми всё более‑менее понят­но, то при­ложе­ния с гра­фичес­ким интерфей­сом перено­сить тра­дици­онно счи­талось труд­ной задачей, ведь натив­ные эле­мен­ты управле­ния в каж­дой сис­теме свои, и заменить одни на дру­гие не так‑то прос­то.

Бы­ло нес­коль­ко попыток это испра­вить, в их чис­ле MAUI и Xamarin.Forms, но по‑нас­тояще­му популяр­ным стал фрей­мворк Avalonia, авто­ры которо­го перес­тали полагать­ся на целевую сис­тему и решили рисовать все эле­мен­ты управле­ния самос­тоятель­но, что силь­но упрости­ло жизнь раз­работ­чикам.

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

Этот под­ход име­ет свою цену: вмес­то исполь­зования готовых эле­мен­тов управле­ния, которые пре­дос­тавля­ет целевая сис­тема, вмес­те с при­ложе­нием нуж­но тас­кать биб­лиоте­ку ком­понен­тов, которые оно исполь­зует. Их нес­коль­ко, и Eremex — как раз такая биб­лиоте­ка.

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

Пример сложного интерфейса
При­мер слож­ного интерфей­са

Под­держи­вает­ся даже WebAssembly, то есть мож­но будет соб­рать всё как веб‑при­ложе­ние и даже фронт реали­зовать на C#!

Avalonia работа­ет по модели MVVM и под­держи­вает отри­сов­ку по необ­ходимос­ти, как React. Эта архи­тек­тура поз­воля­ет раз­вязать логику отоб­ражения от биз­нес‑логики и гиб­ко менять интерфейс, прак­тичес­ки не тро­гая при этом код. Чуть поз­же я покажу, где это осо­бен­но полез­но.

 

Лицензирование

Как извес­тно, для любой проб­лемы (Problem) в C# сущес­тву­ет три решения: Microsoft.Solution, пуб­личный архив Solution.NET из 2016 года и Solution Pro за день­ги. Биб­лиоте­ка Eremex, как мож­но догадать­ся, отно­сит­ся к треть­ему клас­су, зато работа­ет как надо. К тому, как вос­поль­зовать­ся лицен­зией, мы вер­немся чуть поз­же, а сей­час рас­смот­рим дос­тупные вари­анты.

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

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

Триальная версия
Три­аль­ная вер­сия
 

Обзор контролов

Пред­положим, у нас уже есть пол­ноцен­ная вер­сия биб­лиоте­ки. Давай пос­мотрим на самое глав­ное: те эле­мен­ты поль­зователь­ско­го интерфей­са, которые будем исполь­зовать в сво­их про­ектах.

Пер­вый и один из важ­ней­ших кон­тро­лов – DataGrid. Он пред­став­ляет собой таб­лицу, которая может изнутри под­клю­чать­ся к любому источни­ку дан­ных и неверо­ятно гиб­ко нас­тра­ивает­ся.

DataGrid в дикой природе
DataGrid в дикой при­роде

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

Редактирование данных сложного формата прямо в таблице
Ре­дак­тирова­ние дан­ных слож­ного фор­мата пря­мо в таб­лице

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

Выбор цвета
Вы­бор цве­та

Вы­бор цве­та, кста­ти, дает очень уже при­ятные гла­зу цве­та, а не то, что давал сис­темный селек­тор. Рекомен­дую!

Еще один гени­аль­ный кон­трол – это TreeList. Выг­лядит он очень похожим на DataGrid, но в отли­чие от него уме­ет стро­ить иерар­хичес­кую струк­туру со вло­жен­ными эле­мен­тами и кас­томны­ми редак­торами пря­мо в теле таб­лицы.

Древовидный список
Дре­вовид­ный спи­сок

С помощью все­го двух этих кон­тро­лов уже мож­но пос­тро­ить какой‑то даш­борд или демо‑при­ложе­ние для сво­их экспе­римен­тов.

Де­монс­тра­цию кно­пок и флаж­ков про­пус­тим, луч­ше покажу реали­зацию пол­ноцен­ного 3D-прос­мот­рщи­ка, который учи­тыва­ет даже осве­щение!

Eremex Graphics3DControl рендерит сцену с освещением в реальном времени!
Eremex Graphics3DControl рен­дерит сце­ну с осве­щени­ем в реаль­ном вре­мени!

www

Eremex пре­дос­тавля­ет под­робную тех­ничес­кую докумен­тацию по всем кон­тро­лам на рус­ском и англий­ском язы­ках. Она дос­тупна на сай­те раз­работ­чика.

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

 

Как писать с Avalonia

При­ложе­ния с Avalonia UI тра­дици­онно под­разде­ляет­ся на три час­ти сог­ласно пат­терну MVVM (Model, View, ViewModel). В коде это опи­сыва­ется в нес­коль­ких раз­ных фай­лах. View – это толь­ко интерфейс в чис­том виде, Model – толь­ко дан­ные, а ViewModel – это про­межу­точ­ный слой, который свя­зыва­ет View и Model. В отли­чие от Windows Forms, внеш­ний вид при­ложе­ния (View) тут не накиды­вает­ся в визу­аль­ном WYSIWYG-редак­торе, а опи­сыва­ется в спе­циаль­ном фай­ле XAML, где лег­ко поменять интерфейс. Нап­ример, вот код, который помеща­ет селек­тор цве­та на текущее окно:

<UserControl x:Class="DemoCenter.Views.ColorEditorPageView"
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"
xmlns:mx="https://schemas.eremexcontrols.net/avalonia"
xmlns:mxe="https://schemas.eremexcontrols.net/avalonia/editors"
xmlns:vm="using:DemoCenter.ViewModels"
xmlns:mxei="clr-namespace:Eremex.AvaloniaUI.Controls.Editors.Visuals;assembly=Eremex.Avalonia.Controls"
d:DesignHeight="750"
d:DesignWidth="900"
x:DataType="vm:ColorEditorPageViewModel"
mc:Ignorable="d">
<Design.DataContext>
<vm:ColorEditorPageViewModel />
</Design.DataContext>
<Grid ColumnDefinitions="*, 250">
<ContentControl x:Name="DemoControl" Classes="DemoUserControl">
<Grid RowDefinitions="Auto, 60, Auto" ColumnDefinitions="Auto, 26, Auto" HorizontalAlignment="Center" Margin="30">
<!-- <Label Classes="EditorHeader" Content="POPUP COLOR EDITOR"/> -->
<mxe:PopupColorEditor Grid.Row="2" x:Name="PopupColorEditor" VerticalAlignment="Top"
ReadOnly="{Binding IsChecked, ElementName=ReadOnlySelector}"
ShowAlphaChannel="{Binding IsChecked, ElementName=AlphaChannelSelector}"
ColorsShowMode="{Binding ColorsShowMode}"
Color="#FF00B7B8"
CustomColors="{Binding CustomColors2, Mode=TwoWay}">
<mxe:PopupColorEditor.PopupFooterButtons>
<Binding Path="IsChecked" ElementName="ShowConfirmationButtonsSelector">
<Binding.Converter>
<mx:BoolToObjectConverter>
<mx:BoolToObjectConverter.TrueValue>
<mxe:PopupFooterButtons>OkCancel</mxe:PopupFooterButtons>
</mx:BoolToObjectConverter.TrueValue>
<mx:BoolToObjectConverter.FalseValue>
<mxe:PopupFooterButtons>None</mxe:PopupFooterButtons>
</mx:BoolToObjectConverter.FalseValue>
</mx:BoolToObjectConverter>
</Binding.Converter>
</Binding>
</mxe:PopupColorEditor.PopupFooterButtons>
</mxe:PopupColorEditor>
<mxe:ColorEditor x:Name="ColorEditor" VerticalAlignment="Top"
ReadOnly="{Binding IsChecked, ElementName=ReadOnlySelector}"
ShowAlphaChannel="{Binding IsChecked, ElementName=AlphaChannelSelector}"
ColorsShowMode="{Binding ColorsShowMode}"
ShowConfirmationButtons="{Binding IsChecked, ElementName=ShowConfirmationButtonsSelector}"
Color="#FF37C47F"
CustomColors="{Binding CustomColors1, Mode=TwoWay}"/>
<Grid Grid.Column="2" RowDefinitions="Auto, Auto" ColumnDefinitions="Auto, *" MinWidth="135">
<Border Height="40" Width="40" VerticalAlignment="Top" HorizontalAlignment="Left"
CornerRadius="{StaticResource EditorCornerRadius}"
BorderThickness="{StaticResource EditorBorderThickness}"
BorderBrush="{DynamicResource Outline/Neutral/Transparent/Medium}"
Background="{Binding Color, ElementName=ColorEditor, Converter={mxei:SolidColorBrushConverter}}"/>
<Label Grid.Column="1" Margin="{StaticResource leftGroupMargin}" VerticalAlignment="Center"
Content="{Binding Color, ElementName=ColorEditor}"/>
</Grid>
</Grid>
</ContentControl>
<!--Options-->
<StackPanel Grid.Column="1">
<mxe:GroupBox Header="Properties" Classes="PropertiesGroup">
<StackPanel>
<mxe:CheckEditor x:Name="AlphaChannelSelector" Content="Use Alpha Channel" IsChecked="True" Classes="PropertyEditor"/>
<mxe:CheckEditor x:Name="ReadOnlySelector" Content="Read Only" Classes="PropertyEditor"/>
<mxe:CheckEditor x:Name="StandardColorsSelector" Content="Show Standard Colors" IsChecked="{Binding ShowStandardColors, Mode=TwoWay}" Classes="PropertyEditor"/>
<mxe:CheckEditor x:Name="CustomColorsSelector" Content="Show Custom Colors" IsChecked="{Binding ShowCustomColors, Mode=TwoWay}" Classes="PropertyEditor"/>
<mxe:CheckEditor x:Name="ShowConfirmationButtonsSelector" Content="Show Confirmation Buttons"
IsEnabled="{Binding IsChecked, ElementName=CustomColorsSelector}" Classes="PropertyEditor"/>
</StackPanel>
</mxe:GroupBox>
</StackPanel>
</Grid>
</UserControl>

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

Что­бы свя­зать интерфейс с логикой, нуж­на будет ViewModel. Вот как он может выг­лядеть:

public partial class ColorEditorPageViewModel : PageViewModelBase
{
[ObservableProperty] bool showStandardColors = true;
[ObservableProperty] bool showCustomColors = true;
[ObservableProperty] ColorsShowMode colorsShowMode = ColorsShowMode.StandardColors | ColorsShowMode.CustomColors;
[ObservableProperty] ObservableCollection<Color> customColors1 = new ObservableCollection<Color>()
{
Color.FromRgb(0x7d, 0xd7, 0xab), Color.FromRgb(0xc5, 0x94, 0x88), Color.FromRgb(0x47, 0xfe, 0xff), Color.FromRgb(0xe9, 0xbf, 0x3f),
};
public ColorEditorPageViewModel()
{
}
partial void OnShowStandardColorsChanged(bool value)
{
if(value)
ColorsShowMode |= ColorsShowMode.StandardColors;
else
ColorsShowMode &= ~ColorsShowMode.StandardColors;
}
partial void OnShowCustomColorsChanged(bool value)
{
if (value)
ColorsShowMode |= ColorsShowMode.CustomColors;
else
ColorsShowMode &= ~ColorsShowMode.CustomColors;
}
}

Да­вай раз­берем, что здесь про­исхо­дит. Вмес­то того что­бы вруч­ную реали­зовы­вать гро­моз­дкий интерфейс INotifyPropertyChanged для каж­дого свой­ства, мож­но исполь­зовать атри­бут [ObservableProperty] из биб­лиоте­ки CommunityToolkit.Mvvm. Это сов­ремен­ный под­ход с исполь­зовани­ем генера­тора кода: одна строч­ка атри­бута – и ком­пилятор сам дописы­вает всю необ­ходимую обвязку, что­бы интерфейс узна­вал об изме­нени­ях свой­ства. А строч­ка ColorsShowMode="{Binding ColorsShowMode}" из XAML-фай­ла теперь свя­зана со свой­ством ColorsShowMode в ViewModel. Тип ObservableCollection<T> исполь­зует­ся для кол­лекции цве­тов соз­натель­но, что­бы интерфейс узна­вал об изме­нени­ях, даже если они вне­сены прог­рам­мно, а не поль­зовате­лем.

Результат
Ре­зуль­тат

Та­кая связ­ка – XAML-раз­метка для дек­ларатив­ного опи­сания самого интерфей­са и ViewModel для при­дания ему жиз­ни – это и есть суть MVVM, и биб­лиоте­ка Eremex (да и вооб­ще весь фрей­мворк Avalonia) очень хорошо ложит­ся на эту кон­цепцию.

www

Этот и дру­гие при­меры мож­но самос­тоятель­но запус­тить в репози­тории Eremex на GitHub.

 

Запуск в Linux

Ра­зуме­ется, ког­да мы име­ем дело с кросс­плат­формен­ными при­ложе­ниями, грех не про­верить, как же в реаль­нос­ти работа­ет эта кросс­плат­формен­ность. Для это­го уста­новим на Ubuntu зависи­мос­ти: libice6, libsm6 и libfontconfig1, а еще сам .NET (dotnet-sdk-8.0), если он еще не уста­нов­лен.

За­пус­тить мож­но по коман­де dotnet app.dll, если есть уже готовая сбор­ка с прош­лых экспе­римен­тов, а мож­но соб­рать новую. Для это­го есть коман­да сбор­ки с клю­чом -–self-contained, которая соберет самодос­таточ­ную вер­сию, которая не будет тре­бовать наличия .NET на целевой плат­форме:

dotnet publish -c Release -r linux-x64 --self-contained true

Пос­ле это­го в пап­ке bin/Realease/linux-x64/publish ока­жет­ся готовый к миг­рации билд, который запус­кает­ся чуть ли не в чис­том поле.

Результат идентчный
Ре­зуль­тат идент­чный

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

 

Выводы

Биб­лиоте­ка от Eremex показа­ла себя отлично. Если цена тебя не сму­щает, могу сме­ло пореко­мен­довать ее для любых про­ектов. Она откры­вает раз­ные полез­ные воз­можнос­ти, а глав­ное — осно­ван­ный на ней код мож­но запус­кать в раз­ных ОС без изме­нений, что избавля­ет от пос­тоян­ной голов­ной боли при раз­работ­ке кросс­плат­формен­ных при­ложе­ний.

Реклама. АО «ЭРЕМЕКС». ИНН 9723094014. Erid: 2SDnjdn1YQe

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии