바인딩의 기본 개념은 위 그림과 같다.
바인딩 타겟은 항상 DependencyProperty 여야하고 바인딩 소스는 DependencyProperty 나 일반 프로퍼티(.NET 프로퍼티)나 상관없다.
여기서
OneWay 바인딩은 소스 -> 타겟
OneWayToSource 바인딩은 소스 <- 타겟
TwoWay 바인딩은 소스 <-> 타겟
으로 바인딩 된다.
1. TextBox 바인딩
화면을 위아래로 나눠서 상단에 텍스트 박스를 하단에 버튼 1개을 나타낸 간단한 UI를 만든다.
<Window x:Class="TextBoxBind.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:TextBoxBind" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="50*"/> <RowDefinition Height="50*"/> </Grid.RowDefinitions> <TextBox Grid.Row="0"
Text="{Binding InputText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}
" /> <Button x:Name="button" Content="Add Text" HorizontalAlignment="Left" Margin="42,29,0,0" Grid.Row="1" VerticalAlignment="Top" Click="button_Click"/> </Grid> </Window>
위 xaml 코드에서 TextBox는 InputText 프로퍼티와 바인딩을 하며 Mode는 TwoWay, UpdateSourceTrigger는 PropertyChanged 이다.
즉 양방향 바인딩이고 데이터 변환시 즉시 반영되는 바인딩이다.
code behind 는 아래와 같다.
using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace TextBoxBind { /// <summary> /// MainWindow.xaml에 대한 상호 작용 논리 /// </summary> public partial class MainWindow : Window, INotifyPropertyChanged { private string inputText; public string InputText { get { return inputText; } set { if (inputText != value) { Console.WriteLine($"InputText : {value}"); inputText = value;
OnPropertyChanged("InputText");
} } } public MainWindow() { InitializeComponent(); DataContext = this; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } private void button_Click(object sender, RoutedEventArgs e) {
Thread thread = new Thread(new ThreadStart(() => InputText += "AAA")); thread.IsBackground = true; thread.Start();
} } }
Add Text 버튼클릭시 쓰레드로 InputText 프로퍼티의 값을 추가한다.
양방향 바인딩이므로 TextBox에 문자를 입력하면 InputText 값이 바뀌고 버튼을 클릭하면 TextBox 출력문자가 변경된다.('AAA'추가)
여기서 InputText의 setter 부분에 있는 OnPropertyChanged("InputText")
를 주석처리하면 버튼을 클릭해도 TextBox에 수정내용 즉 InputText 값이 반영되지 않는다.
이유는 바인딩을 하는 순간 TextBox는
public event PropertyChangedEventHandler PropertyChanged;
델러게이트 이벤트 핸들러를 구독하게되고 이벤트 발생시 InputText의 getter를 통해 현재 값을 가져와서 UI에 반영(Text 프로퍼티에)하기 때문이다.
Mode=OneWayToSource 를 하면 바인딩 타겟(TextBox) -> 바인딩 소스(InputText) 로만 데이터 전달이 되는데 이 경우는 TextBox가 바로 InputText의 setter 를 호출하게 되는데 이때는 OnPropertyChanged("InputText") 호출이 필요없다.(다른 이유에서 필요할 수 있으므로 호출하는것이 좋다)
2. DataGrid 바인딩
화면을 좌우로 나눠서 왼쪽에 DataGrid 를 오른쪽에 버튼과 기타 텍스트박스 컨트롤을 배치했다.
<Window x:Class="ListBindTest.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:ListBindTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<DataGrid Grid.Column="0" ItemsSource="{Binding People}" SelectedItem="{Binding SelectedItem}" SelectionMode="Single" />
<Canvas Grid.Column="1">
<TextBlock x:Name="textBlock" Canvas.Left="41" TextWrapping="Wrap" Text="Name" Canvas.Top="35"/>
<TextBlock x:Name="textBlock_Copy" Canvas.Left="41" TextWrapping="Wrap" Text="Age" Canvas.Top="87" HorizontalAlignment="Center" VerticalAlignment="Top"/>
<TextBox x:Name="textName" Canvas.Left="101" Text="{Binding SelectedItem.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Canvas.Top="35" Width="120"/>
<TextBox x:Name="textAge" Canvas.Left="101" Text="{Binding SelectedItem.Age, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Canvas.Top="87" Width="120" HorizontalAlignment="Center" VerticalAlignment="Top"/>
<TextBlock x:Name="textBlock_Copy1" Canvas.Left="41" TextWrapping="Wrap" Text="Name" Canvas.Top="183" HorizontalAlignment="Center" VerticalAlignment="Top"/>
<TextBlock x:Name="textBlock_Copy2" Canvas.Left="41" TextWrapping="Wrap" Text="Age" Canvas.Top="235" HorizontalAlignment="Center" VerticalAlignment="Top"/>
<Canvas x:Name="canvasTest">
<TextBox x:Name="textName_Copy" Canvas.Left="101" Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Canvas.Top="183" Width="120" HorizontalAlignment="Center" VerticalAlignment="Top"/>
<TextBox x:Name="textAge_Copy" Canvas.Left="101" Text="{Binding Age, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Canvas.Top="235" Width="120" HorizontalAlignment="Center" VerticalAlignment="Top"/>
</Canvas>
<Button x:Name="button" Content="button" Canvas.Left="22" Canvas.Top="297" Click="button_Click"/>
<Button x:Name="button1" Content="button1" Canvas.Left="78" Canvas.Top="297" Click="button1_Click"/>
</Canvas>
</Grid>
</Window>
code behind 는 아래와 같다.
using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace ListBindTest { /// <summary> /// MainWindow.xaml에 대한 상호 작용 논리 /// </summary> public partial class MainWindow : Window { private MainViewModel viewModel = new MainViewModel(); private Person personTest = new Person(); public MainWindow() { InitializeComponent(); DataContext = viewModel; personTest.Name = "Test"; personTest.Age = 100; canvasTest.DataContext = personTest; } private void button_Click(object sender, RoutedEventArgs e) { Person p = new Person(); p.Name = personTest.Name; p.Age = personTest.Age; Thread thread = new Thread(new ThreadStart(() => {
this.Dispatcher.BeginInvoke(new Action(() => { viewModel.People.Add(p); }));
})); thread.IsBackground = true; thread.Start(); } private void button1_Click(object sender, RoutedEventArgs e) { Thread thread = new Thread(new ThreadStart(() => { Person p = viewModel.SelectedItem;
if (p != null) p.Age++;
})); thread.IsBackground = true; thread.Start(); } } public class Person : INotifyPropertyChanged { private string _name; private int _age; public string Name { get { return _name; } set { _name = value; OnPropertyChanged(nameof(Name)); } } public int Age { get { return _age; } set { _age = value; OnPropertyChanged(nameof(Age)); } }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } public class MainViewModel : INotifyPropertyChanged { private ObservableCollection<Person> _people; private Person _selectedItem; public MainViewModel() { _people = new ObservableCollection<Person>(); _people.Add(new Person { Name = "Alice", Age = 25 }); _people.Add(new Person { Name = "Bob", Age = 30 }); _people.Add(new Person { Name = "Charlie", Age = 35 }); } public ObservableCollection<Person> People { get { return _people; } set { _people = value; OnPropertyChanged(nameof(People)); } } public Person SelectedItem { get { return _selectedItem; } set { _selectedItem = value; OnPropertyChanged(nameof(SelectedItem)); } }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }
button을 클릭하면 DataGrid 에 아이템이 추가되는데 여기서 주의해야할 점은 바인딩된 ObservableCollection 에 아이템을 추가할때 반드시 UI 쓰레드에서 추가해야한다는 점이다.
UI 쓰레드로 추가하지 않으려면 BindingOperations.EnableCollectionSynchronization 메소드를 사용하는 방법이 있다.
namespace ListBindTest { /// <summary> /// MainWindow.xaml에 대한 상호 작용 논리 /// </summary> public partial class MainWindow : Window { private MainViewModel viewModel = new MainViewModel(); private Person personTest = new Person(); public MainWindow() { InitializeComponent(); DataContext = viewModel; personTest.Name = "Test"; personTest.Age = 100; canvasTest.DataContext = personTest; } private void button_Click(object sender, RoutedEventArgs e) { Person p = new Person(); p.Name = personTest.Name; p.Age = personTest.Age; Thread thread = new Thread(new ThreadStart(() => { #if false this.Dispatcher.BeginInvoke(new Action(() => { viewModel.People.Add(p); //viewModel.People.Add(personTest); })); #else
viewModel.People.Add(p);
#endif })); thread.IsBackground = true; thread.Start(); } private void button1_Click(object sender, RoutedEventArgs e) { Thread thread = new Thread(new ThreadStart(() => { //personTest.Name = personTest.Name + "X"; Person p = viewModel.SelectedItem;
if (p != null) p.Age++;
})); thread.IsBackground = true; thread.Start(); } } public class Person : INotifyPropertyChanged { private string _name; private int _age; public string Name { get { return _name; } set { _name = value; OnPropertyChanged(nameof(Name)); } } public int Age { get { return _age; } set { _age = value; OnPropertyChanged(nameof(Age)); } }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } public class MainViewModel : INotifyPropertyChanged { private ObservableCollection<Person> _people; private Person _selectedItem;
private object objLock = new object();
public MainViewModel() { _people = new ObservableCollection<Person>(); _people.Add(new Person { Name = "Alice", Age = 25 }); _people.Add(new Person { Name = "Bob", Age = 30 }); _people.Add(new Person { Name = "Charlie", Age = 35 });
BindingOperations.EnableCollectionSynchronization(_people, objLock);
} public ObservableCollection<Person> People { get { return _people; } set { _people = value; OnPropertyChanged(nameof(People)); } } public Person SelectedItem { get { return _selectedItem; } set { _selectedItem = value; OnPropertyChanged(nameof(SelectedItem)); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }
그리고 button1을 클릭하면 Age 값이 1증가하는데 이것은 Person 클래스가 INotifyPropertyChanged 를 구현해서 OnPropertyChanged(nameof(Age)) 를 호출하여 역시 1번 예제에서 설명한데로 public event PropertyChangedEventHandler PropertyChanged 를 구독중인 DataGrid 에게 변경여부를 알려주기 때문이다.
댓글 없음:
댓글 쓰기