WPF 개발, 연습 애플리케이션과 연락처 페이지 생성

본 글에서는 WPF(Windows Presentation Foundation)를 사용하여 기본 애플리케이션 및 연락처 페이지를 생성하는 방법에 대해 다루겠습니다. WPF는 .NET Framework를 기반으로 하며, 강력한 UI 디자인과 데이터 바인딩 기능을 제공합니다. 우리는 기본적인 연락처 관리 애플리케이션을 개발할 것입니다.

1. WPF란?

WPF는 마이크로소프트에서 제공하는 UI 프레임워크로, 복잡한 데스크탑 애플리케이션을 제작할 수 있도록 돕습니다. WPF는 XAML(Extensible Application Markup Language)을 사용하여 사용자 인터페이스를 정의하고, .NET 언어로 애플리케이션 로직을 구현할 수 있습니다.

WPF의 주요 장점으로는 다음과 같은 점이 있습니다:

  • 우수한 데이터 바인딩 기능.
  • 강력한 그래픽 및 애니메이션 렌더링.
  • MVVM(모델-뷰-뷰모델) 디자인 패턴에 대한 지원.
  • 다양한 UI 컨트롤 제공.

2. 프로젝트 설정

Visual Studio에서 WPF 애플리케이션을 생성하는 방법을 설명하겠습니다.

  1. Visual Studio를 실행하고, “새 프로젝트”를 선택합니다.
  2. WPF 애플리케이션을 선택하고 프로젝트 이름을 “ContactManager”로 지정합니다.
  3. 프로젝트가 생성되면, 기본 제공되는 MainWindow.xaml 파일을 확인할 수 있습니다.

3. XAML을 활용한 기본 UI 구성

XAML은 WPF의 사용자 인터페이스를 정의하는 마크업 언어입니다. 아래의 코드는 기본적인 연락처 관리 UI를 설정하는 XAML 코드입니다.

<Window x:Class="ContactManager.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="연락처 관리" Height="350" Width="525">
    <Grid>
        <TextBox x:Name="txtName" Width="200" Height="25" Margin="10" PlaceholderText="이름 입력"/>
        <TextBox x:Name="txtPhone" Width="200" Height="25" Margin="10,40,10,10" PlaceholderText="전화번호 입력"/>
        <Button x:Name="btnAdd" Content="추가" Width="75" Height="25" Margin="10,80,10,10" Click="BtnAdd_Click"/>
        <ListBox x:Name="lstContacts" Margin="220,10,10,10"></ListBox>
    </Grid>
</Window>

위 XAML 코드는 기본적으로 이름과 전화번호를 입력할 수 있는 두 개의 텍스트 박스와 연락처 목록을 보여주는 리스트 박스, 연락처를 추가하는 버튼을 포함하고 있습니다.

4. C#을 이용한 비즈니스 로직 구현

XAML로 구성된 UI에 대한 비즈니스 로직을 작성해보겠습니다. MainWindow.xaml.cs 파일에 다음 코드를 추가합니다.

using System;
    using System.Collections.Generic;
    using System.Windows;

    namespace ContactManager
    {
        public partial class MainWindow : Window
        {
            private List<Contact> contacts = new List<Contact>();

            public MainWindow()
            {
                InitializeComponent();
            }

            private void BtnAdd_Click(object sender, RoutedEventArgs e)
            {
                var name = txtName.Text;
                var phone = txtPhone.Text;

                if (!string.IsNullOrWhiteSpace(name) && !string.IsNullOrWhiteSpace(phone))
                {
                    var contact = new Contact { Name = name, PhoneNumber = phone };
                    contacts.Add(contact);
                    UpdateContactList();
                    ClearInputs();
                }
                else
                {
                    MessageBox.Show("이름과 전화번호를 입력해 주세요.");
                }
            }

            private void UpdateContactList()
            {
                lstContacts.Items.Clear();
                foreach (var contact in contacts)
                {
                    lstContacts.Items.Add(contact.ToString());
                }
            }

            private void ClearInputs()
            {
                txtName.Clear();
                txtPhone.Clear();
            }
        }

        public class Contact
        {
            public string Name { get; set; }
            public string PhoneNumber { get; set; }

            public override string ToString()
            {
                return $"{Name} ({PhoneNumber})";
            }
        }
    }

이 코드는 사용자가 이름과 전화번호를 입력하여 ‘추가’ 버튼을 클릭할 때 연락처를 리스트에 추가하는 기능을 수행합니다. 이 과정에서 입력값이 비어 있지 않은지 확인하고, 연락처 리스트를 갱신하며, 입력 필드를 초기화합니다.

5. MVVM 패턴 적용하기

이제 MVVM 패턴을 적용해보겠습니다. MVVM은 WPF 애플리케이션에서 유지보수성과 테스트 용이성을 높이는 디자인 패턴입니다. 이를 위해 다음 구조를 사용할 것입니다:

  • Model: 데이터 구조 및 비즈니스 로직.
  • View: UI 구성.
  • ViewModel: View와 Model 간의 연결 및 데이터 바인딩 관리.

5.1. Model 클래스

public class Contact
    {
        public string Name { get; set; }
        public string PhoneNumber { get; set; }

        public override string ToString()
        {
            return $"{Name} ({PhoneNumber})";
        }
    }

5.2. ViewModel 클래스

using System.Collections.ObjectModel;
    using System.Windows.Input;

    public class ContactViewModel : BaseViewModel
    {
        public ObservableCollection<Contact> Contacts { get; set; } = new ObservableCollection<Contact>();
        private string name;
        private string phoneNumber;

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

        public string PhoneNumber
        {
            get { return phoneNumber; }
            set { phoneNumber = value; OnPropertyChanged(); }
        }

        public ICommand AddCommand { get; set; }

        public ContactViewModel()
        {
            AddCommand = new RelayCommand(AddContact);
        }

        private void AddContact()
        {
            if (!string.IsNullOrWhiteSpace(Name) && !string.IsNullOrWhiteSpace(PhoneNumber))
            {
                var contact = new Contact { Name = Name, PhoneNumber = PhoneNumber };
                Contacts.Add(contact);
                ClearInputs();
            }
        }

        private void ClearInputs()
        {
            Name = string.Empty;
            PhoneNumber = string.Empty;
        }
    }

5.3. BaseViewModel 클래스

using System.ComponentModel;

    public class BaseViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

5.4. RelayCommand 클래스

using System;
    using System.Windows.Input;

    public class RelayCommand : ICommand
    {
        private readonly Action execute;
        private readonly Func<bool> canExecute;

        public event EventHandler CanExecuteChanged;

        public RelayCommand(Action execute, Func<bool> canExecute = null)
        {
            this.execute = execute;
            this.canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            return canExecute == null || canExecute();
        }

        public void Execute(object parameter)
        {
            execute();
        }

        public void RaiseCanExecuteChanged()
        {
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }
    }

5.5. View 업데이트

이제 MainWindow.xaml을 수정하여 ViewModel과 데이터 바인딩을 적용합니다:

<Window x:Class="ContactManager.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:ContactManager"
            Title="연락처 관리" Height="350" Width="525">
    <Window.DataContext>
        <local:ContactViewModel />
    </Window.DataContext>
    <Grid>
        <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Width="200" Height="25" Margin="10" PlaceholderText="이름 입력"/>
        <TextBox Text="{Binding PhoneNumber, UpdateSourceTrigger=PropertyChanged}" Width="200" Height="25" Margin="10,40,10,10" PlaceholderText="전화번호 입력"/>
        <Button Command="{Binding AddCommand}" Content="추가" Width="75" Height="25" Margin="10,80,10,10"/>
        <ListBox ItemsSource="{Binding Contacts}" Margin="220,10,10,10">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding}"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

6. 결과 확인

애플리케이션을 실행하면 사용자 인터페이스가 표시됩니다. 이름과 전화번호를 입력하고 ‘추가’ 버튼을 클릭하여 연락처를 추가해 보십시오. 연락처는 리스트에 추가되어야 하며, MVVM 패턴 없이 제어 구조를 활용한 경우보다 더 깨끗하고 유지보수성이 좋은 코드를 경험할 수 있습니다.

7. 결론

이번 강좌에서는 WPF를 사용하여 기본적인 연락처 관리 애플리케이션을 생성하는 방법을 살펴보았습니다. WPF의 MVVM 패턴을 적용하여 코드의 재사용성과 테스트 용이성을 높일 수 있음을 보여주었습니다. 향후 더 복잡한 기능들을 추가하면서 WPF의 다양한 기능을 활용해보시기 바랍니다.

8. 참고자료

WPF는 끊임없이 발전하는 기술입니다. 계속해서 학습하고 실습하여 더 나은 개발자가 되시기 바랍니다.