[Avalonia] Getting Started 入門する

2022-07-06 (水)

公式の Getting Started を参考に、拡張機能のインストールからビルドして実行まで行います。
Avalonia は C# の単一コードで Windows, macOS, Linux のアプリが作成できます。最近は iOS, Android, WebAssembly アプリも作成できるようです。

環境

  • .NET 6.0.301
  • C# 11.0
  • Visual Studio 2022 Version 17.2.5
  • Windows 10 Pro 64bit 21H1 19043.1766

拡張機能のインストール

公式の手順でインストールします。
https://docs.avaloniaui.net/docs/getting-started

Visual Studio を使用していれば、拡張機能をインストールするだけで良いので、ここでは拡張機能をインストールします。
https://docs.avaloniaui.net/docs/getting-started/ide-support#visual-studio

Avalonia Visual Studio Extension をインストールします。
Visual Studio の [拡張機能] -> [拡張機能の管理] からインストールできます。

新しいプロジェクトの作成

拡張機能をインストールすると、プロジェクトテンプレートが追加されます。

テンプレートのソースは、ここにあるようです。

dotnet new のテンプレートのソースは、ここにあるようです。

実際にそれぞれプロジェクトを作成してみると、以下のようなフォルダ構成はなっています。

実行確認

問題なくビルドできました。

Avalonia MVVM Application

Avalonia Application

テンプレートソースの違い

MVVM にした場合のソースの違いを見てみます。

MVVM では ReactiveUI が使われ、ViewLocator.cs により ViewModel をバインドしているのが特徴だと思います。

AvaloniaMvvmApplication.csproj

MVVM の主な違い

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <!--Avalonia doesen't support TrimMode=link currently,but we are working on that https://github.com/AvaloniaUI/Avalonia/issues/6892 -->
    <TrimMode>copyused</TrimMode>
    <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
  </PropertyGroup>
  <ItemGroup>
+   <Folder Include="Models\" />
+   <AvaloniaResource Include="Assets\**" />
    <None Remove=".gitignore" />
  </ItemGroup>
  <ItemGroup>
    <!--This helps with theme dll-s trimming.
	If you will publish your application in self-contained mode with p:PublishTrimmed=true and it will use Fluent theme Default theme will be trimmed from the output and vice versa.
	https://github.com/AvaloniaUI/Avalonia/issues/5593 -->
    <TrimmableAssembly Include="Avalonia.Themes.Fluent" />
    <TrimmableAssembly Include="Avalonia.Themes.Default" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Avalonia" Version="0.10.14" />
    <PackageReference Include="Avalonia.Desktop" Version="0.10.14" />
    <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
    <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.14" />
+   <PackageReference Include="Avalonia.ReactiveUI" Version="0.10.14" />
    <PackageReference Include="XamlNameReferenceGenerator" Version="1.3.4" />
  </ItemGroup>
</Project>
Application ソース
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <!--Avalonia doesen't support TrimMode=link currently,but we are working on that https://github.com/AvaloniaUI/Avalonia/issues/6892 -->
    <TrimMode>copyused</TrimMode>
    <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
  </PropertyGroup>
  <ItemGroup>
    <None Remove=".gitignore" />
  </ItemGroup>
  <ItemGroup>
    <!--This helps with theme dll-s trimming.
	If you will publish your application in self-contained mode with p:PublishTrimmed=true and it will use Fluent theme Default theme will be trimmed from the output and vice versa.
	https://github.com/AvaloniaUI/Avalonia/issues/5593 -->
    <TrimmableAssembly Include="Avalonia.Themes.Fluent" />
    <TrimmableAssembly Include="Avalonia.Themes.Default" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Avalonia" Version="0.10.14" />
    <PackageReference Include="Avalonia.Desktop" Version="0.10.14" />
    <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
    <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.14" />
    <PackageReference Include="XamlNameReferenceGenerator" Version="1.3.4" />
  </ItemGroup>
</Project>
MVVM Application のソース
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <!--Avalonia doesen't support TrimMode=link currently,but we are working on that https://github.com/AvaloniaUI/Avalonia/issues/6892 -->
    <TrimMode>copyused</TrimMode>
    <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
  </PropertyGroup>
  <ItemGroup>
    <Folder Include="Models\" />
    <AvaloniaResource Include="Assets\**" />
    <None Remove=".gitignore" />
  </ItemGroup>
  <ItemGroup>
    <!--This helps with theme dll-s trimming.
	If you will publish your application in self-contained mode with p:PublishTrimmed=true and it will use Fluent theme Default theme will be trimmed from the output and vice versa.
	https://github.com/AvaloniaUI/Avalonia/issues/5593 -->
    <TrimmableAssembly Include="Avalonia.Themes.Fluent" />
    <TrimmableAssembly Include="Avalonia.Themes.Default" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Avalonia" Version="0.10.14" />
    <PackageReference Include="Avalonia.Desktop" Version="0.10.14" />
    <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
    <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.14" />
    <PackageReference Include="Avalonia.ReactiveUI" Version="0.10.14" />
    <PackageReference Include="XamlNameReferenceGenerator" Version="1.3.4" />
  </ItemGroup>
</Project>

Program.cs

MVVM の主な違い

using Avalonia;
-using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.ReactiveUI;
using System;

namespace AvaloniaMvvmApplication1
{
    internal 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.
        [STAThread]
        public static void Main(string[] args) => BuildAvaloniaApp()
            .StartWithClassicDesktopLifetime(args);

        // Avalonia configuration, don't remove; also used by visual designer.
        public static AppBuilder BuildAvaloniaApp()
            => AppBuilder.Configure<App>()
                .UsePlatformDetect()
                .LogToTrace()
+               .UseReactiveUI();
    }
}
Application ソース
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using System;

namespace AvaloniaApplication1
{
    internal 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.
        [STAThread]
        public static void Main(string[] args) => BuildAvaloniaApp()
            .StartWithClassicDesktopLifetime(args);

        // Avalonia configuration, don't remove; also used by visual designer.
        public static AppBuilder BuildAvaloniaApp()
            => AppBuilder.Configure<App>()
                .UsePlatformDetect()
                .LogToTrace();
    }
}
MVVM Application のソース
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.ReactiveUI;
using System;

namespace AvaloniaMvvmApplication1
{
    internal 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.
        [STAThread]
        public static void Main(string[] args) => BuildAvaloniaApp()
            .StartWithClassicDesktopLifetime(args);

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

App.axaml

MVVM の主な違い

<Application xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+            xmlns:local="using:AvaloniaMvvmApplication1"
             x:Class="AvaloniaMvvmApplication1.App">
+   <Application.DataTemplates>
+       <local:ViewLocator/>
+   </Application.DataTemplates>

    <Application.Styles>
        <FluentTheme Mode="Light"/>
    </Application.Styles>
</Application>
Application ソース
<Application xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             x:Class="AvaloniaApplication1.App">
    <Application.Styles>
        <FluentTheme Mode="Light"/>
    </Application.Styles>
</Application>
MVVM Application のソース
XXXXXX

App.axaml.cs

MVVM の主な違い

using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
+using AvaloniaMvvmApplication1.ViewModels;
+using AvaloniaMvvmApplication1.Views;

namespace AvaloniaMvvmApplication1
{
    public partial class App : Application
    {
        public override void Initialize()
        {
            AvaloniaXamlLoader.Load(this);
        }

        public override void OnFrameworkInitializationCompleted()
        {
            if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
            {
                desktop.MainWindow = new MainWindow
+               {
+                   DataContext = new MainWindowViewModel(),
+               };
            }

            base.OnFrameworkInitializationCompleted();
        }
    }
}
Application ソース
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;

namespace AvaloniaApplication1
{
    public partial class App : Application
    {
        public override void Initialize()
        {
            AvaloniaXamlLoader.Load(this);
        }

        public override void OnFrameworkInitializationCompleted()
        {
            if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
            {
                desktop.MainWindow = new MainWindow();
            }

            base.OnFrameworkInitializationCompleted();
        }
    }
}
MVVM Application のソース
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using AvaloniaMvvmApplication1.ViewModels;
using AvaloniaMvvmApplication1.Views;

namespace AvaloniaMvvmApplication1
{
    public partial class App : Application
    {
        public override void Initialize()
        {
            AvaloniaXamlLoader.Load(this);
        }

        public override void OnFrameworkInitializationCompleted()
        {
            if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
            {
                desktop.MainWindow = new MainWindow
                {
                    DataContext = new MainWindowViewModel(),
                };
            }

            base.OnFrameworkInitializationCompleted();
        }
    }
}

MainWindow.axaml

MVVM の主な違い

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+       xmlns:vm="using:AvaloniaMvvmApplication1.ViewModels"
        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="AvaloniaMvvmApplication1.Views.MainWindow"
+       Icon="/Assets/avalonia-logo.ico"
        Title="AvaloniaMvvmApplication1">
-   Welcome to Avalonia!
+   <Design.DataContext>
+       <vm:MainWindowViewModel/>
+   </Design.DataContext>
+
+    <TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
+
</Window>
Application ソース
<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="AvaloniaApplication1.MainWindow"
        Title="AvaloniaApplication1">
    Welcome to Avalonia!
</Window>
MVVM Application のソース
<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="using:AvaloniaMvvmApplication1.ViewModels"
        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="AvaloniaMvvmApplication1.Views.MainWindow"
        Icon="/Assets/avalonia-logo.ico"
        Title="AvaloniaMvvmApplication1">

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

    <TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/>

</Window>

MainWindow.axaml.cs

MVVM の主な違い

using Avalonia.Controls;

-namespace AvaloniaApplication1
+namespace AvaloniaMvvmApplication1.Views
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}
Application ソース
using Avalonia.Controls;

namespace AvaloniaApplication1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}
MVVM Application のソース
using Avalonia.Controls;

namespace AvaloniaMvvmApplication1.Views
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

ViewLocator.cs (MVVM のみ)

using Avalonia.Controls;
using Avalonia.Controls.Templates;
using AvaloniaMvvmApplication1.ViewModels;
using System;

namespace AvaloniaMvvmApplication1
{
    public class ViewLocator : IDataTemplate
    {
        public IControl Build(object data)
        {
            var name = data.GetType().FullName!.Replace("ViewModel", "View");
            var type = Type.GetType(name);

            if (type != null)
            {
                return (Control)Activator.CreateInstance(type)!;
            }
            else
            {
                return new TextBlock { Text = "Not Found: " + name };
            }
        }

        public bool Match(object data)
        {
            return data is ViewModelBase;
        }
    }
}

ViewModelBase.cs.cs (MVVM のみ)

using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Text;

namespace AvaloniaMvvmApplication1.ViewModels
{
    public class ViewModelBase : ReactiveObject
    {
    }
}

MainWindowViewModel.cs (MVVM のみ)

using System;
using System.Collections.Generic;
using System.Text;

namespace AvaloniaMvvmApplication1.ViewModels
{
    public class MainWindowViewModel : ViewModelBase
    {
        public string Greeting => "Welcome to Avalonia!";
    }
}

入門の続き

この辺りを参考にしていけば良さそうです。

Avalonia は感触良さそうな印象があります。

あとがき

小規模なアプリを WPF ではなく Avalonia で作成しようと思ったのですが、WebView2 を使う方向にしたため今回は見送りました。
クロスプラットフォームなデスクトップアプリを作成するなら、使ってみたいと思います。

感謝

2022-07-06 (水)