次の方法で共有


MVVM の概念を使用してアプリをアップグレードする

このチュートリアルは、ノート作成 アプリを作成した .NET MAUI アプリの作成 チュートリアルを続行するように設計されています。 このチュートリアルで学習する内容は次のとおりです。

  • 「model-view-viewmodel (MVVM)」パターンを実装します。
  • ナビゲーション中にデータを渡すために、追加のスタイルのクエリ文字列を使用します。

このチュートリアルで作成したコードがこのチュートリアルの基礎であるため、最初に .NET MAUI アプリ の作成チュートリアルに従うことをお勧めします。 コードを紛失した場合、または新規に開始する場合は、 このプロジェクトをダウンロードします。

MVVM について

.NET MAUI 開発者エクスペリエンスには、通常、XAML でユーザー インターフェイスを作成し、ユーザー インターフェイスで動作する分離コードを追加する必要があります。 複雑なメンテナンスの問題は、アプリが変更され、サイズとスコープが拡大するにつれて発生する可能性があります。 これらの問題には、UI コントロールとビジネス ロジックの緊密な結合が含まれます。これにより、UI を変更するコストが増加し、このようなコードの単体テストが困難になります。

モデル ビュー ビュー モデル (MVVM) パターンは、アプリのビジネス ロジックとプレゼンテーション ロジックをユーザー インターフェイス (UI) からクリーンに分離するのに役立ちます。 アプリ ロジックと UI の間でクリーンな分離を維持することで、多数の開発の問題に対処し、アプリのテスト、保守、進化を容易にします。 また、コード再利用の機会を大幅に向上させ、開発者と UI デザイナーがアプリのそれぞれの部分を開発するときに、より簡単に共同作業を行うことができます。

パターン

MVVM パターンには、モデル、ビュー、ビュー モデルの 3 つの主要なコンポーネントがあります。 それぞれが異なる目的を果たします。 次の図は、3 つのコンポーネント間の関係を示しています。

MVVM でモデル化されたアプリケーションの部分を示す図

各コンポーネントの責任を理解するだけでなく、コンポーネントがどのように相互作用するかを理解することも重要です。 大まかに言うと、ビューはビュー モデルを知っており、ビュー モデルはモデルを知っていますが、モデルはビュー モデルを認識しておらず、ビュー モデルはビューを認識していません。 そのため、ビュー モデルは、ビューをモデルから分離し、ビューとは別にモデルを進化させることができます。

MVVM を効果的に使用するための鍵は、アプリ コードを正しいクラスに組み込む方法と、クラスがどのように相互作用するかを理解することです。

表示

ビューは、ユーザーが画面に表示する内容の構造、レイアウト、および外観を定義する役割を担います。 理想的には、各ビューは XAML で定義され、ビジネス ロジックを含まないコード ビハインドは限られています。 ただし、場合によっては、コードビハインドに、アニメーションなどの XAML で表現するのが難しい視覚的な動作を実装する UI ロジックが含まれている場合があります。

ビューモデル

ビュー モデルは、ビューがデータ バインドできるプロパティとコマンドを実装し、変更通知イベントを通じて状態の変化をビューに通知します。 ビュー モデルが提供するプロパティとコマンドは、UI によって提供される機能を定義しますが、その機能の表示方法はビューによって決まります。

ビュー モデルは、必要なモデル クラスとビューの相互作用を調整する役割も担います。 通常、ビュー モデルとモデル クラスの間には一対多リレーションシップがあります。

各ビュー モデルは、ビューが簡単に使用できる形式でモデルからのデータを提供します。 これを実現するために、ビュー モデルはデータ変換を実行することがあります。 ビュー モデルにこのデータ変換を配置すると、ビューがバインドできるプロパティが提供されるため、良いアイデアです。 たとえば、ビュー モデルでは、2 つのプロパティの値を組み合わせて、ビューで表示しやすくすることができます。

重要

.NET MAUI は、UI スレッドへのバインドの更新をマーシャリングします。 MVVM を使用すると、.NET MAUI のバインド エンジンによって UI スレッドに更新が適用され、データ バインドビューモデルプロパティを任意のスレッドから更新できます。

モデル

モデル クラスは、アプリのデータをカプセル化する非ビジュアル クラスです。 そのため、このモデルはアプリのドメイン モデルを表すものと考えることができます。通常、データ モデルとビジネス ロジックと検証ロジックが含まれます。

モデルを更新する

このチュートリアルの最初の部分では、model-view-viewmodel (MVVM) パターンを実装します。 開始するには、Visual Studio で Notes.sln ソリューションを開きます。

モデルをクリーンアップする

前のチュートリアルでは、モデルの種類は、モデル (データ) とビュー モデル (データ準備) の両方として機能し、ビューに直接マップされていました。 次の表では、モデルについて説明します。

プログラムファイル 説明
モデル/About.cs About モデル。 アプリのタイトルやバージョンなど、アプリ自体を記述する読み取り専用フィールドが含まれています。
Models/Note.cs Note モデル。 メモを示します。
Models/AllNotes.cs AllNotes モデル。 デバイス上のすべてのノートをコレクションに読み込みます。

アプリ自体について考えると、アプリによって使用されるデータは 1 つだけであり、 Note。 ノートはデバイスから読み込まれ、デバイスに保存され、アプリ UI を介して編集されます。 実際には、 About モデルと AllNotes モデルは必要ありません。 プロジェクトからこれらのモデルを削除します。

  1. Visual Studio の ソリューション エクスプローラー ウィンドウを見つけます。
  2. Models\About.cs ファイルを右クリックし、[削除] を選択します[OK] をクリックしてファイルを削除します。
  3. Models\AllNotes.cs ファイルを右クリックし、[削除] を選択します[OK] をクリックしてファイルを削除します。

残っている唯一のモデル ファイルは Models\Note.cs ファイルです。

モデルを更新する

Note モデルには次のものが含まれます。

  • 一意の識別子。デバイスに格納されているメモのファイル名です。
  • ノートのテキスト。
  • メモが作成または最後に更新された日時を示す日付。

現在、モデルの読み込みと保存はビューを通じて行われ、場合によっては削除した他のモデルの種類によって行われました。 Note型のコードは次のようになります。

namespace Notes.Models;

internal class Note
{
    public string Filename { get; set; }
    public string Text { get; set; }
    public DateTime Date { get; set; }
}

Note モデルは、ノートの読み込み、保存、削除を処理するために拡張されます。

  1. Visual Studio の ソリューション エクスプローラー ウィンドウで、 Models\Note.cs をダブルクリックします。

  2. コード エディターで、次の 2 つのメソッドを Note クラスに追加します。 これらのメソッドはインスタンスベースであり、デバイスとの間で現在のメモの保存または削除をそれぞれ処理します。

    public void Save() =>
    File.WriteAllText(System.IO.Path.Combine(FileSystem.AppDataDirectory, Filename), Text);
    
    public void Delete() =>
        File.Delete(System.IO.Path.Combine(FileSystem.AppDataDirectory, Filename));
    
  3. アプリでは、ファイルから個別のノートを読み込み、デバイス上のすべてのノートを読み込むという 2 つの方法でノートを読み込む必要があります。 読み込みを処理するコードはメンバー static することができ、クラス インスタンスを実行する必要はありません。

    次のコードをクラスに追加して、ファイル名でメモを読み込みます。

    public static Note Load(string filename)
    {
        filename = System.IO.Path.Combine(FileSystem.AppDataDirectory, filename);
    
        if (!File.Exists(filename))
            throw new FileNotFoundException("Unable to find file on local storage.", filename);
    
        return
            new()
            {
                Filename = Path.GetFileName(filename),
                Text = File.ReadAllText(filename),
                Date = File.GetLastWriteTime(filename)
            };
    }
    

    このコードは、ファイル名をパラメーターとして受け取り、ノートがデバイスに格納されている場所へのパスを構築し、ファイルが存在する場合は読み込もうとします。

  4. ノートを読み込む 2 つ目の方法は、デバイス上のすべてのノートを列挙し、コレクションに読み込む方法です。

    クラスに次のコードを追加します。

    public static IEnumerable<Note> LoadAll()
    {
        // Get the folder where the notes are stored.
        string appDataPath = FileSystem.AppDataDirectory;
    
        // Use Linq extensions to load the *.notes.txt files.
        return Directory
    
                // Select the file names from the directory
                .EnumerateFiles(appDataPath, "*.notes.txt")
    
                // Each file name is used to load a note
                .Select(filename => Note.Load(Path.GetFileName(filename)))
    
                // With the final collection of notes, order them by date
                .OrderByDescending(note => note.Date);
    }
    

    このコードは、ノート ファイル パターン *.notes.txtに一致するデバイス上のファイルを取得することによって、 Note モデル型の列挙可能なコレクション 返します。 各ファイル名は Load メソッドに渡され、個々のメモが読み込まれます。 最後に、ノートのコレクションは、各ノートの日付順に並べ替え、呼び出し元に返されます。

  5. 最後に、ランダムなファイル名を含むプロパティの既定値を設定するコンストラクターをクラスに追加します。

    public Note()
    {
        Filename = $"{Path.GetRandomFileName()}.notes.txt";
        Date = DateTime.Now;
        Text = "";
    }
    

Note クラス コードは次のようになります。

namespace Notes.Models;

internal class Note
{
    public string Filename { get; set; }
    public string Text { get; set; }
    public DateTime Date { get; set; }

    public Note()
    {
        Filename = $"{Path.GetRandomFileName()}.notes.txt";
        Date = DateTime.Now;
        Text = "";
    }

    public void Save() =>
    File.WriteAllText(System.IO.Path.Combine(FileSystem.AppDataDirectory, Filename), Text);

    public void Delete() =>
        File.Delete(System.IO.Path.Combine(FileSystem.AppDataDirectory, Filename));

    public static Note Load(string filename)
    {
        filename = System.IO.Path.Combine(FileSystem.AppDataDirectory, filename);

        if (!File.Exists(filename))
            throw new FileNotFoundException("Unable to find file on local storage.", filename);

        return
            new()
            {
                Filename = Path.GetFileName(filename),
                Text = File.ReadAllText(filename),
                Date = File.GetLastWriteTime(filename)
            };
    }

    public static IEnumerable<Note> LoadAll()
    {
        // Get the folder where the notes are stored.
        string appDataPath = FileSystem.AppDataDirectory;

        // Use Linq extensions to load the *.notes.txt files.
        return Directory

                // Select the file names from the directory
                .EnumerateFiles(appDataPath, "*.notes.txt")

                // Each file name is used to load a note
                .Select(filename => Note.Load(Path.GetFileName(filename)))

                // With the final collection of notes, order them by date
                .OrderByDescending(note => note.Date);
    }
}

Note モデルが完成したら、ビュー モデルを作成できます。

概要ビューモデルを作成する

ビュー モデルをプロジェクトに追加する前に、MVVM Community Toolkit への参照を追加します。 このライブラリは NuGet で使用でき、MVVM パターンの実装に役立つ型とシステムを提供します。

  1. Visual Studio の ソリューション エクスプローラー ウィンドウで、 Notes プロジェクト >Manage NuGet パッケージを右クリックします。

  2. 「Browse」タブを選択します。

  3. communitytoolkit mvvm を検索し、CommunityToolkit.Mvvm パッケージを選択します。これが最初の結果になります。

  4. バージョン 8 以上が選択されていることを確認します。 このチュートリアルは、バージョン 8.0.0 を使用して作成されました。

  5. 次に、[ インストール ] を選択し、表示されるプロンプトをすべて受け入れます。

    NuGet で CommunityToolkit.Mvvm パッケージを検索します。

これで、ビュー モデルを追加してプロジェクトの更新を開始する準備ができました。

ビュー モデルを使用して分離する

ビューとビューモデルの関係は、.NET マルチプラットフォーム アプリ UI (.NET MAUI) によって提供されるバインディング システムに大きく依存します。 このアプリでは、ビューでバインドを既に使用して、ノートの一覧を表示し、1 つのノートのテキストと日付を表示しています。 アプリ ロジックは現在、ビューの分離コードによって提供され、ビューに直接関連付けられています。 たとえば、ユーザーがメモを編集しているときに [保存 ] ボタンを押すと、ボタンの Clicked イベントが発生します。 次に、イベント ハンドラーの分離コードによってメモ テキストがファイルに保存され、前の画面に移動します。

ビューの分離コードにアプリ ロジックを含めると、ビューが変更されたときに問題になる可能性があります。 たとえば、ボタンが別の入力コントロールに置き換えられたり、コントロールの名前が変更されたりすると、イベント ハンドラーが無効になる可能性があります。 ビューの設計方法に関係なく、ビューの目的は、何らかの種類のアプリ ロジックを呼び出し、ユーザーに情報を提示することです。 このアプリでは、 Save ボタンがメモを保存し、前の画面に戻ります。

ビューモデルは、UI の設計方法やデータの読み込みまたは保存方法に関係なく、アプリ ロジックを配置するための特定の場所をアプリに提供します。 ビューモデルは、ビューの代わりにデータ モデルを表し、操作する接着剤です。

ビュー モデルは ViewModels フォルダーに格納されます。

  1. Visual Studio の ソリューション エクスプローラー ウィンドウを見つけます。
  2. Notes プロジェクトを右クリックし、[追加>新しいフォルダー] を選択します。 ViewModels フォルダーに名前を付けます
  3. ViewModels フォルダー >Add>Class を右クリックし、AboutViewModel.csという名前を付けます。
  4. 前の手順を繰り返し、さらに 2 つのビュー モデルを作成します。
    • NoteViewModel.cs
    • NotesViewModel.cs

プロジェクト構造は次の図のようになります。

MVVM フォルダーを示すソリューション エクスプローラー。

ビューモデルとビューについて

[アバウト] ビューでは、画面にいくつかのデータが表示され、必要に応じて詳細情報を含むWebサイトに移動できるオプションがあります。 このビューには、テキスト 入力コントロールやリストから項目を選択する場合など、変更するデータがないため、ビューモデルの追加を示すのが適しています。 About ビューモデルの場合、バックエンドモデルはありません。

About ビューモデルを生成します。

  1. Visual Studio の ソリューション エクスプローラー ウィンドウで、 ViewModels\AboutViewModel.cs をダブルクリックします。

  2. 次のコードを貼り付けます。

    using CommunityToolkit.Mvvm.Input;
    using System.Windows.Input;
    
    namespace Notes.ViewModels;
    
    internal class AboutViewModel
    {
        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.";
        public ICommand ShowMoreInfoCommand { get; }
    
        public AboutViewModel()
        {
            ShowMoreInfoCommand = new AsyncRelayCommand(ShowMoreInfo);
        }
    
        async Task ShowMoreInfo() =>
            await Launcher.Default.OpenAsync(MoreInfoUrl);
    }
    

前のコード スニペットには、名前やバージョンなど、アプリに関する情報を表すプロパティがいくつか含まれています。 このスニペットは、先ほど削除した About モデル とまったく同じです。 ただし、このビューモデルには、 ShowMoreInfoCommand コマンド プロパティという新しい概念が含まれています。

コマンドは、コードを呼び出すバインド可能なアクションであり、アプリ ロジックを配置するのに最適な場所です。 この例では、 ShowMoreInfoCommandShowMoreInfo メソッドをポイントし、Web ブラウザーを特定のページに開きます。 コマンド システムの詳細については、次のセクションで説明します。

ビューについて

前のセクションで作成したビューモデルに接続するには、 About ビューを少し変更する必要があります。 Views\AboutPage.xaml ファイルで、次の変更を適用します。

  • xmlns:models XML 名前空間を更新してxmlns:viewModelsし、Notes.ViewModels .NET 名前空間を対象とします。
  • ContentPage.BindingContext プロパティを、About ビューモデルの新しいインスタンスに変更します。
  • ボタンの Clicked イベント ハンドラーを削除し、 Command プロパティを使用します。

[概要]ビューを更新します。

  1. Visual Studio の ソリューション エクスプローラー ウィンドウで、 Views\AboutPage.xaml をダブルクリックします。

  2. 次のコードを貼り付けます。

    <?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:viewModels="clr-namespace:Notes.ViewModels"
                 x:Class="Notes.Views.AboutPage"
                 x:DataType="viewModels:AboutViewModel">
        <ContentPage.BindingContext>
            <viewModels:AboutViewModel />
        </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..." Command="{Binding ShowMoreInfoCommand}" />
        </VerticalStackLayout>
    
    </ContentPage>
    

    前のコード スニペットでは、このバージョンのビューで変更された行が強調表示されています。

ボタンが Command プロパティを使用していることに注意してください。 多くのコントロールには、ユーザーがコントロールを操作するときに呼び出される Command プロパティがあります。 ボタンと共に使用すると、ユーザーがボタンを押すとコマンドが呼び出されます。ただし、 Clicked イベント ハンドラーの呼び出し方法と同様です。ただし、ビューモデル内のプロパティに Command をバインドできる点が異なります。

このビューでは、ユーザーがボタンを押すと、 Command が呼び出されます。 CommandはビューモデルのShowMoreInfoCommand プロパティにバインドされ、呼び出されると、ShowMoreInfo メソッド内のコードが実行され、Web ブラウザーが特定のページに表示されます。

About コードビハインドをクリーンアップする

ShowMoreInfo ボタンはイベント ハンドラーを使用していないので、LearnMore_Clicked コードは Views\AboutPage.xaml.cs ファイルから削除する必要があります。 そのコードを削除します。クラスにはコンストラクターのみを含める必要があります。

  1. Visual Studio の ソリューション エクスプローラー ウィンドウで、[ ビュー]\AboutPage.xaml.csをダブルクリックします。

    ヒント

    ファイルを表示するには 、Views\AboutPage.xaml を展開する必要がある場合があります。

  2. コードを次のスニペットに置き換えます。

    namespace Notes.Views;
    
    public partial class AboutPage : ContentPage
    {
        public AboutPage()
        {
            InitializeComponent();
        }
    }
    

ノート ビューモデルを作成する

ノート ビューを更新する目的は、XAML コード ビハインドからできるだけ多くの機能を移動し、それを Note ビューモデルに配置することです。

Viewmodel に注意する

Note ビューに必要な内容に基づいて、Note ビューモデルは次の項目を提供する必要があります。

  • ノートのテキスト。
  • メモが作成または最後に更新された日付/時刻。
  • メモを保存するコマンド。
  • メモを削除するコマンド。

Note ビューモデルを作成します。

  1. Visual Studio の ソリューション エクスプローラー ウィンドウで、 ViewModels\NoteViewModel.cs をダブルクリックします。

  2. このファイル内のコードを次のスニペットに置き換えます。

    using CommunityToolkit.Mvvm.Input;
    using CommunityToolkit.Mvvm.ComponentModel;
    using System.Windows.Input;
    
    namespace Notes.ViewModels;
    
    internal class NoteViewModel : ObservableObject, IQueryAttributable
    {
        private Models.Note _note;
    
    }
    

    このコードは、Note ビューをサポートするプロパティとコマンドを追加する空のNoteビューモデルです。 CommunityToolkit.Mvvm.ComponentModel名前空間がインポートされていることに注意してください。 この名前空間は、基底クラスとして使用される ObservableObject を提供します。 ObservableObjectの詳細については、次の手順で学習します。 CommunityToolkit.Mvvm.Input名前空間もインポートされます。 この名前空間には、メソッドを非同期的に呼び出すコマンド型がいくつか用意されています。

    Models.Note モデルはプライベート フィールドとして格納されています。 このクラスのプロパティとメソッドは、このフィールドを使用します。

  3. クラスに次のプロパティを追加します。

    public string Text
    {
        get => _note.Text;
        set
        {
            if (_note.Text != value)
            {
                _note.Text = value;
                OnPropertyChanged();
            }
        }
    }
    
    public DateTime Date => _note.Date;
    
    public string Identifier => _note.Filename;
    

    DateプロパティとIdentifier プロパティは、モデルから対応する値を取得するだけの単純なプロパティです。

    ヒント

    プロパティの場合、 => 構文では、取得専用のプロパティが作成されます。ここで、 => の右側にあるステートメントは、返される値に評価される必要があります。

    Text プロパティは、最初に、設定されている値が別の値であるかどうかを確認します。 値が異なる場合、その値はモデルのプロパティに渡され、 OnPropertyChanged メソッドが呼び出されます。

    OnPropertyChanged メソッドは、ObservableObject基底クラスによって提供されます。 このメソッドは、呼び出し元のコードの名前 (この場合は Text のプロパティ名) を使用し、 ObservableObject.PropertyChanged イベントを発生させます。 このイベントは、すべてのイベント サブスクライバーにプロパティの名前を提供します。 .NET MAUI によって提供されるバインド システムは、このイベントを認識し、UI 内の関連するバインディングを更新します。 Note ビューモデルの場合、Text プロパティが変更されると、イベントが発生し、Text プロパティにバインドされたすべての UI 要素に、プロパティが変更されたことを通知します。

  4. 次のコマンド プロパティをクラスに追加します。これは、ビューがバインドできるコマンドです。

    public ICommand SaveCommand { get; private set; }
    public ICommand DeleteCommand { get; private set; }
    
  5. クラスに次のコンストラクターを追加します。

    public NoteViewModel()
    {
        _note = new Models.Note();
        SaveCommand = new AsyncRelayCommand(Save);
        DeleteCommand = new AsyncRelayCommand(Delete);
    }
    
    public NoteViewModel(Models.Note note)
    {
        _note = note;
        SaveCommand = new AsyncRelayCommand(Save);
        DeleteCommand = new AsyncRelayCommand(Delete);
    }
    

    これら 2 つのコンストラクターは、新しいバッキング モデル (空のメモ) を使用してビューモデルを作成するか、指定したモデル インスタンスを使用するビューモデルを作成するために使用されます。

    コンストラクターは、ビューモデルのコマンドも設定します。 次に、これらのコマンドのコードを追加します。

  6. SaveメソッドとDeleteメソッドを追加します。

    private async Task Save()
    {
        _note.Date = DateTime.Now;
        _note.Save();
        await Shell.Current.GoToAsync($"..?saved={_note.Filename}");
    }
    
    private async Task Delete()
    {
        _note.Delete();
        await Shell.Current.GoToAsync($"..?deleted={_note.Filename}");
    }
    

    これらのメソッドは、関連付けられているコマンドによって呼び出されます。 モデルに対して関連するアクションを実行し、アプリを前のページに移動します。 クエリ文字列パラメーターが .. ナビゲーション パスに追加され、実行されたアクションとメモの一意識別子が示されます。

  7. 次に、ApplyQueryAttributes インターフェイスの要件を満たす IQueryAttributable メソッドをクラスに追加します。

    void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query)
    {
        if (query.ContainsKey("load"))
        {
            _note = Models.Note.Load(query["load"].ToString());
            RefreshProperties();
        }
    }
    

    ページまたはページのバインド コンテキストがこのインターフェイスを実装すると、ナビゲーションで使用されるクエリ文字列パラメーターが ApplyQueryAttributes メソッドに渡されます。 このビューモデルは、 ノート ビューのバインド コンテキストとして使用されます。 Note ビューに移動すると、ビューのバインド コンテキスト (このビューモデル) に、ナビゲーション中に使用されるクエリ文字列パラメーターが渡されます。

    このコードは、 load キーが query ディクショナリに指定されたかどうかを確認します。 このキーが見つかった場合、値は読み込むメモの識別子 (ファイル名) である必要があります。 この注は、このビューモデル インスタンスの基になるモデル オブジェクトとして読み込まれ、設定されます。

  8. 最後に、次の 2 つのヘルパー メソッドをクラスに追加します。

    public void Reload()
    {
        _note = Models.Note.Load(_note.Filename);
        RefreshProperties();
    }
    
    private void RefreshProperties()
    {
        OnPropertyChanged(nameof(Text));
        OnPropertyChanged(nameof(Date));
    }
    

    Reload メソッドは、バッキング モデル オブジェクトを更新し、デバイス ストレージから再読み込みするヘルパー メソッドです

    RefreshProperties メソッドは、このオブジェクトにバインドされているサブスクライバーに、TextプロパティとDateプロパティが変更されたことを通知するもう 1 つのヘルパー メソッドです。 ナビゲーション中にメモが読み込まれると、基になるモデル ( _note フィールド) が変更されるため、 Text プロパティと Date プロパティは実際には新しい値に設定されません。 これらのプロパティは直接設定されていないため、プロパティごとに OnPropertyChanged が呼び出されないため、これらのプロパティにアタッチされているバインドは通知されません。 RefreshProperties により、これらのプロパティに対するバインディングが更新されます。

クラスのコードは、次のスニペットのようになります。

using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.ComponentModel;
using System.Windows.Input;

namespace Notes.ViewModels;

internal class NoteViewModel : ObservableObject, IQueryAttributable
{
    private Models.Note _note;

    public string Text
    {
        get => _note.Text;
        set
        {
            if (_note.Text != value)
            {
                _note.Text = value;
                OnPropertyChanged();
            }
        }
    }

    public DateTime Date => _note.Date;

    public string Identifier => _note.Filename;

    public ICommand SaveCommand { get; private set; }
    public ICommand DeleteCommand { get; private set; }

    public NoteViewModel()
    {
        _note = new Models.Note();
        SaveCommand = new AsyncRelayCommand(Save);
        DeleteCommand = new AsyncRelayCommand(Delete);
    }

    public NoteViewModel(Models.Note note)
    {
        _note = note;
        SaveCommand = new AsyncRelayCommand(Save);
        DeleteCommand = new AsyncRelayCommand(Delete);
    }

    private async Task Save()
    {
        _note.Date = DateTime.Now;
        _note.Save();
        await Shell.Current.GoToAsync($"..?saved={_note.Filename}");
    }

    private async Task Delete()
    {
        _note.Delete();
        await Shell.Current.GoToAsync($"..?deleted={_note.Filename}");
    }

    void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query)
    {
        if (query.ContainsKey("load"))
        {
            _note = Models.Note.Load(query["load"].ToString());
            RefreshProperties();
        }
    }

    public void Reload()
    {
        _note = Models.Note.Load(_note.Filename);
        RefreshProperties();
    }

    private void RefreshProperties()
    {
        OnPropertyChanged(nameof(Text));
        OnPropertyChanged(nameof(Date));
    }
}

ノートの表示

ビューモデルが作成されたので、 メモ ビューを更新します。 Views\NotePage.xaml ファイルで、次の変更を適用します。

  • xmlns:viewModels .NET 名前空間を対象とするNotes.ViewModels XML 名前空間を追加します。
  • ページに BindingContext を追加します。
  • イベント ハンドラー Clicked 削除および保存ボタンを削除し、コマンドに置き換えます。

ノート ビューを更新します。

  1. Visual Studio の ソリューション エクスプローラー ウィンドウで、 Views\NotePage.xaml をダブルクリックして XAML エディターを開きます。

  2. 次のコードを貼り付けます。

    <?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:viewModels="clr-namespace:Notes.ViewModels"
                 x:Class="Notes.Views.NotePage"
                 Title="Note"
                 x:DataType="viewModels:NoteViewModel">
        <ContentPage.BindingContext>
            <viewModels:NoteViewModel />
        </ContentPage.BindingContext>
        <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"
                        Command="{Binding SaveCommand}"/>
    
                <Button Grid.Column="1"
                        Text="Delete"
                        Command="{Binding DeleteCommand}"/>
    
            </Grid>
        </VerticalStackLayout>
    </ContentPage>
    

以前は、このビューは、ページ自体のコードによって設定されたバインディングコンテキストを宣言していませんでした。 XAML でバインド コンテキストを直接設定すると、次の 2 つのことが行われます。

  • 実行時に、ページに移動すると、空白のメモが表示されます。 これは、バインド コンテキストのパラメーターなしのコンストラクターである viewmodel が呼び出されるためです。 正しく覚えている場合は、 Note ビューモデル のパラメーターなしのコンストラクターによって空白のメモが作成されます。

  • XAML エディターの Intellisense では、構文の入力を開始するとすぐに使用可能なプロパティ {Binding 表示されます。 構文も検証され、無効な値が通知されます。 SaveCommandのバインド構文をSave123Commandに変更してみてください。 テキストの上にマウス カーソルを置くと、 Save123Command が見つからないことを示すヒントが表示されます。 バインドは動的であるため、この通知はエラーとは見なされません。実際には、間違ったプロパティを入力したときに気づくのに役立つ可能性のある小さな警告です。

    SaveCommand を別の値に変更した場合は、ここで復元します。

ノートのコードビハインドをクリーンアップする

ビューとの対話がイベント ハンドラーからコマンドに変更されたので、 Views\NotePage.xaml.cs ファイルを開き、すべてのコードをコンストラクターのみを含むクラスに置き換えます。

  1. Visual Studio の ソリューション エクスプローラー ウィンドウで、 ビュー\NotePage.xaml.csをダブルクリックします。

    ヒント

    ファイルを表示するには 、Views\NotePage.xaml を展開する必要がある場合があります。

  2. コードを次のスニペットに置き換えます。

    namespace Notes.Views;
    
    public partial class NotePage : ContentPage
    {
        public NotePage()
        {
            InitializeComponent();
        }
    }
    

Notes ビューモデルを作成する

最後のビューモデルビューペアは 、Notes ビューモデルAllNotes ビューです。 ただし、現在、ビューはモデルに直接バインドされており、このチュートリアルの開始時に削除されました。 AllNotes ビューを更新する目的は、XAML 分離コードからできるだけ多くの機能を移動し、ビューモデルに配置することです。 ここでも、ビューがコードにほとんど影響を与えずにデザインを変更できるという利点があります。

ノート ビュー・モデル

AllNotes ビューの表示内容とユーザーが行う操作に基づいて、Notes ビューモデルは次の項目を提供する必要があります。

  • メモのコレクション。
  • メモへの移動を処理するコマンド。
  • 新しいメモを作成するコマンド。
  • ノートが作成、削除、または変更されたときに、ノートの一覧を更新します。

Notes ビューモデルを作成します。

  1. Visual Studio の ソリューション エクスプローラー ウィンドウで、 ViewModels\NotesViewModel.cs をダブルクリックします。

  2. このファイル内のコードを次のコードに置き換えます。

    using CommunityToolkit.Mvvm.Input;
    using System.Collections.ObjectModel;
    using System.Windows.Input;
    
    namespace Notes.ViewModels;
    
    internal class NotesViewModel: IQueryAttributable
    {
    }
    

    このコードは、プロパティとコマンドを追加してNotesViewModelビューをサポートするための空のAllNotesです。

  3. NotesViewModel クラス コードで、次のプロパティを追加します。

    public ObservableCollection<ViewModels.NoteViewModel> AllNotes { get; }
    public ICommand NewCommand { get; }
    public ICommand SelectNoteCommand { get; }
    

    AllNotes プロパティは、デバイスから読み込まれたすべてのノートを格納するObservableCollectionです。 この 2 つのコマンドは、ビューでノートの作成または既存のノートの選択のアクションをトリガーするために使用されます。

  4. パラメーターなしのコンストラクターをクラスに追加します。このコンストラクターによってコマンドが初期化され、モデルからメモが読み込まれます。

    public NotesViewModel()
    {
        AllNotes = new ObservableCollection<ViewModels.NoteViewModel>(Models.Note.LoadAll().Select(n => new NoteViewModel(n)));
        NewCommand = new AsyncRelayCommand(NewNoteAsync);
        SelectNoteCommand = new AsyncRelayCommand<ViewModels.NoteViewModel>(SelectNoteAsync);
    }
    

    AllNotes コレクションでは、Models.Note.LoadAll メソッドを使用して、監視可能なコレクションにメモを追加します。 LoadAll メソッドはノートをModels.Note型として返しますが、監視可能なコレクションはViewModels.NoteViewModel型のコレクションです。 このコードでは、 Select Linq 拡張機能を使用して、 LoadAllから返されたノート モデルからビューモデル インスタンスを作成します。

  5. コマンドの対象となるメソッドを作成します。

    private async Task NewNoteAsync()
    {
        await Shell.Current.GoToAsync(nameof(Views.NotePage));
    }
    
    private async Task SelectNoteAsync(ViewModels.NoteViewModel note)
    {
        if (note != null)
            await Shell.Current.GoToAsync($"{nameof(Views.NotePage)}?load={note.Identifier}");
    }
    

    NewNoteAsyncが行っている間、SelectNoteAsync メソッドはパラメーターを受け取らないことに注意してください。 必要に応じて、コマンドの呼び出し時に指定されるパラメーターを 1 つ指定できます。 SelectNoteAsync メソッドの場合、パラメーターは選択されているメモを表します。

  6. 最後に、 IQueryAttributable.ApplyQueryAttributes メソッドを実装します。

    void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query)
    {
        if (query.ContainsKey("deleted"))
        {
            string noteId = query["deleted"].ToString();
            NoteViewModel matchedNote = AllNotes.Where((n) => n.Identifier == noteId).FirstOrDefault();
    
            // If note exists, delete it
            if (matchedNote != null)
                AllNotes.Remove(matchedNote);
        }
        else if (query.ContainsKey("saved"))
        {
            string noteId = query["saved"].ToString();
            NoteViewModel matchedNote = AllNotes.Where((n) => n.Identifier == noteId).FirstOrDefault();
    
            // If note is found, update it
            if (matchedNote != null)
                matchedNote.Reload();
    
            // If note isn't found, it's new; add it.
            else
                AllNotes.Add(new NoteViewModel(Note.Load(noteId)));
        }
    }
    

    前のチュートリアルの手順で作成した ノート ビューモデル では、メモが保存または削除されたときにナビゲーションが使用されていました。 ビューモデルは、このビューモデルが関連付けられている AllNotes ビューに戻ります。 このコードは、クエリ文字列に deleted キーまたは saved キーが含まれているかどうかを検出します。 キーの値はノートのユニークな識別子です。

    メモが 削除された場合、そのメモは指定された識別子によって AllNotes コレクション内で照合され、削除されます。

    メモが 保存される理由は 2 つあります。 メモは作成されたばかりか、既存のノートが変更されました。 メモが既に AllNotes コレクションにある場合は、更新されたメモです。 この場合、コレクション内のノート インスタンスを更新するだけで済みます。 コレクションにメモがない場合は、新しいメモであり、コレクションに追加する必要があります。

クラスのコードは、次のスニペットのようになります。

using CommunityToolkit.Mvvm.Input;
using Notes.Models;
using System.Collections.ObjectModel;
using System.Windows.Input;

namespace Notes.ViewModels;

internal class NotesViewModel : IQueryAttributable
{
    public ObservableCollection<ViewModels.NoteViewModel> AllNotes { get; }
    public ICommand NewCommand { get; }
    public ICommand SelectNoteCommand { get; }

    public NotesViewModel()
    {
        AllNotes = new ObservableCollection<ViewModels.NoteViewModel>(Models.Note.LoadAll().Select(n => new NoteViewModel(n)));
        NewCommand = new AsyncRelayCommand(NewNoteAsync);
        SelectNoteCommand = new AsyncRelayCommand<ViewModels.NoteViewModel>(SelectNoteAsync);
    }

    private async Task NewNoteAsync()
    {
        await Shell.Current.GoToAsync(nameof(Views.NotePage));
    }

    private async Task SelectNoteAsync(ViewModels.NoteViewModel note)
    {
        if (note != null)
            await Shell.Current.GoToAsync($"{nameof(Views.NotePage)}?load={note.Identifier}");
    }

    void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query)
    {
        if (query.ContainsKey("deleted"))
        {
            string noteId = query["deleted"].ToString();
            NoteViewModel matchedNote = AllNotes.Where((n) => n.Identifier == noteId).FirstOrDefault();

            // If note exists, delete it
            if (matchedNote != null)
                AllNotes.Remove(matchedNote);
        }
        else if (query.ContainsKey("saved"))
        {
            string noteId = query["saved"].ToString();
            NoteViewModel matchedNote = AllNotes.Where((n) => n.Identifier == noteId).FirstOrDefault();

            // If note is found, update it
            if (matchedNote != null)
                matchedNote.Reload();

            // If note isn't found, it's new; add it.
            else
                AllNotes.Add(new NoteViewModel(Note.Load(noteId)));
        }
    }
}

AllNotes ビュー

ビューモデルが作成されたので、Viewmodel プロパティを指す AllNotes ビュー を更新します。 Views\AllNotesPage.xaml ファイルで、次の変更を適用します。

  • xmlns:viewModels .NET 名前空間を対象とするNotes.ViewModels XML 名前空間を追加します。
  • ページに BindingContext を追加します。
  • ツール バー ボタンの Clicked イベントを削除し、 Command プロパティを使用します。
  • CollectionViewを変更して、ItemSourceAllNotesにバインドします。
  • 選択した項目が変更されたときに対応するコマンドを使用するように CollectionView を変更します。

AllNotes ビューを更新します。

  1. Visual Studio の ソリューション エクスプローラー ウィンドウで、 Views\AllNotesPage.xaml をダブルクリックします。

  2. 次のコードを貼り付けます。

    <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 xmlns:viewModels="clr-namespace:Notes.ViewModels"
                 x:Class="Notes.Views.AllNotesPage"
                 Title="Your Notes"
                 x:DataType="viewModels:NotesViewModel">
        <ContentPage.BindingContext>
            <viewModels:NotesViewModel />
        </ContentPage.BindingContext>
    
        <!-- Add an item to the toolbar -->
        <ContentPage.ToolbarItems>
            <ToolbarItem Text="Add" Command="{Binding NewCommand}" IconImageSource="{FontImage Glyph='+', Color=Black, Size=22}" />
        </ContentPage.ToolbarItems>
    
        <!-- Display notes in a list -->
        <CollectionView x:Name="notesCollection"
                        ItemsSource="{Binding AllNotes}"
                        Margin="20"
                        SelectionMode="Single"
                        SelectionChangedCommand="{Binding SelectNoteCommand}"
                        SelectionChangedCommandParameter="{Binding x:DataType='CollectionView', Source={RelativeSource Self}, Path=SelectedItem}">
            <!-- 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="viewModels:NoteViewModel">
                    <StackLayout>
                        <Label Text="{Binding Text}" FontSize="22"/>
                        <Label Text="{Binding Date}" FontSize="14" TextColor="Silver"/>
                    </StackLayout>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    </ContentPage>
    

ツール バーでは、 Clicked イベントが使用されなくなり、代わりにコマンドが使用されます。

CollectionViewでは、SelectionChangedCommandプロパティとSelectionChangedCommandParameterプロパティを使用したコマンド実行がサポートされています。 更新された XAML では、 SelectionChangedCommand プロパティはビューモデルの SelectNoteCommandにバインドされます。つまり、選択した項目が変更されたときにコマンドが呼び出されます。 コマンドが呼び出されると、 SelectionChangedCommandParameter プロパティ値がコマンドに渡されます。

CollectionViewに使用されるバインディングを見てください。

<CollectionView x:Name="notesCollection"
                ItemsSource="{Binding AllNotes}"
                Margin="20"
                SelectionMode="Single"
                SelectionChangedCommand="{Binding SelectNoteCommand}"
                SelectionChangedCommandParameter="{Binding x:DataType='CollectionView', Source={RelativeSource Self}, Path=SelectedItem}">

SelectionChangedCommandParameter プロパティは、Source={RelativeSource Self} バインドを使用します。 Selfは、現在のオブジェクト (CollectionView) を参照します。 したがって、 x:DataType は、コンパイル済みバインディングの型として CollectionView を指定します。 バインド パスが SelectedItem プロパティであることに注意してください。 選択した項目を変更してコマンドを呼び出すと、 SelectNoteCommand コマンドが呼び出され、選択した項目がパラメーターとしてコマンドに渡されます。

SelectionChangedCommandParameter プロパティで定義されているバインド式をコンパイルするには、Source プロパティを指定する式でコンパイル済みバインドを有効にするようにプロジェクトに指示する必要があります。 これを行うには、ソリューションのプロジェクト ファイルを編集し、<MauiEnableXamlCBindingWithSourceCompilation>true</MauiEnableXamlCBindingWithSourceCompilation>要素内に<PropertyGroup>を追加します。

<PropertyGroup>
  <MauiEnableXamlCBindingWithSourceCompilation>true</MauiEnableXamlCBindingWithSourceCompilation>
</PropertyGroup>

AllNotes コードビハインドをクリーンアップする

ビューとの対話がイベント ハンドラーからコマンドに変更されたので、 Views\AllNotesPage.xaml.cs ファイルを開き、すべてのコードをコンストラクターのみを含むクラスに置き換えます。

  1. Visual Studio の ソリューション エクスプローラー ウィンドウで、 ビュー\AllNotesPage.xaml.csをダブルクリックします。

    ヒント

    ファイルを表示するには 、Views\AllNotesPage.xaml を展開する必要がある場合があります。

  2. コードを次のスニペットに置き換えます。

    namespace Notes.Views;
    
    public partial class AllNotesPage : ContentPage
    {
        public AllNotesPage()
        {
            InitializeComponent();
        }
    }
    

アプリを実行する

これでアプリを実行できるようになり、すべてが機能しています。 ただし、アプリの動作には 2 つの問題があります。

  • メモを選択すると、エディターが開き、[ 保存] を押してから同じメモを選択しようとすると、機能しません。
  • ノートが変更または追加されるたびに、ノートの一覧が並べ替えられていないので、最新のノートが上部に表示されます。

この 2 つの問題は、次のチュートリアルの手順で修正されます。

アプリの動作を修正する

アプリ コードをコンパイルして実行できるようになったので、アプリの動作に 2 つの欠陥があることに気付いたでしょう。 このアプリでは、既に選択されているノートを再選択することはできません。また、ノートの作成または変更後にノートの一覧が並べ替えられません。

メモを一覧の先頭に移動する

まず、ノート リストの並べ替えの問題を修正します。 ViewModels\NotesViewModel.cs ファイルのAllNotes コレクションには、ユーザーに表示されるすべてのメモが含まれています。 残念ながら、 ObservableCollection を使用する欠点は、手動で並べ替える必要があるということです。 新しい項目または更新された項目を一覧の先頭に表示するには、次の手順を実行します。

  1. Visual Studio の ソリューション エクスプローラー ウィンドウで、 ViewModels\NotesViewModel.cs をダブルクリックします。

  2. ApplyQueryAttributes メソッドで、保存されたクエリ文字列キーのロジックを調びます。

  3. matchedNotenullされていない場合は、メモが更新されます。 AllNotes.Moveメソッドを使用して、matchedNoteをインデックス 0 (リストの先頭) に移動します。

    string noteId = query["saved"].ToString();
    NoteViewModel matchedNote = AllNotes.Where((n) => n.Identifier == noteId).FirstOrDefault();
    
    // If note is found, update it
    if (matchedNote != null)
    {
        matchedNote.Reload();
        AllNotes.Move(AllNotes.IndexOf(matchedNote), 0);
    }
    

    AllNotes.Move メソッドは、コレクション内のオブジェクトの位置を移動する 2 つのパラメーターを受け取ります。 最初のパラメーターは移動するオブジェクトのインデックスであり、2 番目のパラメーターはオブジェクトを移動する場所のインデックスです。 AllNotes.IndexOf メソッドは、メモのインデックスを取得します。

  4. matchedNotenullされると、メモは新規になり、リストに追加されます。 リストの末尾にメモを追加するのではなく、リストの先頭にあるインデックス0にメモを挿入します。 AllNotes.AddメソッドをAllNotes.Insertに変更します。

    string noteId = query["saved"].ToString();
    NoteViewModel matchedNote = AllNotes.Where((n) => n.Identifier == noteId).FirstOrDefault();
    
    // If note is found, update it
    if (matchedNote != null)
    {
        matchedNote.Reload();
        AllNotes.Move(AllNotes.IndexOf(matchedNote), 0);
    }
    // If note isn't found, it's new; add it.
    else
        AllNotes.Insert(0, new NoteViewModel(Models.Note.Load(noteId)));
    

ApplyQueryAttributes メソッドは、次のコード スニペットのようになります。

void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query)
{
    if (query.ContainsKey("deleted"))
    {
        string noteId = query["deleted"].ToString();
        NoteViewModel matchedNote = AllNotes.Where((n) => n.Identifier == noteId).FirstOrDefault();

        // If note exists, delete it
        if (matchedNote != null)
            AllNotes.Remove(matchedNote);
    }
    else if (query.ContainsKey("saved"))
    {
        string noteId = query["saved"].ToString();
        NoteViewModel matchedNote = AllNotes.Where((n) => n.Identifier == noteId).FirstOrDefault();

        // If note is found, update it
        if (matchedNote != null)
        {
            matchedNote.Reload();
            AllNotes.Move(AllNotes.IndexOf(matchedNote), 0);
        }
        // If note isn't found, it's new; add it.
        else
            AllNotes.Insert(0, new NoteViewModel(Models.Note.Load(noteId)));
    }
}

ノートの 2 回の選択を許可する

AllNotes ビューでは、CollectionViewはすべてのノートを一覧表示しますが、同じノートを 2 回選択することはできません。 項目を選択したままにしておく方法は 2 つあります。ユーザーが既存のメモを変更したときと、ユーザーが強制的に後方に移動したときです。 ユーザーがメモを保存するケースは、 AllNotes.Moveを使用する前のセクションのコード変更で修正されるため、その場合について心配する必要はありません。

ここで解決する必要がある問題は、ナビゲーションに関連しています。 Allnotes ビューの移動方法に関係なく、ページに対してNavigatedTo イベントが発生します。 このイベントは、 CollectionViewで選択した項目を強制的に選択解除するのに最適な場所です。

ただし、MVVM パターンがここで適用されている場合、ビューモデルは、メモの保存後に選択した項目をクリアするなど、ビューで直接何かをトリガーすることはできません。 では、どうすればそれが起こるのでしょうか。 MVVM パターンを適切に実装すると、ビュー内のコードビハインドが最小限に抑えられます。 MVVM 分離パターンをサポートするために、この問題を解決するには、いくつかの異なる方法があります。 ただし、ビュービハインドのコードにコードを配置することもでき、特にビューに直接関連している場合にはそれが許されます。 MVVM には、アプリのコンパートメント化、保守性の向上、新機能の追加を容易にする優れた設計と概念が多数用意されています。 ただし、場合によっては、MVVM が過剰なエンジニアリングを奨励している場合があります。

この問題の解決策を過剰に設計しないでください。 NavigatedTo イベントを使用して、選択した項目を CollectionViewからクリアするだけです。

  1. Visual Studio の ソリューション エクスプローラー ウィンドウで、 Views\AllNotesPage.xaml をダブルクリックします。

  2. <ContentPage>の XAML で、NavigatedTo イベントを追加します。

    <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 xmlns:viewModels="clr-namespace:Notes.ViewModels"
                 x:Class="Notes.Views.AllNotesPage"
                 Title="Your Notes"
                 NavigatedTo="ContentPage_NavigatedTo"
                 x:DataType="viewModels:NotesViewModel">
        <ContentPage.BindingContext>
            <viewModels:NotesViewModel />
    
  3. 既定のイベント ハンドラーを追加するには、イベント メソッド名を右クリックして ContentPage_NavigatedToし、[ 定義へ移動] を選択します。 この操作により、コード エディターで Views\AllNotesPage.xaml.cs が開きます。

  4. イベント ハンドラー コードを次のスニペットに置き換えます。

    private void ContentPage_NavigatedTo(object sender, NavigatedToEventArgs e)
    {
        notesCollection.SelectedItem = null;
    }
    

    XAML では、 CollectionView には notesCollectionの名前が付けられます。 このコードでは、その名前を使用して CollectionViewにアクセスし、 SelectedItemnull に設定します。 選択した項目は、ページが移動するたびにクリアされます。

次に、アプリを実行します。 ノートに移動し、戻るボタンを押して、同じノートをもう一度選択してみてください。 アプリの動作が修正されました。

コードを探します。 このチュートリアルのコードを探します。 完成したプロジェクトのコピーをダウンロードしてコードを比較する場合は、このプロジェクトをダウンロードします。

アプリで MVVM パターンが使用されるようになりました。

次のステップ

次のリンクは、このチュートリアルで学習した概念の一部に関する詳しい情報を記載しています。