このチュートリアルは、クロスプラットフォーム コードのみを使用する .NET マルチプラットフォーム アプリ UI (.NET MAUI) アプリを作成する方法を示すために設計されています。 つまり、記述するコードは、Windows、Android、iOS、または macOS に固有のコードにはなりません。 作成するアプリはノート作成アプリであり、ユーザーは複数のノートを作成、保存、読み込むことができます。
このチュートリアルでは、以下の内容を学習します。
- .NET MAUI Shell アプリを作成します。
- 選択したプラットフォームでアプリを実行します。
- 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ファイルが定義する クラスを指します。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 のコードビハインドが含まれています。
"このアプリについて" ページを追加する
最初に行うカスタマイズは、別のページをプロジェクトに追加することです。 このページは、作成者、バージョン、詳細情報へのリンクなど、このアプリに関する情報を表す "このアプリについて" ページです。
Visual Studio の ソリューション エクスプローラー ウィンドウで、 Notes プロジェクト >Add>New Item を右クリックします。
[新しい項目の追加] ダイアログのウィンドウの左側にあるテンプレート リストで [.NET MAUI] を選択します。 次に、[.NET MAUI ContentPage (XAML)] テンプレートを選択します。 ファイルに AboutPage.xaml という名前を付け、[追加] を選択します。
AboutPage.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 キーを押すか、[ファイル] メニューの [>を選択して、ファイルを保存します。
以下でページに配置された XAML コントロールの主要なパーツを理解しましょう。
<ContentPage>は、AboutPageクラスのルート オブジェクトです。<VerticalStackLayout>は、ContentPage の唯一の子オブジェクトです。 ContentPage は、子オブジェクトを 1 つだけ持つことができます。 VerticalStackLayout 型は複数の子を持つことができます。 このレイアウト コントロールは、子要素を垂直方向に順番に配置します。<HorizontalStackLayout>は、子が横方向に配置される点を除き、<VerticalStackLayout>と同じように機能します。<Image>はイメージを表示します。この場合は、すべての .NET MAUI プロジェクトに付属するdotnet_bot.pngイメージを使用しています。Important
プロジェクトに追加されたファイルは実際には
dotnet_bot.svgです。 .NET MAUI は、ターゲット デバイスに基づいて Scalable Vector Graphics (SVG) ファイルを Portable Network Graphic (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キーワードを使用できます。+ キーを押すか、メニューの [ファイルを選択して、>を保存します。
これで AboutPage の XAML とコード ビハインドが完成したため、アプリに表示する必要があります。
画像リソースを追加する
一部のコントロールでは、ユーザーがアプリを操作する方法を強化する画像を使用できます。 このセクションでは、アプリで使用する 2 つの画像と、iOS で使用する 2 つの代替画像をダウンロードします。
次の画像をダウンロードします。
- アイコン: 概要。 この画像は、前に作成した "このアプリについて" ページのアイコンとして使用されます。
- アイコン: メモ。 この画像は、このチュートリアルの次の部分で作成するメモ ページのアイコンとして使用します。
- アイコン: About (iOS)
- アイコン: Notes (iOS)
画像をダウンロードしたら、ファイル エクスプローラーを使用してプロジェクトの Resources\Images フォルダーに移動できます。 このフォルダー内のすべてのファイルは、MauiImage リソースとして自動的にプロジェクトに含まれます。 Visual Studio を使用して、プロジェクトにイメージを追加することもできます。 画像を手動で移動する場合は、次の手順をスキップします。
Important
iOS 固有のイメージのダウンロードはスキップしないでください。このチュートリアルを完了する必要があります。
Visual Studio を使用して画像を移動する
Visual Studio の ソリューション エクスプローラー ウィンドウで、Resources フォルダーを展開すると、Images フォルダーが表示されます。
Tip
ファイル エクスプローラーを使用すると、images フォルダーの上にある [ソリューション エクスプローラー] ペインに直接画像をドラッグ アンド ドロップできます。 これにより、ファイルがフォルダーに自動的に移動され、プロジェクトに含まれます。 ファイルをドラッグ アンド ドロップする場合は、この手順の残りの部分を省略します。
イメージを右クリックし、追加>Existing Item を選択します。
ダウンロードしたイメージを含むフォルダーに移動します。
ファイルの種類フィルターを [画像ファイル] に変更します。
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"> <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 キーを押すか、メニューの [ファイル] を選択してファイルを保存します>AppShell.xaml を保存します。
XAML の重要な部分を次に示します。
-
<Shell>は XAML マークアップのルート オブジェクトです。 -
<TabBar>は Shell のコンテンツです。 -
<ShellContent>内の 2 つの<TabBar>オブジェクト。 テンプレート コードを置き換える前に、<ShellContent>ページを指す1 つのMainPageオブジェクトが存在していました。
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 の ソリューション エクスプローラー ウィンドウで、 Notes プロジェクト >Add>New Item を右クリックします。
[新しい項目の追加] ダイアログのウィンドウの左側にあるテンプレート リストで [.NET MAUI] を選択します。 次に、[.NET MAUI ContentPage (XAML)] テンプレートを選択します。 ファイルに NotePage.xaml という名前を付け、[追加] を選択します。
NotePage.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変数によって表されます。stringクラスに 次のNotePage変数宣言を追加します。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 キーを押すか、[ファイル] メニューの [を選択して、>を保存します。
コードビハインド ファイルの最終的なコードは、次のようになります。
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 エントリを NotePage ではなく MainPage を指すように変更します。
<?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">
<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>
ファイルを保存し、アプリを実行します。 入力ボックスに入力して、[保存] ボタンを押します。 アプリを閉じて、再度開きます。 入力したメモは、デバイスのストレージから読み込まれるはずです。
UI にデータをバインドしページ内を移動する
チュートリアルのこの部分では、ビュー、モデル、アプリ内ナビゲーションの概念について説明します。
チュートリアルのこれまでの手順では、プロジェクトに NotePage と AboutPage の 2 つのページを追加しました。 これらのページはデータのビューを表します。
NotePage は "メモ データ" を表示する "ビュー" で、AboutPage は "アプリ情報データ" を表示する "ビュー" です。どちらのビューでも、そのデータのモデルがハードコーディングされているかビューに埋め込まれており、データ モデルをビューから分離する必要があります。
モデルをビューから分離する利点は何ですか? 分離することで、モデルを実装する実際のコードを気にすることなく、モデルの任意の部分を示し、操作するようにビューを設計できます。 これは、データ バインディングを使用して実現されます。データ バインディングについては、このチュートリアルで後述します。 ここではひとまず、プロジェクトを再構築します。
ビューとモデルを分離する
既存のコードをリファクタリングして、モデルをビューから分離します。 次のいくつかの手順では、ビューとモデルが互いに独立して定義されるようにコードを編成します。
プロジェクトから MainPage.xaml と MainPage.xaml.cs を削除します。これらはもう必要ありません。 [ソリューション エクスプローラー] ペインで、[MainPage.xaml] のエントリを見つけて、右クリックして [削除] を選択します。
Tip
MainPage.xaml 項目を削除すると、MainPage.xaml.cs 項目も削除されます。 MainPage.xaml.cs が削除されていない場合は、それを右クリックして [削除] を選択します。
Notes プロジェクトを右クリックし、[追加]>、[新規フォルダ] の順に選択します。 フォルダーに「 Modelsで行うことができます。
Notes プロジェクトを右クリックし、[追加]>、[新規フォルダ] の順に選択します。 フォルダーに「 Viewsで行うことができます。
NotePage.xaml 項目を見つけて、Views フォルダーにドラッグします。 NotePage.xaml.cs も一緒に移動します。
Important
ファイルを移動すると、通常、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">
<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">
<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 フォルダーを右クリックし、追加>Class を選択します。
クラスに 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; } }ファイルを保存します。
次に、[このアプリについて] ページのモデルを作成します。
ソリューション エクスプローラー ウィンドウで、Models フォルダーを右クリックし、追加>Class を選択します。
クラスに 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."; }ファイルを保存します。
[このアプリについて] ページを更新する
[このアプリについて] ページは更新が最も簡単なページであり、アプリを実行して、モデルからデータを読み込む方法を確認できます。
[ソリューション エクスプローラー] ペインで、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" x:DataType="models:About"> <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 名前空間をmodelsXML 名前空間にマップします。x:DataType="models:About"この行は、ランタイム パフォーマンスを向上させるためにすべてのバインド式をコンパイルするように XAML コンパイラに指示し、
Notes.Models.About型に対してバインド式を解決します。XML 名前空間と
BindingContextのオブジェクトを使用して、ContentPage のNote.Models.Aboutプロパティを、models:Aboutクラスのインスタンスに設定します。 これは、XML 属性の代わりに、プロパティ要素構文を使用して設定されています。Important
これまで、プロパティは XML 属性を使用して設定されていました。 これは、
Label.FontSizeプロパティなどの単純な値に適しています。 ただし、プロパティ値がより複雑な場合は、プロパティ要素構文を使用してオブジェクトを作成する必要があります。FontSizeプロパティ セットを使用してラベルを作成する例を次に示します。<Label FontSize="22" />FontSizeを使用すると、同じ プロパティを設定できます。<Label> <Label.FontSize> 22 </Label.FontSize> </Label>3 つの
<Label>コントロールのTextプロパティ値は、ハードコードされた文字からバインド構文{Binding PATH}に変更されました。{Binding}構文は実行時に処理され、バインディングから返される値を動的にすることができます。PATHの{Binding PATH}部分は、バインドするプロパティ パスです。 このプロパティは、現在のコントロールのBindingContextから取得されます。<Label>コントロールでは、BindingContextは設定解除されます。 コンテキストは、コントロールによって設定解除されると親から継承されます。この場合、親オブジェクト設定コンテキストは ContentPage のルート オブジェクトです。BindingContext内のオブジェクトは、Aboutモデルのインスタンスです。 いずれかのラベルのバインド パスは、Label.TextプロパティをAbout.Titleプロパティにバインドします。
[このアプリについて] ページに対する最後の変更は、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 ファイルを開きます。
コンテンツを、次のスニペットに置き換えます。
<?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.NotePage" Title="Note" x:DataType="models: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>
前のスニペットで強調表示されている変更された行を見てみましょう。
xmlns:models="clr-namespace:Notes.Models"この行は、
Notes.Models.NET 名前空間をmodelsXML 名前空間にマップします。x:DataType="models:Note"この行は、ランタイム パフォーマンスを向上させるためにすべてのバインド式をコンパイルするように XAML コンパイラに指示し、
Notes.Models.Note型に対してバインド式を解決します。Text="{Binding Text}"この行は、
<Editor>プロパティを追加し、プロパティをTextプロパティにバインドすることによって、Textコントロールを変更します。
コードビハインドの変更は、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 フォルダーを右クリックし、追加>Class を選択します。
- クラスに 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" xmlns:models="clr-namespace:Notes.Models" x:Class="Notes.Views.AllNotesPage" Title="Your Notes" x:DataType="models:AllNotes"> <!-- Add an item to the toolbar --> <ContentPage.ToolbarItems> <ToolbarItem Text="Add" Clicked="Add_Clicked" IconImageSource="{FontImageSource 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 x:DataType="models:Note"> <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プロパティは、ボタンに表示するアイコンを設定します。 アイコンには、プロジェクトによって定義されている任意の画像リソースを指定できます。この例ではFontImageSourceが使用されます。FontImageSourceは、フォントの 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 メソッドは、基本クラスからオーバーライドされます。 このメソッドは、ページの移動時など、ページが表示されるたびに自動的に呼び出されます。 ここでのコードは、モデルにメモを読み込むよう指示します。
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 プロパティが存在しないことを示している可能性があります。このプロパティは存在しません。 次のステップでは、作成する Note パラメータに基づいてモデルを読み込むように、を変更します。
クエリ文字列パラメーター
Note ビュー は、クエリ文字列パラメーター ItemId をサポートする必要があります。 今すぐ作成する:
[ソリューション エクスプローラー] ペインで、Views/NotePage.xaml.cs ファイルを開きます。
QueryProperty属性をclassキーワードに追加し、クエリ文字列プロパティの名前と、それがマップされるクラス プロパティ、それぞれItemIdとItemIdを指定します。[QueryProperty(nameof(ItemId), nameof(ItemId))] public partial class NotePage : ContentPagestringという名前の新しいItemIdプロパティを追加します。 このプロパティはメソッドを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 エントリを AllNotesPage ではなく 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:views="clr-namespace:Notes.Views">
<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