ListView
ListView
는 WPF에서 데이터를 표시하고 관리할 수 있는 강력한 컨트롤입니다. ListView
는 ItemsControl
을 확장한 컨트롤로, 데이터를 리스트 형식으로 보여주며, 다양한 레이아웃과 사용자 지정이 가능합니다. 특히 컬럼 기반의 그리드 형식으로 데이터를 표시하거나, 템플릿을 활용한 커스터마이징이 가능해 유연한 UI 설계를 지원합니다.
기본 구성
ListView
는 데이터를 항목별로 나열하며, ItemsSource
를 사용하여 데이터를 바인딩하거나, 개별 항목을 직접 추가할 수 있습니다.
<ListView>
<ListViewItem>Item 1</ListViewItem>
<ListViewItem>Item 2</ListViewItem>
<ListViewItem>Item 3</ListViewItem>
</ListView>
데이터 바인딩
ItemsSource
를 사용하면 컬렉션 데이터를 ListView
에 바인딩할 수 있습니다.
데이터 바인딩 예제
<ListView ItemsSource="{Binding MyItems}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Age" DisplayMemberBinding="{Binding Age}" />
</GridView>
</ListView.View>
</ListView>
코드 비하인드 (ViewModel 또는 코드에서)
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
public partial class MainWindow : Window
{
public ObservableCollection<Person> MyItems { get; set; }
public MainWindow()
{
InitializeComponent();
MyItems = new ObservableCollection<Person>
{
new Person { Name = "Alice", Age = 25 },
new Person { Name = "Bob", Age = 30 },
new Person { Name = "Charlie", Age = 35 }
};
DataContext = this;
}
}
ObservableCollection
사용
ObservableCollection
은 데이터가 변경될 때 ListView
를 자동으로 업데이트합니다.
public ObservableCollection<string> Items { get; set; } = new ObservableCollection<string>();
public MainWindow()
{
InitializeComponent();
Items.Add("Item 1");
Items.Add("Item 2");
DataContext = this;
}
네, List
자체를 ListView
에 바인딩할 수 있습니다. 그러나 List
는 변경 알림을 제공하지 않기 때문에 ListView
가 데이터 변경 사항을 자동으로 업데이트하지 않습니다. 만약 데이터 변경 사항을 자동으로 반영하고 싶다면, ObservableCollection
을 사용하는 것이 더 적합합니다.
List
바인딩
List
를 ListView
의 ItemsSource
에 바인딩하면 데이터를 표시할 수 있습니다.
XAML
<ListView ItemsSource="{Binding MyList}">
<ListView.View>
<GridView>
<GridViewColumn Header="Item" DisplayMemberBinding="{Binding}" />
</GridView>
</ListView.View>
</ListView>
코드 비하인드
public partial class MainWindow : Window
{
public List<string> MyList { get; set; }
public MainWindow()
{
InitializeComponent();
// List 데이터 초기화
MyList = new List<string> { "Apple", "Banana", "Cherry" };
// DataContext 설정
DataContext = this;
}
}
주요 특징
- 바인딩 가능:
List
는 기본적으로IEnumerable
를 구현하므로,ItemsSource
에 바로 바인딩할 수 있습니다. - 변경 알림 불가능: 데이터 추가, 삭제 등의 변경 사항은 자동으로 반영되지 않습니다. 데이터를 수정한 후 UI를 업데이트하려면
ListView.Items.Refresh()
를 호출해야 합니다.
변경 알림이 필요한 경우
데이터가 변경되면 ListView
에 자동으로 반영되기를 원한다면, ObservableCollection
을 사용하는 것이 좋습니다.
데이터 모델
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
public bool InStock { get; set; }
}
XAML
<ListView ItemsSource="{Binding Products}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Price" DisplayMemberBinding="{Binding Price, StringFormat={}{0:C}}" />
<GridViewColumn Header="In Stock">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding InStock}" IsEnabled="False" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
코드 비하인드
public partial class MainWindow : Window
{
public ObservableCollection<Product> Products { get; set; }
public MainWindow()
{
InitializeComponent();
Products = new ObservableCollection<Product>
{
new Product { Name = "Laptop", Price = 1200.99m, InStock = true },
new Product { Name = "Smartphone", Price = 799.49m, InStock = false },
new Product { Name = "Tablet", Price = 499.00m, InStock = true }
};
DataContext = this;
}
}
GridView
를 사용한 컬럼 기반 레이아웃
GridView
를 사용하면 ListView
를 테이블 형식으로 표시할 수 있습니다.
<ListView>
<ListView.View>
<GridView>
<GridViewColumn Header="Product Name" DisplayMemberBinding="{Binding ProductName}" />
<GridViewColumn Header="Price" DisplayMemberBinding="{Binding Price}" />
<GridViewColumn Header="Stock" DisplayMemberBinding="{Binding Stock}" />
</GridView>
</ListView.View>
</ListView>
템플릿 사용
ListView
는 항목의 UI를 커스터마이징할 수 있도록 DataTemplate
을 지원합니다.
<ListView ItemsSource="{Binding MyItems}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" Width="100" />
<TextBlock Text="{Binding Age}" Width="50" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
선택 이벤트 처리
ListView
에서 항목 선택을 감지하려면 SelectionChanged
이벤트를 사용합니다.
XAML
<ListView SelectionChanged="ListView_SelectionChanged" ItemsSource="{Binding MyItems}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Age" DisplayMemberBinding="{Binding Age}" />
</GridView>
</ListView.View>
</ListView>
코드 비하인드
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0)
{
var selectedPerson = e.AddedItems[0] as Person;
MessageBox.Show($"Selected: {selectedPerson.Name}, Age: {selectedPerson.Age}");
}
}
항목 스타일 및 트리거
ListView
의 항목 스타일을 변경하거나 조건부 스타일을 적용할 수 있습니다.
<ListView ItemsSource="{Binding MyItems}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Background" Value="LightGray" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="LightBlue" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
</ListView>
스크롤링 및 가상화
많은 데이터를 표시해야 하는 경우 성능을 개선하려면 가상화를 활용할 수 있습니다.
WPF의 ListView
는 기본적으로 VirtualizingStackPanel
을 사용하여 가상화 기능을 제공합니다.
<ListView VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling">
<!-- Content -->
</ListView>
Drag-and-Drop 지원
ListView
는 항목을 드래그 앤 드롭으로 재배치할 수 있습니다.
간단한 드래그 앤 드롭 예제
<ListView Name="listView" PreviewMouseLeftButtonDown="ListView_PreviewMouseLeftButtonDown" Drop="ListView_Drop" AllowDrop="True">
<ListView.ItemsSource>
<x:Array Type="sys:String" xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String>Item 1</sys:String>
<sys:String>Item 2</sys:String>
<sys:String>Item 3</sys:String>
</x:Array>
</ListView.ItemsSource>
</ListView>
코드 비하인드
private void ListView_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var listView = sender as ListView;
var item = listView?.SelectedItem;
if (item != null)
{
DragDrop.DoDragDrop(listView, item, DragDropEffects.Move);
}
}
private void ListView_Drop(object sender, DragEventArgs e)
{
var target = sender as ListView;
var data = e.Data.GetData(typeof(string)) as string;
if (data != null && target != null)
{
// 재배치 로직 구현
MessageBox.Show($"Dropped: {data}");
}
}
검색 및 필터링
컬렉션을 필터링하려면 CollectionViewSource
를 사용합니다.
XAML
<StackPanel>
<TextBox Width="200" Margin="5" Text="{Binding FilterText, UpdateSourceTrigger=PropertyChanged}" PlaceholderText="Filter by name..." />
<ListView ItemsSource="{Binding FilteredProducts}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Price" DisplayMemberBinding="{Binding Price, StringFormat={}{0:C}}" />
</GridView>
</ListView.View>
</ListView>
</StackPanel>
ViewModel
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<Product> Products { get; set; }
public ICollectionView FilteredProducts { get; }
private string _filterText;
public string FilterText
{
get => _filterText;
set
{
_filterText = value;
OnPropertyChanged();
FilteredProducts.Refresh();
}
}
public MainViewModel()
{
Products = new ObservableCollection<Product>
{
new Product { Name = "Laptop", Price = 1200.99m },
new Product { Name = "Smartphone", Price = 799.49m },
new Product { Name = "Tablet", Price = 499.00m }
};
FilteredProducts = CollectionViewSource.GetDefaultView(Products);
FilteredProducts.Filter = FilterProducts;
}
private bool FilterProducts(object obj)
{
if (obj is not Product product || string.IsNullOrEmpty(FilterText))
return true;
return product.Name.Contains(FilterText, StringComparison.InvariantCultureIgnoreCase);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
그룹화 데이터
그룹화는 데이터를 카테고리별로 묶어서 표시하는 기능입니다. 예를 들어, 제품 목록을 재고 상태나 가격 범위에 따라 그룹화하여 표시할 수 있습니다.
동작 원리
ListView
의ItemsSource
에 바인딩된 데이터는CollectionViewSource
를 통해 그룹화할 수 있습니다.PropertyGroupDescription
을 사용하여 그룹화 기준이 되는 속성을 지정합니다.- 각 그룹은
GroupStyle
로 커스터마이징할 수 있습니다.
데이터 모델
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
public bool InStock { get; set; } // 그룹화 기준
}
XAML
<ListView ItemsSource="{Binding Products}">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" FontWeight="Bold" FontSize="16" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Price" DisplayMemberBinding="{Binding Price, StringFormat={}{0:C}}" />
</GridView>
</ListView.View>
</ListView>
ViewModel (그룹화 추가)
public MainViewModel()
{
Products = new ObservableCollection<Product>
{
new Product { Name = "Laptop", Price = 1200.99m, InStock = true },
new Product { Name = "Smartphone", Price = 799.49m, InStock = false },
new Product { Name = "Tablet", Price = 499.00m, InStock = true }
};
var collectionView = CollectionViewSource.GetDefaultView(Products);
collectionView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(Product.InStock)));
}
컨텍스트 메뉴
컨텍스트 메뉴는 마우스 오른쪽 버튼 클릭 시 표시되는 메뉴로, 일반적으로 항목에 대해 특정 작업(예: 편집, 삭제)을 수행하도록 합니다.
동작 원리
ContextMenu
를ListView
나 특정ListViewItem
에 연결합니다.- 메뉴 항목은
MenuItem
으로 구성되며, 명령(ICommand
)을 사용하여 동작을 정의할 수 있습니다. CommandParameter
로 클릭된 항목을 전달할 수 있습니다.
XAML
<ListView ItemsSource="{Binding Products}">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete" Command="{Binding DeleteCommand}" CommandParameter="{Binding}" />
<MenuItem Header="Edit" Command="{Binding EditCommand}" CommandParameter="{Binding}" />
</ContextMenu>
</ListView.ContextMenu>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Price" DisplayMemberBinding="{Binding Price, StringFormat={}{0:C}}" />
</GridView>
</ListView.View>
</ListView>
ViewModel (Command 추가)
public ICommand DeleteCommand { get; }
public ICommand EditCommand { get; }
public MainViewModel()
{
DeleteCommand = new RelayCommand(DeleteProduct);
EditCommand = new RelayCommand(EditProduct);
}
private void DeleteProduct(object parameter)
{
if (parameter is Product product)
Products.Remove(product);
}
private void EditProduct(object parameter)
{
if (parameter is Product product)
MessageBox.Show($"Editing {product.Name}");
}
RelayCommand Class
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute?.Invoke(parameter) ?? true;
public void Execute(object parameter) => _execute(parameter);
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
}
```