VSCodeとC#(.NET 5.0)とXAMLでLinux GUIプログラミング

この記事は公開から3年以上経過しています。

WPFでお馴染みのXAMLでGUIアプリケーションを作成できるフレームワークAvaloniaを使い、Linux上で実行可能なGUIデスクトップアプリケーションをVisual Studio Codeで開発する方法を紹介します。
本記事はLinuxを対象としていますが、AvaloniaはマルチプラットフォームなのでLinux/Windows/Macでも利用可能です。

.NET 5.0 SDKが導入済、VSCodeにC#拡張が導入済である前提です。


手順

  1. 以下のコマンドで作業フォルダを作成し、その中にAvaloniaのテンプレートをダウンロードする。

    mkdir /tmp/avalonia && cd $_
    git clone --depth 1 https://github.com/AvaloniaUI/avalonia-dotnet-templates
  2. ダウンロードしたAvaloniaのテンプレートを以下のコマンドでインストールする。

    dotnet new --install avalonia-dotnet-templates

    インストールが成功すると、以下のようにインストールされたパッケージの一覧が表示される。

    次のパッケージがインストールされます:
       /tmp/avalonia/avalonia-dotnet-templates
    
    成功: /tmp/avalonia/avalonia-dotnet-templatesにより次のテンプレートがインストールされました。
    テンプレート名                       短い名前                       言語       タグ                         
    ----------------------------  -------------------------  -------  ---------------------------
    Avalonia .NET Core App        avalonia.app               [C#],F#  ui/xaml/avalonia/avaloniaui
    Avalonia .NET Core MVVM App   avalonia.mvvm              [C#],F#  ui/xaml/avalonia/avaloniaui
    Avalonia Resource Dictionary  avalonia.resource                   ui/xaml/avalonia/avaloniaui
    Avalonia Styles               avalonia.styles                     ui/xaml/avalonia/avaloniaui
    Avalonia TemplatedControl     avalonia.templatedcontrol  [C#]     ui/xaml/avalonia/avaloniaui
    Avalonia UserControl          avalonia.usercontrol       [C#],F#  ui/xaml/avalonia/avaloniaui
    Avalonia Window               avalonia.window            [C#],F#  ui/xaml/avalonia/avaloniaui
  3. 以下のコマンドで開発を行うプロジェクト用のディレクトリを作成してカレントディレクトリを移動する。

    mkdir HelloAvalonia && cd $_
  4. 以下のコマンドでAvalonia .NET Core Appのテンプレートプロジェクトを作成する。

    dotnet new avalonia.app

    プロジェクトが作成されると、以下のように表示される。

    テンプレート "Avalonia .NET Core App" が正常に作成されました。
  5. 手順3で作成したディレクトリをVSCodeで開く。コマンドで開く場合は以下のとおり。

    code ./
  6. VSCodeに以下のメッセージが表示されたらYesを押下してビルド/デバッグ用のアセット(.vscodelaunch.jsontask.json)を追加する(表示されない場合はF1キーを押下してコマンドパレットから.net generate assets for build and debugコマンドを実行)。

    file

    ここまでで作成されたファイル群は以下のとおり。

    file

  7. VSCodeでF5キーを押下してプログラムを実行すると、以下のようなウィンドウが表示される。

    file


サンプルソースコード

折角なので、Avaloniaを使って入力テキストをBASE64エンコードするだけの簡単なMVVMアプリケーションを作成してみました。

MainWindow.axaml(拡張子がaxamlであることに注意)

<Window
    x:Class="HelloAvalonia.MainWindow"
    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"
    Title="Convert to BASE64"
    Width="500"
    Height="400"
    d:DesignHeight="450"
    d:DesignWidth="800"
    FontFamily="Sans"
    mc:Ignorable="d">
    <Grid Margin="5">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
            <RowDefinition />
            <RowDefinition Height="50" />
        </Grid.RowDefinitions>
        <TextBox Grid.Row="0" Text="{Binding InputText}" />
        <GridSplitter
            Grid.Row="1"
            Height="5"
            HorizontalAlignment="Stretch" />
        <TextBox
            Grid.Row="2"
            IsReadOnly="True"
            Text="{Binding OutputText, Mode=OneWay}" />
        <Grid Grid.Row="3" Margin="0,5,0,0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <Button
                Grid.Column="1"
                HorizontalAlignment="Stretch"
                VerticalAlignment="Stretch"
                HorizontalContentAlignment="Center"
                VerticalContentAlignment="Center"
                Command="{Binding ConvertCmd}"
                Content="Convert to BASE64" />
        </Grid>
    </Grid>
</Window>

MainWindow.axaml.cs

using System;
using System.Windows;
using System.Windows.Input;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Interactivity;

namespace HelloAvalonia
{
    /// <summary>
    /// View
    /// </summary>
    public partial class MainWindow : Window
    {
        private readonly ViewModel _viewModel;

        private readonly Model _model;

        public MainWindow()
        {
            InitializeComponent();
#if DEBUG
            this.AttachDevTools();
#endif

            _model = new Model();
            _viewModel = new ViewModel(_model);
            DataContext = _viewModel;
        }

        private void InitializeComponent()
        {
            AvaloniaXamlLoader.Load(this);
        }
    }

    /// <summary>
    /// ViewModel
    /// </summary>
    public sealed class ViewModel : NotificationBase
    {
        private readonly Model _model;

        public string InputText
        {
            get => _model.InputText;
            set
            {
                if (_model.InputText != value)
                {
                    _model.InputText = value;
                    ConvertCmd.NotifyChanged();
                }
            }
        }

        public string OutputText { get; private set; } = string.Empty;

        public RelayCommand ConvertCmd { get; }

        public ViewModel(Model model)
        {
            _model = model;

            ConvertCmd = new RelayCommand(
               (_) => _model.InputText.Length > 0,
               (_) =>
               {
                   _model.ConvertText();

                   if (OutputText != _model.OutputText)
                   {
                       OutputText = _model.OutputText;
                       NotifyPropChanged(nameof(OutputText));
                   }
               }
           );
        }
    }

    /// <summary>
    /// Model
    /// </summary>
    public sealed class Model
    {
        public string InputText { get; set; } = string.Empty;

        public string OutputText { get; private set; } = string.Empty;

        public void ConvertText()
        {
            var bin = Encoding.UTF8.GetBytes(InputText);
            OutputText = Convert.ToBase64String(bin);
        }
    }

    public abstract class NotificationBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler? PropertyChanged;

        public bool SetProp<T>(ref T obj, T value, [CallerMemberName] string? propName = null)
        {
            if (EqualityComparer<T>.Default.Equals(obj, value))
                return false;

            obj = value;
            NotifyPropChanged(propName);

            return true;
        }

        public void NotifyPropChanged([CallerMemberName] string? propName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
        }
    }

    public sealed class RelayCommand : ICommand
    {
        private readonly Func<object?, bool> _fncCanExecute;

        private readonly Action<object?> _subExecute;

        public event EventHandler? CanExecuteChanged;

        public RelayCommand(Func<object?, bool> canExecute, Action<object?> execute)
        {
            _fncCanExecute = canExecute;
            _subExecute = execute;
        }

        public bool CanExecute(object? parameter) => _fncCanExecute(parameter);

        public void Execute(object? parameter) => _subExecute(parameter);

        public void NotifyChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}


実行結果

file

上図のように、Linuxで簡単にGUIアプリケーションが作成できました。

ちなみに、今回AvaloniaをLinuxで使ってみての率直な感想はWPFと比べるとまだまだ未成熟な部分も多く本気で使おうとするとハマる要素が多々あるという印象でしたが、WPF/MVVMに慣れていればLinuxで簡単にGUIプログラミングができるAvaloniaは非常に有用なフレームワークではないかと思います。


参考ウェブサイトなど

以上です。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする