AvaloniaUIでマルチプラットフォームなGUI (入門)


この記事はSFC-RG Advent Calendar 2019の10日目です。

概要

AvaloniaUIで簡単になんか作る。

動機

マルチプラットフォームなGUIを作りたい。
子プロセスで別のプログラム動かしたい。
HTMLとCSSが苦手。
C#が好き。

注意

筆者もそこまでAvaloniaUIとか詳しくない。
MVVMを全く理解していない。
XAMLの書き方も我流。

間違ってたら優しく教えてくれると嬉しいです。

環境構築

  1. DotnetCoreSDKを入れる。(本記事では3.0.100)
    https://dotnet.microsoft.com/download

  2. AvaloniaUIのテンプレートを入れる。

cd ~
mkdir workspace
cd workspace
git clone https://github.com/AvaloniaUI/avalonia-dotnet-templates
dotnet new --install ~/workspace/avalonia-dotnet-templates

プロジェクトの作成

MyAppという名前でプロジェクトを作成

cd ~/workspace
dotnet new avalonia.mvvm -o MyApp
cd MyApp

初期状態はこんな感じ

> ls
App.xaml    Assets      MyApp.csproj    ViewLocator.cs  Views
App.xaml.cs Models      Program.cs  ViewModels  nuget.config

動かしてみる

dotnet run

windowが出ました。

UIを作っていく

Views/MainWindow.xamlを弄っていく。

WidthとHeightを追加してwindowのサイズを決める。(ここでは800 * 800)

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

ボタンを足してやる。あとTextBlockは邪魔なので消す。
Window.Stylesでは各パーツのデザインをクラス毎にまとめて設定できる。
(ここではButtonパーツのtestというクラス)

    <Design.DataContext>
        <vm:MainWindowViewModel/>
    </Design.DataContext>
    <Window.Styles>
        <Style Selector="Button.test">
            <Setter Property="Width" Value="100" />
            <Setter Property="Height" Value="25" />
        </Style>
    </Window.Styles>

    <Button Classes="test"> Click Me! </Button>

</Window>

実行してみる

ど真ん中にボタンが登場。

バインドしてみる

ボタンに名前をつける。

<Button Classes="test" Name ="testButton"> Click Me! </Button>

MainzWindow.xaml.csを弄る。

using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Interactivity;

namespace MyApp.Views
{
    public class MainWindow : Window
    {
        private Button _testButton;

        public MainWindow()
        {
            InitializeComponent();
            // xaml内から該当のボタンを探す
            _testButton = this.FindControl<Button>("testButton");
            // バインドする
            _testButton.Click += TestButtonClicked;
        }

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

        private void TestButtonClicked(object sender, RoutedEventArgs e)
        {
            // クリックされたらボタンのサイズを変える
            _testButton.Height = 200;
        }
    }
}

実行

dotnet run

結果

まとめ

とりあえず簡単にバインドするところまで。

デフォルトでファイル選択ダイアログとかも使えるし、グラフとかもhttps://github.com/AvaloniaUI/oxyplot-avalonia あたり使えば書けるので簡単なGUIはこれで足りそう。

ただ、バージョンがが1.0未満でドキュメントがほとんどないのでgithubで他の人のコードを漁ることになる。

xamlの書き方がUWPと微妙に違うので結構困る。

困ったら、公式のAPI等をみることになる。
https://avaloniaui.net/api/

あと公式からチュートリアルも出てるのでそちらも是非に。
https://avaloniaui.net/docs/tutorial/

気が向いたら続き書きます。

追記

@lensouko 様よりコメントを頂いていた(1年以上経って気付いた...)ので貼り付けておきます。
通常、
_testButton.Click += TestButtonClicked;
はバインドとは言いません。
イベントハンドラを登録するとかそういう感じの表現にします。
WPFやAvaloniaではCommandをバインドします。
やり方はこちらを参照
大雑把に説明すると、Nameは不要になります。
Window.csに処理を書く必要がなくなります。
やりたいことややった結果などと画面の紐づけはViewModelで完結します。(コードは多少増えるけど、あっちゃこっちゃに分散しない)
WPFだとコマンドクラスを自作しなきゃいけなかったのでPrismなどのフレームワークやライブラリを使うのが常套手段でしたが、Avaloniaだとテンプレでプロジェクトを作れば用意してくれているようです。
記事の書き方はFormアプリケーションしか知らない人が手を出しやすくするための互換的なものであってWPFやAvaloniaの本来の形ではないということになります。

public ReactiveCommand<Unit, Unit> やりたい処理コマンド { get; }

public コンストラクタ()
{
    やりたい処理コマンド = ReactiveCommand.Create(やりたい処理);
}

void やりたい処理()
{
}
<Button Command="{Binding やりたい処理コマンド}">Do the thing!</Button>