.NET MAUI アプリを作成する
このチュートリアル シリーズは、クロス プラットフォーム コードのみを使用する .NET Multi-platform App UI (.NET MAUI) アプリを作成する方法を示す目的で設計されています。 つまり、記述するコードは、Windows、Android、iOS、または macOS に固有のコードにはなりません。 作成するアプリはメモを取るアプリであり、ユーザーは複数のノートを作成、保存、読み込むことができます。
このチュートリアルでは、次の作業を行う方法について説明します。
- .NET MAUI シェル アプリを作成します。
- 選択したプラットフォームでアプリを実行します。
- eXtensible Application Markup Language (XAML) を使用してユーザー インターフェイスを定義し、コードで XAML 要素を操作します。
- ビューを作成し、データにバインドします。
- ナビゲーションを使用してページ間を移動します。
Visual Studio 2022 を使用し、メモを入力してデバイス ストレージに保存できるアプリケーションを作成します。 最終的なアプリケーションは、次のとおりです。
プロジェクトの作成
このチュートリアルを開始する前に、最初のアプリのビルドに関する記事 の手順を行う必要があります。 プロジェクトの作成時には、次の設定を使用します。
プロジェクト名
これは、
Notes
に設定する必要があります。 プロジェクトに別の名前が付けられている場合、このチュートリアルからコピーして貼り付けたコードによってビルド エラーが発生する可能性があります。ソリューションとプロジェクトを同じディレクトリに配置する
この設定をオフにします。
プロジェクトの作成時に最新の .NET フレームワークを選択します。
ターゲット デバイスを選択する
.NET MAUI アプリは、複数のオペレーティング システムとデバイス上で実行できるように設計されています。 どのターゲットでアプリをテストしてデバッグしたいかを選択する必要があります。
Visual Studio ツール バーの [デバッグ ターゲット] を、デバッグしてテストしたいデバイスに設定します。 次の手順は、[デバッグ ターゲット] を Android に設定する方法を示します。
- [デバッグ ターゲット] ドロップダウン ボタンをし選択ます。
- [Android エミュレーター] の項目を選択します。
- エミュレーター デバイスを選択します。
アプリ シェルをカスタマイズする
Visual Studio で .NET MAUI プロジェクトを作成すると、4 つの重要なコード ファイルが生成されます。 これらは、Visual Studio の [ソリューション エクスプローラー] ウィンドウに表示されます。
これらのファイルは、.NET MAUI アプリを構成して実行するのに役立ちます。 各ファイルは、以下で説明する異なる目的で機能します。
MauiProgram.cs
これは、アプリをブートストラップするコード ファイルです。 このファイル内のコードは、アプリのクロス プラットフォーム エントリ ポイントとして機能し、アプリを構成して起動します。 テンプレートのスタートアップ コードは、App.xaml ファイルが定義する
App
クラスを指します。App.xaml と App.xaml.cs
わかりやすくするために、これらの両方のファイルを単一ファイルと呼びます。 通常、XAML ファイルには、.xaml ファイルそのものと、[ソリューション エクスプローラー] の子項目である対応するコード ファイルの 2 つのファイルがあります。 .xaml ファイルには XAML マークアップが含まれており、コード ファイルには XAML マークアップを操作するためにユーザーが作成したコードが含まれます。
App.xaml ファイルには、色、スタイル、テンプレートなどのアプリ全体の XAML リソースが含まれます。 App.xaml.cs ファイルには通常、シェル アプリケーションをインスタンス化するコードが含まれます。 このプロジェクトでは、
AppShell
クラスを指します。AppShell.xaml と AppShell.xaml.cs
このファイルは、アプリのビジュアル階層を定義するために使用する
AppShell
クラスを定義します。MainPage.xaml と MainPage.xaml.cs
これは、アプリによって表示されるスタートアップ ページです。 MainPage.xaml ファイルは、ページの UI (ユーザー インターフェイス) を定義します。 MainPage.xaml.cs には、ボタン クリック イベントのコードなど、XAML の分離コードが含まれています。
"About" ページを追加する
最初に行うカスタマイズは、別のページをプロジェクトに追加することです。 このページは、作成者、バージョン、詳細情報へのリンクなど、このアプリに関する情報を表す "About" ページです。
Visual Studio の [ソリューション エクスプローラー] ウィンドウで、[メモ] プロジェクト >[追加]>[新しい項目...] を右クリックします。
[新しい項目の追加] ダイアログで、ウィンドウの左側にあるテンプレート リストで [.NET MAUI] を選択します。 次に、[.NET MAUI ContentPage (XAML)] テンプレートを選択します。 ファイルに AboutPage.xaml という名前を付け、[追加] を選択します。
MainView.xaml ファイルが新しいドキュメント タブを開き、ページの UI を表すすべての XAML マークアップが表示されます。 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="Notes.AboutPage"> <VerticalStackLayout Spacing="10" Margin="10"> <HorizontalStackLayout Spacing="10"> <Image Source="dotnet_bot.png" SemanticProperties.Description="The dot net bot waving hello!" HeightRequest="64" /> <Label FontSize="22" FontAttributes="Bold" Text="Notes" VerticalOptions="End" /> <Label FontSize="22" Text="v1.0" VerticalOptions="End" /> </HorizontalStackLayout> <Label Text="This app is written in XAML and C# with .NET MAUI." /> <Button Text="Learn more..." Clicked="LearnMore_Clicked" /> </VerticalStackLayout> </ContentPage>
Ctrl+S を押すか、メニュー [ファイル]>[AboutPage.xaml を保存] を選び、ファイルを保存します。
以下でページに配置された XAML コントロールの主要なパーツを理解しましょう。
<ContentPage>
はAboutPage
クラスのルート オブジェクトです。<VerticalStackLayout>
は、ContentPage の唯一の子オブジェクトです。 ContentPage は、子オブジェクトを 1 つだけ持つことができます。 VerticalStackLayout 型は複数の子を持つことができます。 このレイアウト コントロールは、子要素を垂直方向に順番に配置します。<HorizontalStackLayout>
は、子が横方向に配置される点を除き、<VerticalStackLayout>
と同じように機能します。<Image>
はイメージを表示します。この場合は、すべての .NET MAUI プロジェクトに付属するdotnet_bot.png
イメージを使用しています。重要
プロジェクトに追加されたファイルは実際には
dotnet_bot.svg
です。 .NET MAUI は、ターゲット デバイスに基づいてスケーラブル ベクター グラフィックス (SVG) ファイルをポータブル ネットワーク グラフィックス (PNG) ファイルに変換します。 したがって、SVG ファイルを .NET MAUI アプリ プロジェクトに追加する場合は、.png
拡張子が付いた XAML または C# から参照する必要があります。 SVG ファイルへの唯一の参照は、プロジェクト ファイルに含まれている必要があります。<Label>
コントロールはテキストを表示します。<Button>
コントロールはユーザーが押すことができ、それによってClicked
イベントが発生します。Clicked
イベントに応じてコードを実行できます。Clicked="LearnMore_Clicked"
ボタンの
Clicked
イベントはLearnMore_Clicked
イベント ハンドラーに割り当てられます。このイベント ハンドラーは分離コード ファイルで定義されます。 このコードを次の手順で作成します。
Clicked イベントを処理する
次の手順は、ボタンの Clicked
イベントのコードを追加することです。
Visual Studio の [ソリューション エクスプローラー] ウィンドウで、AboutPage.xaml ファイルを展開して、分離コード ファイル AboutPage.xaml.cs を表示します。 次に、AboutPage.xaml.cs ファイルをダブルクリックして、コード エディターで開きます。
次の
LearnMore_Clicked
イベント ハンドラー コードを追加して、システム ブラウザーで特定の URL を開きます。private async void LearnMore_Clicked(object sender, EventArgs e) { // Navigate to the specified URL in the system browser. await Launcher.Default.OpenAsync("https://aka.ms/maui"); }
async
キーワードがメソッド宣言に追加されていることに注意してください。これにより、システム ブラウザーを開くときにawait
キーワードを使用できます。Ctrl+S を押すか、メニュー [ファイル]>[AboutPage.xaml の保存] を選び、ファイルを保存します。
これで AboutPage
の XAML と分離コードが完成したので、アプリに表示する必要があります。
画像リソースを追加する
一部のコントロールでは、ユーザーがアプリを操作する方法を強化する画像を使用できます。 このセクションでは、アプリで使用する 2 つの画像と、iOS で使用する 2 つの代替画像をダウンロードします。
次の画像をダウンロードします。
アイコン: About
この画像は、前に作成した About ページのアイコンとして使用されます。アイコン: メモ
この画像は、このチュートリアルの次の部分で作成するメモ ページのアイコンとして使用します。
画像をダウンロードしたら、ファイル エクスプローラーを使用してプロジェクトの Resources\Images フォルダーに移動できます。 このフォルダー内のすべてのファイルは、MauiImage リソースとして自動的にプロジェクトに含まれます。 Visual Studio を使用して、プロジェクトにイメージを追加することもできます。 画像を手動で移動する場合は、次の手順をスキップします。
重要
iOS 特有の画像のダウンロードはスキップしないでください。ファイルはこのチュートリアルを完了するために必要です。
Visual Studio を使用して画像を移動する
Visual Studio の [ソリューション エクスプローラー] ペインで、[リソース] フォルダーを展開すると、[画像] フォルダーが表示されます。
ヒント
エクスプローラーを使用して、images フォルダーの上にある [ソリューション エクスプローラー] ウィンドウに直接画像をドラッグ アンド ドロップできます。 これにより、ファイルがフォルダーに自動的に移動され、プロジェクトに含まれます。 ファイルをドラッグ アンド ドロップする場合は、この手順の残りの部分を省略します。
[画像] を右クリックし、[追加]>[既存の項目...] を選択します。
ダウンロードされた画像を含むフォルダーに移動します。
フィルターを [ファイルの種類] フィルターの [画像ファイル] に変更します。
Ctrl キーを押しながら、ダウンロードした各イメージをクリックし、[追加] を押します
アプリ シェルを変更する
この記事の冒頭で触れたように、AppShell
クラスは、アプリのビジュアル階層、つまりアプリの UI の作成に使用される XAML マークアップを定義します。 XAML を更新して TabBar コントロールを追加します。
[ソリューション エクスプローラー] ペインで AppShell.xaml ファイルをダブルクリックして、XAML エディターを開きます。 XAML マークアップを次のコードで置き換えます。
<?xml version="1.0" encoding="UTF-8" ?> <Shell x:Class="Notes.AppShell" xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:Notes" Shell.FlyoutBehavior="Disabled"> <TabBar> <ShellContent Title="Notes" ContentTemplate="{DataTemplate local:MainPage}" Icon="{OnPlatform 'icon_notes.png', iOS='icon_notes_ios.png', MacCatalyst='icon_notes_ios.png'}" /> <ShellContent Title="About" ContentTemplate="{DataTemplate local:AboutPage}" Icon="{OnPlatform 'icon_about.png', iOS='icon_about_ios.png', MacCatalyst='icon_about_ios.png'}" /> </TabBar> </Shell>
Ctrl キーを押しながら S キーを押す か、メニューの [フィイル] > [Save AppShell.xaml] を選択して、ファイルを保存します。
XAML の重要な部分を次に示します。
<Shell>
は XAML マークアップのルート オブジェクトです。<TabBar>
は Shell のコンテンツです。<TabBar>
内の2 つの<ShellContent>
オブジェクト。 テンプレート コードを置き換える前に、MainPage
ページを指す1 つの<ShellContent>
オブジェクトが存在していました。
TabBar
やその子が表しているのは、ユーザー インターフェイス要素ではなく、アプリのビジュアル階層の編成です。 シェルはこれらのオブジェクトを受け取り、各ページを表す上部にバーがあるコンテンツのユーザー インターフェイスを生成します。 各ページの ShellContent.Icon
プロパティは、{OnPlatform ...}
という特殊な構文を使用します。 この構文は、プラットフォームごとに XAML ページがコンパイルされるときに処理され、プラットフォームごとにプロパティ値を指定できます。 この場合、すべてのプラットフォームで既定で icon_about.png
アイコンが使用されますが、iOS と MacCatalyst では icon_about_ios.png
が使用されます。
各 <ShellContent>
オブジェクトは、表示するページを指しています。 これは ContentTemplate
プロパティで設定します。
アプリを実行する
アプリを実行するには、F5 キーを押すか、Visual Studio の上部にある再生ボタンを押します。
[メモ] と [情報] の 2 つのタブがあることがわかります。 [バージョン情報] タブを押すと、アプリは作成した AboutPage
に移動します。 [詳細情報...] ボタンを押して、Web ブラウザーを開きます。
アプリを閉じ、Visual Studio に戻ります。 Android エミュレーターを使用している場合は、仮想デバイスでアプリを終了するか、Visual Studio の上部にある停止ボタンを押します。
メモのページを作成する
アプリに MainPage
と AboutPage
が含まれるので、アプリの残りの部分の作成を開始できます。 まず、ユーザーがメモを作成して表示できるページを作成し、メモを読み込んで保存するコードを記述します。
メモ ページにメモが表示され、メモを保存または削除できます。 最初に、新しいページをプロジェクトに追加します。
Visual Studio の [ソリューション エクスプローラー] ウィンドウで、[メモ] プロジェクト >[追加]>[新しい項目...] を右クリックします。
[新しい項目の追加] ダイアログで、ウィンドウの左側にあるテンプレート リストで [.NET MAUI] を選択します。 次に、[.NET MAUI ContentPage (XAML)] テンプレートを選択します。 ファイルに NotePage.xaml という名前を付け、[追加] を選択します。
MainView.xaml ファイルが新しいタブに開き、ページの UI を表す XAML マークアップがすべて表示されます。 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="Notes.NotePage" Title="Note"> <VerticalStackLayout Spacing="10" Margin="5"> <Editor x:Name="TextEditor" Placeholder="Enter your note" HeightRequest="100" /> <Grid ColumnDefinitions="*,*" ColumnSpacing="4"> <Button Text="Save" Clicked="SaveButton_Clicked" /> <Button Grid.Column="1" Text="Delete" Clicked="DeleteButton_Clicked" /> </Grid> </VerticalStackLayout> </ContentPage>
CTRL+S を押すか、メニューの [ファイル] > [NotePage.xaml の保存] を選択して、ファイルを保存します。
以下でページに配置された XAML コントロールの主要なパーツを理解しましょう。
<VerticalStackLayout>
は、子コントロールを垂直方向に順番に配置します。<Editor>
は複数行のテキスト エディタ コントロールであり、VerticalStackLayout 内の最初のコントロールです。<Grid>
はレイアウト コントロールであり、VerticalStackLayout 内の 2 番目のコントロールです。このコントロールは、セルを作成する列と行を定義します。 子コントロールは、それらのセル内に配置されます。
既定では、Grid コントロールには 1 つの行と列が含まれており、1 つのセルが作成されます。 列は幅で定義され、幅の
*
値は列に可能な限り多くのスペースを埋めるように指示します。 前のスニペットは 2 つの列を定義しており、どちらも可能な限り多くのスペースを使用し、割り当てられたスペース内で列を均等に分散します (ColumnDefinitions="*,*"
)。 列のサイズが,
文字で区切られます。Grid によって定義された列と行には、0 から始まるインデックスが付けられます。 そのため、最初の列はインデックス 0、2 番目の列はインデックス 1 になります。
2 つの
<Button>
コントロールは<Grid>
内にあり、列が割り当てられます。 子コントロールで列の割り当てが定義されていない場合は、最初の列に自動的に割り当てられます。 このマークアップでは、最初のボタンは [保存] ボタンであり、最初の列 0 に自動的に割り当てられます。 2 番目のボタンは [削除] ボタンで、2 列目の列 1 に割り当てられます。2 つのボタンで
Clicked
イベントが処理されることに注目してください。 これらのハンドラーのコードは、次のセクションで追加します。
メモを読み込んで保存する
NotePage.xaml.cs 分離コード ファイルを開きます。 NotePage.xaml ファイルの分離コードは、次の 3 つの方法で開くことができます。
- NotePage.xaml が開いており、編集中のアクティブなドキュメントである場合は、F7 を押します。
- NotePage.xaml が開いており、編集中のアクティブなドキュメントである場合は、テキスト エディタで右クリックし、[コードの表示] を選択します。
- ソリューション エクスプローラーを使用して NotePage.xaml エントリを展開し、NotePage.xaml.cs ファイルを表示します。 ファイルをダブルクリックして開きます。
新しい XAML ファイルを追加すると、分離コードにはコンストラクターに InitializeComponent
メソッドの呼び出しという 1 行が含まれます。
namespace Notes;
public partial class NotePage : ContentPage
{
public NotePage()
{
InitializeComponent();
}
}
InitializeComponent
メソッドは、XAML マークアップを読み取り、マークアップによって定義されたすべてのオブジェクトを初期化します。 オブジェクトは親子関係で接続され、コードで定義されているイベント ハンドラーは XAML で設定されたイベントにアタッチされます。
分離コード ファイルの理解を深めたところで、NotePage.xaml.cs 分離コード ファイルにコードを追加して、メモの読み込みと保存を処理します。
メモが作成されると、デバイスにテキスト ファイルとして保存されます。 ファイルの名前は
_fileName
変数によって表されます。NotePage
クラスに次のstring
変数宣言を追加します。public partial class NotePage : ContentPage { string _fileName = Path.Combine(FileSystem.AppDataDirectory, "notes.txt");
上記のコードは、ファイルへのパスを構築し、アプリのローカル データ ディレクトリに格納します。 ファイル名は notes.txt です。
クラスのコンストラクターで、
InitializeComponent
メソッドが呼び出された後、デバイスからファイルを読み取り、その内容をTextEditor
コントロールのText
プロパティに保存します。public NotePage() { InitializeComponent(); if (File.Exists(_fileName)) TextEditor.Text = File.ReadAllText(_fileName); }
次に、XAML で定義された
Clicked
イベントを処理するコードを追加します。private void SaveButton_Clicked(object sender, EventArgs e) { // Save the file. File.WriteAllText(_fileName, TextEditor.Text); } private void DeleteButton_Clicked(object sender, EventArgs e) { // Delete the file. if (File.Exists(_fileName)) File.Delete(_fileName); TextEditor.Text = string.Empty; }
SaveButton_Clicked
メソッドは、Editor コントロール内のテキストを_fileName
変数で表されるファイルに書き込みます。DeleteButton_Clicked
メソッドは、まず_fileName
変数で表されるファイルかどうかを確認し、存在する場合はそれを削除します。 次に、Editor コントロールのテキストがクリアされます。CTRL+S を押すか、メニューから [ファイル] > [NotePage.xaml.cs の保存] を選択して、ファイルを保存します。
分離コード ファイルの最終的なコードは、次のようになります。
namespace Notes;
public partial class NotePage : ContentPage
{
string _fileName = Path.Combine(FileSystem.AppDataDirectory, "notes.txt");
public NotePage()
{
InitializeComponent();
if (File.Exists(_fileName))
TextEditor.Text = File.ReadAllText(_fileName);
}
private void SaveButton_Clicked(object sender, EventArgs e)
{
// Save the file.
File.WriteAllText(_fileName, TextEditor.Text);
}
private void DeleteButton_Clicked(object sender, EventArgs e)
{
// Delete the file.
if (File.Exists(_fileName))
File.Delete(_fileName);
TextEditor.Text = string.Empty;
}
}
メモをテストする
メモ ページが完成したら、ユーザーに表示する方法が必要です。 AppShell.xaml ファイルを開き、最初の ShellContent エントリを MainPage
ではなく NotePage
を指すように変更します。
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
x:Class="Notes.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Notes"
Shell.FlyoutBehavior="Disabled">
<TabBar>
<ShellContent
Title="Notes"
ContentTemplate="{DataTemplate local:NotePage}"
Icon="{OnPlatform 'icon_notes.png', iOS='icon_notes_ios.png', MacCatalyst='icon_notes_ios.png'}" />
<ShellContent
Title="About"
ContentTemplate="{DataTemplate local:AboutPage}"
Icon="{OnPlatform 'icon_about.png', iOS='icon_about_ios.png', MacCatalyst='icon_about_ios.png'}" />
</TabBar>
</Shell>
ファイルを保存し、アプリを実行します。 入力ボックスに入力して、[Save] ボタンを押します。 アプリを閉じて、再び開きます。 入力したメモは、デバイスのストレージから読み込まれる必要があります。
UI にデータをバインドしページ内を移動する
チュートリアルのこの部分では、ビュー、モデル、アプリ内ナビゲーションの概念について説明します。
チュートリアルのこれまでの手順では、プロジェクトに NotePage
と AboutPage
の 2 つのページを追加しました。 これらのページはデータのビューを表します。 NotePage
は "メモ データ" を表示する "ビュー" で、AboutPage
は "アプリ情報データ" を表示する "ビュー" です。どちらのビューでも、そのデータのモデルがハードコーディングされているかビューに埋め込まれており、データ モデルをビューから分離する必要があります。
モデルをビューから分離する利点は何でしょうか。 分離することで、モデルを実装する実際のコードを気にすることなく、モデルの任意の部分を示し、操作するようにビューを設計できます。 これは、データ バインディングを使用して実現されます。データ バインディングについては、このチュートリアルで後ほど説明します。 ここではひとまず、プロジェクトを再構築しましょう。
ビューとモデルを分離する
既存のコードをリファクタリングして、モデルをビューから分離します。 次のいくつかの手順では、ビューとモデルが互いに独立して定義されるようにコードを編成します。
プロジェクトから MainPage.xaml と MainPage.xaml.cs を削除します。これらはもう必要ありません。 [ソリューション エクスプローラー] ウィンドウで、MainPage.xaml のエントリを見つけて、右クリックして [削除] を選択します。
ヒント
MainPage.xaml 項目を削除すると、MainPage.xaml.cs 項目も削除されます。 MainPage.xaml.cs が削除されていない場合は、それを右クリックして [削除] を選択します。
Notes プロジェクトを右クリックし、[追加] > [新しいフォルダー] を選択します。 フォルダーに "Models" という名前を付けます。
Notes プロジェクトを右クリックし、[追加] > [新しいフォルダー] を選択します。 フォルダーに "Views" という名前を付けます。
NotePage.xaml 項目を見つけて、Views フォルダーにドラッグします。 NotePage.xaml.cs も共に移動します。
重要
ファイルを移動するとき、Visual Studio では通常、移動操作に時間がかかる場合があるという警告が表示されます。 ここではこの警告は問題ではありません。この警告が表示された場合は、[OK] を押してください。
Visual Studio では、移動後のファイルの名前空間を調整するかどうかを確認するメッセージが表示される場合もあります。 次の手順で名前空間を変更するため、[いいえ] を選択します。
AboutPage.xaml 項目を見つけて、Views フォルダーにドラッグします。 AboutPage.xaml.cs も共に移動します。
ビューの名前空間を更新する
ビューが Views フォルダーに移動されたので、一致するように名前空間を更新する必要があります。 ページの XAML ファイルと分離コード ファイルの名前空間は Notes
に設定されます。 これを Notes.Views
に更新する必要があります。
[ソリューション エクスプローラー] ウィンドウで、NotePage.xaml と AboutPage.xaml の両方を展開して、分離コード ファイルを表示します。
NotePage.xaml.cs 項目をダブルクリックして、コード エディターを開きます。 名前空間を
Notes.Views
に変更します。namespace Notes.Views;
AboutPage.xaml.cs 項目に対して前のステップを繰り返します。
NotePage.xaml 項目をダブルクリックして、XAML エディターを開きます。 前の名前空間は、
x:Class
属性を通じて参照されています。この属性は、XAML の分離コードのクラス型を定義するものです。 このエントリは単なる名前空間ではなく、型を持つ名前空間です。x:Class
の値をNotes.Views.NotePage
に変更します。x:Class="Notes.Views.NotePage"
AboutPage.xaml 項目に対して前の手順を繰り返しますが、
x:Class
の値をNotes.Views.AboutPage
に設定します。
シェルでの名前空間参照を修正する
AppShell.xaml は、2 つのタブを定義します。一方は NotesPage
用で、もう一方は AboutPage
用です。 これら 2 つのページが新しい名前空間に移動されたので、XAML の型マッピングが無効になりました。 [ソリューション エクスプローラー] ウィンドウで、AppShell.xaml エントリをダブルクリックして、XAML エディターで開きます。 次のスニペットのようになります。
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
x:Class="Notes.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Notes"
Shell.FlyoutBehavior="Disabled">
<TabBar>
<ShellContent
Title="Notes"
ContentTemplate="{DataTemplate local:NotePage}"
Icon="{OnPlatform 'icon_notes.png', iOS='icon_notes_ios.png', MacCatalyst='icon_notes_ios.png'}" />
<ShellContent
Title="About"
ContentTemplate="{DataTemplate local:AboutPage}"
Icon="{OnPlatform 'icon_about.png', iOS='icon_about_ios.png', MacCatalyst='icon_about_ios.png'}" />
</TabBar>
</Shell>
.NET 名前空間は、XML 名前空間宣言を使用して XAML にインポートされます。 前の XAML マークアップでは、ルート要素の xmlns:local="clr-namespace:Notes"
属性は <Shell>
です。 同じアセンブリに .NET 名前空間をインポートするための XML 名前空間を宣言する形式は次のとおりです。
xmlns:{XML namespace name}="clr-namespace:{.NET namespace}"
したがって、前の宣言では、local
の XML 名前空間が Notes
の .NET 名前空間にマップされます。 プロジェクトのルート名前空間に local
の名前をマップするのが一般的です。
local
XML 名前空間を削除し、新しい名前空間を追加します。 この新しい XML 名前空間は Notes.Views
.NET 名前空間にマップされるので、views
という名前を付けます。 宣言は属性 xmlns:views="clr-namespace:Notes.Views"
のようになります。
local
XML 名前空間は ShellContent.ContentTemplate
プロパティで使用されていたため、views
に変更します。 現時点で、XAML は次のスニペットのようになります。
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
x:Class="Notes.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Notes.Views"
Shell.FlyoutBehavior="Disabled">
<TabBar>
<ShellContent
Title="Notes"
ContentTemplate="{DataTemplate views:NotePage}"
Icon="{OnPlatform 'icon_notes.png', iOS='icon_notes_ios.png', MacCatalyst='icon_notes_ios.png'}" />
<ShellContent
Title="About"
ContentTemplate="{DataTemplate views:AboutPage}"
Icon="{OnPlatform 'icon_about.png', iOS='icon_about_ios.png', MacCatalyst='icon_about_ios.png'}" />
</TabBar>
</Shell>
これで、コンパイラ エラーなしでアプリを実行できるようになり、すべてが以前と同様に機能します。
モデルを定義する
現在、モデルはメモとビューに埋め込まれているデータです。 そのデータを表す新しいクラスを作成します。 まず、ノート ページのデータを表すモデルを次に示します。
[ソリューション エクスプローラー] ウィンドウで、Models フォルダーを右クリックし、[追加] > [クラス...] を選びます。
クラスに Note.cs という名前を付け、[追加] を押します。
Note.cs を開き、コードを次のスニペットに置き換えます。
namespace Notes.Models; internal class Note { public string Filename { get; set; } public string Text { get; set; } public DateTime Date { get; set; } }
ファイルを保存します。
次に、About ページのモデルを作成します。
[ソリューション エクスプローラー] ウィンドウで、Models フォルダーを右クリックし、[追加] > [クラス...] を選びます。
クラスに About.cs という名前を付け、[追加] を押します。
About.cs を開き、コードを次のスニペットに置き換えます。
namespace Notes.Models; internal class About { public string Title => AppInfo.Name; public string Version => AppInfo.VersionString; public string MoreInfoUrl => "https://aka.ms/maui"; public string Message => "This app is written in XAML and C# with .NET MAUI."; }
ファイルを保存します。
About ページを更新する
About ページは更新が最も簡単なページであり、アプリを実行して、モデルからデータを読み込む方法を確認できます。
[ソリューション エクスプローラー] ウィンドウで、Views\AboutPage.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" xmlns:models="clr-namespace:Notes.Models" x:Class="Notes.Views.AboutPage"> <ContentPage.BindingContext> <models:About /> </ContentPage.BindingContext> <VerticalStackLayout Spacing="10" Margin="10"> <HorizontalStackLayout Spacing="10"> <Image Source="dotnet_bot.png" SemanticProperties.Description="The dot net bot waving hello!" HeightRequest="64" /> <Label FontSize="22" FontAttributes="Bold" Text="{Binding Title}" VerticalOptions="End" /> <Label FontSize="22" Text="{Binding Version}" VerticalOptions="End" /> </HorizontalStackLayout> <Label Text="{Binding Message}" /> <Button Text="Learn more..." Clicked="LearnMore_Clicked" /> </VerticalStackLayout> </ContentPage>
前のスニペットで強調表示されている変更された行を見てみましょう。
xmlns:models="clr-namespace:Notes.Models"
この行は、
Notes.Models
.NET 名前空間をmodels
XML 名前空間にマップします。models:About
の XML 名前空間とオブジェクトを使用して、Note.Models.About
クラスのインスタンスに ContentPage のBindingContext
プロパティが設定されます。 これは、XML 属性の代わりにプロパティ要素構文を使用して設定されました。重要
これまで、プロパティは XML 属性を使用して設定されていました。 これは、
Label.FontSize
プロパティなどの単純な値に適しています。 ただし、プロパティ値がより複雑な場合は、プロパティ要素の構文を使用してオブジェクトを作成する必要があります。FontSize
プロパティ セットを使用してラベルを作成する例を次に示します。<Label FontSize="22" />
プロパティ要素の構文を使用して、同じ
FontSize
プロパティを設定できます。<Label> <Label.FontSize> 22 </Label.FontSize> </Label>
3 つの
<Label>
コントロールのText
プロパティ値がハードコードされた文字列から{Binding PATH}
のバインド構文に変更されました。{Binding}
構文は実行時に処理され、バインディングから返される値を動的にすることができます。{Binding PATH}
のPATH
の部分は、バインドするプロパティ パスです。 このプロパティは、現在のコントロールのBindingContext
から取得されます。<Label>
コントロールでは、BindingContext
設定が解除されます。 コンテキストは、コントロールによって設定解除されると親から継承されます。この場合、親オブジェクト設定コンテキストは ContentPage のルート オブジェクトです。BindingContext
内のオブジェクトはAbout
モデルのインスタンスです。 いずれかのラベルのバインド パスは、Label.Text
プロパティをAbout.Title
プロパティにバインドします。
About ページに対する最後の変更は、[Web ページを開く] ボタン クリックを更新することにあります。 URL は分離コードでハードコーディングされましたが、URL は BindingContext
プロパティ内のモデルから取得する必要があります。
[ソリューション エクスプローラー] ウィンドウで、Views\AboutPage.xaml.cs ファイルを開きます。
LearnMore_Clicked
メソッドを次のコードで置き換えます。private async void LearnMore_Clicked(object sender, EventArgs e) { if (BindingContext is Models.About about) { // Navigate to the specified URL in the system browser. await Launcher.Default.OpenAsync(about.MoreInfoUrl); } }
強調表示された行を見ると、コードは BindingContext
が Models.About
型であるかどうかを確認し、 about
変数に割り当てます。 if
ステートメント内の次の行では、ブラウザーを開いて、about.MoreInfoUrl
プロパティから提供される URL に移動します。
アプリを実行すると、以前とまったく同じように実行されていることがわかります。 About モデルの値を変更してみて、ブラウザーで開かれる UI と URL も変更されることを確認します。
メモ ページを更新する
前のセクションでは、about ページ ビューを about モデルにバインドしました。ここでは、同様に note ビューを note モデルにバインドします。 ただし、この場合、モデルは XAML では作成されませんが、次の手順で分離コードで提供されます。
[ソリューション エクスプローラー] ウィンドウで、Views\NotePage.xaml ファイルを開きます。
<Editor>
コントロールを変更し、Text
プロパティを追加します。 プロパティをText
プロパティにバインドします (<Editor ... Text="{Binding Text}"
)。<?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="Notes.Views.NotePage" Title="Note"> <VerticalStackLayout Spacing="10" Margin="5"> <Editor x:Name="TextEditor" Placeholder="Enter your note" Text="{Binding Text}" HeightRequest="100" /> <Grid ColumnDefinitions="*,*" ColumnSpacing="4"> <Button Text="Save" Clicked="SaveButton_Clicked" /> <Button Grid.Column="1" Text="Delete" Clicked="DeleteButton_Clicked" /> </Grid> </VerticalStackLayout> </ContentPage>
分離コードの変更は、XAML よりも複雑です。 現在のコードでは、コンストラクターでファイル コンテンツを読み込み、その後 TextEditor.Text
プロパティに直接設定しています。 現在のコードは次のようになります。
public NotePage()
{
InitializeComponent();
if (File.Exists(_fileName))
TextEditor.Text = File.ReadAllText(_fileName);
}
コンストラクターにメモを読み込む代わりに、新しい LoadNote
メソッドを作成します。 このメソッドでは次のことを行います。
- ファイル名パラメーターを受け取ります。
- 新しいメモ モデルを作成し、ファイル名を設定します。
- ファイルが存在する場合は、その内容をモデルに読み込みます。
- ファイルが存在する場合は、ファイルが作成された日付でモデルを更新します。
- ページの
BindingContext
をモデルに設定します。
[ソリューション エクスプローラー] ウィンドウで、Views\NotePage.xaml.cs ファイルを開きます。
クラスに次のメソッドを追加します。
private void LoadNote(string fileName) { Models.Note noteModel = new Models.Note(); noteModel.Filename = fileName; if (File.Exists(fileName)) { noteModel.Date = File.GetCreationTime(fileName); noteModel.Text = File.ReadAllText(fileName); } BindingContext = noteModel; }
クラス コンストラクターを更新して、
LoadNote
を呼び出します。 メモのファイル名は、アプリのローカル データ ディレクトリに作成されるランダムに生成される名前である必要があります。public NotePage() { InitializeComponent(); string appDataPath = FileSystem.AppDataDirectory; string randomFileName = $"{Path.GetRandomFileName()}.notes.txt"; LoadNote(Path.Combine(appDataPath, randomFileName)); }
すべてのノートを一覧表示するビューとモデルの追加
このチュートリアルでは、アプリの最終段階である、以前に作成したすべてのノートを表示するビューを追加します。
複数のメモとナビゲーション
現在、 ノート ビューには 1 つのノートが表示されています。 複数のメモを表示するには、新しいビューとモデル AllNotes を作成します。
- [ソリューション エクスプローラー] ウィンドウで、Views フォルダーを右クリックし、[追加] > [新しい項目...] を選択します。
- [新しい項目の追加] ダイアログで、ウィンドウの左側にあるテンプレート リストで [.NET MAUI] を選択します。 次に、[.NET MAUI ContentPage (XAML)] テンプレートを選択します。 ファイルに AllNotesPage.xaml という名前を付け、[追加] を選択します。
- [ソリューション エクスプローラー] ウィンドウで、Models フォルダーを右クリックし、[追加] > [クラス...] を選択します。
- クラスに AllNotes.cs という名前を付け、[追加] を選択します。
AllNotes モデルをコーディングする
この新しいモデルは、複数のメモを表示するために必要なデータを表します。 このデータは、メモのコレクションを表すプロパティになります。 このコレクションは、特殊なコレクションである ObservableCollection
になります。 ListView など、複数の項目を一覧表示するコントロールが ObservableCollection
にバインドされている場合、2 つのコントロールが連携して、項目の一覧がコレクションと同期された状態を自動的に維持します。 リストに項目が追加されると、コレクションが更新されます。 コレクションに項目が追加されると、コントロールに新しい項目が自動的に更新されます。
[ソリューション エクスプローラー] ウィンドウで、Models\AllNotes.cs ファイルを開きます。
コードを次のスニペットに置き換えます。
using System.Collections.ObjectModel; namespace Notes.Models; internal class AllNotes { public ObservableCollection<Note> Notes { get; set; } = new ObservableCollection<Note>(); public AllNotes() => LoadNotes(); public void LoadNotes() { Notes.Clear(); // Get the folder where the notes are stored. string appDataPath = FileSystem.AppDataDirectory; // Use Linq extensions to load the *.notes.txt files. IEnumerable<Note> notes = Directory // Select the file names from the directory .EnumerateFiles(appDataPath, "*.notes.txt") // Each file name is used to create a new Note .Select(filename => new Note() { Filename = filename, Text = File.ReadAllText(filename), Date = File.GetLastWriteTime(filename) }) // With the final collection of notes, order them by date .OrderBy(note => note.Date); // Add each note into the ObservableCollection foreach (Note note in notes) Notes.Add(note); } }
前のコードでは、Notes
という名前のコレクションを宣言し、LoadNotes
メソッドを使用してデバイスからメモを読み込みます。 このメソッドでは、LINQ 拡張機能を使用して、データを Notes
コレクションに読み込み、変換し、並べ替えます。
AllNotes ページを設計する
次に、AllNotes モデルをサポートするようにビューを設計する必要があります。
[ソリューション エクスプローラー] ウィンドウで、Views\AllNotesPage.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="Notes.Views.AllNotesPage" Title="Your Notes"> <!-- Add an item to the toolbar --> <ContentPage.ToolbarItems> <ToolbarItem Text="Add" Clicked="Add_Clicked" IconImageSource="{FontImage Glyph='+', Color=Black, Size=22}" /> </ContentPage.ToolbarItems> <!-- Display notes in a list --> <CollectionView x:Name="notesCollection" ItemsSource="{Binding Notes}" Margin="20" SelectionMode="Single" SelectionChanged="notesCollection_SelectionChanged"> <!-- Designate how the collection of items are laid out --> <CollectionView.ItemsLayout> <LinearItemsLayout Orientation="Vertical" ItemSpacing="10" /> </CollectionView.ItemsLayout> <!-- Define the appearance of each item in the list --> <CollectionView.ItemTemplate> <DataTemplate> <StackLayout> <Label Text="{Binding Text}" FontSize="22"/> <Label Text="{Binding Date}" FontSize="14" TextColor="Silver"/> </StackLayout> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> </ContentPage>
前の XAML では、いくつかの新しい概念が導入されています。
ContentPage.ToolbarItems
プロパティにはToolbarItem
が含まれています。 ここで定義されているボタンは、通常、アプリの上部にページ タイトルに沿って表示されます。 ただし、プラットフォームによっては、異なる位置にある場合があります。 これらのボタンのいずれかが押されると、通常のボタンと同様にClicked
イベントが発生します。ToolbarItem.IconImageSource
プロパティは、ボタンに表示するアイコンを設定します。 アイコンには、プロジェクトによって定義されている任意の画像リソースを指定できます。この例ではFontImage
が使用されます。FontImage
は、フォントの 1 つのグリフを画像として使用できます。CollectionView コントロールには項目のコレクションが表示され、この場合はモデルの
Notes
プロパティにバインドされます。 コレクション ビューで各項目を表示する方法は、CollectionView.ItemsLayout
プロパティとCollectionView.ItemTemplate
プロパティで設定されます。コレクション内の各項目に対して、
CollectionView.ItemTemplate
は宣言された XAML を生成します。 その XAML のBindingContext
は、コレクション項目自体、この場合は個々のメモになります。 メモのテンプレートは、メモのText
プロパティとDate
プロパティにバインドされている 2 つのラベルを使用します。CollectionView は、コレクション ビュー内の項目が選択されたときに発生する、
SelectionChanged
イベントを処理します。
メモを読み込んでイベントを処理するには、ビューの分離コードを記述する必要があります。
[ソリューション エクスプローラー] ウィンドウで、Views/AllNotesPage.xaml.cs ファイルを開きます。
コードを次のスニペットに置き換えます。
namespace Notes.Views; public partial class AllNotesPage : ContentPage { public AllNotesPage() { InitializeComponent(); BindingContext = new Models.AllNotes(); } protected override void OnAppearing() { ((Models.AllNotes)BindingContext).LoadNotes(); } private async void Add_Clicked(object sender, EventArgs e) { await Shell.Current.GoToAsync(nameof(NotePage)); } private async void notesCollection_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (e.CurrentSelection.Count != 0) { // Get the note model var note = (Models.Note)e.CurrentSelection[0]; // Should navigate to "NotePage?ItemId=path\on\device\XYZ.notes.txt" await Shell.Current.GoToAsync($"{nameof(NotePage)}?{nameof(NotePage.ItemId)}={note.Filename}"); // Unselect the UI notesCollection.SelectedItem = null; } } }
このコードは、コンストラクターを使用して、ページの BindingContext
をモデルに設定します。
OnAppearing
メソッドは、基本クラスからオーバーライドされます。 このメソッドは、ページの移動時など、ページが表示されるたびに自動的に呼び出されます。 ここでのコードは、モデルにメモを読み込むよう指示します。 AllNotes ビュー内の CollectionView は AllNotes モデルの Notes
プロパティにバインドされており、これが ObservableCollection
であるため、ノートが読み込まれるたびに CollectionView が自動的に更新されます。
Add_Clicked
ハンドラーには、ナビゲーションという別の新しい概念が導入されています。 アプリは .NET MAUI シェルを使用しているため、Shell.Current.GoToAsync
メソッドを呼び出すことでページを移動できます。 ハンドラーが async
キーワードを使用して宣言されていることに注目してください。これにより、移動時に await
キーワードを使用できるようになります。 このハンドラーは NotePage
に移動します。
前のスニペットの最後のコード部分は、notesCollection_SelectionChanged
ハンドラーです。 このメソッドは、現在選択されている項目である Note モデルを取得し、その情報を使用して NotePage
に移動します。 GoToAsync は、ナビゲーションに URI 文字列を使用します。 この場合、クエリ文字列パラメータを使用して移動先ページにプロパティを設定する文字列が構築されます。 URI を表す補間された文字列は、次の文字列のようになります。
NotePage?ItemId=path\on\device\XYZ.notes.txt
ItemId=
パラメータは、メモが格納されているデバイス上のファイル名に設定されます。
Visual Studio は、NotePage.ItemId
プロパティが存在しないことを示している可能性があります。このプロパティは存在しません。 次のステップでは、作成する ItemId
パラメータに基づいてモデルを読み込むように、Note ビューを変更します。
クエリ文字列パラメーター
Note ビュー は、クエリ文字列パラメータ ItemId
をサポートする必要があります。 今すぐ作成する:
[ソリューション エクスプローラー] ウィンドウで、Views/NotePage.xaml.cs ファイルを開きます。
QueryProperty
属性をclass
キーワードに追加し、クエリ文字列プロパティの名前と、それがマップされるクラス プロパティ、それぞれItemId
とItemId
を指定します。[QueryProperty(nameof(ItemId), nameof(ItemId))] public partial class NotePage : ContentPage
ItemId
という名前の新しいstring
プロパティを追加します。 このプロパティはメソッドをLoadNote
呼び出し、プロパティの値を渡します。この値がメモのファイル名になります。public string ItemId { set { LoadNote(value); } }
SaveButton_Clicked
とDeleteButton_Clicked
のハンドラーを次のコードで置き換えます。private async void SaveButton_Clicked(object sender, EventArgs e) { if (BindingContext is Models.Note note) File.WriteAllText(note.Filename, TextEditor.Text); await Shell.Current.GoToAsync(".."); } private async void DeleteButton_Clicked(object sender, EventArgs e) { if (BindingContext is Models.Note note) { // Delete the file. if (File.Exists(note.Filename)) File.Delete(note.Filename); } await Shell.Current.GoToAsync(".."); }
ボタンは
async
になりました。 これらが押されると、ページは..
の URI を使用して前のページに戻ります。_fileName
変数はクラスで使用されなくなったので、コードの先頭から削除します。
アプリのビジュアル ツリーを変更する
AppShell
はまだ 1 つのメモ ページを読み込んでいますが、代わりに [AllPages ビュー] を読み込む必要があります。 AppShell.xaml ファイルを開き、最初の ShellContent エントリを NotePage
ではなく AllNotesPage
を指すように変更します。
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
x:Class="Notes.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Notes.Views"
Shell.FlyoutBehavior="Disabled">
<TabBar>
<ShellContent
Title="Notes"
ContentTemplate="{DataTemplate views:AllNotesPage}"
Icon="{OnPlatform 'icon_notes.png', iOS='icon_notes_ios.png', MacCatalyst='icon_notes_ios.png'}" />
<ShellContent
Title="About"
ContentTemplate="{DataTemplate views:AboutPage}"
Icon="{OnPlatform 'icon_about.png', iOS='icon_about_ios.png', MacCatalyst='icon_about_ios.png'}" />
</TabBar>
</Shell>
ここでアプリを実行すると、[追加] ボタンを押すとアプリがクラッシュし、NotesPage
に移動できないというメッセージが表示されます。 別のページから移動できるすべてのページは、ナビゲーション システムに登録する必要があります。 AllNotesPage
と AboutPage
ページは、TabBar で宣言されることにより、ナビゲーション システムに自動的に登録されます。
NotesPage
をナビゲーション システムに登録します。
[ソリューション エクスプローラー] ウィンドウで、AppShell.xaml.cs ファイルを開きます。
ナビゲーション ルートを登録する行をコンストラクターに追加します。
namespace Notes; public partial class AppShell : Shell { public AppShell() { InitializeComponent(); Routing.RegisterRoute(nameof(Views.NotePage), typeof(Views.NotePage)); } }
Routing.RegisterRoute
メソッドは、次の 2 つのパラメーターを受け取ります。
- 最初のパラメーターは、登録する URI の文字列名です。この例では、解決された名前は
"NotePage"
です。 - 2 番目のパラメーターは、
"NotePage"
に移動したときに読み込むページの種類です。
これで、アプリを実行できます。 新しいメモの追加、メモ間の前後の移動、メモの削除を試してみてください。
このチュートリアルのコードを探します。 完成したプロジェクトのコピーをダウンロードしてコードを比較する場合は、このプロジェクトをダウンロードします。
おめでとうございます。
「.NET MAUI アプリを作成する」チュートリアルを完了しました。
次のステップ
チュートリアル シリーズの次のパートでは、プロジェクトに Model-View-ViewModel (MVVM) パターンを実装する方法について説明します。
次のリンクは、このチュートリアルで学習した概念の一部に関する詳しい情報を記載しています。
このセクションに問題がある場合 このセクションを改善できるよう、フィードバックをお送りください。
.NET MAUI