[WPF] 데이터 소스 변경에 반응하기
작성날짜 2024/09/07
Code-behind에서 변경된 데이터를 UI에서 업데이트하기
결론부터 말하자면 숨김 코드(xaml.cs)에서 데이터가 변경되어도 UI(xaml)에 바로 적용되지 않는다.
따라서 몇가지 조치를 취해야 숨김 코드에서 일어난 변경 사항이 UI에 바로 업데이트 된다.
예제를 살펴보자
ChangeNotificationSample.xaml
<Window x:Class="FirstWpfApp.ChangeNotificationSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ChangeNotificationSample" Height="450" Width="800">
<DockPanel Margin="10">
<StackPanel DockPanel.Dock="Right" Margin="10,0,0,0">
<Button Name="btnAddUser" Click="btnAddUser_Click">Add user</Button>
<Button Name="btnChangeUser" Click="btnChangeUser_Click" Margin="0, 5">Change User</Button>
<Button Name="btnDeleteUser" Click="btnDeleteUser_Click">Delete User</Button>
</StackPanel>
<ListBox Name="lbUsers" DisplayMemberPath="Name"></ListBox>
</DockPanel>
</Window>
ChangeNotificationSample.xaml.cs
public partial class ChangeNotificationSample : Window
{
private List<User> users = new();
public ChangeNotificationSample()
{
InitializeComponent();
users.Add(new User() { Name = "John Doe" });
users.Add(new User() { Name = "Jane Doe" });
lbUsers.ItemsSource = users;
}
private void btnAddUser_Click(object sender, RoutedEventArgs e)
{
users.Add(new User() { Name = "New user" });
}
private void btnChangeUser_Click(object sender, RoutedEventArgs e)
{
if (lbUsers.SelectedItem != null)
{
(lbUsers.SelectedItem as User).Name = "Random Name";
}
}
private void btnDeleteUser_Click(object sender, RoutedEventArgs e)
{
if (lbUsers.SelectedItem != null)
{
users.Remove(lbUsers.SelectedItem as User);
}
}
}
public class User
{
public string Name { get; set; }
}
위 코드를 실행하면 아래와 같은 창이 뜬다.
users를 표시해주는 ListBox와 users에 새로운 유저를 추가하는 추가 버튼, 이름을 바꾸는 변경 버튼, 삭제하는 삭제 버튼이 있다.
이제 추가 버튼을 눌러보면...아무런 일도 일어나지 않는다!
글의 처음에서 말했듯이 숨김 코드에서 데이터의 변경이 일어나도 UI에 자동으로 반영되지 않기 때문이다. users는 xaml 마크업이 아니라 xaml.cs 코드 상에 있으니 UI는 users가 변경되었다는 사실조차 알 수 없다.
그렇다면 어떻게 users가 변경되었다고 알릴 수 있을까?
ObservableCollection<T>
항목이 추가 또는 제거되거나 전체 목록을 새로 고칠 때 알림을 제공하는 동적 데이터 컬렉션을 나타냅니다.
ObservableCollection은 collection은 collection인데 observe 가능한 콜렉션이다. mdsn에 따르면 ObservableCollection<T>은 List<T>와 비슷한 콜렉션이다. 하지만 콜렉션의 아이템에 추가, 제거 등의 변경이 일어나면 UI에 알림이 가고 UI는 업데이트가 일어난다.
users를 List<User>에서 ObservableCollection<User>로 변경하고 다시 실행해보자.
ChangeNotificationSample.xaml.cs
private ObservableCollection<User> users = new();
이제 추가 버튼과 삭제 버튼이 제대로 동작하는 것을 확인할 수 있다.
그런데 변경 버튼은 여전히 작동하지 않는다. 코드에서는 별 차이 없는 것 같은데 변경 버튼은 왜 작동하지 않는 걸까?
INotifyPropertyChanged 인터페이스
삭제 버튼과 추가 버튼 클릭 이벤트 코드를 확인해보자.
ChangeNotificationSample.xaml.cs
private void btnChangeUser_Click(object sender, RoutedEventArgs e)
{
if (lbUsers.SelectedItem != null)
{
(lbUsers.SelectedItem as User).Name = "Random Name";
}
}
private void btnDeleteUser_Click(object sender, RoutedEventArgs e)
{
if (lbUsers.SelectedItem != null)
{
users.Remove(lbUsers.SelectedItem as User);
}
}
삭제 이벤트는 users의 아이템을 직접 삭제하고 있다.
변경 이벤트는 lbUsers의 아이템을 변경하고 있다.
두 이벤트의 차이점은, 삭제는 숨김 코드의 데이터를 변경하지만 변경은 바인딩된 데이터를 변경한다는 점이다.
User 클래스를 다음과 같이 변경하자
public class User : INotifyPropertyChanged
{
private string name = "New user";
public string Name {
get { return name; }
set
{
if(name != value)
{
name = value;
NotifyPropertyChanged("Name");
}
}
}
public event PropertyChangedEventHandler? PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
이제 User클래스는 INotifyPropertyChanged 인터페이스를 상속하고 Name에 값이 할당될 때 마다 PropertyChanged 이벤트를 호출한다.
PropertyChanged 이벤트는 호출하는 주체와 변경이 일어난 속성의 이름을 매개변수로 UI에 변경사항이 있다는 것을 알려준다.
이제 다시 실행해보자.
결론
변경 버튼도 잘 작동하는 것을 확인할 수 있다.
UI(xaml)는 숨김 코드(xaml.cs)에서 일어난 변경 사항을 알 수 없다.
바인딩되지 않은 데이터는 ObservableCollection<T>로 UI에 알릴 수 있다.
바인딩된 데이터는 INotifyPropertyChanged 인터페이스를 상속, 구현하여 UI에 알릴 수 있다.
참고: https://wpf-tutorial.com/data-binding/responding-to-changes/