이번 포스팅에서는 도서 추가 기능과 삭제 기능을 구현해 볼 예정입니다.아래와 같은 과정으로 진행할 예정입니다.
- 도서 리스트 화면 (MainPage.xaml) 화면 변경 (AddBook 버튼 및 Delete 버튼 추가)
- 도서 추가를 위한 AddBook 페이지 추가
- 도서 추가와 삭제를 위한 ViewModel 수정
MainPage.xaml 수정
아래와 같이 MainPage 화면 레이아웃을 수정합니다.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="MyBookStore.MainPage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:model="clr-namespace:MyBookStore.Models"
xmlns:viewmodel="clr-namespace:MyBookStore.ViewModels"
Title="{Binding Title}"
x:DataType="viewmodel:BookListViewModel">
<Grid
Padding="5"
ColumnSpacing="1"
RowSpacing="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<VerticalStackLayout
Grid.Row="0"
Margin="5"
Padding="5">
<Button
Margin="5"
Command="{Binding GoToAddBookPageCommand}"
Text="Add Book" />
<Button
Margin="5"
Command="{Binding LoadBooksCommand}"
IsEnabled="{Binding IsNotLoading}"
Text="Search Book" />
</VerticalStackLayout>
<RefreshView
Grid.Row="1"
Command="{Binding LoadBooksCommand}"
IsRefreshing="{Binding IsRefreshing}">
<CollectionView
ItemsSource="{Binding Books}"
SelectionMode="Single"
VerticalScrollBarVisibility="Always">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:Book">
<Grid Padding="10" ColumnDefinitions="*,auto">
<Frame Grid.Column="0" HeightRequest="90">
<Frame.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:BookListViewModel}}, Path=GetBookDetailCommand}" CommandParameter="{Binding Id}" />
</Frame.GestureRecognizers>
<HorizontalStackLayout Padding="10" Spacing="5">
<Label Text="{Binding Id}" />
<Label Text="{Binding BookTitle}" />
</HorizontalStackLayout>
</Frame>
<HorizontalStackLayout Grid.Column="1" Padding="5">
<Button
Margin="10"
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:BookListViewModel}}, Path=DeleteBookCommand}"
CommandParameter="{Binding Id}"
Text="Delete" />
</HorizontalStackLayout>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</RefreshView>
</Grid>
</ContentPage>
이전 포스팅의 내용과 거의 동일하지만 추가된 부분 중 중요한 부분은 아래 부분입니다.
<Button
Margin="5"
Command="{Binding GoToAddBookPageCommand}"
Text="Add Book" />
...
...
<HorizontalStackLayout Grid.Column="1" Padding="5">
<Button
Margin="10"
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:BookListViewModel}}, Path=DeleteBookCommand}"
CommandParameter="{Binding Id}"
Text="Delete" />
</HorizontalStackLayout>
“Add Book” 이라는 버튼을 화면 상단에 추가하였으며 해당 버튼을 누르면 BookListViewModel 의 GoToAddBookPageCommand 를 호출하게 됩니다. 도서 리스트를 표현하는 화면에서는 각각의 도서 리스트 옆에 “Delete” 라는 버튼을 추가하였습니다.
Add Book 기능 추가
먼저 “도서 추가” 기능을 추가해 보겠습니다. View 에서는 이미 “Add Book” 이라는 버튼을 추가하였고, 해당 버튼에 바인딩된 Command 를 ViewModel 에 생성해야 합니다.
[RelayCommand]
public async Task GoToAddBookPage()
{
await Shell.Current.GoToAsync($"{nameof(AddBookPage)}");
}
GoToAddBookPage 메서드는 단순히 AddBookPage 로 화면을 이동시켜주고 있습니다. 이제 추가되는 AddBookPage 의 내용을 살펴보겠습니다.
AddBookPage 추가
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="MyBookStore.Views.AddBookPage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodel="clr-namespace:MyBookStore.ViewModels"
Title="AddBookPage"
x:DataType="viewmodel:BookListViewModel">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackLayout Grid.Row="0">
<Entry
x:Name="Title"
Placeholder="Enter Book Title"
Text="{Binding BookTitle}" />
<Entry
x:Name="Author"
Placeholder="Enter Author Name"
Text="{Binding Author}" />
<Entry
x:Name="Genre"
Placeholder="Enter Genre"
Text="{Binding Genre}" />
<Entry
x:Name="Year"
Placeholder="Enter Year"
Text="{Binding Year}" />
</StackLayout>
<Button
Grid.Row="1"
Margin="10"
Command="{Binding AddBookCommand}"
HorizontalOptions="Center"
IsEnabled="{Binding IsNotLoading}"
Text="Save" />
</Grid>
</ContentPage>
public partial class AddBookPage : ContentPage
{
public AddBookPage(BookListViewModel bookListViewModel)
{
InitializeComponent();
BindingContext = bookListViewModel;
}
}
AddBookPage 의 화면은 아래 그림에서 보듯이 간단하게 구성되어 있습니다. Grid Layout 을 사용하여 Book 의 속성을 입력 받는 Entry 를 구성하였고, BindingContext 는 BookListViewModel 로 설정하였습니다.
아래와 같은 과정으로 새로운 도서 정보를 추가할 예정입니다.
- AddBookPage 에서 도서의 상세 정보를 입력하고 “Save” 버튼을 클릭
- “Save” 버튼을 누르면 BookListViewModel 의 AddBookCommand 실행
이제 ViewModel 을 수정해 보겠습니다.
ViewModel 기능 추가
[ObservableProperty]
private string author;
[ObservableProperty]
private string year;
[ObservableProperty]
private string genre;
[ObservableProperty]
private string bookTitle;
...
...
[RelayCommand]
public async Task AddBook()
{
if (string.IsNullOrEmpty(BookTitle) || string.IsNullOrEmpty(Author) || string.IsNullOrEmpty(Genre) || string.IsNullOrEmpty(Year))
{
await Shell.Current.DisplayAlert("Error", "모든 필드를 입력하세요.", "OK");
return;
}
var book = new Book
{
BookTitle = BookTitle,
Author = Author,
Genre = Genre,
Year = int.Parse(Year)
};
bookService.AddBook(book);
await LoadBooks();
}
BookListViewModel 클래스에는 Book 의 속성에 해당되는 네개의 필드가 ObservableProperty 로 정의되어 있는 것을 확인할 수 있습니다. AddBookPage 는 이 속성들과 Binding 되어 있기 때문에 페이지에서 입력한 값이 해당 속성에 자동으로 업데이트 됩니다. 바인딩 타겟에서 바인딩 소스로 변경값이 반영되는 것이라고 이해하시면 됩니다. AddBook 메서드에서는 변경된 속성값을 기반으로 새로운 Book 객체를 만들고 BookService 의 AddBook 을 호출하고 있습니다.
public void AddBook(Book book)
{
book.Id = books.Max(b => b.Id) + 1;
books.Add(book);
}
AddBook 메서드에서는 파라미터로 넘어온 새로운 Book 객체를 기존의 Book 정보를 저장하고 있는 books 컬렉션에 추가하는 역할을 수행합니다. 이렇게 하면 새롭게 입력한 정보를 기반으로 새로운 도서가 추가되게 되는 것이죠. . 이후, BookListViewModel 에서 LoadBooks 메서드를 호출하게 되면 여러개의 Book 정보를 담고 있는 ObservableCollection 이 새로운 내용으로 갱신됩니다. MainPage 는 BookListViewModel 의 ObservableCollection 과 Binding 되어 있기 때문에 해당 컬렉션의 변동 사항을 자동으로 감지해서 화면을 갱신하게 됩니다. 아래는 추가된 도서 정보를 보여주고 있는 화면입니다.
Delete Book 기능 추가
“도서 삭제” 기능 구현은 더 간단합니다. View 에서 정의한 Delete 버튼과 바인딩된 Command 를 ViewModel 에 생성해 보겠습니다.
[RelayCommand]
public async Task DeleteBook(int id)
{
if (id == 0) return;
bookService.DeleteBook(id);
await LoadBooks();
}
DeleteBook 메서드는 BookService 의 DeleteBook 을 호출하고 있습니다.
public void DeleteBook(int id)
{
var book = books.FirstOrDefault(b => b.Id == id);
if (book != null)
{
books.Remove(book);
}
}
AddBook 기능과 마찬가지로 DeleteBook 에서도 파라미터로 받은 id 값을 갖고 있는 Book 객체를 books 컬렉션에서 제거합니다. 삭제한 후 다시 LoadBooks() 를 호출하면 BookListViewModel 에 있는 ObservableCollection 이 변경되게 됩니다. 이 변경 내용은 View 에 전파가 되어 화면까지 갱신됩니다.