MVVM 개념으로 앱 업그레이드
이 자습서 시리즈는 노트 작성 앱을 만든 .NET MAUI 앱 만들기 자습서를 계속 진행하도록 설계되었습니다. 시리즈의 2부에서는 다음 방법에 대해 알아봅니다.
- MVVM(model-view-viewmodel) 패턴을 구현합니다.
- 탐색하는 동안 데이터를 전달하기 위해 추가 쿼리 문자열 스타일을 사용합니다.
이 자습서에서 만든 코드가 이 자습서의 기초이므로 먼저 .NET MAUI 앱 만들기 자습서를 따르는 것이 좋습니다. 코드를 분실했거나 새로 시작하려면 이 프로젝트를 다운로드합니다.
MVVM 이해
.NET MAUI 개발자 환경에는 일반적으로 XAML에서 사용자 인터페이스를 만든 다음 사용자 인터페이스에서 작동하는 코드 숨김을 추가하는 작업이 포함됩니다. 앱이 수정되고 크기 및 범위가 증가함에 따라 복잡한 유지 관리 문제가 발생할 수 있습니다. 이러한 문제에는 UI 컨트롤과 비즈니스 논리 간의 긴밀한 결합이 포함되며, 이로 인해 UI 수정 비용이 증가하고 이러한 코드를 단위 테스트하기 어려워집니다.
MVVM(model-view-viewmodel) 패턴을 사용하면 앱의 비즈니스 및 프레젠테이션 논리를 UI(사용자 인터페이스)와 명확하게 구분할 수 있습니다. 앱 논리와 UI를 완전히 분리하여 유지 관리하면 수많은 개발 문제를 해결하고 앱을 더 쉽게 테스트, 유지 관리 및 발전할 수 있습니다. 또한 코드 재사용 기회를 크게 향상시킬 수 있으며 개발자와 UI 디자이너가 앱의 해당 부분을 개발할 때 더 쉽게 공동 작업할 수 있습니다.
패턴
MVVM 패턴에는 모델, 뷰 및 뷰 모델의 세 가지 핵심 구성 요소가 있습니다. 각 구성 요소는 다른 용도로 사용됩니다. 다음 다이어그램에서는 세 구성 요소 간의 관계를 보여 줍니다.
각 구성 요소의 책임을 이해하는 것 외에 상호 작용하는 방법을 이해하는 것도 중요합니다. 개략적으로 뷰는 뷰 모델을 "인식"하고, 뷰 모델은 모델을 "인식"하지만 모델은 뷰 모델을 인식하지 못하고 뷰 모델은 뷰를 인식하지 못합니다. 따라서 뷰 모델은 뷰를 모델에서 격리하고 모델이 뷰와 독립적으로 발전할 수 있도록 합니다.
MVVM을 효과적으로 사용하는 관건은 앱 코드를 올바른 클래스로 팩터링하는 방법과 클래스가 상호 작용하는 방식을 이해하는 것입니다.
보기
뷰는 사용자가 화면에 표시되는 구조, 레이아웃 및 모양을 정의합니다. 이상적으로 각 보기는 비즈니스 논리를 포함하지 않는 제한된 코드 숨김을 사용하여 XAML에 정의됩니다. 그러나 경우에 따라 코드 숨김에는 애니메이션과 같이 XAML에서 표현하기 어려운 시각적 동작을 구현하는 UI 논리가 포함될 수 있습니다.
ViewModel
뷰 모델은 뷰가 데이터 바인딩될 수 있는 속성 및 명령을 구현하고 변경 알림 이벤트를 통해 뷰에 상태 변경을 알릴 수 있습니다. 뷰 모델에서 제공하는 속성 및 명령은 UI에서 제공할 기능을 정의하지만 뷰는 해당 기능을 표시하는 방법을 결정합니다.
또한 뷰 모델은 필요한 모든 모델 클래스와 뷰의 상호 작용을 조정해야 합니다. 일반적으로 뷰 모델과 모델 클래스 간에 일대다 관계가 있습니다.
각 뷰 모델은 뷰에서 쉽게 사용할 수 있는 형식으로 모델의 데이터를 제공합니다. 이를 위해 뷰 모델은 때때로 데이터 변환을 수행합니다. 뷰 모델에 이 데이터 변환을 배치하는 것은 뷰가 바인딩될 수 있는 속성을 제공하기 때문에 좋은 생각입니다. 예를 들어 뷰 모델은 뷰에서 더 쉽게 표시할 수 있도록 두 속성의 값을 결합할 수 있습니다.
Important
.NET MAUI는 바인딩 업데이트를 UI 스레드에 마샬링합니다. MVVM을 사용하는 경우 .NET MAUI의 바인딩 엔진을 사용하여 모든 스레드에서 데이터 바인딩 viewmodel 속성을 업데이트하여 UI 스레드에 대한 업데이트를 가져올 수 있습니다.
모델
모델 클래스는 앱의 데이터를 캡슐화하는 비 시각적 클래스입니다. 따라서 모델은 일반적으로 비즈니스 및 유효성 검사 논리와 함께 데이터 모델을 포함하는 앱의 도메인 모델을 나타내는 것으로 생각할 수 있습니다.
모델 업데이트
자습서의 첫 번째 부분에서는 MVVM(model-view-viewmodel) 패턴을 구현합니다. 시작하려면 Visual Studio에서 Notes.sln 솔루션을 엽니다.
모델 정리
이전 자습서에서 모델 형식은 뷰에 직접 매핑된 모델(데이터) 및 뷰 모델(데이터 준비)으로 작동했습니다. 다음 표에서는 모델을 설명합니다.
코드 파일 | 설명 |
---|---|
모델/About.cs | 모델입니다 About . 앱 제목 및 버전과 같이 앱 자체를 설명하는 읽기 전용 필드를 포함합니다. |
모델/Note.cs | 모델입니다 Note . 메모를 나타냅니다. |
모델/AllNotes.cs | 모델입니다 AllNotes . 디바이스의 모든 노트를 컬렉션에 로드합니다. |
앱 자체에 대해 생각할 때 앱에서 사용하는 Note
데이터는 하나뿐입니다. 노트는 디바이스에서 로드되고, 디바이스에 저장되고, 앱 UI를 통해 편집됩니다. 실제로 모델과 AllNotes
모델이 필요하지 About
않습니다. 프로젝트에서 다음 모델을 제거합니다.
- Visual Studio의 솔루션 탐색기 창을 찾습니다.
- Models\About.cs 파일을 마우스 오른쪽 단추로 클릭하고 삭제를 선택합니다. 파일을 삭제하려면 [확인]을 누릅니다.
- Models\AllNotes.cs 파일을 마우스 오른쪽 단추로 클릭하고 삭제를 선택합니다. 파일을 삭제하려면 [확인]을 누릅니다.
남은 유일한 모델 파일은 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
모델을 확장하여 노트 로드, 저장 및 삭제를 처리할 예정입니다.
Visual Studio의 솔루션 탐색기 창에서 Models\Note.cs 두 번 클릭합니다.
코드 편집기에서 클래스에 다음 두 가지 메서드를 추가합니다
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));
앱은 파일에서 개별 메모를 로드하고 디바이스의 모든 노트를 로드하는 두 가지 방법으로 노트를 로드해야 합니다. 로드를 처리하는 코드는 클래스 인스턴스를 실행할 필요가 없는 멤버일
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) }; }
이 코드는 파일 이름을 매개 변수로 사용하고, 노트가 디바이스에 저장되는 경로를 빌드하고, 파일이 있는 경우 로드를 시도합니다.
노트를 로드하는 두 번째 방법은 디바이스의 모든 노트를 열거하고 컬렉션에 로드하는 것입니다.
다음 코드를 클래스에 추가합니다.
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
열거 가능한 컬렉션을 반환합니다. *.notes.txt. 각 파일 이름은 메서드에Load
전달되어 개별 메모를 로드합니다. 마지막으로 노트 컬렉션은 각 노트의 날짜별로 정렬되고 호출자에게 반환됩니다.마지막으로 임의의 파일 이름을 포함하여 속성의 기본값을 설정하는 생성자를 클래스에 추가합니다.
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
이제 모델이 완료되었으므로 뷰 모델을 만들 수 있습니다.
About viewmodel 만들기
프로젝트에 보기 모델을 추가하기 전에 MVVM 커뮤니티 도구 키트에 대한 참조를 추가합니다. 이 라이브러리는 NuGet에서 사용할 수 있으며 MVVM 패턴을 구현하는 데 도움이 되는 형식과 시스템을 제공합니다.
Visual Studio의 솔루션 탐색기 창에서 NuGet 패키지 관리 노트 프로젝트를 >마우스 오른쪽 단추로 클릭합니다.
찾아보기 탭을 선택합니다.
communitytoolkit mvvm을 검색하고 첫 번째 결과여야 하는 패키지를 선택합니다
CommunityToolkit.Mvvm
.버전 8 이상이 선택되어 있는지 확인합니다. 이 자습서는 버전 8.0.0을 사용하여 작성되었습니다.
다음으로 설치를 선택하고 표시되는 프롬프트를 수락합니다.
이제 보기 모델을 추가하여 프로젝트 업데이트를 시작할 준비가 되었습니다.
뷰 모델과 분리
view-to-viewmodel 관계는 .NET 다중 플랫폼 앱 UI(.NET MAUI)에서 제공하는 바인딩 시스템에 크게 의존합니다. 앱은 이미 보기에 바인딩을 사용하여 노트 목록을 표시하고 단일 노트의 텍스트와 날짜를 표시하고 있습니다. 앱 논리는 현재 뷰의 코드 숨김에서 제공되며 뷰에 직접 연결됩니다. 예를 들어 사용자가 메모를 편집하고 저장 단추를 누르면 단추에 Clicked
대한 이벤트가 발생합니다. 그런 다음 이벤트 처리기의 코드 숨김은 메모 텍스트를 파일에 저장하고 이전 화면으로 이동합니다.
보기의 코드 숨김에 앱 논리가 있으면 보기가 변경될 때 문제가 될 수 있습니다. 예를 들어 단추가 다른 입력 컨트롤로 바뀌거나 컨트롤 이름이 변경되면 이벤트 처리기가 유효하지 않을 수 있습니다. 뷰를 디자인하는 방법에 관계없이 보기의 목적은 일종의 앱 논리를 호출하고 사용자에게 정보를 제공하는 것입니다. 이 앱의 경우 단추는 Save
메모를 저장한 다음 이전 화면으로 다시 탐색합니다.
viewmodel은 UI를 디자인하는 방법 또는 데이터를 로드하거나 저장하는 방법에 관계없이 앱 논리를 배치할 수 있는 특정 위치를 앱에 제공합니다. viewmodel은 뷰를 대신하여 데이터 모델을 나타내고 상호 작용하는 접착제입니다.
보기 모델은 ViewModels 폴더에 저장됩니다 .
- Visual Studio의 솔루션 탐색기 창을 찾습니다.
- 메모 프로젝트를 마우스 오른쪽 단추로 클릭하고 새 폴더 추가>를 선택합니다. 폴더 이름을 ViewModels로 지정합니다.
- ViewModels 폴더 >클래스 추가>를 마우스 오른쪽 단추로 클릭하고 이름을 AboutViewModel.cs.
- 이전 단계를 반복하고 두 개의 뷰 모델을 더 만듭니다.
- NoteViewModel.cs
- NotesViewModel.cs
전체 프로젝트 구조는 다음 이미지와 같아야 합니다.
viewmodel 및 정보 보기 정보
정보 보기는 화면에 일부 데이터를 표시하고 필요에 따라 자세한 정보가 포함된 웹 사이트로 이동합니다. 이 보기에는 텍스트 입력 컨트롤이나 목록에서 항목 선택과 같이 변경할 데이터가 없으므로 viewmodel을 추가하는 방법을 보여 주는 것이 좋습니다. About viewmodel의 경우 지원 모델이 없습니다.
About viewmodel을 만듭니다.
Visual Studio의 솔루션 탐색기 창에서 ViewModels\AboutViewModel.cs 두 번 클릭합니다.
다음 코드를 붙여넣습니다.
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); }
이전 코드 조각에는 이름 및 버전과 같은 앱에 대한 정보를 나타내는 일부 속성이 포함되어 있습니다. 이 코드 조각은 이전에 삭제한 정보 모델 과 정확히 동일합니다. 그러나 이 viewmodel에는 명령 속성이라는 새로운 개념이 ShowMoreInfoCommand
포함되어 있습니다.
명령은 코드를 호출하는 바인딩 가능한 작업이며 앱 논리를 배치하기에 좋은 장소입니다. 이 예제에서는 ShowMoreInfoCommand
웹 브라우저를 ShowMoreInfo
특정 페이지로 여는 메서드를 가리킵니다. 다음 섹션에서 명령 시스템에 대해 자세히 알아봅니다.
보기 정보
이전 섹션에서 만든 viewmodel에 연결하려면 정보 보기를 약간 변경해야 합니다. Views\AboutPage.xaml 파일에서 다음 변경 내용을 적용합니다.
- XML 네임스페이
xmlns:models
스를 업데이트하고xmlns:viewModels
.NET 네임스페이스를Notes.ViewModels
대상으로 지정합니다. ContentPage.BindingContext
속성을 viewmodel의 새 인스턴스로About
변경합니다.- 단추의
Clicked
이벤트 처리기를 제거하고 속성을 사용합니다Command
.
정보 보기를 업데이트합니다.
Visual Studio의 솔루션 탐색기 창에서 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:viewModels="clr-namespace:Notes.ViewModels" x:Class="Notes.Views.AboutPage"> <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
사용자가 컨트롤과 상호 작용할 때 호출되는 속성이 있습니다. 단추와 함께 사용하면 viewmodel의 속성에 바인딩 Command
할 수 있다는 점을 제외하고 이벤트 처리기가 호출되는 방식 Clicked
과 유사하게 사용자가 단추를 누를 때 명령이 호출됩니다.
이 보기에서 사용자가 단추를 Command
누르면 호출됩니다. viewmodel Command
의 ShowMoreInfoCommand
속성에 바인딩되고 호출될 때 메서드에서 ShowMoreInfo
코드를 실행하여 웹 브라우저를 특정 페이지로 엽니다.
코드 숨김 정보 정리
단추는 ShowMoreInfo
이벤트 처리기를 사용하지 않으므로 LearnMore_Clicked
Views\AboutPage.xaml.cs 파일에서 코드를 제거해야 합니다. 해당 코드를 삭제하면 클래스는 생성자만 포함해야 합니다.
Visual Studio의 솔루션 탐색기 창에서 Views\AboutPage.xaml.cs 두 번 클릭합니다.
팁
파일을 표시하려면 Views\AboutPage.xaml을 확장해야 할 수 있습니다.
해당 코드를 다음 코드 조각으로 바꿉니다.
namespace Notes.Views; public partial class AboutPage : ContentPage { public AboutPage() { InitializeComponent(); } }
Note viewmodel 만들기
메모 보기를 업데이트하는 목표는 XAML 코드 숨김에서 최대한 많은 기능을 이동하여 Note viewmodel에 배치하는 것입니다.
참고 viewmodel
참고 보기에 필요한 항목에 따라 Note viewmodel은 다음 항목을 제공해야 합니다.
- 메모의 텍스트입니다.
- 메모를 만들거나 마지막으로 업데이트한 날짜/시간입니다.
- 메모를 저장하는 명령입니다.
- 메모를 삭제하는 명령입니다.
Note viewmodel을 만듭니다.
Visual Studio의 솔루션 탐색기 창에서 ViewModels\NoteViewModel.cs 두 번 클릭합니다.
이 파일의 코드를 다음 코드 조각으로 바꿉다.
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
viewmodel입니다. 네임스페이CommunityToolkit.Mvvm.ComponentModel
스를 가져오고 있습니다. 이 네임스페이ObservableObject
스는 기본 클래스로 사용되는 것을 제공합니다. 다음 단계에서 자세히ObservableObject
알아봅니다.CommunityToolkit.Mvvm.Input
네임스페이스도 가져옵니다. 이 네임스페이스는 메서드를 비동기적으로 호출하는 몇 가지 명령 형식을 제공합니다.Models.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;
및
Identifier
속성은Date
모델에서 해당 값을 검색하는 간단한 속성입니다.팁
속성의 경우 구문은
=>
오른쪽=>
에 있는 문이 반환할 값으로 계산되어야 하는 get-only 속성을 만듭니다.이 속성은
Text
먼저 설정되는 값이 다른 값인지 확인합니다. 값이 다르면 해당 값이 모델의 속성에 전달되고 메서드가OnPropertyChanged
호출됩니다.메서드는
OnPropertyChanged
기본 클래스에서ObservableObject
제공됩니다. 이 메서드는 호출 코드의 이름(이 경우 Text의 속성 이름)을 사용하여 이벤트를 발생합니다ObservableObject.PropertyChanged
. 이 이벤트는 모든 이벤트 구독자에게 속성의 이름을 제공합니다. .NET MAUI에서 제공하는 바인딩 시스템은 이 이벤트를 인식하고 UI의 모든 관련 바인딩을 업데이트합니다. Note viewmodel의 경우 속성이Text
변경되면 이벤트가 발생하고 속성에Text
바인딩된 모든 UI 요소에 속성이 변경되었다는 알림이 표시됩니다.뷰가 바인딩할 수 있는 명령인 다음 명령 속성을 클래스에 추가합니다.
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); }
이러한 두 생성자는 빈 메모인 새 백업 모델을 사용하여 viewmodel을 만들거나 지정된 모델 인스턴스를 사용하는 viewmodel을 만드는 데 사용됩니다.
또한 생성자는 viewmodel에 대한 명령을 설치합니다. 다음으로, 이러한 명령에 대한 코드를 추가합니다.
및
Delete
메서드를Save
추가합니다.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}"); }
이러한 메서드는 연결된 명령에 의해 호출됩니다. 모델에서 관련 작업을 수행하고 앱이 이전 페이지로 이동하도록 합니다. 수행된 작업과 메모의 고유 식별자를 나타내는 쿼리 문자열 매개 변수가 탐색 경로에 추가
..
됩니다.다음으로, 인터페이스의
ApplyQueryAttributes
요구 사항을 충족하는 메서드를 클래스에 IQueryAttributable 추가합니다.void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query) { if (query.ContainsKey("load")) { _note = Models.Note.Load(query["load"].ToString()); RefreshProperties(); } }
페이지 또는 페이지의 바인딩 컨텍스트가 이 인터페이스를 구현하면 탐색에 사용되는 쿼리 문자열 매개 변수가 메서드에
ApplyQueryAttributes
전달됩니다. 이 viewmodel은 메모 보기에 대한 바인딩 컨텍스트로 사용됩니다. 메모 보기를 탐색할 때 뷰의 바인딩 컨텍스트(이 viewmodel)는 탐색 중에 사용되는 쿼리 문자열 매개 변수를 전달합니다.이 코드는 키가 사전에 제공되었는지
query
확인load
합니다. 이 키를 찾은 경우 값은 로드할 메모의 식별자(파일 이름)여야 합니다. 이 메모는 로드되고 이 viewmodel 인스턴스의 기본 모델 개체로 설정됩니다.마지막으로 다음 두 도우미 메서드를 클래스에 추가합니다.
public void Reload() { _note = Models.Note.Load(_note.Filename); RefreshProperties(); } private void RefreshProperties() { OnPropertyChanged(nameof(Text)); OnPropertyChanged(nameof(Date)); }
이
Reload
메서드는 백업 모델 개체를 새로 고쳐 디바이스 스토리지에서 다시 로드하는 도우미 메서드입니다.이
RefreshProperties
메서드는 이 개체에 바인딩된 모든 구독자에게 해당 및Date
속성이Text
변경되었다는 알림을 받도록 하는 또 다른 도우미 메서드입니다. 탐색Text
중에 노트가 로드될 때 기본 모델(_note
필드)이 변경되므로 속성은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));
}
}
참고 보기
viewmodel을 만들었으므로 메모 보기를 업데이트합니다. Views\NotePage.xaml 파일에서 다음 변경 내용을 적용합니다.
xmlns:viewModels
.NET 네임스페이스를 대상으로 하는 XML 네임스페이스를 추가합니다Notes.ViewModels
.- 페이지에 a
BindingContext
를 추가합니다. - 삭제 및 저장 단추
Clicked
이벤트 처리기를 제거하고 명령으로 바꿉니다.
메모 보기를 업데이트합니다.
- Visual Studio의 솔루션 탐색기 창에서 Views\NotePage.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"
xmlns:viewModels="clr-namespace:Notes.ViewModels"
x:Class="Notes.Views.NotePage"
Title="Note">
<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에서 직접 바인딩 컨텍스트를 설정하면 다음 두 가지가 제공됩니다.
런타임에 페이지가 탐색되면 빈 메모가 표시됩니다. 이는 바인딩 컨텍스트에 대한 매개 변수가 없는 생성자인 viewmodel이 호출되기 때문입니다. 올바르게 기억하면 Note viewmodel에 대한 매개 변수가 없는 생성자가 빈 메모를 만듭니다.
XAML 편집기에서 intellisense는 구문 입력을
{Binding
시작하는 즉시 사용 가능한 속성을 표시합니다. 구문도 유효성을 검사하고 잘못된 값을 경고합니다. 에 대한SaveCommand
바인딩 구문을 변경해Save123Command
보세요. 마우스 커서를 텍스트 위로 가져가면 Save123Command를 찾을 수 없다는 것을 알리는 도구 설명이 표시됩니다. 바인딩이 동적이므로 이 알림은 오류로 간주되지 않습니다. 잘못된 속성을 입력했을 때 알아차릴 수 있는 작은 경고입니다.SaveCommand를 다른 값으로 변경한 경우 지금 복원합니다.
메모 코드 숨김 정리
이제 뷰와의 상호 작용이 이벤트 처리기에서 명령으로 변경되었으므로 Views\NotePage.xaml.cs 파일을 열고 모든 코드를 생성자만 포함하는 클래스로 바꿉니다.
Visual Studio의 솔루션 탐색기 창에서 Views\NotePage.xaml.cs 두 번 클릭합니다.
팁
파일을 표시하려면 Views\NotePage.xaml을 확장해야 할 수 있습니다.
해당 코드를 다음 코드 조각으로 바꿉니다.
namespace Notes.Views; public partial class NotePage : ContentPage { public NotePage() { InitializeComponent(); } }
Notes viewmodel 만들기
최종 viewmodel-view 쌍은 Notes viewmodel 및 AllNotes 보기입니다. 그러나 현재 보기는 이 자습서의 시작 부분에 삭제된 모델에 직접 바인딩됩니다. AllNotes 보기를 업데이트하는 목표는 XAML 코드 숨김에서 최대한 많은 기능을 이동하고 viewmodel에 배치하는 것입니다. 다시 말하지만, 보기가 코드에 거의 영향을 미치지 않고 디자인을 변경할 수 있다는 이점이 있습니다.
Notes viewmodel
AllNotes 보기가 표시할 내용과 사용자가 수행할 상호 작용에 따라 Notes viewmodel은 다음 항목을 제공해야 합니다.
- 노트의 컬렉션입니다.
- 메모 탐색을 처리하는 명령입니다.
- 새 메모를 만드는 명령입니다.
- 노트 목록을 만들거나 삭제하거나 변경할 때 업데이트합니다.
Notes viewmodel을 만듭니다.
Visual Studio의 솔루션 탐색기 창에서 ViewModels\NotesViewModel.cs 두 번 클릭합니다.
이 파일의 코드를 다음 코드로 바꿉다.
using CommunityToolkit.Mvvm.Input; using System.Collections.ObjectModel; using System.Windows.Input; namespace Notes.ViewModels; internal class NotesViewModel: IQueryAttributable { }
이 코드는 보기를 지원하는 속성 및 명령을 추가할 빈
NotesViewModel
코드입니다AllNotes
.클래스 코드에서
NotesViewModel
다음 속성을 추가합니다.public ObservableCollection<ViewModels.NoteViewModel> AllNotes { get; } public ICommand NewCommand { get; } public ICommand SelectNoteCommand { get; }
이
AllNotes
속성은 디바이스에서 로드된 모든 노트를 저장하는 속성입니다ObservableCollection
. 두 명령은 뷰에서 노트를 만들거나 기존 메모를 선택하는 작업을 트리거하는 데 사용됩니다.매개 변수가 없는 생성자를 클래스에 추가합니다. 이 생성자는 명령을 초기화하고 모델에서 노트를 로드합니다.
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
컬렉션입니다. 이 코드는 Linq 확장을 사용하여Select
반환LoadAll
된 메모 모델에서 viewmodel 인스턴스를 만듭니다.명령의 대상이 되는 메서드를 만듭니다.
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
취하지 않습니다. 명령에는 필요에 따라 명령이 호출될 때 제공되는 단일 매개 변수가 있을 수 있습니다. 메서드의SelectNoteAsync
경우 매개 변수는 선택 중인 메모를 나타냅니다.마지막으로 다음 메서드를 구현합니다
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))); } }
이전 자습서 단계에서 만든 Note viewmodel은 메모를 저장하거나 삭제할 때 탐색을 사용했습니다. viewmodel은 이 viewmodel이 연결된 AllNotes 보기로 다시 이동했습니다. 이 코드는 쿼리 문자열에 키 또는
saved
키가 포함되어 있는지 검색합니다deleted
. 키 값은 메모의 고유 식별자입니다.메모가 삭제된 경우 해당 메모는 제공된 식별자에 의해 컬렉션에서
AllNotes
일치하고 제거됩니다.메모가 저장되는 이유는 두 가지가 있습니다. 메모가 방금 만들어졌거나 기존 메모가 변경되었습니다. 메모가 컬렉션에
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을 만들었으므로 이제 viewmodel 속성을 가리키도록 AllNotes 보기를 업데이트합니다. Views\AllNotesPage.xaml 파일에서 다음 변경 내용을 적용합니다.
xmlns:viewModels
.NET 네임스페이스를 대상으로 하는 XML 네임스페이스를 추가합니다Notes.ViewModels
.- 페이지에 a
BindingContext
를 추가합니다. - 도구 모음 단추의
Clicked
이벤트를 제거하고 속성을 사용합니다Command
. - 바인딩할 값을
CollectionView
변경합니다AllNotes
.ItemSource
- 선택한 항목이
CollectionView
변경될 때 대응하도록 명령을 사용하도록 변경합니다.
AllNotes 보기를 업데이트합니다.
Visual Studio의 솔루션 탐색기 창에서 Views\AllNotesPage.xaml을 두 번 클릭합니다.
다음 코드를 붙여넣습니다.
<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"> <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 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> <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
에서 속성은 viewmodel에 SelectNoteCommand
바인딩됩니다. 즉, 선택한 항목이 변경될 때 명령이 호출됩니다. 명령이 호출되면 SelectionChangedCommandParameter
속성 값이 명령에 전달됩니다.
에 사용되는 바인딩을 확인합니다 CollectionView
.
<CollectionView x:Name="notesCollection"
ItemsSource="{Binding AllNotes}"
Margin="20"
SelectionMode="Single"
SelectionChangedCommand="{Binding SelectNoteCommand}"
SelectionChangedCommandParameter="{Binding Source={RelativeSource Self}, Path=SelectedItem}">
이 속성은 SelectionChangedCommandParameter
바인딩을 사용합니다 Source={RelativeSource Self}
. 현재 Self
개체(. CollectionView
)를 참조합니다. 바인딩 경로는 속성입니다 SelectedItem
. 선택한 항목을 SelectNoteCommand
변경하여 명령을 호출하면 명령이 호출되고 선택한 항목이 매개 변수로 명령에 전달됩니다.
AllNotes 코드 숨김 정리
이제 뷰와의 상호 작용이 이벤트 처리기에서 명령으로 변경되었으므로 Views\AllNotesPage.xaml.cs 파일을 열고 모든 코드를 생성자만 포함하는 클래스로 바꿉니다.
Visual Studio의 솔루션 탐색기 창에서 Views\AllNotesPage.xaml.cs 두 번 클릭합니다.
팁
파일을 표시하려면 Views\AllNotesPage.xaml을 확장해야 할 수 있습니다.
해당 코드를 다음 코드 조각으로 바꿉니다.
namespace Notes.Views; public partial class AllNotesPage : ContentPage { public AllNotesPage() { InitializeComponent(); } }
앱 실행
이제 앱을 실행할 수 있으며 모든 것이 작동합니다. 그러나 앱의 작동 방식에는 두 가지 문제가 있습니다.
- 편집기를 여는 노트를 선택하고 저장을 누른 다음 동일한 메모를 선택하려고 하면 작동하지 않습니다.
- 메모가 변경되거나 추가될 때마다 노트 목록은 맨 위에 최신 노트를 표시하도록 순서가 다시 지정되지 않습니다.
이러한 두 가지 문제는 다음 자습서 단계에서 해결됩니다.
앱 동작 수정
이제 앱 코드가 컴파일되고 실행될 수 있으므로 앱 작동 방식에 두 가지 결함이 있음을 알게 될 것입니다. 앱에서는 이미 선택된 노트를 다시 선택할 수 없으며 노트를 만들거나 변경한 후에는 노트 목록이 다시 정렬되지 않습니다.
목록 맨 위에 있는 노트 가져오기
먼저 노트 목록의 순서 다시 지정 문제를 해결합니다. ViewModels\NotesViewModel.cs 파일에서 AllNotes
컬렉션에는 사용자에게 표시할 모든 노트가 포함됩니다. 아쉽게도 사용의 단점 ObservableCollection
은 수동으로 정렬해야 한다는 것입니다. 새 항목 또는 업데이트된 항목을 목록 맨 위로 가져오려면 다음 단계를 수행합니다.
Visual Studio의 솔루션 탐색기 창에서 ViewModels\NotesViewModel.cs 두 번 클릭합니다.
ApplyQueryAttributes
메서드에서 저장된 쿼리 문자열 키에 대한 논리를 확인합니다.matchedNote
그렇지 않으면null
메모가 업데이트됩니다. 이 메서드를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
두 개의 매개 변수를 사용하여 컬렉션에서 개체의 위치를 이동합니다. 첫 번째 매개 변수는 이동할 개체의 인덱스이고, 두 번째 매개 변수는 개체를 이동할 위치의 인덱스입니다. 메서드는AllNotes.IndexOf
메모의 인덱스를 검색합니다.이
matchedNote
null
경우 메모가 새로 추가되고 목록에 추가됩니다. 목록 끝에 메모를 추가하는 대신 목록 맨 위에 있는 인덱스 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)));
}
}
메모를 두 번 선택하도록 허용
AllNotes 보기CollectionView
에서 모든 노트를 나열하지만 동일한 노트를 두 번 선택할 수는 없습니다. 항목이 선택된 상태로 유지되는 방법에는 사용자가 기존 메모를 변경하는 경우와 사용자가 강제로 뒤로 이동하는 경우의 두 가지가 있습니다. 사용자가 메모를 저장하는 경우는 이전 섹션에서 사용하는 AllNotes.Move
코드 변경으로 수정되므로 해당 사례에 대해 걱정할 필요가 없습니다.
지금 해결해야 하는 문제는 탐색과 관련이 있습니다. Allnotes 보기를 탐색하는 NavigatedTo
방법에 관계없이 페이지에 대한 이벤트가 발생합니다. 이 이벤트는 에서 선택한 항목을 CollectionView
강제로 선택 취소하기에 완벽한 장소입니다.
그러나 여기에 MVVM 패턴이 적용되면 viewmodel은 노트를 저장한 후 선택한 항목을 지우는 등 보기에서 직접 무언가를 트리거할 수 없습니다. 그렇다면 어떻게 해야 할까요? MVVM 패턴을 잘 구현하면 뷰의 코드 숨김이 최소화됩니다. MVVM 분리 패턴을 지원하기 위해 이 문제를 해결하는 몇 가지 방법이 있습니다. 그러나 뷰의 코드 숨김에 코드를 배치하는 것도 괜찮습니다. 특히 뷰에 직접 연결된 경우 그렇습니다. MVVM에는 앱을 구획화하고 유지 관리 기능을 개선하며 새 기능을 더 쉽게 추가할 수 있도록 도와주는 많은 훌륭한 디자인과 개념이 있습니다. 그러나 경우에 따라 MVVM이 과잉 학습을 권장하는 경우가 있습니다.
이 문제에 대한 해결 방법을 재정의하지 말고 이벤트를 사용하여 NavigatedTo
선택한 항목을 CollectionView
선택 취소합니다.
Visual Studio의 솔루션 탐색기 창에서 Views\AllNotesPage.xaml을 두 번 클릭합니다.
에 대한 XAML에서
<ContentPage>
이벤트를 추가합니다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"> <ContentPage.BindingContext> <viewModels:NotesViewModel /> </ContentPage.BindingContext>
이벤트 메서드 이름을
ContentPage_NavigatedTo
마우스 오른쪽 단추로 클릭하고 정의로 이동을 선택하여 기본 이벤트 처리기를 추가할 수 있습니다. 이 작업은 코드 편집기 에서 Views\AllNotesPage.xaml.cs 엽니다.이벤트 처리기 코드를 다음 코드 조각으로 바꿉다.
private void ContentPage_NavigatedTo(object sender, NavigatedToEventArgs e) { notesCollection.SelectedItem = null; }
XAML에서 이름이
CollectionView
지정notesCollection
되었습니다. 이 코드는 해당 이름을 사용하여CollectionView
에 액세스하고 로 설정합니다SelectedItem
null
. 선택한 항목은 페이지가 탐색될 때마다 지워집니다.
이제 앱을 실행합니다. 메모로 이동하고 뒤로 단추를 누른 다음 같은 메모를 두 번째로 선택합니다. 앱 동작이 수정되었습니다.
이 자습서의 코드를 살펴보세요. 완료된 프로젝트의 복사본을 다운로드하여 코드를 비교하려면 이 프로젝트를 다운로드합니다.
축하합니다!
이제 앱에서 MVVM 패턴을 사용하고 있습니다.
다음 단계
다음 링크는 이 자습서에서 배운 몇 가지 개념과 관련된 자세한 정보를 제공합니다.
본 섹션과 관련하여 문제가 있으십니까? 문제가 있으시면 본 섹션을 개선하기 위해 피드백을 제출해 주세요.
.NET MAUI