20장. WPF 계산기, MVVM, Command, 데이터바인딩이용

20장. WPF 계산기, MVVM, Command, 데이터바인딩이용

참조

목적

  • WPF 계산기를 MVVM, Command, 데이터바인딩을 이용하여 만들어 봅니다.

실습

CalcCommand.cs

using System;
using System.Windows.Input;

namespace WPF18_Test
{
    class Append : ICommand
    {
        private CalcViewModel c;
        public event EventHandler CanExecuteChanged;

        public Append(CalcViewModel c)
        {
            this.c = c;
        }

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public void Execute(object parameter)
        {
            c.InputString += parameter;
        }
    }

    class BackSpace : ICommand
    {
        private CalcViewModel c;

        public BackSpace(CalcViewModel c)
        {
            this.c = c;
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public bool CanExecute(object parameter)
        {
            return c.DisplayText.Length > 0;
        }

        public void Execute(object parameter)
        {
            int length = c.InputString.Length - 1;
            if(0 < length)
            {
                c.InputString = c.InputString.Substring(0, length);
            }
            else
            {
                c.InputString = c.DisplayText = string.Empty;
            }
        }
    }

    class Clear : ICommand
    {
        private CalcViewModel c;

        public Clear(CalcViewModel c)
        {
            this.c = c;
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested += value; }
        }

        public bool CanExecute(object parameter)
        {
            return c.DisplayText.Length > 0;
        }

        public void Execute(object parameter)
        {
            c.InputString = c.DisplayText = string.Empty;
            c.Op1 = null;
        }
    }

    class Operator : ICommand
    {
        private CalcViewModel c;
        public Operator(CalcViewModel c)
        {
            this.c = c;
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public bool CanExecute(object parameter)
        {
            return 0 < c.InputString.Length;
        }

        public void Execute(object parameter)
        {
            string op = parameter.ToString();
            double op1;
            if(double.TryParse(c.InputString, out op1))
            {
                c.Op1 = op1;
                c.Op = op;
                c.InputString = ""; //3 그리고 + 누르면 DisplayText는 3, InputString는 Clear
            }
            else if(c.InputString == "" && op == "-")
            {
                c.InputString = "-";
            }
        }
    }

    class Calculate : ICommand
    {
        private CalcViewModel c;

        public Calculate(CalcViewModel c)
        {
            this.c = c;
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public bool CanExecute(object parameter)
        {
            double op2;
            return c.Op1 != null && double.TryParse(c.InputString, out op2);
        }

        public void Execute(object parameter)
        {
            double op2 = double.Parse(c.InputString);
            c.InputString = calculate(c.Op, (double)c.Op1, op2).ToString();
            c.Op1 = null;
        }

        private static double calculate(string op, double op1, double op2)
        {
            switch(op)
            {
                case "+" : return op1 + op2;
                case "-" : return op1 - op2;
                case "*" : return op1 * op2;
                case "/" : return op1 / op2;
            }

            return 0;
        }
    }

    class CalcCommand
    {

    }
}

CalcViewModel.cs

using System.ComponentModel;
using System.Windows.Input;

namespace WPF18_Test
{
    public class CalcViewModel : INotifyPropertyChanged
    {
        //아래 두 필드는 속성으로 구현되어 있다.
        //출력될 문자들을 담아둘 변수
        string inputString = string.Empty;

        //계산기 화면의 출력 텍스트박스에 대응되는 필드
        string displayText = "";

        //View와 바인딩된 속성값이 바뀔때 이를 WPF에게 알리기 위한 이벤트
        public event PropertyChangedEventHandler PropertyChanged;

        //생성자, 명령 객체들을 초기화
        //명령 객체들은 UI쪽 버튼의 Command에 바인딩되어 있다.
        public CalcViewModel()
        {
            //이벤트 핸들러 정의
            //숫자 버튼을 클릭할 때 실행
            this.Append = new Append(this);

            //백 스페이스 버튼을 클릭할 때 실행, 한글자 삭제
            this.BackSpace = new BackSpace(this);

            //출력화면 클리어
            this.Clear = new Clear(this);

            //+,-,*,/ 등 연산자 클릭할 때 실행
            this.Operator = new Operator(this);

            //"=" 버튼을 클릭할 때 실행
            this.Calculate = new Calculate(this);
        }

        public string InputString
        {
            internal set
            {
                if (inputString != value)
                {
                    inputString = value;
                    OnPropertyChanged("InputString");
                    if (value != "")
                    {
                        //숫자를 여러개 입력하면 계속 화면에 출력하기 위해
                        DisplayText = value;
                    }
                }
            }
            get
            {
                return inputString;
            }
        }

        /// <summary>
        /// 계산기의 출력창과 바인딩된 속성
        /// </summary>
        public string DisplayText
        {
            internal set
            {
                if (displayText != value)
                {
                    displayText = value;
                    OnPropertyChanged("DisplayText");
                }
            }
            get
            {
                return displayText;
            }
        }

        public string Op { get; set; } //Operator
        public double? Op1 { get; set; } //Operand 1

        public ICommand Append { protected set; get; }
        public ICommand BackSpace { protected set; get; }
        public ICommand Clear { protected set; get; }
        public ICommand Operator { protected set; get; }
        public ICommand Calculate { protected set; get; }

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

    }
}

MainWindow.xaml

<Window x:Class="WPF18_Test.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:WPF18_Test"
        mc:Ignorable="d"
        Title="MainWindow" Height="400" Width="400">
    <Grid Margin="10" HorizontalAlignment="Center" 
          VerticalAlignment="Center">
        <Grid.RowDefinitions>
            <RowDefinition Height="60"/>
            <RowDefinition Height="60"/>
            <RowDefinition Height="60"/>
            <RowDefinition Height="60"/>
            <RowDefinition Height="60"/>
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition Width="60"/>
        </Grid.ColumnDefinitions>

        <Border Grid.Row="0" Grid.ColumnSpan="2"
                BorderBrush="Black" BorderThickness="3">
            <TextBlock x:Name="txtInput"
                       FontSize="15" VerticalAlignment="Center"
                       HorizontalAlignment="Center"
                       Text="{Binding DisplayText}"/>
        </Border>

        <Button Grid.Row="0" Grid.Column="2"
                Content="BACK" 
                x:Name="BtnBack"
                Command="{Binding BackSpace}"/>
        <Button Grid.Row="0" Grid.Column="3"
                Content="Clear" 
                x:Name="BtnClear"
                Command="{Binding Clear}"/>

        <Button Grid.Row="1" Grid.Column="0"
                Content="1"
                x:Name="BtnOne"
                Command="{Binding Append}"
                CommandParameter="1"/>
        <Button Grid.Row="1" Grid.Column="1"
                Content="2" 
                x:Name="BtnTwo"
                Command="{Binding Append}"
                CommandParameter="2"/>
        <Button Grid.Row="1" Grid.Column="2"
                Content="3" 
                x:Name="BtnThree"
                Command="{Binding Append}"
                CommandParameter="3"/>
        <Button Grid.Row="1" Grid.Column="3"
                Content="+" 
                x:Name="BtnPlus"
                Command="{Binding Operator}"
                CommandParameter="+"/>

        <Button Grid.Row="2" Grid.Column="0"
                Content="4"
                x:Name="BtnFour"
                Command="{Binding Append}"
                CommandParameter="4"/>
        <Button Grid.Row="2" Grid.Column="1"
                Content="5" 
                x:Name="BtnFive"
                Command="{Binding Append}"
                CommandParameter="5"/>
        <Button Grid.Row="2" Grid.Column="2"
                Content="6" 
                x:Name="BtnSix"
                Command="{Binding Append}"
                CommandParameter="6"/>
        <Button Grid.Row="2" Grid.Column="3"
                Content="-" 
                x:Name="BtnMinus"
                Command="{Binding Operator}"
                CommandParameter="-"/>

        <Button Grid.Row="3" Grid.Column="0"
                Content="7"
                x:Name="BtnSeven"
                Command="{Binding Append}"
                CommandParameter="7"/>
        <Button Grid.Row="3" Grid.Column="1"
                Content="8" 
                x:Name="BtnEight"
                Command="{Binding Append}"
                CommandParameter="8"/>
        <Button Grid.Row="3" Grid.Column="2"
                Content="9" 
                x:Name="BtnNine"
                Command="{Binding Append}"
                CommandParameter="9"/>
        <Button Grid.Row="3" Grid.Column="3"
                Content="*" 
                x:Name="BtnMulitple"
                Command="{Binding Operator}"
                CommandParameter="*"/>

        <Button Grid.Row="4" Grid.Column="0"
                Content="0"
                x:Name="BtnZero"
                Command="{Binding Append}"
                CommandParameter="0"/>
        <Button Grid.Row="4" Grid.Column="1"
                Content="." 
                x:Name="BtnDot"/>
        <Button Grid.Row="4" Grid.Column="2"
                Content="="
                x:Name="BtnEqual"
                 Command="{Binding Calculate}"/>
        <Button Grid.Row="4" Grid.Column="3"
                Content="/" 
                x:Name="BtnDivision"
                Command="{Binding Operator}"
                CommandParameter="/"/>
    </Grid>
</Window>

MainWindow.xaml.cs

using System.Windows;

namespace WPF18_Test
{
    /// <summary>
    /// MainWindow.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new CalcViewModel();
        }
    }
}

실행결과

1

728x90

이 글을 공유하기

댓글

Designed by JB FACTORY