[C# WPF] 바인딩 #03 - UI와 데이터 간 바인딩, 속성 변경 이벤트

728x90

1. UI와 데이터 간 바인딩

  • 바인딩은 클래스와 클래스간의 속성을 연결시키는 작업이고, 컨트롤 또한 하나의 클래스이기 때문에 바인딩 작업이 가능하다.

  • WPF 어플리케이션을 개발하면서 가장 많이 바인딩 하는 경우는 CS 파일 등에서 정의된 데이터 형의 클래스와 컨트롤을 연결할 때 인데, 예를들어 사람의 인적 사항을 정의하는 클래스에 이름이라는 속성을 TextBox 에 Text 속성에 바인딩하여 자동으로 TextBox에 사람의 이름이 표시되도록 하는 방식이다.

  • ECDIS 에서 특히 자주 사용되며, 수많은 ECDIS에 세부 설정들은 그 종류에 따라서 개별 클래스로 정의가 되어 있다.

→ UI와 클래스를 바인딩하여 별도의 이벤트 처리 없이 자동으로 UI와 내부 데이터가 변경되도록 처리하는 방식

2. 예제 UI만들기

  • 인적사항을 표시하거나 설정하는 예제 만들기

(1) UI 만들기

<Window x:Class="WpfApp1.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:WpfApp1"
        mc:Ignorable="d"
        Title="Window"
        SizeToContent="WidthAndHeight" 
        >

    <Grid Height="180" Width="180">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition Width="80"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <TextBlock Grid.Column="0" Grid.Row="0" Text="Name" Margin="3" VerticalAlignment="Center" />
        <TextBlock Grid.Column="0" Grid.Row="1" Text="Age" Margin="3" VerticalAlignment="Center" />
        <TextBlock Grid.Column="0" Grid.Row="2" Text="Address" Margin="3" VerticalAlignment="Center" />
        <TextBlock Grid.Column="0" Grid.Row="3" Text="Job" Margin="3" VerticalAlignment="Center" />
        <TextBlock Grid.Column="0" Grid.Row="4" Text="Marrued" Margin="3" VerticalAlignment="Center" />

        <TextBox Grid.Column="1" Grid.Row="0" Margin="3" />
        <ComboBox Grid.Column="1" Grid.Row="1" Margin="3" >
            <ComboBoxItem>20~30</ComboBoxItem>
            <ComboBoxItem>30~40</ComboBoxItem>
            <ComboBoxItem>40~50</ComboBoxItem>
            <ComboBoxItem>50~</ComboBoxItem>
        </ComboBox>
        <TextBox Grid.Column="1" Grid.Row="2" Margin="3" />
        <ComboBox Grid.Column="1" Grid.Row="3" Margin="3">
            <ComboBoxItem>Engineer</ComboBoxItem>
            <ComboBoxItem>Teacher</ComboBoxItem>
            <ComboBoxItem>Doctor</ComboBoxItem>
            
        </ComboBox>
        <CheckBox Grid.Column="1" Grid.Row="4" Margin="3" VerticalAlignment="Center"/>

        <Button Grid.Column="1" Grid.Row="5" Content="Save" Margin="3" VerticalAlignment="Center"/>
    </Grid>

</Window>

 

(2) 바인딩 할 클래스 만들기

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
     }

    public class PersonInfo
    {
        private string name = null;
        private string address = null;
        private int age = 0;
        private int job = 0;
        private bool married = false;

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        public string Address
        {
            get { return address; }
            set { address = value; }
        }

        public int Age
        {
            get { return age; }
            set { age = value; }
        }

        public int Job
        {
            get { return job; }
            set { job = value; }
        }

        public bool Married
        {
            get { return married; }
            set { married = value; }
        }
    }
}

 

(3) 클래스 객체 만들기

  • 바인딩은 클래스 속성간의 연결이므로 객체를 만든 후 이를 이용해서 별도로 작성한 클래스 형의 속성을 get, set을 이용해 만들어줘야한다.
    public partial class MainWindow : Window
    {

        private PersonInfo person = new PersonInfo();
        public PersonInfo Person
        {
            get { return person; }
            set { person = value; }
        }

        public MainWindow()
        {
            InitializeComponent();
        }
     }
     ...

→ 객체를 따로 만들고 이를 이용해 속성을 만들었다.
객체를 만들지 않고 속성만 public PersonInfo Person {get; set;} 과 같은 방식으로 만들어도 무방하다.

 

(4) 바인딩 작업

  • CS 파일에 정의된 속성에 정의한 속성은 MainWindow 에 포함되어 있으니 XAML에서 Window 태그에 Name속성을 정의해서 접근하면된다.
  • Window에 Name을 지정하면, 바인딩시에 ElementName을 지정된 이름으로 설정하고 Path를 작업한 클래스형의 속성으로 해주면 된다.

<Window x:Class="WpfApp1.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:WpfApp1"
        mc:Ignorable="d"
        Title="Window"
        SizeToContent="WidthAndHeight" Name="mainWindow"
        >
  • 바인딩을 걸 때 ElementName은 앞서 설정한 Window태그 이름은 window로 설정하고 path는 윈도우 클래스인 MainWindow에서 만들어준 Person 속성으로 지정하는데, 우리는 여기서 person 속성 하위의 name이라는 속성을 표시해야하므로 Person.Name 과 같은 형식으로 path를 지정해준다.
    위와 같은 형식을 매번 반복하지 않도록 DataContext를 사용하여 특정 컨트롤 하위에서 사용할 기본 속성을 지정 해 준다.
...
    <Grid Height="180" Width="180" DataContext="{Binding ElementName=mainWindow, Path=Person}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
...

 

  • PersonInfo 클래스에서 정의한 각종 속성의 데이터형과 바인딩 할 컨트롤의 속성 데이터형은 동일해야한다. int, bool과 같은 형태가 아니라 XAML코드 상에서 표시되는 최종적인 형태가 중요하다.
    만약 형태가 다른 데이터를 바인딩 해야 한다면, 컨버터를 만들어 사용한다.
...
       <TextBlock Grid.Column="0" Grid.Row="0" 
                 Text="Name" Margin="3" VerticalAlignment="Center" />
        <TextBlock Grid.Column="0" Grid.Row="1" 
                 Text="Age" Margin="3" VerticalAlignment="Center" />
        <TextBlock Grid.Column="0" Grid.Row="2" Text="Address" Margin="3" VerticalAlignment="Center" />
        <TextBlock Grid.Column="0" Grid.Row="3" Text="Job" Margin="3" VerticalAlignment="Center" />
        <TextBlock Grid.Column="0" Grid.Row="4" Text="Married" Margin="3" VerticalAlignment="Center" />

        <TextBox Grid.Column="1" Grid.Row="0" 
                Text="{Binding Path=Name}" Margin="3" />
        <ComboBox Grid.Column="1" Grid.Row="1" 
                  SelectedIndex="{Binding Path=Age}" Margin="3" >
            <ComboBoxItem>20~30</ComboBoxItem>
            <ComboBoxItem>30~40</ComboBoxItem>
            <ComboBoxItem>40~50</ComboBoxItem>
            <ComboBoxItem>50~</ComboBoxItem>
        </ComboBox>
        <TextBox Grid.Column="1" Grid.Row="2" 
                Text="{Binding Path=Address}" Margin="3" />
        <ComboBox Grid.Column="1" Grid.Row="3" 
                 SelectedIndex="{Binding Path=Job}" Margin="3">
            <ComboBoxItem>Engineer</ComboBoxItem>
            <ComboBoxItem>Teacher</ComboBoxItem>
            <ComboBoxItem>Doctor</ComboBoxItem>
            
        </ComboBox>
        <CheckBox Grid.Column="1" Grid.Row="4" Margin="3" 
                  IsChecked="{Binding Path=Married}" VerticalAlignment="Center"/>

        <Button Grid.Column="1" Grid.Row="5" Content="Save" Margin="3" VerticalAlignment="Center" Click="Button_Click"/>
    </Grid>

</Window>

 

(5) 바인딩 결과 갱신버튼 추가

  • UI수정
...
        <CheckBox Grid.Column="1" Grid.Row="4" Margin="3" 
                  IsChecked="{Binding Path=Married}" VerticalAlignment="Center"/>

        <Button Grid.Column="0" Grid.Row="5" Content="change" Margin="3" VerticalAlignment="Center" Click="Button_Click_1"/>
        <Button Grid.Column="1" Grid.Row="5" Content="Save" Margin="3" VerticalAlignment="Center" Click="Button_Click"/>
    </Grid>
  • CS 파일 수정
...
        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            person.Name = "Lee";
            person.Age = 3;
            person.Address = "Mangpo";
            person.Job = 2;
            person.Married = false;
        }
    }

→ change 버튼을 누르면 아무 변화가 없지만 save 버튼을 누르면 임의로 변경한 값이 반영되어 나온다.
즉, 내부 데이터는 변경되었지만 UI에서만 변경이 되지 않은 것.

  • WPF는 C#에서 사용하는 일반적인 방식으로 속성을 만들면 실행할 때, 초기 데이터는 반영을 하지만 중간에 속성이 변경되면 반영이 되지 않는다.
    개별 속성 별로 그 속성이 변경되었다는 이벤트를 발생시켜 내부적으로 속성이 변경되었다는 것을 알려줘야만 바인딩을 했을 때에도 바로 반영이 된다.

3. 속성 변경 이벤트

  • INotifyPropertyChanged 인터페이스를 상속 받는다.
    INotifyPropertyChanged를 사용하기 위해서는 System.ComponentMode 네임스페이스를 추가해준다.

  • INotifyPropertyChanged 라는 인터페이스를 상속 받은 후 클래스 내부에 이벤트 함수를 만들어준다. 이 이벤트 함수를 각각의 속성이 변경 될 때 마다 발생시켜서 WPF상에서 바인딩 된 데이터가 변경되는 것을 알릴 수 있도록 해야한다.
...
        public event PropertyChangedEventHandler? PropertyChanged;

        private void OnPropertyChanged(string name)
        {
            if(PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(name));
        }
    }
}
  • 이벤트 함수를 작성했으면 클래스 내 각 송성마다 모두 값이 변경되는 set 부분에서 이벤트를 발생시킨다. 이벤트 파라미터는 그 속성의 이름으로 한다. 모든 속성에 대해 이벤트 메서드를 추가해주고 실행 해 보면 속성이 변경 될 떄 바인딩된 UI도 바로 변경이 된다.
728x90