본문 바로가기
Development/Toy Projects

[프로젝트] 급식 데이터를 이용해 간단한 윈도우 응용 프로그램 만들어보기🔥 #3

by Kyunghoon Kim 2020. 5. 14.

안녕하세요 이번 포스트는 1편과 2편의 내용을 토대로 급식 데이터를 통해 XAML 코드를 만져보면서 간단하게 UI 상에 정보를 띄어보는 시간을 가져보겠습니다. 이번 내용은 급식 데이터의 내용을 ViewModel 분리하고 Property를 하나 만들어서 넣어준 다음 XAML의 ListView Control로 간단하게 제작해 볼게요. 2편의 내용과 이어지니 아직 2편을 보지 않으셨다면 2편을 먼저 보고 오시는 것을 추천드립니다.

 

https://devkyunghoon.tistory.com/4

 

#2 급식 데이터 모델 제작 & JsonConvert.DeserializeObject(역직렬화)를 이용한 객체화해보기 🔥

안녕하세요 이번 포스트에서는 지난 포스트에서 진행하였던 Web Client & Web Request를 이용해 NEIS 급식 정보를 파싱 해온 것에 대한 모델을 만들고 JsonConvert.DeserializeObject를 이용한 역직렬화(객체화)�

devkyunghoon.tistory.com

 


포스트 🔑 포인트

🎄 1. 메인 로직 부분을 ViewModel로 분리할 수 있다.

 

🎄 2. XAML 코드를 다뤄보며 ListViewControl를 활용할 수 있다.(Data Binding)

 


UI를 다루는 XAML 코드를 작성하기 전에 이전 시간에 작성해두었던 로직 부분의 코드들을 ViewModel로 분리하려고 합니다. 그래서 프로젝트 하단에 새 폴더를 추가하고 ViewModel로 이름을 지었습니다. 그리고 그 안에 MealViewModel.cs라는 파일을 하나 만들어 주었습니다. 그리고 로직 내용들을 옮겨보도록 하겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
namespace MealProgram.ViewModel
{
    public class MealViewModel
    {
        const string API_URL = "API 주소 입니다!";
 
        public string mealWebClient()
        {
            string result = string.Empty;
 
            try
            {
                WebClient webClient = new WebClient();
                webClient.Headers["Content-Type"= "application/json";
                webClient.Encoding = Encoding.UTF8;
 
                using (Stream data = webClient.OpenRead(API_URL))
                {
                    using (StreamReader reader = new StreamReader(data))
                    {
                        string json = reader.ReadToEnd();
                        result = json;
                        JsonToObject(json);
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
 
            return result;
        }
 
        public void JsonToObject(string jsonStr)
        {
            JObject jObject = JObject.Parse(jsonStr);
            MealInfo mealInfo = JsonConvert.DeserializeObject<MealInfo>(jObject.ToString());
        }
    }
}
cs

 

여기까지는 지난번에 작성했었던 코드라 따로 설명은 하지 않도록 하겠습니다. 지금 한 것은 ViewModel이라는 폴더를 만들고 MealViewModel.cs라는 클래스 파일을 만든 뒤 작성한 내용을 옮긴 것이 다입니다.

 

이제 이 데이터는 Binding하여 나타낼 것입니다. 그러기 위해선는 Property(속성)을 만들어 주어야 합니다. 그리고 만든 프로퍼티에 대한 UI에 알림을 주기 위해서 INotifyPropertyChanged라는 interface를 상속받아 이벤트에 대한 메서드를 구현해 주어야 합니다.

 

1
2
3
4
5
6
7
8
9
public class MealViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    
    public void NotifyPropertyChanged(string propertyName)
    {
        PropertyChanged?.Inovke(thisnew PropertyChangedEventArgs(propertyName);    
    }
}
cs

 

MealViewModel : INotifyPropertyChanged 이렇게 작성을 해 주시고 Alt + Enter 키를 입력해 주시면 사용이 가능합니다. 그리고 기본적으로 public event PropertyChangedEventHandler PropertyChanged;라는 게 나타나게 됩니다. 이 상태에서 알림을 주기 위한 메서드를 만들어 주어야 합니다. 그것이 바로 위의 NotifyPropertyChanged 메서드입니다. 인자로 받는 propertyName은 나중에 만들 프로퍼티의 이름을 넣어주셔야 합니다. 다음은 사용할 프로퍼티를 만들어 볼게요.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class MealViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    
    public void NotifyPropertyChanged(string propertyName)
    {
        PropertyChanged?.Inovke(this, new PropertyChangedEventArgs(propertyName);    
    }
 
    private List<MealServiceDietInfo> _mealInfoItems;
    public List<MealServiceDietInfo> MealInfoItems
    {
        get => _mealInfoItems;
        set
        {
            _mealInfoItems = value;
            NotifyPropertyChanged(nameof(MealInfoItems));
        }
    }
 
   private List<Row> _rowList;
   public List<Row> RowList
   {
        get => _rowList;
        set
        {
           _rowList = value;
           NotifyPropertyChanged(nameof(RowList));
        }
    }
 
    private ObservableCollection<Row> _rowItems = new ObservableCollection<Row>();
    public ObservableCollection<Row> RowItems
    {
        get => _rowItems;
        set
        {
            _rowItems = value;
            NotifyPropertyChanged(nameof(RowItems));
        }
    }
}
cs

 

제작한 프로퍼티는 총 3가지로 MealServiceDietInfo를 List로 가지는 MealInfoItems, Row를 List로 가지는 RowList, Row를 ObservableCollection으로 가지는 RowItems입니다. 여기서 잘 보시면 get과 set을 보실 수 있는데 set 부분에 NotifyPropertyChanged(프로퍼티 이름); 메서드가 사용되고 있는 것을 볼 수 있습니다. 위에서 언급했듯이 만든 프로퍼티의 이름을 그대로 넣어주었습니다. 그리고 반드시 nameof를 붙여 주어야 합니다. 그리고 이 프로퍼티들이 어디서 쓰이는지 알아보겠습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 public void JsonToObject(string jsonStr)
 {
     JObject jObject = JObject.Parse(jsonStr);
     MealInfo mealInfo = JsonConvert.DeserializeObject<MealInfo>(jObject.ToString());
            
     foreach(var item in mealInfo.MealServiceDietInfos)
     {
         RowList = item.row;
                
        if (item.row != null)
        {
            foreach (var data in RowList)
            {
                 if (data != null)
                 {
                     RowItems.Add(data);
                 }
             }
        }
     }
 }
cs

 

JsonToObject() 메서드에 foreach 문을 추가해 주었습니다. 여기서 프로퍼티에 값을 넣어줄 것입니다. 첫 번째 foreach 문은 MealInfo의 내용을 담고 있는 mealInfo에.으로 MealServiceDietInfos에 접근합니다. 그리고 여러 개의 row를 가지고 있기 때문에 RowList에 item.row를 넣어줍니다. 만약에 그게 null이라면 데이터가 없기 때문에 넘어갑니다.

 

null 조건문 처리를 해주는 이유 head[0]

 

아래 사진을 보면 알수있듯이 item.row가 null인 것을 확인할 수 있습니다.

 

 

 

 

그렇지 않으면 RowList안의 급식 정보를 가져와야 하기 때문에 마찬가지 방법으로 foreach문으로 이번에는 RowList에 대하여 접근합니다. 그리고 그 data가 null이 아니면 RowItems안의 실제 값을 Add메서드를 통해 추가해 줍니다. 여기서 RowItems만 ObservableCollection인 이유는 데이터를 찍어보면 여러 개의 프로퍼티를 담고 있는 row가 1개가 아닌 여러 개이기 때문입니다. 이러고 나서 디버깅을 통해 값을 찍어보겠습니다.

 

 

row가 null이 아닐 때 정상적으로 들어 있는 값

 

Row가 null 아닐 때 정상적으로 값이 들어가 있는 것을 볼 수 있습니다. 그리고 Count를 보면 5가 있는 것을 확인할 수 있는데 이는 위의 사진에서 보이듯이 여러 개의 프로퍼티를 1개로 취급해 총 5개를 가지고 있습니다. 그렇기 때문에 다시 한번 언급하지만 ObservableCollection을 사용하는 것입니다. 그러면 이제 ObservableCollection에 제대로 값이 담기고 있는지 확인해 볼까요?

 

 

ObservableCollection에 정상적으로 값이 담기는 모습

 

이로써 UI에 나타내기 위한 데이터를 프로퍼티에 담았습니다. 이제는 XAML 코드를 이용하여 데이터를 실제 화면에 띄어보도록 하겠습니다. XAML에서 Binding을 통해 데이터를 나타내려면 DataContext를 설정해주어야 합니다. 그렇기 때문에 MainWindow.xaml.cs로 이동하셔서 기존에 있던 Loaded 메서드 안에 다음과 같이 넣어주세요.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
namespace MealProgram
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Loaded += MainWindow_Loaded;
        }
 
        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            string mealWebClientResult = App.mealViewModel.mealWebClient();
            this.DataContext = App.mealViewModel;
        }
    }
}
cs

 

MealViewModel에 있는 mealWebClient() 메서드를 호출해주기 위해서 App.xaml에 공유해주었고 DataContext를 설정해 주었습니다. 이제 정말 마지막 과정인 XAML 코드를 간단하게 다뤄볼게요. 

아래는 UI에 나타내주기 위한 코드들입니다. 위에서부터 차근차근 볼게요.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="4*"/>
        <ColumnDefinition Width="6*"/>
    </Grid.ColumnDefinitions>
 
    <Grid Grid.Column="0">
        <StackPanel>
            <TextBlock Text="급식 정보 UI에 나타내기" FontSize="35" FontFamily="나눔스퀘어_ac"
                       HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,50,0,0"
                       TextWrapping="Wrap"/>
            <Image Source="shayda-torabi-3iexvMShGfQ-unsplash.jpg" Margin="5"/>
        </StackPanel>
    </Grid>
 
    <Grid Grid.Column="1">
        <ListView ItemsSource="{Binding RowItems}" Margin="5" 
                  ScrollViewer.HorizontalScrollBarVisibility="Disabled">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <StackPanel>
                            <StackPanel.Resources>
                                <Style TargetType="TextBlock">
                                    <Setter Property="FontSize" Value="25"/>
                                    <Setter Property="TextWrapping" Value="Wrap"/>
                                    <Setter Property="FontFamily" Value="나눔스퀘어_ac"/>
                                </Style>
                            </StackPanel.Resources>
                            <TextBlock Text="{Binding ATPT_OFCDC_SC_CODE}"/>
                            <TextBlock Text="{Binding ATPT_OFCDC_SC_NM}"/>
                            <TextBlock Text="{Binding SD_SCHUL_CODE, StringFormat={}학교 코드 : {0}}"/>
                            <TextBlock Text="{Binding SCHUL_NM, StringFormat={}학교 명 : {0}}"/>
                            <TextBlock Text="{Binding MMEAL_SC_CODE}"/>
                            <TextBlock Text="{Binding MMEAL_SC_NM}"/>
                            <TextBlock Text="{Binding MLSV_YMD}"/>
                            <TextBlock Text="{Binding MLSV_FGR}"/>
                            <TextBlock Text="{Binding DDISH_NM}"/>
                            <TextBlock Text="{Binding ORPLC_INFO}"/>
                            <TextBlock Text="{Binding CAL_INFO}"/>
                            <TextBlock Text="{Binding NTR_INFO}"/>
                            <TextBlock Text="{Binding MLSV_FROM_YMD}"/>
                            <TextBlock Text="{Binding MLSV_TO_YMD}"/>
                        </StackPanel>
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</Grid>
cs

 

우선 화면의 분할을 위해서 Grid의 Column을 나누어 주었습니다. 세로로 나누었기 때문에 왼쪽에는 간단한 텍스트 문구와 이미지 오른쪽에는 급식 정보를 나타내 주려고 합니다. 그리고 아래의 ListViewControl을 보시면 ItemSource에 "{Binding RowItems}"라는 게 보이실 텐데 이 부분은 DataContext로 설정해준 mealViewModel의 RowItem을 넣어 준다고 생각하시면 됩니다. 그러면 소스를 넣어주었기 때문에 아래에 보이시는 TextBlock의 Text에 각각의 프로퍼티에 대한 바인딩이 가능한 것입니다.

 

StackPanel로 감싸 두었는데 이는 하나씩 쌓아두는 느낌입니다. 실제 UI 화면을 보시면 이해가 되실 겁니다. 그리고 StakPanel.Resources는 아래 여러 TextBlock에 대한 Style을 묶어서 사용한 것입니다. 그러면 이제 UI 화면을 보러 가실까요?

 

실제 실행한 프로그램 UI 화면

 

어떤가요? 데이터가 잘 나오시나요? 디자인에 대해서 마음에 안 드실 수도 있습니다.😅 왜냐하면 데이터를 나타내기 위한 기본적인 코드만을 작성하였기 때문입니다. 오른쪽 정보에 불필요한 정보가 있어서 필요한 데이터만 남겨보도록 할게요.

 

 

필요 없는 데이터는 아예 빼버렸습니다. 그래도 메뉴에 나타나는 HTML 코드는 보기가 불편하네요. 이 부분에 대해 서 보다 예쁘고 퀄리티 있게 제작한 급식 프로그램 제작을 정리해둔 곳이 있으니 아래를 참조해 주세요. GitHub(깃허브) 소스코드의 주소도 남겨놓겠습니다.

 

blog.naver.com/kkh03kkh/221763007839

 

WPF로 우리 학교 급식 정보 프로그램 만들기

이번에는 Visual Studio에서 제공하는 WPF(Windows Presentation Foundation)를 이용하여 만든 급...

blog.naver.com

 

https://github.com/KyungHoon0126/Meals-Program

 

KyungHoon0126/Meals-Program

🍱 우리 학교 급식정보 프로그램 (2019.12.25. ~ 2020.01.1). Contribute to KyungHoon0126/Meals-Program development by creating an account on GitHub.

github.com

 


 

지금까지 1편, 2편 그리고 마지막 3편까지 내용을 정리해 보았습니다. 제가 공부하면서 적용해보며 만든 것들이라 많이 부족한 내용이 많습니다. 지금까지 정보를 파싱 하고 이에 대한 모델을 만들어서 역직렬화 하고 간단하게 XAML 코드를 이용해서 UI 상에 나타내 보는 시간을 가졌습니다. 감사합니다. 🙌

댓글