次の方法で共有


メモのビューとモデルを追加する

チュートリアルのこの部分では、データ ビューとモデルの概念について説明します。

チュートリアルの前の手順では、ユーザーが 1 つのメモを保存、編集、または削除できる新しいページをプロジェクトに追加しました。 ただし、アプリは複数のメモを処理する必要があるため、すべてのノートを表示する別のページを追加する必要があります ( AllNotesPage呼び出します)。 このページでは、エディター ページで開くメモを選択して、表示、編集、または削除できるようにします。 また、ユーザーが新しいメモを作成できるようにする必要もあります。

これを実現するには、 AllNotesPage ノートのコレクションと、コレクションを表示する方法が必要です。 ここで、ノート データは NotePage ファイルに緊密にバインドされているため、アプリが問題に陥ります。 AllNotesPageでは、すべてのノートをリストまたは他のコレクション ビューに表示し、各ノートに関する情報 (作成日やテキストのプレビューなど) を表示するだけです。 ノート テキストが TextBox コントロールに厳密にバインドされている場合、これを行う方法はありません。

すべてのノートを表示するページを追加する前に、ノート データをノート プレゼンテーションから分離するためにいくつかの変更を加えてみましょう。

ビューとモデル

通常、WinUI アプリには、少なくとも ビュー レイヤーデータ レイヤーがあります。

ビュー レイヤーは、XAML マークアップを使用して UI を定義します。 マークアップには、特定の UI コンポーネントとデータ メンバー間の接続を定義するデータ バインディング式 (x:Bind など) が含まれています。 分離コード ファイルは、ビュー レイヤーの一部として、UI をカスタマイズまたは操作したり、データに対する処理を実行するメソッドを呼び出す前にイベント ハンドラー引数からデータを抽出したりするために必要な追加のコードを含めるために使用される場合があります。

データ レイヤー ( モデル) は、アプリ データと関連するロジックを表す型を定義します。 このレイヤーはビュー レイヤーとは独立しており、データを操作する複数の異なるビューを作成できます。

現在、 NotePage はデータのビュー (ノート テキスト) を表します。 ただし、データがシステム ファイルからアプリに読み込まれると、TextTextBoxNotePage プロパティにのみ存在します。 さまざまな方法や場所でデータを表示できる方法でアプリで表されるわけではありません。つまり、アプリにはデータ レイヤーがありません。 ここでプロジェクトを再構築して、データ レイヤーを作成します。

ビューとモデルを分離する

ヒント

このチュートリアルのコードは 、GitHub リポジトリからダウンロードまたは表示できます。 この手順のコードをそのまま表示するには、コミットに関するページ ( view-model) を参照してください。

既存のコードをリファクタリングして、モデルをビューから分離します。 次のいくつかの手順では、ビューとモデルが互いに独立して定義されるようにコードを編成します。

  1. ソリューション エクスプローラーでWinUINotes プロジェクトを右クリックし、[追加>新しいフォルダー] を選択します。 フォルダーに「 Modelsで行うことができます。

  2. WinUINotes プロジェクトをもう一度右クリックし、[追加>新しいフォルダー] を選択します。 フォルダーに「 Viewsで行うことができます。

  3. NotePage.xaml項目を見つけて、Views フォルダーにドラッグします。 NotePage.xaml.cs ファイルは移動する必要があります。

    ファイルを移動するとき、Visual Studio では通常、移動操作に時間がかかる場合があるという警告が表示されます。 ここではこの警告は問題ではありません。この警告が表示された場合は、[OK] を押します。

    Visual Studio では、移動後のファイルの名前空間を調整するかどうかを確認するメッセージが表示される場合もあります。 [ いいえ] を選択します。 次の手順で名前空間を変更します。

ビューの名前空間を更新する

ビューが Views フォルダーに移動されたので、一致するように名前空間を更新する必要があります。 ページの XAML ファイルとコードビハインド ファイルの名前空間は WinUINotes に設定されます。 これを WinUINotes.Views に更新する必要があります。

  1. ソリューション エクスプローラー ウィンドウで、NotePage.xaml展開して分離コード ファイルを表示します。

  2. NotePage.xaml.cs項目をダブルクリックして、まだ開いていない場合はコード エディターを開きます。 名前空間を WinUINotes.Views に変更します。

    namespace WinUINotes.Views
    
  3. NotePage.xaml項目をダブルクリックして、まだ開いていない場合は XAML エディターを開きます。 前の名前空間は、x:Class 属性を通じて参照されています。この属性は、XAML のコードビハインドのクラス型を定義するものです。 このエントリは単なる名前空間ではなく、型を持つ名前空間です。 x:Class の値を WinUINotes.Views.NotePage に変更します。

    x:Class="WinUINotes.Views.NotePage"
    

MainWindow の名前空間参照を修正する

前の手順では、ノート ページを作成し、 MainWindow.xaml を更新してそこに移動しました。 local:名前空間マッピングにマップされていることに注意してください。 名前 local をプロジェクトのルート名前空間にマップするのが一般的です。Visual Studio プロジェクト テンプレートでは既にこれを行っています (xmlns:local="using:WinUINotes")。 ページが新しい名前空間に移動されたので、XAML での型マッピングが無効になりました。

さいわい、必要に応じて独自の名前空間マッピングを追加できます。 プロジェクトで作成したさまざまなフォルダー内のアイテムにアクセスするには、この操作を行う必要があります。 この新しい XAML 名前空間は、 WinUINotes.Viewsの名前空間にマップされるため、 views名前を付けます。 宣言は属性 xmlns:views="using:WinUINotes.Views" のようになります。

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

  2. この新しい名前空間マッピングを、 localのマッピングの下の行に追加します。

    xmlns:views="using:WinUINotes.Views"
    
  3. local XAML 名前空間は、Frame.SourcePageType プロパティを設定するために使用されたため、viewsに変更します。 これで、XAML は次のようになります。

    <Window
        x:Class="WinUINotes.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:WinUINotes"
        xmlns:views="using:WinUINotes.Views"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="WinUI Notes">
    
        <!-- ... Unchanged XAML not shown. -->
    
            <Frame x:Name="rootFrame" Grid.Row="1"
                   SourcePageType="views:NotePage"/>
    
        <!-- ... Unchanged XAML not shown. -->
    
    </Window>
    
  4. アプリケーションをビルドし、実行します。 アプリはコンパイラ エラーなしで実行する必要があり、すべてが以前と同様に動作する必要があります。

モデルを定義する

現在、モデル (データ) はメモ ビューに埋め込まれています。 ノート ページのデータを表す新しいクラスを作成します。

  1. [ソリューション エクスプローラー] ウィンドウで、Models フォルダーを右クリックし、[追加]>[クラス...] を選びます。

  2. クラスに Note.cs 名前を付け、 Add キーを押します。 Note.cs ファイルがコード エディターで開きます。

  3. Note.cs ファイル内のコードを次のコードに置き換えます。これにより、クラスがpublicされ、メモを処理するためのプロパティとメソッドが追加されます。

    using System;
    using System.Threading.Tasks;
    using Windows.Storage;
    
    namespace WinUINotes.Models
    {
        public class Note
        {
            private StorageFolder storageFolder = ApplicationData.Current.LocalFolder;
            public string Filename { get; set; } = string.Empty;
            public string Text { get; set; } = string.Empty;
            public DateTime Date { get; set; } = DateTime.Now;
    
            public Note()
            {
                Filename = "notes" + DateTime.Now.ToBinary().ToString() + ".txt";
            }
    
            public async Task SaveAsync()
            {
                // Save the note to a file.
                StorageFile noteFile = (StorageFile)await storageFolder.TryGetItemAsync(Filename);
                if (noteFile is null)
                {
                    noteFile = await storageFolder.CreateFileAsync(Filename, CreationCollisionOption.ReplaceExisting);
                }
                await FileIO.WriteTextAsync(noteFile, Text);
            }
    
            public async Task DeleteAsync()
            {
                // Delete the note from the file system.
                StorageFile noteFile = (StorageFile)await storageFolder.TryGetItemAsync(Filename);
                if (noteFile is not null)
                {
                    await noteFile.DeleteAsync();
                }
            }
        }
    }
    
  4. ファイルを保存します。

このコードは NotePage.xaml.csのコードによく似ていますが、いくつかの変更と追加があります。

Filename Textpublic プロパティに変更され、新しいDate プロパティが追加されました。

ファイルを保存および削除するコードは、 public メソッドに配置されています。 これは、Clickのイベント ハンドラーNotePageボタンで使用したコードとほぼ同じですが、ファイルが削除された後にビューを更新するための追加のコードは削除されています。 データ バインディングを使用してモデルとビューの同期を維持するため、ここでは必要ありません。

これらの非同期メソッド シグネチャは、の代わりに void返します。 Task クラスは、値を返さない 1 つの非同期操作を表します。 メソッドシグネチャが voidを必要とする場合を除き、 Click イベント ハンドラーの場合と同様に、 async メソッドは Taskを返す必要があります。

また、メモを保持する StorageFile への参照は保持されなくなります。 ファイルを保存または削除する必要がある場合は、ファイルを取得してみてください。

NotePageでは、ファイル名にプレースホルダー (note.txt) を使用しました。 アプリで複数のメモがサポートされたので、保存済みのノートのファイル名を異なる一意にする必要があります。 これを行うには、コンストラクターで Filename プロパティを設定します。 DateTime.ToBinary メソッドを使用すると、現在の時刻に基づいてファイル名の一部を作成し、ファイル名を一意にすることができます。 生成されたファイル名は次のようになります: notes-8584626598945870392.txt

ノート ページを更新する

NotePage データ モデルを使用するようにNote ビューを更新し、Note モデルに移動されたコードを削除できるようになりました。

  1. エディターでまだ開いていない場合は、 Views\NotePage.xaml.cs ファイルを開きます。

  2. ページの上部にある最後の using ステートメントの後に、新しい using ステートメントを追加して、 Models フォルダーと名前空間内のクラスにコードアクセスできるようにします。

    using WinUINotes.Models;
    
  3. クラスから次の行を削除します。

    private StorageFolder storageFolder = ApplicationData.Current.LocalFolder;
    private StorageFile? noteFile = null;
    private string fileName = "note.txt";
    
  4. 代わりに、Noteという名前のnoteModelオブジェクトをその場所に追加します。 これは、 NotePage がビューを提供するノート データを表します。

    private Note? noteModel;
    
  5. また、 NotePage_Loaded イベント ハンドラーは不要です。 テキスト ファイルから TextBox にテキストを直接読み込むことはありません。 代わりに、ノート テキストは Note オブジェクトに読み込まれます。 このコードは、後の手順で AllNotesPage を追加するときに追加します。 これらの行を削除します。

    Loaded += NotePage_Loaded;
    
    ...
    
    private async void NotePage_Loaded(object sender, RoutedEventArgs e)
    {
        noteFile = (StorageFile)await storageFolder.TryGetItemAsync(fileName);
        if (noteFile is not null)
        {
          NoteEditor.Text = await FileIO.ReadTextAsync(noteFile);
        }
    }
    
  6. SaveButton_Click メソッドのコードを次のように置き換えます。

    if (noteModel is not null)
    {
        await noteModel.SaveAsync();
    }
    
  7. DeleteButton_Click メソッドのコードを次のように置き換えます。

    if (noteModel is not null)
    {
        await noteModel.DeleteAsync();
    }
    

これで、 Note モデルを使用するように XAML ファイルを更新できます。 以前は、テキスト ファイルから分離コード ファイルの TextBox.Text プロパティにテキストを直接読み取ります。 ここでは、 Text プロパティにデータ バインディングを使用します。

  1. エディターでまだ開いていない場合は、 Views\NotePage.xaml ファイルを開きます。

  2. Text コントロールにTextBox属性を追加します。 Text: noteModelText="{x:Bind noteModel.Text, Mode=TwoWay}" プロパティにバインドします。

  3. Header: DatenoteModel プロパティにバインドするようにHeader="{x:Bind noteModel.Date.ToString()}"を更新します。

    <TextBox x:Name="NoteEditor"
             <!-- ↓ Add this line. ↓ -->
             Text="{x:Bind noteModel.Text, Mode=TwoWay}"
             AcceptsReturn="True"
             TextWrapping="Wrap"
             PlaceholderText="Enter your note"
             <!-- ↓ Update this line. ↓ -->
             Header="{x:Bind noteModel.Date.ToString()}"
             ScrollViewer.VerticalScrollBarVisibility="Auto"
             Width="400"
             Grid.Column="1"/>
    

データ バインディングは、アプリの UI でデータを表示し、必要に応じてそのデータと同期し続ける方法です。 バインドの Mode=TwoWay 設定は、 TextBox.Text プロパティと noteModel.Text プロパティが自動的に同期されることを意味します。 TextBoxでテキストが更新されると、TextnoteModel プロパティに変更が反映され、noteModel.Textが変更されると、更新がTextBoxに反映されます。

Header プロパティは、ファイルの作成後に Mode プロパティが変更されないため、OneTimeの既定のnoteModel.Dateを使用します。 このコードでは、関数x:Bindと呼ばれるの強力な機能も示しています。これにより、ToStringなどの関数をバインド パスのステップとして使用できます。

Important

適切な BindingMode を選択することが重要です。そうしないと、データ バインディングが期待どおりに動作しない可能性があります。 ({x:Bind}の一般的な間違いは、BindingModeまたはOneWayが必要な場合は、既定のTwoWayを忘れて変更することです)。

名前 Description
OneTime バインディングが作成された場合にのみ、ターゲット プロパティを更新します。 {x:Bind}の既定値。
OneWay バインディングの作成時にターゲット プロパティを更新します。 ソース オブジェクトに対する変更は、ターゲットにも反映できます。 {Binding}の既定値。
TwoWay いずれかの変更時に、ターゲット オブジェクトまたはソース オブジェクトを更新します。 バインディングが作成されると、ターゲット プロパティがソースから更新されます。

データ バインディングでは、データと UI の分離がサポートされており、その結果、概念モデルがシンプルになり、アプリの読みやすさ、テスト容易性、保守性が向上します。

WinUI では、次の 2 種類のバインドから選択できます。

  • {x:Bind}マークアップ拡張はコンパイル時に処理されます。 その利点の一部は、バインディング式のパフォーマンスの向上とコンパイル時の検証です。 WinUI アプリでバインドすることをお勧めします。
  • {Binding} マークアップ拡張は実行時に処理され、汎用ランタイム オブジェクト検査が使用されます。

詳細については、次のドキュメントを参照してください。

データ バインディングと MVVM

Model-View-ViewModel (MVVM) は、.NET 開発者に人気のある UI コードと UI 以外のコードを分離するための UI アーキテクチャ 設計パターンです。 WinUI アプリの作成に関する詳細を学習すると、その説明が表示され、読み上げられる可能性があります。 ここで行ったビューとモデルの分離は、アプリの完全な MVVM 実装に向けた最初のステップですが、このチュートリアルに進む限りです。

このチュートリアルでは"model" という用語を使用してデータ モデルを参照してきましたが、モデルの側面を組み込む一方で、このモデルは完全な MVVM 実装で ViewModel とより密接に連携することに注意してください。

MVVM の詳細については、次のリソースを参照してください。