CHashtag

[C# WPF] MVVM 패턴 ListView Filter (Binding) 본문

C#/WPF

[C# WPF] MVVM 패턴 ListView Filter (Binding)

HyoSeong 2021. 2. 9. 01:33
반응형

결론부터 알려드리겠습니다.

public class MainViewModel
{
    private string _filter = string.Empty;
    public string Filter
    {
        get => _filter;
        set
        {
            _filter = value;
            OnFilterChanged();
        }
    }

    private ObservableCollection<string> StringFilter { get; set; }

    private CollectionViewSource StringCollectionViewSource { get; set; }

    public ICollectionView StringCollection
    {
        get { return StringCollectionViewSource.View; }
    }

    public MainViewModel()
    {
        StringFilter = new ObservableCollection<string>();

        StringCollectionViewSource = new CollectionViewSource();
        StringCollectionViewSource.Source = this.StringFilter;
        StringCollectionViewSource.Filter += ApplyFilter;
    }

    private void OnFilterChanged()
    {
        StringCollectionViewSource.View.Refresh();
    }

    void ApplyFilter(object sender, FilterEventArgs e)
    {
        string str = (string)e.Item;

        if (string.IsNullOrWhiteSpace(this.Filter) || this.Filter.Length == 0)
        {
            e.Accepted = true;
        }
        else
        {
            e.Accepted = Filter == str;
        }
    }
}

 

<!-- xaml -->

<TextBox Width="400" Height="30" Padding="5" FontSize="14" HorizontalAlignment="Center" Margin="5" Text="{Binding Filter, UpdateSourceTrigger=PropertyChanged}"/>
<ListView Grid.Row="1" ItemsSource="{Binding StringCollection}" />

 

안녕하세요.

오늘은 WPF ListView를 사용하여 특정 콘텐츠로 검색하는 방법에 대해 알아보기 위해

학생 관리 시스템을 만들어 학생을 검색할 수 있는 프로그램을 제작해 보도록 하겠습니다.

 

 

프로그램에 대한 설명은 주석으로 달아 두었습니다.

 

// Student.cs

using System.ComponentModel;

namespace WPF_Listview_Filter.Models
{
    public class Student : INotifyPropertyChanged
    {
        private string _name;
        public string Name
        {
            get => _name;
            set
            {
                _name = value;
                OnPropertyChanged(nameof(Name));
            }
        }

        private int _age;
        public int Age
        {
            get => _age;
            set
            {
                _age = value;
                OnPropertyChanged(nameof(Age));
            }
        }

        private string _country;
        public  string Country
        {
            get => _country;
            set
            {
                _country = value;
                OnPropertyChanged(nameof(Country));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

 

// MainViewModel.cs

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Data;
using WPF_Listview_Filter.Models;

namespace WPF_Listview_Filter.ViewModels
{
    public class MainViewModel
    {
        #region Properties
        private string _filter = string.Empty;
        public string Filter
        {
            get => _filter;
            set
            {
                _filter = value;
                // filter text가 변경되면 검색 조건이 변한 것이므로 필터를 refresh하여 데이터를 다시 검색하도록 합니다.
                OnFilterChanged();
            }
        }

        /// <summary>
        /// Data의 변경 사항이 View에도 Notify되어야 하기 때문에 Source는 ObservableCollection이여야 합니다.
        /// </summary>
        private ObservableCollection<Student> StudentFilter { get; set; }

        private CollectionViewSource StudentCollectionViewSource { get; set; }

        public ICollectionView StudentCollection
        {
            get { return StudentCollectionViewSource.View; }
        }
        #endregion

        public MainViewModel()
        {
            StudentFilter = new ObservableCollection<Student>();

            AddDummyData();

            StudentCollectionViewSource = new CollectionViewSource();
            StudentCollectionViewSource.Source = this.StudentFilter;
            StudentCollectionViewSource.Filter += ApplyFilter;
        }

        private void AddDummyData()
        {
            StudentFilter.Add(new Student { Age = 20, Country = "Korea", Name = "Henry" });
            StudentFilter.Add(new Student { Age = 22, Country = "United States", Name = "James" });
            StudentFilter.Add(new Student { Age = 24, Country = "Japan", Name = "Dami" });
            StudentFilter.Add(new Student { Age = 21, Country = "Russia", Name = "Cad" });
            StudentFilter.Add(new Student { Age = 18, Country = "China", Name = "이효성" });
            StudentFilter.Add(new Student { Age = 17, Country = "China", Name = "Tim" });
            StudentFilter.Add(new Student { Age = 17, Country = "Japan", Name = "Jane" });
            StudentFilter.Add(new Student { Age = 17, Country = "Japan", Name = "Kiru" });
        }

        private void OnFilterChanged()
        {
            // Refresh하게되면 CollectionViewSource의 Source로 지정된 StudentFilter가 차례로 ApplyFilter의 FilterEventArgs로 들어가게 됩니다.
            StudentCollectionViewSource.View.Refresh();
        }

        /// <summary>
        /// 조건에 부합한다면 e.Accepted 에 true를, 그렇지 않다면 e.Accepted에 false를 넣어주면 됩니다.
        /// </summary>
        void ApplyFilter(object sender, FilterEventArgs e)
        {
            Student svm = (Student)e.Item;

            // Filter Text 가 비어있다면 필터를 적용하지 않고 모든 콘텐츠를 보여주어야 하기때문에 true를 대입합니다.
            if (string.IsNullOrWhiteSpace(this.Filter) || this.Filter.Length == 0)
            {
                e.Accepted = true;
            }
            else
            {
                // 대소문자 구분없이 찾기 위해 모두 소문자로 변경하여 검색하도록 합니다.
                // 또한 완전 일치가 아닌 함유하고 있어도 찾기 위하여 Equals가 아닌 Contains를 사용하였습니다.
                e.Accepted = svm.Name.ToLower().Contains(Filter.ToLower());
            }
        }
    }
}

 

<!-- MainWindow.xaml -->

<Window x:Class="WPF_Listview_Filter.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF_Listview_Filter"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="600">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <!-- 검색 영역 -->
        <StackPanel Margin="3" Orientation="Horizontal" Grid.Row="0">
            <TextBlock Margin="5" Text="이름으로 검색" VerticalAlignment="Center" FontWeight="Bold" FontSize="16"/>
            <TextBox Width="400" Height="30" Padding="5" FontSize="14" HorizontalAlignment="Center" Margin="5" Text="{Binding Filter, UpdateSourceTrigger=PropertyChanged}"/>
        </StackPanel>
        <!-- ListView 영역 -->
        <ListView Grid.Row="1" ItemsSource="{Binding StudentCollection}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <!-- DataTemplate 에는 하나의 자식만 허용하기 때문에 Grid로 감싸면 편리합니다. -->
                    <Grid>
                        <TextBlock FontSize="16">
                            <TextBlock.Text>
                                <MultiBinding StringFormat="{}{0}, Age: {1}, Country: {2}">
                                    <Binding Path="Name" />
                                    <Binding Path="Age" />
                                    <Binding Path="Country" />
                                </MultiBinding>
                            </TextBlock.Text>
                        </TextBlock>
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</Window>

 

// MainWindow.cs

using System.Windows;
using WPF_Listview_Filter.ViewModels;

namespace WPF_Listview_Filter
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private MainViewModel _viewModel;
        public MainWindow()
        {
            InitializeComponent();

            _viewModel = new MainViewModel();
            this.DataContext = _viewModel;
        }
    }
}

필터 조건이 없을때에는 모든 콘텐츠들이 보인다.
필터 조건에 해당하는 텍스트를 찾는다.

 

전체 코드는 아래 링크에서 확인하실 수 있습니다.

github.com/Hyo-Seong/CHashtag/tree/master/WPF_Listview_Filter

 

Hyo-Seong/CHashtag

https://chashtag.tistory.com/. Contribute to Hyo-Seong/CHashtag development by creating an account on GitHub.

github.com

 


감사합니다.

반응형