WPF

[WPF] 계산기 만들기

연향동큰손 2026. 1. 9. 17:06

WPF

  • Windows Presentation Foundation의 약자
  • 마이크로소프트에서 만든 UI 프레임워크
  • .NET으로 빌드가 가능

 

MVVM 패턴

model : 데이터를 다루는 부분, 비즈니스 로직을 포함

 

View : 레이아웃과 화면을 보여주는 역할, 사용자 인터페이스 담당

 

ViewModel

  • View에서 발생하는 이벤트 감지하고, 해당 이벤트에 맞는 비즈니스 로직을 수행
  • Model과 상호작용하여 데이터를 가져오거나 업데이터
  • View에 데이터를 업데이터하는 역할
  • View에 표시할 데이터를 가공하여 제공

 

MVVM패턴 동작과정

  1. 사용자의 Action이 View를 통해 들어온다.
  2. View에 Action이 들어오면 ViewModel에 Action을 전달
  3. ViewModel은 Model에게 데이터를 요청
  4. Model이 ViewModel에게 데이터를 전달
  5. ViewModel이 응답받은 데이터를 가공하여 저장
  6. View는 Data Binding을 이용해 UI를 갱신

 

MVVM 패턴의 장점
- 뷰 로직과 비즈니스 로직을 분리하여 생산성 증가
- 테스트 수월
- 하나의 ViewModel을 여러 View가 공유할 수 있다( 하나의 로그인 ViewModel을 모바일뷰,PC뷰,테블릿뷰가 공유할 수 있다. (코드 재사용성 용이)
MVVM 패턴의 단점
- 설계가 복잡함
- 데이터 바인딩으로 인한 메모리 소모가 심하다.
- ViewModel 설계가 복잡함

 

 

XAML

  • eXtensible Markup Language
  • WPF에서 View를 그리기위해 사용하는 언어
  • Markup Language이기 때문에 태그를 사용

계산기 만들어보기

 

partial : 하나의 클래스를 여러 파일에 나눠서 작성할 수 있게 해주는 키워드

 

WPF 프로젝트를 보면 UI와 로직을 분리해서 관리하고 이 둘이 합쳐져서 하나의 클래스를 정의하기 때문에 partial키워드를 넣어준다.

 

public partial class MainWindow : Window 부분을 보면 Window클래스를 상속받는 것을 확인할 수 있다.

 

Window클래스는 창 하나에 필요한 모든 기본 기능을 구현해둔 클래스이고 이 클래스를 MainWindow에서 상속받기 때문에 창을 만들 때 필요한 여러 값이나 기능을 사용할 수 있게 된다.

 

 

MainWindow 클래스를 생성할때 InitializeComponent()를 통해 XAML을 읽고, Display나 Button등의 컨트롤 객체를 생성하는 등의 초기화 작업이 이루어 진다.

public MainWindow()
{
    InitializeComponent(); // XAML UI 초기화
}

 

 

 

값을 저장하기 위한 멤버 변수

private double _currentValue = 0; // 현재 계산 값
private string _currentOperator = ""; // 현재 연산자
private bool _isNewEntry = true; // 새 입력 여부

 

 

Number_Click메서드는 숫자 버튼을 눌렀을 때 실행되는 이벤트 로직이다.

  • object sender : 이벤트를 발생시킨 객체
    • 왜 object 타입일까? : 이벤트를 발생 시키는 객체가 Button, TextBox, Window등 다양할 수 있기 때문이다.
    • 왜 Button button = sender as Button; 와 같이 sender를 Button타입으로 바꿔줄까? :  sender가 object타입이기 때문에 버튼 클래스의 메서드나 속성에 접근하기 위해서 형변환을 해준다.
  • RoutedEventArgs e : WPF 이벤트에 대한 추가 정보
private void Number_Click(object sender, RoutedEventArgs e)
{
    Button button = sender as Button;
    if (_isNewEntry) //지금 숫자 입력이 첫 숫자 입력인가?
    {
        Display.Text = button.Content.ToString(); //지금 누른 숫자를 화면에 넣음
        _isNewEntry = false;
    }
    else //숫자 입력중일때는 
    {
        Display.Text += button.Content.ToString(); //새 숫자를 뒤에 붙임.
    }
}

 

 

해당 메서드는 입력된 연산자에 따라 계산결과를 화면에 출력하는 메서드이다.

private void Calculate(double newValue)
{
    switch (_currentOperator)
    {
        case "+":
            _currentValue += newValue;
            break;
        case "-":
            _currentValue -= newValue;
            break;
        case "×":
            _currentValue *= newValue;
            break;
        case "÷":
            _currentValue /= newValue;
            break;
    }
    Display.Text = _currentValue.ToString();
}

 

 

연산자 버튼을 누를때 수행되는 Operator_Click 메서드

private void Operator_Click(object sender, RoutedEventArgs e) //연산자 버튼을 누를때 수행
{
    Button button = sender as Button;
    if (double.TryParse(Display.Text, out double newValue)) //화면에 있는 값을 숫자로 변환
    {
        if (!_isNewEntry)
        {
            Calculate(newValue);
        }
        _currentOperator = button.Content.ToString(); //현재 연산자 저장

        if (_currentOperator == "%")
        {
            double percentValue = _currentValue * (newValue / 100);
            Calculate(percentValue);
            _currentOperator = "";
        }
        else
        {
            _currentValue = newValue;
        }
        _isNewEntry = true;

    }
}

 

= 버튼을 누를때 계산이 수행되도록 하는 메서드

 private void Result_Click(object sender, RoutedEventArgs e)
 {
     if(double.TryParse(Display.Text, out double newValue))
     {
         Calculate(newValue); //계산 수행
         _currentOperator = ""; //연산자 초기화
         _isNewEntry = true; //새로운 값을 받을 준비
     }
 }

 

<전체 코드>

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
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 WpfApp1
{
    /// <summary>
    /// MainWindow.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class MainWindow : Window
    {
        private double _currentValue = 0; // 현재 계산 값
        private string _currentOperator = ""; // 현재 연산자
        private bool _isNewEntry = true; // 새 입력 여부

        public MainWindow()
        {
            InitializeComponent(); // XAML UI 초기화
        }

        private void Number_Click(object sender, RoutedEventArgs e)
        {
            Button button = sender as Button;
            if (_isNewEntry)
            {
                Display.Text = button.Content.ToString();
                _isNewEntry = false;
            }
            else
            {
                Display.Text += button.Content.ToString();
            }
        }

        private void Operator_Click(object sender, RoutedEventArgs e) //연산자 버튼을 누를때 수행
        {
            Button button = sender as Button;
            if (double.TryParse(Display.Text, out double newValue)) //화면에 있는 값을 숫자로 변환
            {
                if (!_isNewEntry)
                {
                    Calculate(newValue);
                }
                _currentOperator = button.Content.ToString(); //현재 연산자 저장

                if (_currentOperator == "%")
                {
                    double percentValue = _currentValue * (newValue / 100);
                    Calculate(percentValue);
                    _currentOperator = "";
                }
                else
                {
                    _currentValue = newValue; //지금 화면에 있는 값을 피연산 값으로 저장
                }
                _isNewEntry = true;

            }
        }


        private void Calculate(double newValue)
        {
            switch (_currentOperator)
            {
                case "+":
                    _currentValue += newValue;
                    break;
                case "-":
                    _currentValue -= newValue;
                    break;
                case "×":
                    _currentValue *= newValue;
                    break;
                case "÷":
                    _currentValue /= newValue;
                    break;
            }
            Display.Text = _currentValue.ToString();
        }


        //지우기 버튼: 초기화
        private void Clear_Click(object sender, RoutedEventArgs e)
        {
            _currentValue = 0;
            _currentOperator = "";
            Display.Text = "0";
            _isNewEntry = true;
        }

        private void Backspace_Click(object sender, RoutedEventArgs e)
        {
            if (Display.Text.Length > 1)
            {
                Display.Text = Display.Text.Substring(0, Display.Text.Length - 1); //지우기

            }
            else
            {
                Display.Text = "0"; //화면의 숫자는 0으로 설정
                _isNewEntry = true; // 값을 새롭게 받음
            }
        }

        private void Point_Click(object sender, RoutedEventArgs e)
        {
            if (_isNewEntry) //새롭게 숫자를 받아야 하는데 .을 찍으면 0.으로 표기
            {
                Display.Text = "0.";
                _isNewEntry = false;
                return;
            }

            if (!Display.Text.Contains("."))
            {
                Display.Text += ".";
                
            }
        }

        private void Result_Click(object sender, RoutedEventArgs e)
        {
            if(double.TryParse(Display.Text, out double newValue))
            {
                Calculate(newValue); //계산 수행
                _currentOperator = ""; //연산자 초기화
                _isNewEntry = true; //새로운 값을 받을 준비
            }
        }
    }
}
<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Calculator"
        Height="520"
        Width="360"
        WindowStartupLocation="CenterScreen"
        ResizeMode="NoResize">

    <Grid Margin="10">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="99*"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <!-- 전체 레이아웃: 표시창 + 버튼 -->
        <Grid.RowDefinitions>
            <RowDefinition Height="100"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!-- ================= 표시창 ================= -->
        <TextBox x:Name="Display"
                 Grid.Row="0"
                 Text="0"
                 FontSize="36"
                 IsReadOnly="True"
                 TextAlignment="Right"
                 VerticalContentAlignment="Center"
                 Padding="10"
                 Margin="0,0,0,10" Grid.ColumnSpan="2"/>

        <!-- ================= 버튼 영역 ================= -->
        <Grid Grid.Row="1" Grid.ColumnSpan="2">
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>

            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>

            <!-- 1행 -->
            <Button Content="C"  Grid.Row="0" Grid.Column="0" Click="Clear_Click"/>
            <Button Content="CE" Grid.Row="0" Grid.Column="1" Click="Clear_Click"/>
            <Button Content="⌫"  Grid.Row="0" Grid.Column="2" Click="Backspace_Click"/>
            <Button Content="÷"  Grid.Row="0" Grid.Column="3" Click="Operator_Click"/>

            <!-- 2행 -->
            <Button Content="7" Grid.Row="1" Grid.Column="0" Click="Number_Click"/>
            <Button Content="8" Grid.Row="1" Grid.Column="1" Click="Number_Click"/>
            <Button Content="9" Grid.Row="1" Grid.Column="2" Click="Number_Click"/>
            <Button Content="×" Grid.Row="1" Grid.Column="3" Click="Operator_Click"/>

            <!-- 3행 -->
            <Button Content="4" Grid.Row="2" Grid.Column="0" Click="Number_Click"/>
            <Button Content="5" Grid.Row="2" Grid.Column="1" Click="Number_Click"/>
            <Button Content="6" Grid.Row="2" Grid.Column="2" Click="Number_Click"/>
            <Button Content="-" Grid.Row="2" Grid.Column="3" Click="Operator_Click"/>

            <!-- 4행 -->
            <Button Content="1" Grid.Row="3" Grid.Column="0" Click="Number_Click"/>
            <Button Content="2" Grid.Row="3" Grid.Column="1" Click="Number_Click"/>
            <Button Content="3" Grid.Row="3" Grid.Column="2" Click="Number_Click"/>
            <Button Content="+" Grid.Row="3" Grid.Column="3" Click="Operator_Click"/>

            <!-- 5행 -->
            <Button Content="0" Grid.Row="4" Grid.Column="1" Click="Number_Click"/>
            <Button Content="." Grid.Row="4" Grid.Column="2" Click="Point_Click"/>
            <Button Content="=" Grid.Row="4" Grid.Column="3" Click="Result_Click"/>
        </Grid>
    </Grid>
</Window>