해당 포스팅은 개인적으로 C# WPF를 공부하면서 익힌 내용을 기억에 남기기 위한 작업의 일환으로 작성된 글로서
일부 내용에 오류가 있을 수도 있으니 참고하시기 바랍니다.
C#의 WPF를 활용하여 User Control을 만들고,
MVVM패턴에서 Model객체의 멤버(Property)를
Binding하는 방법에 대해 알아보겠습니다.
우선, C# WPF에 대해 공부를 시작하면서...
C# 으로 WPF를 처음 접하게 되면 기존에 Winows Form Apps으로 구현하던 방식과
매우 다르다는 것을 느끼게된다.
Winows Form Apps이 Drag and Drop 방식으로 Window App을 꾸민 다음,
원하는 동작을 구현하는 방식으로 했다면,
WPF(Windows Presentation Foudation)에서는
철저히 화면 UI와 UI에서 보여주기 위한 정보가
독립적인 공간에 존재하게 되어
유지/보수성 및 분업화에
매우 효율적일 것으로 생각되었다.
또한, WPF는
XAML(Extensible Application Markup Language)을 이용하여
내가 원하는 컨트롤(Button, TextBox 등)을
내 스타일 대로 만들 수 있다는 장점이 있다.
내용 요약
내용을 요약하면,
MVVM 패턴 방식으로 프로젝트를 만들기 위해 Model, View, ViewModel, MVVM 폴더를 생성하여
1.Model에 Family class를 정의 (2번의 BaseViewModel을 상속)
2.MVVM에 속성(=Family 객체 멤버) 변경에 대한 Binding을 위해 BaseViewModel 생성
3.View에 MainWindow에 삽입하기 위한 User Control 생성
4.VIewModel에 Data Context를 통해 UI와 데이터를 주고받을 MainWindowVIewModel 생성 및
Family 객체에 대한 정보를 보여줄 FamilyDisplay User Control 생성
하고,
Button을 통해
Family 객체 속성(멤버)값을
MessageBox를 통해 보여주는
App을 제작할 것이다.
목차
- MVVM 패턴 설정
- Family class(Model) / BaseViewModel 만들기
- FamilyDisplay(View) 만들기
- MainWindowViewModel 만들기
- Model 객체와 User Control 연결하기(Binding)
- 실행 결과
1. MVVM 패턴 설정
Visual Studio를 실행하여 WPF Application으로
Creat Project를 하고 나면 아래 그림과 같이 나타나는데,
여기에서 MVVM 패턴(아직 자세히는 모르지만 기본적으로 Model, View, ViewModel을 따로 구분해주고, 각각의 영역을 서로 침범(?)하지 않도록 하는 패턴)으로 하기 위해
아래 그림처럼 프로젝트 파일명을 마우스 우 클릭하여
Add >> New Folder
하여 Model, View, ViewModel, MVVM 이라는 폴더4개를 각각 생성해준다.
폴더를 다 만들고 나면, 4개의 폴더가 아래와 같이 생성된다.
그러면 우선 MainWindow.xaml을 View 폴더로 Drag and Drop 방식으로 옮겨 준다.
그리고나서
아래와 같이 App.xaml 파일의 StartupUri의 값을 변경 시켜 주어야 한다.
MainWindow.xaml의 파일 경로가 바뀌었기 때문에...
기존 App.xaml
변경 App.xaml
위와 같이 변경한 후 실행하여 MainWindow가 보이는지를 확인해야 한다.
정상적으로 보인다면 App.xaml이 실행 될때
View 폴더 밑의 MainWindow.xaml이
정상적으로 연결이 되었다는 뜻이다.
2. Family class(Model) / BaseViewModel 만들기
그럼 다음으로
UI와의 데이터 바인딩을 위한 모델, Family class를 만들어 준다.
Family class는 아래 그림과 같이
4개의 속성(FirstName, LastName, FamilyMember, Address)을 가지고 있다.
해당 클래스는 Model 폴더를 마우스 우 클릭하여
Add >> class...
하여 만들 수 있다.
using WpfDataBinding.MVVM;
namespace WpfDataBinding.Model
{
public class Family : BaseViewModel
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set {
_firstName = value;
OnPropertyChanged();
}
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set {
_lastName = value;
OnPropertyChanged();
}
}
private int _familyMember;
public int FamilyMember
{
get { return _familyMember; }
set {
_familyMember = value;
OnPropertyChanged();
}
}
private string _address;
public string Address
{
get { return _address; }
set {
_address = value;
OnPropertyChanged();
}
}
}
}
그 다음으로는 INotifyPropertyChanged를 상속받는 BaseViewModel 클래스 정의 및
ICommand를 상속받는 RelayCommand라는 클래스를 하나 만든다.
이 두 클래스를 만드는 이유는 추후에 만들게 될 TextBox와 Button의 속성이
변경될때 발생할 이벤트를 처리기 위함이다.
해당 클래스는 MVVM 폴더를 마우스 우 클릭하여
Add >> class...
하여 만들 수 있다.
(INotifyPropertyChanged 및 ICommand에 대한 자세한 동작 설명은 추후에 진행 하도록 하겠다.)
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
namespace WpfDataBinding.MVVM
{
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class RelayCommand : ICommand
{
Action<object> _execute;
Func<object>? _canExecute;
public RelayCommand() { }
public RelayCommand(Action<object> execute)
{
_execute = execute;
_canExecute = null;
}
public RelayCommand(Action<object> execute, Func<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object? parameter)
{
return true;
}
public void Execute(object? parameter)
{
_execute(parameter);
}
public event EventHandler? CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
}
3. FamilyDisplay(View) 만들기
이제 User Control을 추가해 주자.
해당 클래스는 View 폴더를 마우스 우 클릭하여
Add >> User Control (WPF)...
하여 만들 수 있다.
이름은 마음대로 적어도 되며, 여기에서는 FamilyDisplay로 하였다.
만들어진 FamilyDisplay.xaml파일에 아래와 같이 작성해 준다.
총 3개의 Row와 4개의 Column을 설정하여 TextBlock, TextBox, Button을 만들어 주었다.
<UserControl x:Class="WpfDataBinding.View.FamilyDisplay"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfDataBinding.View"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" Background="White">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40*"/>
<RowDefinition Height="40*"/>
<RowDefinition Height="30*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="First Name : " FontSize="15" FontWeight="Bold"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBox x:Name="txtBoxFirstName" Grid.Row="0" Grid.Column="1" Margin="5"/>
<TextBlock Grid.Row="0" Grid.Column="2" Text="Last Name : " FontSize="15" FontWeight="Bold"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBox x:Name="txtBoxLastName" Grid.Row="0" Grid.Column="3" Margin="5"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Family Member : " FontSize="15" FontWeight="Bold"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBox x:Name="txtBoxFamilyMember" Grid.Row="1" Grid.Column="1" Margin="5"/>
<TextBlock Grid.Row="1" Grid.Column="2" Text="City : " FontSize="15" FontWeight="Bold"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBox x:Name="txtBoxAddress" Grid.Row="1" Grid.Column="3" Margin="5"/>
<Button Grid.Row="2" Grid.ColumnSpan="4" Margin="20" Content="Show Family Info."
FontSize="20" FontWeight="Bold"/>
</Grid>
</UserControl>
만들어진 TextBox 및 Button은
현재까지는 MVVM 패턴에서 Model과 UI를 연결하는
ViewModel 객체와 아무런 바인딩(Binding)이
되어있지 않기 때문에
실행했을 때 TextBox에는 아무런 글자가 나타나지 않으며,
Button을 눌러도 아무런 동작을 하지 않는다.
using WpfDataBinding.ViewModel;
namespace WpfDataBinding
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MainWindowViewModel vm = new MainWindowViewModel();
this.DataContext = vm;
}
}
}
4. MainWindowViewModel 만들기
MVVM 패턴 방식을 사용하여 프로젝트를 만들 경우
대부분의 경우 MainWindowViewModel.cs 파일을 새로 만들어서 아래와 같이
기존의 MainWindow에서 DataContext를 통해
UI의 컨트롤과 Model의 속성을 연결 시켜줄 수 있다.
여기서 만든 MainWindowViewModel.cs 파일은
Solution Explorer에서 아래와 같이 ViewModel폴더에 만들어주면 된다.
그리고 나서
MainWindowViewModel에
TextBox, Button의 속성과 Binding을 하기 위한
객체를 선언해 준다.
public class MainWindowViewModel : RelayCommand
{
public Family Family { get; set; } = new Family() {
FirstName = "KilDong", LastName = "Hong", FamilyMember = 4, Address = "Seoul" };
private ICommand _showFamilyInfo;
public ICommand ShowFamilyInfo
{
get
{
_showFamilyInfo = new RelayCommand(ShowFamilyInfoMessage);
return _showFamilyInfo;
}
set
{
_showFamilyInfo = value;
}
}
public void ShowFamilyInfoMessage(object obj)
{
MessageBox.Show($"FirstName : {Family.FirstName}, LastName : {Family.LastName}\n
FamilyMember : {Family.FamilyMember}, Address : {Family.Address}");
}
}
위 코드 상에서 ShowFamilyInfo 객체는 Button과 Binding하기 위한 속성이며,
ICommand를 상속받은 RelayCommand class를 통해 생성 되면서,
파라미터 1개를 받는 생성자가 호출 될 것이다.
ICommand를 상속받는 RelayCommand 클래스는 EventHandler를 멤버로 가지고 있으며,
넘겨받은 ShowFamilyInfoMessage 메서드를
특정 이벤트 발생 시 실행 하게 된다.
(EventHandler에 대한 자세한 설명은 추후 좀 더 Study를 한 후에 포스팅 하도록 하겠다.)
5. Model 객체와 User Control 연결하기(Binding)
이제 MainWindow.xaml에 앞서 만든 UserContorl을
삽입해 보자.
UserControl을 MainWindow.xaml에 삽입하기 위해서는 아래와 같이 해주어야 한다.
우선, MainWindow.xaml에 해당 경로를 uc라는 이름으로
추가를 해준다.
StackPanel에 아래와 같이 삽입해 주면 된다.
(꼭 StackPanel 내에 삽입 할 필요는 없다.)
<uc:FamilyDisplay/>
위와 같이 하게 되면,
MainWindow.xaml이 아래와 같이 나타난다.
그럼 이제 마지막으로,
TextBox의 Text 속성과 Button의 Command 속성을
MainWindowViewModel에서 설정한
객체(Family, ShowFamilyInfo)와 Binding 시켜준다.
<TextBlock Grid.Row="0" Grid.Column="0" Text="First Name : " FontSize="15" FontWeight="Bold"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBox x:Name="txtBoxFirstName" Grid.Row="0" Grid.Column="1" Margin="5"
Text="{Binding Family.FirstName}"/>
<TextBlock Grid.Row="0" Grid.Column="2" Text="Last Name : " FontSize="15" FontWeight="Bold"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBox x:Name="txtBoxLastName" Grid.Row="0" Grid.Column="3" Margin="5"
Text="{Binding Family.LastName}"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Family Member : " FontSize="15" FontWeight="Bold"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBox x:Name="txtBoxFamilyMember" Grid.Row="1" Grid.Column="1" Margin="5"
Text="{Binding Family.FamilyMember}"/>
<TextBlock Grid.Row="1" Grid.Column="2" Text="City : " FontSize="15" FontWeight="Bold"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBox x:Name="txtBoxAddress" Grid.Row="1" Grid.Column="3" Margin="5"
Text="{Binding Family.Address}"/>
<Button Grid.Row="2" Grid.ColumnSpan="4" Margin="20" Content="Show Family Info."
FontSize="20" FontWeight="Bold"
Command="{Binding ShowFamilyInfo}"/>
여기에서 Binding 모드는 Default가 TwoWay 방식으로
TextBox에서 값을 바꾸면 Family 객체의 값도 바뀌며
반대로
Family 객체의 값이 바뀔 경우 TextBox의 값도 바뀌게 된다.
6. 실행 결과
실행하게 되면, 아래와 같은 창이 뜨고,
Button(Show Family Info.)을 누르면 아래와 같이
MessageBox 가 나타나며 Family 정보가
Display 된다.
여기에서 메시지 박스의 확인 버튼을 누르고,
MainWindow의 TextBox 내용을 변경 후 다시
Button을 누르면 바뀐 내용이 나타나는 것을
확인(Binding mode=TwoWay) 할 수 있다.
"
마치며...
C#으로 WPF를 이용하여 첫번째 프로젝트를 만들어 보았다.
아직 부족한 부분이 많다는 것을 많이 느낄 수 있었으며,
어떤 부분에 대한 공부를 더 해야 하는지 알게되어
계획을 세울 수 있게 되었다.
"
'프로그래밍 > C# (WPF)' 카테고리의 다른 글
C# WPF에서 필드(field)와 속성(property)에 대해 알아보고 get/set 개념 이해하기 (2) | 2024.10.12 |
---|---|
C# WPF에 Style과 Trigger 설명 추가 (4) | 2024.10.09 |
C# WPF에서 Property Trigger 이해하기 (1) | 2024.10.09 |
C# WPF에서의 Data Trigger 이해하기 (0) | 2024.10.09 |
C# WPF에서 데이터 바인딩(Binding)하기 (Control to Control & Model to Control (0) | 2024.10.09 |