[WPF] MVVM 패턴으로 WPF 시작하기 - 04 Textbox와 ComboBox

WPF 시리즈 포스팅을 진행하고 있습니다. 아래 링크를 클릭하시면 연관된 다른 포스트들을 보실 수 있습니다.

 

목록

  1. MVVM 패턴으로 WPF 시작하기 - 01 프로젝트 만들기
  2. MVVM 패턴으로 WPF 시작하기 - 02 UI 구성 (Grid, ListView, StackPanel)
  3. MVVM 패턴으로 WPF 시작하기 - 03 ViewModel 구성
  4. MVVM 패턴으로 WPF 시작하기 - 05 Converter

저번 포스트에 이어서 프로그램을 계속 꾸며보도록 할게요.

 

이번 포스트에서는 ListView에서 데이터를 변경 시, 선택화면에서도 바뀌도록 만들어 보겠습니다. 오늘 사용될 컨트롤은 바로 TextBox와 ComboBox입니다. 

 

etc-image-0

TextBox는 이렇게 생겼고

 

etc-image-1

ComboBox는 이렇게 생겼습니다.

 

 

WPF에는 Text에 대한 대표 컨트롤이 2가지가 존재 하는데, 바로 TextBox와 TextBlock입니다. 두개의 큰 차이는 TextBox는 Enable 상태에서는 유저가 편집이 가능하지만 TextBlock은 편집을 아예 할 수가 없다는 것입니다.

 

저는 TextBox와 ComboBox를 활용하여 직접 데이터를 바꿔 실시간으로 변경이되도록 하겠습니다.

 

1. ListView 수정

먼저 MainWindow.xaml 파일에서 기존 ListView를 다음과 같이 수정하도록 하겠습니다.

<Window
    x:Class="MyFirstWPFApp.Views.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:local="clr-namespace:MyFirstWPFApp.Views"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800"
    Height="450"
    mc:Ignorable="d">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <ListView
            Grid.Row="0"
            ItemsSource="{Binding People}"
            SelectedItem="{Binding SelectedPerson, Mode=TwoWay}">
            <ListView.View>
                <GridView>
                    <GridView.Columns>
                        <GridViewColumn DisplayMemberBinding="{Binding Id}" Header="Id" />
                        <GridViewColumn Header="Name">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <TextBox Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
                        <GridViewColumn Header="Gender">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <ComboBox
                                        Width="60"
                                        ItemsSource="{Binding Path=DataContext.GenderTypes, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
                                        SelectedValue="{Binding Gender, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
                                        <ComboBox.ItemTemplate>
                                            <DataTemplate>
                                                <TextBlock Text="{Binding}" />
                                            </DataTemplate>
                                        </ComboBox.ItemTemplate>
                                    </ComboBox>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
                    </GridView.Columns>
                </GridView>
            </ListView.View>
        </ListView>
        <StackPanel Grid.Row="1" Orientation="Horizontal">
            <TextBlock Margin="5" Text="{Binding SelectedPerson.Id}" />
            <TextBlock Margin="5" Text="{Binding SelectedPerson.Name}" />
            <TextBlock Margin="5" Text="{Binding SelectedPerson.Gender}" />
        </StackPanel>
    </Grid>
</Window>

보시는 바와 같이, ListView 안에 GridView란 요소를 추가 하였습니다. GridView는 데이터를 가로와 세로로, 즉 테이블 형태로 표현하고자 할 때 사용됩니다.

 

각 GridView.Columns를 통하여 컴럼을 설정하고 해당 컬럼안에 들어갈 데이터들을 설정해 주었습니다.

 

DisplayMemberBinding 속성은 어떤 특정 바인딩된 ViewModel의 속성을 아무 꾸밈없이 바로 설정할때 사용합니다. 그리고 Header 속성으로 설정하여 각 컬럼의 타이틀을 설정해 주었습니다. 저는 MainViewModel의 Id를 바인딩하여 첫번째 컬럼에 보여지도록 하였습니다.

 

두번째 컬럼에서는 DataTemplate이라는 속성을 사용하였는데, DataTemplate은 기존 포멧에서 벗어나, 사용자 입맛에 맞게 표현될 수 있도록 커스터마이징을 할 수 있는 속성입니다. 저는 여기에 TextBox를 추가하고 Text속성에 Name 뷰모델을 바인딩하여 데이터가 로딩 될 때 TextBox에 쓰여지도록 하였습니다.

 

마지막 컬럼도 마찬가지로 DataTempate을 통하여 ComboBox를 설정해 주었습니다. ComboBox는 데이터를 선택 할 수 있도록 하여야 합니다. 즉, 기준이 될 데이터가 있어야 하는데 현재 ViewModel에서는 각 Person에 대한 Gender값만 존재하지 어떤 타입의 Gender들을 정의하지는 않았습니다. Gender에 대한 기준 값 true, false를 가진 속성을 ViewModel에 정의해 주어야 합니다.

 

여기서 주목해야할 코드는 바로 여기 입니다.

  {Binding Path=DataContext.GenderTypes, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}

Binding 시, Path를 사용할 때는 ViewModel의 어떤 속성의 출처를 정확히 기입할 때 사용합니다. 

 

여기에서는 ListView안에서 바인딩 되어지고 있기에 Binding 뒤에 오는 속성들은 ListView의 ItemsSource로 등록된 People 속성의 자식들(Person)의 요소들을 타깃팅 합니다. 하지만 위에서도 언급하였듯이, Gender의 타입들은 MainWindowViewModel은 가지고 있지만, 각 Person은 가지고 있지 않습니다. 그래서 Path를 통하여 DataContext.GenderType이라고 명시해주어야 합니다.

 

RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window} 코드의 뜻은 "이 코드가 사용된 요소를 기준으로 가장 가까운 Window 요소에 속한 ViewModel을 찾아라" 라는 뜻과 같습니다.

Mode=FindAncestor 를 통해 조상요소를 탐색하고, AncestorType={x:Type Window}를 지정하여 Window 타입의 조상을 말하고 있습니다.

현재 Window의 ViewModel 중 GenderTypes라는 요소를 가져와 ComboBox의 ItemsSource로 넣었습니다. 그리고 SelectedValue를 설정하여 현재 선택된 GenderType이 무엇인지를 가져올 것입니다.

 

 

 

2. ViewModel 수정

앞서 언급했듯, 현재 MainWindowViewModel은 GenderTypes라는 요소를 가지고 있지 않습니다. MainWindowViewModel에 GenderTypes라는 요소를 만들어 추가합니다. GenderType은 수정할 필요가 없는 요소이기 때문에 Set은 생략하고 미리 값이 있어야 하기 때문에 new 로 선언하였습니다.

using MyFirstWPFApp.Models;
using System.Collections.Generic;
using MyFirstWPFApp.ViewModels.Bases;

namespace MyFirstWPFApp.ViewModels
{
    public class MainWindowViewModel : ViewModelBase
    {
        private IList<bool>? _genderTypes = new List<bool>() { true, false };
        public IList<bool>? GenderTypes
        {
            get => _genderTypes;
        }

        private IList<Person>? _people;
        public IList<Person>? People
        {
            get => _people;
            set => SetProperty(ref _people, value);
        }

        private Person? _selectedPerson;
        public Person? SelectedPerson
        {
            get => _selectedPerson;
            set => SetProperty(ref _selectedPerson, value);
        }

        public MainWindowViewModel()
        {
            People = new List<Person>
            {
                new Person{Id = 119302, Name = "김정현", Gender = true},
                new Person{Id = 119801, Name = "이현재", Gender = true},
                new Person{Id = 111202, Name = "송정환", Gender = true},
                new Person{Id = 111319, Name = "이보현", Gender = true},
                new Person{Id = 112215, Name = "고진욱", Gender = true},
                new Person{Id = 112221, Name = "황보아", Gender = true},
                new Person{Id = 200111, Name = "김푸름", Gender = true},
                new Person{Id = 112113, Name = "백아인", Gender = true},
                new Person{Id = 112109, Name = "신소현", Gender = false},
                new Person{Id = 112206, Name = "주명길", Gender = true},
            };
        }

    }
}

 

 

 

3. 프로그램 실행

그럼 프로그램을 실행해 보도록 하겠습니다.

etc-image-2

잘 실행되는 것을 볼 수 있습니다. 

 

한번 TextBox와 ComboBox의 값을 홍길동, False로 변경해 보겠습니다.

etc-image-3

아래 StackPanel에서도 변경되는 것을 보실 수 있습니다.

 

 


오늘 포스트는 값 변경 시에 다른 요소에서도 실시간으로 값이 변하도록 하는것에 대해 TextBox와 ComboBox를 통해 알아 보았습니다.

 

다음 포스트에는 Converter라는 기능을 사용하여 True, False 데이터를 남자, 여자로 보여지는 부분만 바꾸도록 하겠습니다.

 

이 포스트에 대한 코드는 아래 깃헙에서 다운 받으실 수 있습니다.

https://github.com/epqmqhd/BasicWPF_04

 

etc-image-4