개발노트

15. [.NET MAUI] SwipeView 만들기(with CollectionView) 본문

앱 개발/.NET MAUI

15. [.NET MAUI] SwipeView 만들기(with CollectionView)

mroh1226 2022. 4. 22. 15:47
반응형

 SwipeView

: 좌우상하 4개의 방향으로 특정 컨트롤을 밀었을때 Item을 노출시키고 Mode라는 Property로  Execute 또는 Reveal 를 선택 할 수 있다. (또한, Command 나 Invoked 로 이벤트 발생 시킬 수 있음)

 

- 참고링크: https://docs.microsoft.com/en-us/dotnet/maui/user-interface/controls/swipeview

 

SwipeView - .NET MAUI

The .NET MAUI SwipeView is a container control that wraps around an item of content, and provides context menu items that are revealed by a swipe gesture.

docs.microsoft.com

​​

CollectionView

: 전체적으로 ListView와 유사하지만, 다음과 같은 차이점이 있다.(제가 생각했을 때 가장 큰 차이점만 나열했습니다.)
1) 데이터목록을 Grid처럼 세로나 가로로 선택하여 표시할수있다.

2) 단일 혹은 다중으로 선택이 가능하다.

3) 데이터 템플릿을 사용하여 각 항목의 모양을 정의할수 있다.

 

- 참고링크: https://docs.microsoft.com/ko-kr/dotnet/maui/user-interface/controls/collectionview/

 

CollectionView - .NET MAUI

.NET MAUI CollectionView는 다양한 레이아웃 사양을 사용하여 선택 가능한 데이터 항목의 정렬 가능한 목록을 표시합니다.

docs.microsoft.com


MVVM 패턴을 유지하기 위해 아래와 같이 작성한다.

- Model:       Quest.cs

- View:         Page22.xaml

- ViewModel: Page22_ViewModel.cs


1. CollectionView에 사용할 Model을 생성한다.

 

Quest.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AppMaui.Models
{
    public class Quest
    {
        public Quest(string QName,int QLevel)
        {
            this.QuestName = QName;
            this.QuestLevel = QLevel;
        }

        public string QuestName { get; set; }
        public int QuestLevel { get; set; }

    }
}

 

 


2. Quest에 데이터값을 넣어주기위해 ViewModel을 아래와같이 작성한다.

Page22_ViewModel.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AppMaui.Models;
using AppMaui.Services;
using System.Windows.Input;

namespace AppMaui.ViewModels
{
    class Page22_ViewModel : Notify
    {
        public ObservableCollection<Quest> _Quests { get; set; } = new ObservableCollection<Quest>();
        
        public ICommand cmd_swipe { get; set; }
        public ICommand cmd_rm { get; set; }
        public INavigation Navigation { get; set; }

        public Page22_ViewModel()
        {
            
        } 
        public Page22_ViewModel(INavigation navigation)
        {
            this.Navigation = navigation;
            Quests.Add(new Quest("수학 문제", 5));
            Quests.Add(new Quest("국어 문제", 3));
            Quests.Add(new Quest("영어 문제", 5));
            Quests.Add(new Quest("사회 문제", 4));
            Quests.Add(new Quest("과학 문제", 2));

            cmd_swipe = new Command(async(object QName) =>
            {
                await App.Current.MainPage.DisplayAlert("확인", QName + "문제로 이동합니다","OK");
                switch (QName.ToString())
                {
                    case "수학 문제": await navigation.PushAsync(new Page30());
                        break;
                    case "국어 문제": await navigation.PushAsync(new Page31());
                        break;
                    case "영어 문제": await navigation.PushAsync(new Page32());
                        break;
                       
                    default: break;
                }

            },
            (object QName) =>
            {

                return true;
            });

            cmd_rm = new Command((object QName) =>
            {
                foreach(Quest quests in Quests.ToList())
                {
                    if(quests.QuestName == QName.ToString())
                    {
                        Quests.Remove(quests);
                    }
                }
                
            }, 
            (object QName) =>
            {
                return true;
            });

        }


        public ObservableCollection<Quest> Quests
        {
            get => _Quests;
            set
            {
                if(_Quests != value)
                {
                    _Quests = value;
                    OnPropertyChanged("Quests");
                }
            }
        }

    }
}

객체, 변수 선언문

1) 문제 이름(QuestName)과 문제 난이도(QuestLevel)을 가지고있는 Quest(Model)를 ObservableCollection<Quest>로 CollectionView의 ItemSource로 넣기위해 생성한다.

2) Left Swipe 때는 문제를 삭제하고(cmd_rm) Right Swipe 일때는 문제 Page로 가기위한(cmd_swipe) Command를 작성한다.

 

cmd_swipe (RightSwipe Command)

3) Page간의 이동을 위해 Navigation을 넣어준다.

4) 위에서 선언한 Quests에 Quest들을 Add 메소드로 몇가지(수학문제, 국어 문제 ...) 넣어준다.

5) CommandParameter로 Label Text에 들어있는 값을 가져오기 위해 Command에 object형 QName을 매개변수로 넣어준다.

6) QName에 따라 이동할 Page의 Navigation을 작성해준다.

 

cmd_rm (LeftSwipe Command)

7) foreach로 Quests에 들어있는 QuestName을 QName과 비교하여 같으면 제거하는(Remove) Command를 작성한다.

*Quests에 .ToList() 를 해줘야 오류가 나지않는다.

 

Why the error Collection was modified; enumeration operation may not execute occurs and how to handle it in C#?

Why the error Collection was modified; enumeration operation may not execute occurs and how to handle it in C#? This error occurs when a looping process is being running on a collection (Ex: List) and the collection is modified (data added or removed) duri

www.tutorialspoint.com

 

8) 지워졌을 때 바로 CollectionView에 적용될 수 있도록 상속받은 Notify Class의 OnPropertyChanged("Quests") 메소드를 추가해준다.

 

*Page30, Page31, Page32 에는 간단한 문제가 적힌 라벨을 띄워주는 Page를 생성해줬고, 값 확인을 위해 DisplayAlert를 사용하였습니다.


 

3. 코드비하인드에 View와 ViewModel을 바인딩시킨다.

Page22.xaml.cs
using AppMaui.ViewModels;
namespace AppMaui;

public partial class Page22 : ContentPage
{
	public Page22()
	{
		InitializeComponent();
		BindingContext = new Page22_ViewModel(Navigation);
	}
}

4. View의 마크업 소스에 아래와 같이 작성한다.

Page22.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"
             x:Class="AppMaui.Page22"
             Title="Page22"
             x:Name="QuestList"
             BackgroundColor="White">

    <NavigationPage.TitleView>
        <Label Text="Page22"/>
    </NavigationPage.TitleView>

    <CollectionView x:Name="QuestCollection" ItemsSource="{Binding Quests}">
        <CollectionView.ItemTemplate>
            <DataTemplate>
                <SwipeView>
                    <SwipeView.RightItems>
                        <SwipeItems Mode="Reveal">
                            <SwipeItem Text= "{Binding QuestLevel, StringFormat='난이도: {0}'}" BackgroundColor="Green"
                                        Command="{Binding Source= {x:Reference QuestList},Path=BindingContext.cmd_swipe}"
                                       CommandParameter="{Binding Source={Reference lb_Quest},Path= Text}"/>
                        </SwipeItems>
                    </SwipeView.RightItems>
                    <SwipeView.LeftItems>
                        <SwipeItems Mode="Execute">
                            <SwipeItem Text="문제 제거" BackgroundColor="Red" 
                                       Command="{Binding Source={x:Reference QuestList},Path=BindingContext.cmd_rm}"
                                       CommandParameter="{Binding Source={Reference lb_Quest},Path= Text}"/>
                        </SwipeItems>
                    </SwipeView.LeftItems>
                    <Frame>
                        <Label x:Name="lb_Quest" Text="{Binding QuestName, Mode=TwoWay}" 
                               HorizontalOptions="Center" Padding="10"/> 
                    </Frame>
                </SwipeView>
                
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>


</ContentPage>

1) CollectionView에 ItemSource를 ViewModel에 있는 Quests와 바인딩해준다.

2) ItemTemplate로 들어가 SwipeView를 작성한다.

3) SwipeView.RightItems에 오른쪽으로 스윕했을 경우 나타낼 Item을 작성한다.

4) SwipeView.LeftItems에 왼쪽으로 스윕했을 경우 나타낼 Item을 작성한다.

5) SwipeItems의 Mode를 "Reveal"로 주면 스윕 했을 때 Item이 열리고 자동으로 닫히지않고 그대로 있는다.

6) SwipeItems의 Mode를 "Execute"로 주면 스윕을 길게 했을 때 Item이 열리고 자동으로 Command가 실행된다.

7) 각각의 SwipeItem에 Command와 View에서 ViewModel로 값을 전달받을 CommandParameter를 작성한다.

8) CollectionView에 바인딩된 Quests의 QuestName 값을 보여줄 Label을 생성 및 바인딩한다.

Label은 꼭 <Frame>으로 감싸줘야 정상작동한다.


Command 주의사항

- 문제: SwipeView에 단순히 ViewModel에서 정의한 Command명를 추가했더니 동작하지않는다.

- 해결: Command가 동작하기까지 끊임없이 구글링을 하고 테스트 해본 결과 Binding Source를 사용하여 Path에 View의 Name부터 BindingContext, Command명 까지 지정해줘야한다. 

 

Binding 문구를 아래와 같이 작성한 뒤 테스트해본 결과, 드디어 작동한다.

Command="{Binding Source= {x:Reference QuestList},Path=BindingContext.cmd_swipe}"

따라서 Page에 x:Name을 정해주고 그 Name부터 Command 명까지 작성해줘야한다.

 

CommandParameter

CommandParameter="{Binding Source={Reference lb_Quest},Path= Text}"/>

- 여기서 CommandParameter의 역할은 Label에 있는 Text를 Command에 바인딩된 cmd_swipe와 cmd_rm에 매개변수로 값을 전달하는 역할을 해준다.(View에 있는 값을 ViewModel로 전달받을 수 있게된다.!)

 cmd_rm = new Command((object QName) =>
            {
                foreach(Quest quests in Quests.ToList())
                {
                    if(quests.QuestName == QName.ToString())
                    {
                        Quests.Remove(quests);
                    }
                }
                
            }, 
            (object QName) =>
            {
                return true;
            });​

위와같이 ViewModel 에서 Label의 Text를 CommandParameter를 통해 object QName으로 전달받아 사용할 수 있다.


5.빌드된 모습

CollectionView에 적용된 SwipeView 모습

 

 

이번 포스팅에 많은 개념이 들어가 있기에 글이 조금 길어졌습니다.

 

 

 

간단하게 CollectionView와 SwipeView만 만들어서 설명하려고했지만,

 

실제로 사용할법한 Command 및 CommandParameter 까지 적용하는 글이 되어버렸습니다.

 

제가 코딩하면서 어려움을 겪었던 사항들은 붉은색 글씨와 함께 작성해뒀습니다.



반응형
Comments