.Net MAUI に入門する (新規作成したプロジェクトについて)

# はじめに

.NET初心者をターゲットに、「なんとなくMAUI分かったかも」と思ってもらうための記事です。

> 私は主にMacで作業することが多いですが、Windowsでも同様に作業が進められると思います。

# 動作環境
- Apple M2 Mac mini (Sonoma 14.4.1)
- .NET 8.0

# まずは動かしてみる

### プロジェクトの作成

Visual Studio で　`.NET MAUI アプリ` を選び、プロジェクト名: `MauiExample` でプロジェクトを新規作成します。

> VSCodeの場合は `dotnet new maui -o MauiExample` で同じことができます。

### 実行

Visual Studioでプロジェクトを開き、デバッグ ターゲットがWindowsの状態で実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/611565/f658947b-e325-319b-95d5-73344ca6244c.png)

> Mac+VSCodeの場合は `dotnet build -t:Run -f net8.0-maccatalyst`で実行できます。

以下の画面が起動したらOKです。

ボタンをクリックしたら何が起こるのかを確認してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/611565/2226f971-31be-1c45-b25d-2c62d0d29007.png)

うまく動かなかったら[初めてのアプリを構築する](https://learn.microsoft.com/ja-jp/dotnet/maui/get-started/first-app?pivots=devices-windows&view=net-maui-8.0&tabs=vswin)を参考にしてください。

# プロジェクトの構造について

作成されたプロジェクトの`MauiExample`フォルダを開くと以下のようになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/611565/8af496b4-7dd8-ac1d-9ac0-8b01f45dd835.png)

### MauiProgram.cs

プログラムを起動した時、最初に実行されるファイルです。

```C#:MauiProgram.cs
using Microsoft.Extensions.Logging;

namespace MauiExample;

public static class MauiProgram
{
	public static MauiApp CreateMauiApp()
	{
		var builder = MauiApp.CreateBuilder();
		builder
			.UseMauiApp<App>()
			.ConfigureFonts(fonts =>
			{
				fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
				fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
			});

#if DEBUG
		builder.Logging.AddDebug();
#endif

		return builder.Build();
	}
}

```

`builder`インスタンスを作り、このインスタンスに対して色々設定をしています。

> この `◯◯builder`に設定をしていくパターンは、[ASP.NET Core](https://learn.microsoft.com/ja-jp/aspnet/core/fundamentals/?view=aspnetcore-8.0&tabs=macos)などの別フレームワークでも同様です。覚えておいて損はありません。

```
var builder = MauiApp.CreateBuilder();
```

「`App`(`App.xaml` + `App.xaml.cs`)を使いますよ」って宣言(`.UseMauiApp<App>()`部分)と

使用するフォントの設定（`.ConfigureFonts(...)`）をしています。

### App.xaml, App.xaml.cs

`App.xaml`にはアプリ全体から参照できるリソース(参考:[リソース ディクショナリ
](https://learn.microsoft.com/ja-jp/dotnet/maui/fundamentals/resource-dictionaries?view=net-maui-8.0))を指定します。リソースの本体は「Resources」フォルダーに保存されています。

ここでは`Colors.xaml`(使用する色に関する定義), `Style.xaml`(使用するボタンなどのコントロールに関する定義)が指定されています。

例えば、「ボタンを全部赤色にしたい」といった場合、全ボタンに一つずつ`backgroundColor="Red"`を書いていくのは面倒なので、赤色をボタンに設定するスタイルをResources」フォルダー内に作ってしてそれを各ページに適用するって感じで使います。

```xml:App.xaml
<?xml version = "1.0" encoding = "UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:MauiExample"
             x:Class="MauiExample.App">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Resources/Styles/Colors.xaml" />
                <ResourceDictionary Source="Resources/Styles/Styles.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>
```

`App.xaml.cs`ではルートページとして`AppShell`(`AppShell.xaml` + `AppShell.xaml.cs`)を指定しています。

> `MainPage`はAppの継承元である`Application`クラスで定義されているプロパティですので、例えば`RootPage`などに変更するとエラーになります。一応。

```C#:App.xaml.cs
namespace MauiExample;

public partial class App : Application
{
	public App()
	{
		InitializeComponent();

		MainPage = new AppShell();
	}
}
```

### AppShell.xaml, AppShell.xaml.cs

`AppShell`はナビゲーション機能などを提供するための[Shell](https://learn.microsoft.com/ja-jp/dotnet/maui/fundamentals/shell/?view=net-maui-8.0)を継承したクラスです。

簡単にいうと画面の切り替えを行うためのものですが、今はMainPageしかないのであまり意味ないですね。

```xml:AppShell.xaml
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
    x:Class="MauiExample.AppShell"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:local="clr-namespace:MauiExample"
    Shell.FlyoutBehavior="Disabled"
    Title="MauiExample">

    <ShellContent
        Title="Home"
        ContentTemplate="{DataTemplate local:MainPage}"
        Route="MainPage" />

</Shell>
```

ナビゲーション方式は[Flyout](https://learn.microsoft.com/ja-jp/dotnet/maui/fundamentals/shell/flyout?view=net-maui-8.0)、または[Tab](https://learn.microsoft.com/ja-jp/dotnet/maui/fundamentals/shell/tabs?view=net-maui-8.0)があります。リンク先のサンプルコードを見ればイメージが掴みやすいと思います。

`AppShell.xaml.cs`の方は初期化だけで、特に何もしていません。

```C#:AppShell.xaml.cs
namespace MauiExample;

public partial class AppShell : Shell
{
	public AppShell()
	{
		InitializeComponent();
	}
}
```

### MainPage.xaml, MainPage.xaml.cs

プログラム実行時に表示された画面のメイン部分です。

- [ScrollView](https://learn.microsoft.com/ja-jp/dotnet/maui/user-interface/controls/scrollview?view=net-maui-8.0): スクロールを有効にする
- [VerticalStackLayout](https://learn.microsoft.com/ja-jp/dotnet/maui/user-interface/layouts/verticalstacklayout?view=net-maui-8.0): コントロールを垂直方向に並べる
- [Image](https://learn.microsoft.com/ja-jp/dotnet/maui/user-interface/controls/image?view=net-maui-8.0): 画像を表示する
- [Label](https://learn.microsoft.com/ja-jp/dotnet/maui/user-interface/controls/label?view=net-maui-8.0): テキストを表示する
- [Button](https://learn.microsoft.com/ja-jp/dotnet/maui/user-interface/controls/button?view=net-maui-8.0): ボタンを表示する

を使用して実装されています。

```xml:MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiExample.MainPage">

    <ScrollView>
        <VerticalStackLayout
            Spacing="25"　 // コンポーネントとコンポーネントに 25px 隙間を設ける
            Padding="30,0" // パディングを左右に30px, 上下に　0pxに設定
            VerticalOptions="Center"> // 垂直方向に中央揃え

            <Image
                Source="dotnet_bot.png" // Resources¥Imagesにある画像ファイルを指定
                SemanticProperties.Description="Cute dot net bot waving hi to you!"
                HeightRequest="200" // 画像の高さを指定
                HorizontalOptions="Center" />　// 水平方向に中央揃え

            <Label
                Text="Hello, World!" // 表示するテキスト
                SemanticProperties.HeadingLevel="Level1"
                FontSize="32" // テキストのフォントサイズ
                HorizontalOptions="Center" />　 // 水平方向に中央揃え

            <Label
                Text="Welcome to .NET Multi-platform App UI"
                SemanticProperties.HeadingLevel="Level2"
                SemanticProperties.Description="Welcome to dot net Multi platform App U I"
                FontSize="18"
                HorizontalOptions="Center" />　

            <Button
                x:Name="CounterBtn" // ButtonオブジェクトをMainPage.xaml.cs内で参照するための指定
                Text="Click me" // ボタンの中に表示するテキスト
                SemanticProperties.Hint="Counts the number of times you click"
                Clicked="OnCounterClicked" // クリックした時の処理
                HorizontalOptions="Center" /> // 水平方向に中央揃え

        </VerticalStackLayout>
    </ScrollView>

</ContentPage>
```

> Imageに`Source="dotnet_bot.png"`を指定していますが、不思議なことに`Resouces/Images`フォルダには`dotnet_bot.svg`ファイルしかありません。
> MAUIにはビルド時にsvgをpngに変換する仕組みがあるので`dotnet_bot.png`をちゃんと参照できるのです。
> 参考: [ローカル画像を読み込む](https://learn.microsoft.com/ja-jp/dotnet/maui/user-interface/controls/image?view=net-maui-8.0#load-a-local-image)


> `SemanticProperties`で始まるプロパティについては[セマンティック プロパティを使用してアクセス可能なアプリを構築する](https://learn.microsoft.com/ja-jp/dotnet/maui/fundamentals/accessibility?view=net-maui-8.0)を参照してください。

注目すべきは[Button](https://learn.microsoft.com/ja-jp/dotnet/maui/user-interface/controls/button?view=net-maui-8.0)に指定された以下の２つです。

- [x:Name](https://learn.microsoft.com/ja-jp/dotnet/desktop/xaml-services/xname-directive)="CounterBtn"
- Clicked="OnCounterClicked"

これを踏まえて、`MainPage.xaml.cs`を見てみましょう

```C#:MainPage.xaml.cs
namespace MauiExample;

public partial class MainPage : ContentPage
{
	int count = 0;

	public MainPage()
	{
		InitializeComponent();
	}

	private void OnCounterClicked(object sender, EventArgs e)
	{
		count++;

		if (count == 1)
			CounterBtn.Text = $"Clicked {count} time";
		else
			CounterBtn.Text = $"Clicked {count} times";

		SemanticScreenReader.Announce(CounterBtn.Text);
	}
}
```

`MainPage.xaml.cs`にはプロパティ: `CounterBtn`が定義されていないのに、まるで定義済みかのように`CounterBtn.Text`でボタンのテキストを参照・変更しています。

また、`OnCounterClicked`は`MainPage.xaml`上で[Button](https://learn.microsoft.com/ja-jp/dotnet/maui/user-interface/controls/button?view=net-maui-8.0)の`Clicked`に指定されていました。

これが`Xaml`と`C#`を組み合わせて画面＋処理を実装する基本的な形になります。

### XamlとC#（コードビハインド）の連携

これまで説明してきた`APP`, `AppShell`, `MainPage`には`.xaml`と`.xaml.cs`が対になって存在していました。これらの対のファイルは連携して動作します。

対応付けは

- `.xaml.cs`で定義されている `名前空間+クラス名`
- `.xaml`の`x:Class`で指定された `名前空間+クラス名`

で行われます。

> ファイル名やファイルパスは関係ないので、両ファイルを全く異なるパス、ファイル名で置いておくことはできますがオススメしません。同階層に`.xaml`と`.xaml.cs`のペアで置いておくのが基本です。

試しに`MainPage.xaml`の　`x:Class="MauiExample.MainPage"`を変えてみましょう。

```xml
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiExample.TopPage"> // TopPageに変更
```

この状態で`MainPage.xaml.cs`を開くと警告が出るようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/611565/2aab6d15-2313-9e6b-1994-5b55935949e3.png)

`CounterBtn`が認識出来なくなっていますね。

逆に`MainPage.xaml.cs`側のクラス名を変更しても同じ状態になります。

### 補足：Xamlは必須ではない

「`.xaml`と`.xaml.cs`のペアで動作するのが基本」と書きましたが、実は`.xaml`無しでも実装はできます。

例えば、画面上に[Label](https://learn.microsoft.com/ja-jp/dotnet/maui/user-interface/controls/label?view=net-maui-8.0)を表示させる場合、`.xaml.cs`内でインスタンスを作成すれば`Xaml`内で`<Label　... />`を記述したのと同等になります。

```C#
Label label = new Label { Text = "Hello world" };
// <Label Text="Hello world" /> と同等
```

とはいえ、UIに関するコードは`Xaml`、動作に関わるコードは`.xaml.cs`に実装する方がコードの見通しがいいのでそちらをオススメします。

### その他のフォルダについて

- bin: ビルドの出力結果である実行可能なファイルの保存先

- obj: ビルドの一時ファイル置き場

- Platforms: プログラム起動時に、OSごとに異なる処理を実行させたい場合に使います(参考: [マルチターゲットの構成](https://learn.microsoft.com/ja-jp/dotnet/maui/platform-integration/configure-multi-targeting?view=net-maui-8.0))

- Properties: アプリケーションの起動とデバッグの設定を構成するために使用される `launchSettings.json` 置き場。`launchSettings.json`では、プロファイル、環境変数、引数などを構成する。

- Resouces: リソース ディクショナリ置き場。

# 今後の予定
- MVVM Toolkitを使用してMVVMにする
- CollectionViewでコレクションを表示する
- ContentPageを複数のContentViewに分割する
- Layoutのコツなど