Не Windows единой: как писать кроссплатформенные приложения с GUI на C#

На C# можно создавать красивые приложения, но до недавних пор — не для всех платформ. Рассказываем, как писать одно приложение для всех ОС сразу.

Microsoft выпустила уже третью версию кроссплатформенного .NET Core с открытым исходным кодом, но мы до сих пор не получили инструмента для создания графических интерфейсов.

Пока есть Xamarin, который можно использовать только для Windows 10 и мобильных устройств. Но что делать тем, кто хочет создавать графические интерфейсы для Linux или Mac OS?

Тут помогут фреймворки от сторонних разработчиков.

Евгений Кучерявый

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


Какой фреймворк выбрать

Мне удалось найти 2 более-менее популярных фреймворка (оба основаны на Skia):

  1. SpaceVIL. Он привлёк меня тем, что в нём элементы GUI отрисовываются, а не берутся из API операционной системы. Поэтому приложение будет выглядеть одинаково на всех устройствах. В нём даже можно создавать собственные элементы любого вида. Однако он пока достаточно сырой — об этом говорит уже то, что в официальной документации есть ошибки в инструкции для новичков.
  2. AvaloniaUI. Это более популярный и проработанный фреймворк, который позволяет создавать интерфейсы как для мобильных устройств, так и для Linux и OS X. Также в нём используется диалект XAML, что будет плюсом для тех, кто пробовал создавать приложения для Windows. В нём даже есть поддержка MVVM.

Я попробовал оба, и второй показался мне более удобным: в нём есть язык разметки, поддержка MVVM, быстрая установка, лёгкий переход с WPF. Поэтому я выбрал его.

Как начать использовать AvaloniaUI

Я буду создавать приложение в Linux Ubuntu, но эта инструкция подойдёт всем, кто использует .NET Core. Создавать приложения можно и в Visual Studio, скачав для него расширение, но так как его нет на Linux, я буду пользоваться терминалом.

Для начала клонируйте себе на компьютер этот репозиторий:

В нём находятся шаблоны для создания приложения с AvaloniaUI. Если вы не умеете пользоваться git, то просто скачайте содержимое и распакуйте куда-нибудь на компьютере. Затем откройте консоль и введите следующую команду:

dotnet new --install [путь к скачанному репозиторию]

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

dotnet new --list

Вы увидите список всех установленных шаблонов. Среди них должны быть Avalonia Window, Avalonia .NET Core MVVM App, Avalonia UserControl и Avalonia .NET Core App. Если они на месте, можно продолжать.

Откройте в консоли папку, в которой хотите создать проект, и введите:

dotnet new avalonia.mvvm

Будет создано приложение с использованием MVVM. Практически вся документация по AvaloniaUI написана с использованием этого паттерна, поэтому проще будет разрабатывать на нём.

Теперь можно приступать к работе над приложением.

Создаём калькулятор на AvaloniaUI

У вас будут созданы следующие папки

  • Assets — сюда можно загружать различные компоненты программы вроде иконок, изображений, звуков и прочего.
  • Models — эта папка предназначена для классов, которые будут выступать в роли модели.
  • ViewModels — здесь находятся классы-посредники между видом и моделью.
  • Views — все окна будут находиться здесь.

Сначала посмотрим в файл Program.cs в корневом каталоге:

using System;
using Avalonia;
using Avalonia.Logging.Serilog;
using AvaloniaMVVM.ViewModels;
using AvaloniaMVVM.Views;

namespace AvaloniaMVVM
{
	class Program
	{
    	// Initialization code. Don't use any Avalonia, third-party APIs or any
    	// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
    	// yet and stuff might break.
    	public static void Main(string[] args) => BuildAvaloniaApp().Start(AppMain, args);

    	// Avalonia configuration, don't remove; also used by visual designer.
    	public static AppBuilder BuildAvaloniaApp()
        	=> AppBuilder.Configure<App>()
            	.UsePlatformDetect()
            	.LogToDebug()
            	.UseReactiveUI();

    	// Your application's entry point. Here you can initialize your MVVM framework, DI
    	// container, etc.
    	private static void AppMain(Application app, string[] args)
    	{
        	var window = new MainWindow
        	{
            	DataContext = new MainWindowViewModel(),
        	};

        	app.Run(window);
    	}
	}
}

Нас интересует метод AppMain(). В нём создаётся окно (MainWindow) с указанием DataContext (используется для привязки данных), а потом это окно запускается.

В этом методе можно определить свою логику инициализации приложения. Например, объявить экземпляр модели и передать его в конструктор MainWindowViewModel(). Однако перед этим нужно определить конструктор, который будет принимать такой аргумент.

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

Для начала нужно подключить пространство имён ReactiveUI, которое в AvaloniaUI используется для реализации паттерна MVVM:

using ReactiveUI;

Затем можно писать сам код:

private string _message = "";

private string _num1 = "0";
private string _num2 = "0";
private string _result = "0";

public void Click()
{
    int num1 = 0;
    int num2 = 0;
    int result = 0;
    Message = "";

    try
    {
   	 num1 = Convert.ToInt32(Num1);
   	 num2 = Convert.ToInt32(Num2);
    }
    catch(Exception e)
    {
   	 Message = "Wrong input!";
   	 Console.WriteLine(e.Message);
    }

    result = num1 + num2;

    Result = result.ToString();
}

public string Message //Так свойства используются для привязки в ReactiveUI
{
    get => _message;
    set => this.RaiseAndSetIfChanged(ref _message, value);
}

public string Num1 
{
    get => _num1; 
    set => this.RaiseAndSetIfChanged(ref _num1, value);
}


public string Num2
{
    get => _num2;
    set => this.RaiseAndSetIfChanged(ref _num2, value);
}

public string Result
{
    get => _result;
    set => this.RaiseAndSetIfChanged(ref _result, value);
}

Теперь нужно написать код интерфейса для окна в файле MainWindow.xaml:

<Window xmlns="https://github.com/avaloniaui"
   	 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   	 xmlns:vm="clr-namespace:AvaloniaMVVM.ViewModels;assembly=AvaloniaMVVM"
   	 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="AvaloniaMVVM.Views.MainWindow"
   	 Icon="/Assets/avalonia-logo.ico"
   	 Title="AvaloniaMVVM">

    <Design.DataContext>
   	 <vm:MainWindowViewModel/>
    </Design.DataContext>

    <Grid>
   	 <Grid.RowDefinitions>
   		 <RowDefinition Height="1*"/>
   		 <RowDefinition Height="2*"/>
   		 <RowDefinition Height="1*"/>
   		 <RowDefinition Height="2*"/>
   		 <RowDefinition Height="1*"/>
   	 </Grid.RowDefinitions>

   	 <Grid.ColumnDefinitions>
   		 <ColumnDefinition Width="1*"/>
   		 <ColumnDefinition Width="2*"/>
   		 <ColumnDefinition Width="1*"/>
   		 <ColumnDefinition Width="2*"/>
   		 <ColumnDefinition Width="1*"/>
   		 <ColumnDefinition Width="1*"/>
   	 </Grid.ColumnDefinitions>

   	 <TextBox Text="{Binding Num1, Mode=TwoWay}" Grid.Row="1" Grid.Column="1"/>
   	 <TextBlock Text="+" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="1" Grid.Column="2"/>
   	 <TextBox Text="{Binding Num2, Mode=TwoWay}" Grid.Row="1" Grid.Column="3"/>

   	 <Button Content="=" Grid.Row="2" Grid.Column="2" HorizontalAlignment="Center" VerticalAlignment="Center" Command="{Binding Click}"/>

   	 <TextBlock Text="{Binding Result}" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="3" Grid.Column="2"/>
   	 <TextBlock Text="{Binding Message}" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="Red" Grid.Row="4" Grid.Column="2"/>
    </Grid>
</Window>

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

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

dotnet run

Результат должен получиться таким:

Теперь приложение можно просто скомпилировать для разных ОС — никаких дополнительных манипуляций для переноса на новую платформу не требуется.

Заключение

10-15 лет назад это показалось бы извращением, но сейчас мы можем писать программы для Linux на языке, который изначально был предназначен для разработки приложений под Windows.

Программирование — это сфера, которая очень быстро меняется. Поэтому необходимо постоянно заниматься самообразованием и узнавать что-то новое. Для этого, например, вы можете записаться на курс «Профессия C#-разработчик» и освоить современные подходы к написанию программ на C#.

Курс

Профессия C#-разработчик


130 часов — и вы научитесь писать программы на языке, созданном Microsoft. Вы создадите 5 проектов для портфолио, даже если до этого никогда не программировали. После прохождения обучения — помощь в трудоустройстве.

Хочешь получать крутые статьи по программированию?
Подпишись на рассылку Skillbox