[MAUI 활용] BookStore 만들기 (8) – Book List 페이지 만들기 Part2

이제 실제 페이지에 도서 정보를 조회하는 기능을 추가해 보겠습니다. 이 페이지는 책 목록을 불러와서 화면에 보여주는  역할을 합니다. 페이지 상단에는 책 목록이 CollectionView를 통해 표시되며, 사용자가 Get Books 버튼을 클릭하면 BookListViewModel 에 있는  LoadBooksCommand가 실행되어 Books 컬렉션에 데이터를 로드합니다. 로딩이 완료되면  View 에서는 자동으로 데이터 변경을 감지하여 화면에 데이터를 표시하며, 각 항목은 책의 ID와 제목을 나란히 보여줍니다.

우선 완성된 코드를 확인하고 자세하게 설명드리겠습니다.

  • MainPage.xaml
<?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
        ColumnDefinitions="*,*"
        ColumnSpacing="5"
        RowDefinitions="*, Auto">
        <CollectionView
            Grid.ColumnSpan="2"
            ItemsSource="{Binding Books}"
            SelectionMode="Single">
            <CollectionView.ItemTemplate>
                <DataTemplate x:DataType="model:Book">
                    <Frame HeightRequest="90">
                        <HorizontalStackLayout Padding="10" Spacing="5">
                            <Label Text="{Binding Id}" />
                            <Label Text="{Binding Title}" />
                        </HorizontalStackLayout>
                    </Frame>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
        <Button
            Grid.Row="1"
            Grid.Column="0"
            Command="{Binding LoadBooksCommand}"
            IsEnabled="{Binding IsNotLoading}"
            Text="Get Books" />
    </Grid>
</ContentPage>

 

소스의 각 요소에 대해서 자세히 설명드리겠습니다.  처음 도입 부분은 이전 포스트에서 설명드린 내용이지만, 한번 더 설명하겠습니다. 

  • xmlns:model=”clr-namespace:MyBookStore.Models”: MyBookStore.Models 네임스페이스를 참조합니다. 이 네임스페이스는 모델, 즉 데이터를 정의한 클래스가 있는 곳입니다.
  • xmlns:viewmodel=”clr-namespace:MyBookStore.ViewModels”: MyBookStore.ViewModels 네임스페이스를 참조하며, 여기에는 뷰 모델이 정의되어 있습니다. 이 뷰 모델은 UI에 필요한 데이터를 제공하고 로직을 관리합니다.
  • Title=”{Binding Title}”: BookListViewModel의 Title 속성을 페이지의 제목으로 바인딩합니다.
  • x:DataType=”viewmodel:BookListViewModel”: 페이지에서 사용되는 데이터는 BookListViewModel에 기반한다는 것을 명시합니다. 이는 컴파일 타임 데이터 바인딩을 가능하게 하여 성능을 향상시킵니다.
<Grid
    ColumnDefinitions="*,*"
    ColumnSpacing="5"
    RowDefinitions="*, Auto">
  • ColumnDefinitions=”*,*”: 두 개의 열이 설정되며, 두 열은 각각 동일한 비율로 남은 화면 공간을 나눕니다.
  • ColumnSpacing=”5″: 두 열 사이에 5픽셀의 간격을 둡니다.
  • RowDefinitions=”*, Auto”: 첫 번째 행은 남은 공간을 모두 차지하고, 두 번째 행은 Auto로 설정되어 요소의 높이에 맞춰 자동으로 조정됩니다.
<CollectionView
    Grid.ColumnSpan="2"
    ItemsSource="{Binding Books}"
    SelectionMode="Single">
    <CollectionView.ItemTemplate>
        <DataTemplate x:DataType="model:Book">
            <Frame HeightRequest="90">
                <HorizontalStackLayout Padding="10" Spacing="5">
                    <Label Text="{Binding Id}" />
                    <Label Text="{Binding BookTitle}" />
                </HorizontalStackLayout>
            </Frame>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

CollectionView는 데이터 목록을 표시하는 데 사용됩니다. 여기에서는 도서 목록을 표시하는데 사용됩니다.  Book 클래스의 속성인 Id, Title 의 값을 갖고 오기 위해서 {Binding Id}, {Binding Title} 이라고 표시하였습니다. 

  • Grid.ColumnSpan=”2″: 이 컨트롤이 그리드의 두 개의 열 전체를 차지하도록 설정합니다.
  • ItemsSource=”{Binding Books}”: BookListViewModel의 Books 컬렉션에 바인딩하여 책 데이터를 가져옵니다.
  • SelectionMode=”Single”: 사용자가 하나의 항목만 선택할 수 있도록 설정합니다.
  • CollectionView.ItemTemplate 을 사용하여  도서 항목의 표시 형식을 정의합니다.
  • <DataTemplate x:DataType=”model:Book”>: 데이터 템플릿은 Book 모델을 기반으로 하며, 각 항목을 어떻게 표시할지 정의합니다. 이전 포스팅에서 설명했듯이 Compiled Binding 을 위해서 부모 요소에서  x:DataType=”x:DataType=”viewmodel:BookListViewModel” 이라고 정의했기 때문에 DataTemplate 에서는 별도로 DataType 을 Book 으로 설정하였습니다. 
  • <Frame HeightRequest=”90″> 각 책 항목을 프레임으로 감싸며, 프레임의 높이를 90픽셀로 설정합니다.
  • <HorizontalStackLayout Padding=”10″ Spacing=”5″> 수평으로 레이블을 배치하는 레이아웃입니다.
  • <Label Text=”{Binding Id}” />  Book 의 Id 속성과 바인딩 되어서 책의 Id를 표시합니다.
  • <Label Text=”{Binding Title}” /> Book 의 Titlte 속성과 바인딩 되어서 Title을 표시합니다.
<Button
    Grid.Row="1"
    Grid.Column="0"
    Command="{Binding LoadBooksCommand}"
    IsEnabled="{Binding IsNotLoading}"
    Text="Get Books" />
  • Grid.Row=”1″: 이 버튼은 그리드의 두 번째 행에 배치됩니다.
  • Grid.Column=”0″: 버튼은 그리드의 첫 번째 열에 배치됩니다.
  • Command=”{Binding LoadBooksCommand}”: 버튼이 클릭되었을 때 LoadBooksCommand가 실행됩니다. 이는 책 데이터를 로드하는 명령이며, BookListViewModel 에 정의되어 있습니다. 
  • IsEnabled=”{Binding IsNotLoading}”: IsNotLoading 속성이 true일 때 버튼이 활성화되며, 로딩 중에는 버튼이 비활성화됩니다.
  • Text=”Get Books”: 버튼에 표시될 텍스트는 “Get Books”입니다.

실행한 화면은 아래와 같습니다. 

도서 정보 조회 화면

화면에서 도서 목록이 표시되는 것을 확인하실 수 있습니다. 

Binding 원리

Binding 에 대해서 좀 더 자세히 알아보겠습니다. 

Model 클래스와 ViewModel의 관계

Book 클래스는 데이터 구조를 정의하는 모델입니다. 모델은 주로 데이터 저장소로부터 데이터를 가져오거나, 데이터를 구조화하는 역할을 담당합니다. ViewModel은 Model을 사용하여 데이터를 관리하고, UI와 상호작용하는 중간 관리자 역할을 합니다. 여기서 중요한 점은, ViewModel이 Model을 포함하고 관리하며, ViewModel을 통해 View에 바인딩되는 것이지 Model 자체가 직접적으로 바인딩되는 것은 아닙니다.

MainPage.xaml에서의 Book 클래스 바인딩

<CollectionView
    Grid.ColumnSpan="2"
    ItemsSource="{Binding Books}"
    SelectionMode="Single">
    <CollectionView.ItemTemplate>
        <DataTemplate x:DataType="model:Book">
            <Frame HeightRequest="90">
                <HorizontalStackLayout Padding="10" Spacing="5">
                    <Label Text="{Binding Id}" />
                    <Label Text="{Binding BookTitle}" />
                </HorizontalStackLayout>
            </Frame>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

여기서 Book 클래스의 속성(Id, Title)이 바인딩되는 위치는 ViewModel이 제공하는 데이터 컬렉션입니다. 우리 코드에서는 BookListViewModel에서 Books라는 ObservableCollection<Book>을 제공하고 있습니다.

[ BookListViewModel.cs ]

public ObservableCollection<Book> Books { get; set; } = new ObservableCollection<Book>();
...

그리고, BookListViewModel 은 MainPage.xaml.cs 에서 아래와 같이 BindingContext 로 지정하였습니다.   이 설정 덕분에 ViewModel 내에서 정의된 모든 속성 및 명령어(Command)는 XAML에서 바인딩이 가능합니다. 그러나 Model인 Book 자체는 ViewModel의 속성에 포함된 형태로 간접적으로 바인딩됩니다.

[ MainPage.xaml.cs ]

 public MainPage(BookListViewModel bookListViewModel)
 {
     InitializeComponent();
     BindingContext = bookListViewModel;
 }
 ...

BookListViewModel 의 Books 컬렉션은 MainPage.xaml에서 다음과 같이 바인딩됩니다.

<CollectionView ItemsSource="{Binding Books}" />
  • ItemsSource=”{Binding Books}”: CollectionView의 데이터 소스로 ViewModel에 정의된 Books 컬렉션을 사용하겠다는 뜻입니다.
  • Books는 ObservableCollection<Book> 타입이므로, Book 객체의 리스트를 담고 있습니다.

따라서 CollectionView는 이 Books 컬렉션에 있는 각각의 Book 객체를 반복해서 보여주게 됩니다. 그리고 DataTemplate은 Book 객체를 어떻게 표시할지 정의합니다. 각 Book 객체는 DataTemplate 내에서 Id와 BookTitle 속성을 바인딩하여 Label로 표시되죠

Book 클래스가 바인딩되는 원리

ViewModel에서 정의된 Books 컬렉션이 Book 객체들을 포함하고 있고, 이 컬렉션이 CollectionView에 바인딩되었기 때문에 CollectionView는 각각의 Book 객체를 템플릿을 사용해 UI로 렌더링할 수 있는 것입니다. 위에서 Books는 ObservableCollection<Book> 타입이므로, ViewModel에서 Books 컬렉션을 통해 Book 객체에 접근할 수 있게 되는 것이죠.

정리

  1. Book 클래스는 데이터를 담는 단순한 모델입니다. 이 클래스는 뷰모델에서 데이터를 구조화하고 관리하는 데 사용됩니다.
  2. ViewModel 은 모델(Book)의 데이터를 담고 있는 컬렉션(Books)을 가지고 있으며, 이 컬렉션을  UI(View) 에 바인딩합니다.
  3. ViewModel에서 제공된 ObservableCollection<Book> 은 CollectionView에 바인딩되어 Book 객체의 목록을 UI에 표시할 수 있게 합니다.
  4. DataTemplate 은 Book 객체를 어떻게 렌더링할지 정의하며, 각각의 Book 객체는 Id와 BookTitle 같은 속성을 Label로 표시합니다.

즉, Book 클래스는 데이터의 구조를 정의하는 역할을 하고, 이 데이터를 사용하여 ViewModel이 UI와 연결되는 역할을 하게 되는 겁니다. 

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다