[WPF] 키오스크 예제 소스코드 (C#)

WPF를 기반으로 만든 키오스크 예제 소스코드입니다.

키오스크 예제

병원, 의원에서 고객 정보를 입력하여 진료비를 확인하고 결제하는 키오스크를 간단히 만들어 보았습니다.

키오스크 기본 창

정보 조회

키오스크 기본창은 윈도우를 상속받도록 하겠습니다. 해당 화면에는 프레임 컨트롤이 있으며 해당 프레임에서 각종 페이지를 이동하도록 진행하겠습니다.

using BeomKiosk.Model;
using BeomKiosk.Pages;
using System;
using System.Collections.Generic;
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.Shapes;

namespace BeomKiosk.View
{
    public partial class Beom_MainWindow : Window, IBeomNavigation
    {
        public Beom_MainWindow()
        {
            InitializeComponent();            
        }       

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            Beom_Core.Init();
            Beom_Pages.g_beom_mainWindow = this;
        }

        public void Navigate(Page page)
        {
            frame.Content = page;
        }
    }
}

기본 페이지

정보 없음

기본 창의 프레임에서 보여주는 최초의 기본 페이지입니다. 환자번호나 주민등록번호와 같은 고객의 정보를 식별할 수 있는 정보를 입력하였을 때, 정보를 조회하도록 하겠습니다. 해당 예제는 테스트 자료라서 별도의 데이터베이스 연동은 생략하였습니다. 대신 임의의 번호를 입력하여 조회할 수 있도록 추가하였습니다.

하단의 숫자를 입력하는 버튼은 이번 예제에서 가장 공을 많이 들였으며, 사용자 정의 컨트롤을 사용하였습니다. 그리고 MVVM 모델과 유사하게 디펜던시 프로퍼티를 사용하는 예제를 추가해 보았습니다.

MVVM 패턴 예제 자료는 여기를 참고해 주세요.

결제내역이 없는 경우 다이얼로그를 띄우도록 하겠습니다. 물론 사용자 경험을 고려하면, 창 보다는 메시지를 눈에 띄게 보이도록 하는 방법이 더 좋을 것입니다. 해당 예제는 WPF를 사용하는 여러가지 방법을 추가해 본 것이라 참고만 해주십시오~

using BeomKiosk.Controls;
using BeomKiosk.Model;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
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;
using static BeomKiosk.Model.Beom_TestDatabase;

namespace BeomKiosk.Pages
{
    /// <summary>
    /// Beom_MainPage.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class Beom_MainPage : Page, IBeomPage
    {
        private static Beom_MainPage thisPage = null;
        private static readonly object padlock = new object();
        private string m_nums = string.Empty;

        public Beom_MainPage()
        {
            InitializeComponent();
        }

        public static Beom_MainPage GetInstance()
        {
            lock (padlock)
            {
                if (thisPage == null)
                {
                    thisPage = new Beom_MainPage();
                }
                return thisPage;
            }
        }

        public void PageLoaded()
        {
            try
            {
                Beom_Core.Clear();

                Test();
            }
            catch (Exception ex)
            {
                ex.SaveException();
            }            
        }

        public void PageUnloaded()
        {
            try
            {

            }
            catch (Exception ex)
            {
                ex.SaveException();
            }
        }

        private void Test()
        {
            //m_nums = "99999999";
            //lblNums.Content = m_nums;
        }

        private void SetData(bool _isSet)
        {
            try
            {
                Beom_Core.Clear();

                if (_isSet)
                {
                    switch (m_nums.Length)
                    {
                        case 13:
                            Beom_Core.g_구분 = Beom_Core.구분.주민등록번호;
                            Beom_Core.g_list = Beom_Core.Database.AsEnumerable().Where(x => x.주민등록번호.ToString() == m_nums).ToList();
                            break;
                        case 8:
                            Beom_Core.g_구분 = Beom_Core.구분.환자번호;
                            Beom_Core.g_list = Beom_Core.Database.AsEnumerable().Where(x => x.환자번호.ToString() == m_nums).ToList();
                            break;
                        default:
                            Beom_Core.g_구분 = Beom_Core.구분.없음;
                            Beom_Core.g_list.Clear();
                            break;
                    }
                }
            }
            catch (Exception ex)
            {
                ex.SaveException();
            }
        }

        private bool Validate()
        {
            try
            {
                SetData(true);

                if (m_nums == string.Empty)
                {
                    Beom_Core.GetBeomMsgOk("입력", "주민등록번호 또는 환자번호를 입력해 주세요.");
                    return false;
                }               

                if (!Regex.IsMatch(m_nums, "^[0-9]{13}$") && !Regex.IsMatch(m_nums, "^[0-9]{8}$"))
                {
                    Beom_Core.GetBeomMsgOk("숫자", "주민등록번호 13자리 또는 환자번호 8자리를 입력해 주세요.");
                    return false;
                }

                //포트폴리오
                //그외 인적정보 데이터베이스 실제 등록 여부, 미수금 내역 확인 등은 생략합니다.
                //별도 감면 대상 진료내역, 확인이 필요한 제증명, 건강보험 이외 확인 필요 진료내역 등
                if (Beom_Core.g_list.Count > 0)
                {

                }
                else
                {
                    Beom_Core.GetBeomMsgOk("확인", $"진료내역이 없습니다. 입력값 : {m_nums}");
                    return false;
                }

                return true;
            }
            catch (Exception ex)
            {
                ex.SaveException();
                return false;
            }
        }
       
        public void Button_Click(object sender, EventArgs e)
        {
            try
            {
                Beom_Num b = sender as Beom_Num;
                if (b != null)
                {
                    string content = b.BeomContent.ToString();
                    switch (content)
                    {
                        case "확인":
                            if (Validate())
                            {
                                Beom_Pages.Navigate(Beom_Pages.M_Beom_PaymentPage);
                            }
                            else
                            {
                                SetData(false);
                            }
                            break;
                        case "정정":
                            if (lblNums.Content.ToString().Length > 0)
                            {
                                m_nums = lblNums.Content.ToString().Substring(0, lblNums.Content.ToString().Length - 1);
                                lblNums.Content = m_nums;
                            }
                            break;
                        default:
                            if (Regex.IsMatch(content, "[0-9]"))
                            {
                                m_nums = lblNums.Content + content;
                                lblNums.Content = m_nums;
                            }
                            else
                                throw new NotImplementedException("키 확인!");
                            break;
                    }
                }
            }
            catch (Exception ex)
            {
                ex.SaveException();
            }
        }

        private void Page_Loaded(object sender, RoutedEventArgs e)
        {
            PageLoaded();            
        }

        private void Page_Unloaded(object sender, RoutedEventArgs e)
        {
            PageUnloaded();
        }
    }
}

결제 정보 조회 페이지

결제 정보 조회

고객정보를 제대로 입력한 경우, 결제할 내역을 조회하도록 합니다. 결제내역이 많은 경우, 스크롤로 표시할 수 있도록 해보았습니다. 그리고 그리드의 로우 디피니션, 컬럼 디피니션을 프로그래밍 방법으로 늘릴 수 있는 예제를 추가해 보았습니다.

using BeomKiosk.Controls;
using BeomKiosk.Model;
using BeomKiosk.View;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
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 BeomKiosk.Pages
{
    /// <summary>
    /// Beom_PaymentPage.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class Beom_PaymentPage : Page, IBeomPage
    {
        private static Beom_PaymentPage thisPage = null;
        private static readonly object padlock = new object();

        public Beom_PaymentPage()
        {
            InitializeComponent();
        }

        public static Beom_PaymentPage GetInstance()
        {
            lock (padlock)
            {
                if (thisPage == null)
                {
                    thisPage = new Beom_PaymentPage();
                }
                return thisPage;
            }
        }

        public void PageLoaded()
        {
            try
            {
                grdPayList.Children.Clear();
                grdPayList.RowDefinitions.Clear();
                SetPage();
            }
            catch (Exception ex)
            {
                ex.SaveException();
            }
        }

        public void PageUnloaded()
        {
            
        }

        private void SetPage()
        {
            try
            {
                Grid grd;
                TextBlock ctr;
                decimal totAmt = decimal.Zero;

                for (int i = 0; i < Beom_Core.g_list.Count; i++)
                {
                    totAmt += Beom_Core.g_list[i].진료비;

                    txtName.Text = $"{ Beom_Core.g_list[i].환자명} 님";

                    //진료비 내역 추가
                    grdPayList.RowDefinitions.Add(new RowDefinition { Height = new GridLength(75, GridUnitType.Pixel) });
                    grd = new Grid();
                    for (int c = 0; c < 3; c++)
                    {
                        ctr = new TextBlock()
                        {
                            //테스트
                            //Text = $"{grdPayList.RowDefinitions.Count - 1}",
                            Margin = new Thickness(1),
                            FontSize = 15,
                            FontWeight = FontWeights.Bold,
                            HorizontalAlignment = HorizontalAlignment.Center,
                            VerticalAlignment = VerticalAlignment.Center
                        };

                        switch (c)
                        {
                            case 0:
                                ctr.Text = Beom_Core.g_list[i].진료과;
                                break;
                            case 1:
                                ctr.Text = Beom_Core.g_list[i].의사;
                                break;
                            case 2:
                                ctr.Text = Beom_Core.g_list[i].진료비.ToString("#,##0");
                                break;
                            default:
                                throw new NotImplementedException("컬럼 확인!");
                        }

                        Grid.SetRow(ctr, grdPayList.RowDefinitions.Count - 1);
                        Grid.SetColumn(ctr, c);
                        grdPayList.Children.Add(ctr);
                    }
                }

                txtTotAmt.Text = totAmt.ToString("#,##0");
            }
            catch (Exception ex)
            {
                ex.SaveException();
            }
        }

        private void btnBeom_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (sender == btnYes)
                {
                    decimal amt = Convert.ToDecimal(Regex.Replace(txtTotAmt.Text, "[^0-9]", string.Empty));
                    if (amt > 50000)
                    {
                        Beom_Core.SetVolume(Beom_Core.m_speeches[Beom_Core.g_s_결제방법]);
                        Beom_Pages.Navigate(Beom_Pages.M_Beom_PayMethodPage);
                    }
                    else
                    {
                        if (Beom_Core.GetBeomMsgYesNo("일시불", $"일시불 결제 ({txtTotAmt.Text}원)를 진행하시겠습니까?") == true)
                        {
                            Beom_Core.SetVolume(Beom_Core.m_speeches[Beom_Core.g_s_결제진행]);
                            Beom_Core.g_payType = Beom_Pay.Type.Card;
                            Beom_Pages.Navigate(Beom_Pages.M_Beom_PayProcessPage);
                        }
                    }
                }
                else if (sender == btnNo)
                {
                    Beom_Core.SetVolume(Beom_Core.m_speeches[Beom_Core.g_s_취소]);
                    Beom_Pages.Navigate(Beom_Pages.M_Beom_MainPage);
                }
                else
                {
                    throw new NotImplementedException("버튼 클릭 이벤트 확인!");
                }
            }
            catch (Exception ex)
            {
                ex.SaveException();
            }
        }



        private void Page_Loaded(object sender, RoutedEventArgs e)
        {
            PageLoaded();
        }

        private void Page_Unloaded(object sender, RoutedEventArgs e)
        {
            PageUnloaded();
        }
    }
}

결제 방법 선택

결제 방법 선택

5만 원을 초과하는 경우 할부를 선택할 수 있도록 추가하였습니다.

결제 도움말

그리고 결제 도움말을 한 번 띄워보도록 하였습니다.

using BeomKiosk.Controls;
using BeomKiosk.Model;
using BeomKiosk.View;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
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 BeomKiosk.Pages
{
    /// <summary>
    /// Beom_PayMethodPage.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class Beom_PayMethodPage : Page, IBeomPage
    {
        private static Beom_PayMethodPage thisPage = null;
        private static readonly object padlock = new object(); 

        public Beom_PayMethodPage()
        {
            InitializeComponent();
        }

        public static Beom_PayMethodPage GetInstance()
        {
            lock (padlock)
            {
                if (thisPage == null)
                {
                    thisPage = new Beom_PayMethodPage();
                }
                return thisPage;
            }
        }

        public void PageLoaded()
        {
          
        }

        public void PageUnloaded()
        {
            
        }

        private void btnBeom_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                string msgTitle = ((Button)sender).Content.ToString();
                string installment = Regex.Replace(((Button)sender).Content.ToString(), "[^0-9]", string.Empty);
                if (installment == string.Empty)
                {
                    installment = "00";
                }

                if (Beom_Core.GetBeomMsgYesNo(msgTitle, $"{msgTitle} 결제를 진행하시겠습니까?") == true)
                {
                    Beom_Core.SetVolume(Beom_Core.m_speeches[Beom_Core.g_s_결제진행]);
                    Beom_Core.g_payType = Beom_Pay.Type.Card;
                    Beom_Core.g_installment = Convert.ToInt32(installment);
                    Beom_Pages.Navigate(Beom_Pages.M_Beom_PayProcessPage);
                }       
            }
            catch (Exception ex)
            {
                ex.SaveException();
            }
        }       
    }
}

결제 테스트

결제 테스트

결제 연동은 생략하였으며, 카드 결제는 성공과 실패가 있을 것입니다. 테스트 화면으로 성공과 실패하는 기능을 각각 추가해보았습니다. 각 화면을 보여준 다음에 첫 째 화면으로 이동합니다.

using BeomKiosk.Controls;
using BeomKiosk.Model;
using BeomKiosk.View;
using System;
using System.Collections.Generic;
using System.Diagnostics;
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 BeomKiosk.Pages
{
    /// <summary>
    /// Beom_PayProcessPage.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class Beom_PayProcessPage : Page, IBeomPage
    {
        private static Beom_PayProcessPage thisPage = null;
        private static readonly object padlock = new object();

        public Beom_PayProcessPage()
        {
            InitializeComponent();
        }

        public void PageLoaded()
        {
            try
            {
                PayProcess();
            }
            catch (Exception ex)
            {
                ex.SaveException();
            }
        }

        public void PageUnloaded()
        {
            
        }

        private void PayProcess()
        {
            try
            {
                if (Beom_Pay.Pay(Beom_Core.g_payType, Beom_Core.g_amt, Beom_Core.g_installment) == 1)
                {
                    //성공
                    Beom_Core.g_결제결과 = Beom_Core.결제결과.성공;
                    //포트폴리오~입금정보 데이터 저장 생략
                }
                else
                {
                    //실패
                    Beom_Core.g_결제결과 = Beom_Core.결제결과.실패;
                }

                //결제 결과 페이지로 이동하여 안내합니다. 해당 테스트 자료는 결과를 수동으로 누르도록 진행합니다.
                //Beom_Pages.Navigate(Beom_Pages.M_Beom_PayResultPage);
            }
            catch (Exception ex)
            {
                ex.SaveException();
            }            
        }

        public static Beom_PayProcessPage GetInstance()
        {
            lock (padlock)
            {
                if (thisPage == null)
                {
                    thisPage = new Beom_PayProcessPage();
                }
                return thisPage;
            }
        }

        private void btnBeom_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (sender == btnSuccess)
                {
                    Beom_Core.g_결제결과 = Beom_Core.결제결과.성공;
                }
                else if (sender == btnFailure)
                {
                    Beom_Core.g_결제결과 = Beom_Core.결제결과.실패;
                }
                else
                {
                    throw new NotImplementedException("이벤트 확인");
                }

                Beom_Pages.Navigate(Beom_Pages.M_Beom_PayResultPage);
            }
            catch (Exception ex)
            {
                ex.SaveException();
            }
        }

        private void Page_Loaded(object sender, RoutedEventArgs e)
        {
            PageLoaded();
        }
    }
}

결제 결과 성공

결제가 성공한 경우, 푸른색으로 표시해보겠습니다.

결제 결과 실패

반대로 결제가 실패한 경우, 붉은색으로 표시하겠습니다.

using BeomKiosk.Controls;
using BeomKiosk.Model;
using BeomKiosk.View;
using System;
using System.Collections.Generic;
using System.Diagnostics;
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;
using System.Windows.Threading;

namespace BeomKiosk.Pages
{
    /// <summary>
    /// Beom_PayResultPage.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class Beom_PayResultPage : Page, IBeomPage
    {
        private static Beom_PayResultPage thisPage = null;
        private static readonly object padlock = new object();
        private DispatcherTimer m_timer = null;
        private int m_interval = 0;

        public Beom_PayResultPage()
        {
            InitializeComponent();
        }

        public static Beom_PayResultPage GetInstance()
        {
            lock (padlock)
            {
                if (thisPage == null)
                {
                    thisPage = new Beom_PayResultPage();
                }
                return thisPage;
            }
        }

        public void PageLoaded()
        {
            try
            {
                switch (Beom_Core.g_결제결과)
                {
                    case Beom_Core.결제결과.성공:
                        grdBack.Background = (Brush)new BrushConverter().ConvertFrom("#4AA7FF");
                        grdItv.Background = grdBack.Background;
                        txtResult.Text = "결제 성공, (출력물을 기다려달라는 문구 생략)";
                        break;
                    case Beom_Core.결제결과.실패:
                        grdBack.Background = (Brush)new BrushConverter().ConvertFrom("#DB4455");
                        grdItv.Background = grdBack.Background;
                        txtResult.Text = "결제 실패, 잠시 후 첫 화면으로 이동합니다.";
                        break;
                    default:
                        throw new NotImplementedException("결제 결과 알 수 없음!");
                }

                lblInterval.Content = $"대기({Properties.Settings.Default.메시지표시시간 - m_interval}초)";

                m_timer = new DispatcherTimer();
                m_timer.Interval = TimeSpan.FromMilliseconds(1000);
                m_timer.Tick += new EventHandler(timer_Tick);
                m_timer.Start();
            }
            catch (Exception ex)
            {
                ex.SaveException();
            }
        }

        public void PageUnloaded()
        {
            try
            {

            }
            catch (Exception ex)
            {
                ex.SaveException();
            }
        }

        private void timer_Tick(object sender, EventArgs e)
        {
            try
            {
                if (m_timer != null)
                {
                    if (Properties.Settings.Default.메시지표시시간 == m_interval + 1)
                    {
                        if (m_timer != null)
                            m_timer.Stop();

                        Beom_Pages.Navigate(Beom_Pages.M_Beom_MainPage);

                        return;
                    }
                    else
                    {
                        m_interval++;
                        lblInterval.Content = $"대기({Properties.Settings.Default.메시지표시시간 - m_interval}초)";
                    }
                }
            }
            catch (Exception ex)
            {
                ex.SaveException();
            }
        }

        private void Page_Loaded(object sender, RoutedEventArgs e)
        {
            PageLoaded();           
        }
    }
}

예제 소스 드라이브 공유입니다. 해당 드라이브는 용량 정리 필요시 예고 없이 삭제될 수 있습니다.
https://drive.google.com/file/d/1aGLH27MH9WrP97hdYl3fKEDhiFn85Xpq/view?usp=sharing

댓글 쓰기
가져가실 때, 출처 표시 부탁드려요! 감사합니다. 💗