Microsoft MVP 박문찬입니다. 날씨가 추워졌네요..오전에 보일러 고쳐서 참 다행인듯합니다. -0-;;

이전 포스트들을 진행 한 후에 이번 포스트를 보시면 좀 접근하는데 쉬울 것 같습니다.


Face API 사용해보기


Template10을 이용해서 UWP 앱 개발하기 Part1


MVVM Pattern에 대해서 자세히 알고 싶으시면 아래 내용을 참고하시면 됩니다.

Todo list Universal & UWP app


MVVM pattern 설명 동영상(오래전에 녹화한 내용입니다.)

http://youtu.be/f9aQkuoiPz4
http://youtu.be/uGxboAUwciI
http://youtu.be/2lQQiBEjbtU


MVVM Light Toolkit Fundamentals


삽잡이님 블로그

http://shovelman.tistory.com/695
http://shovelman.tistory.com/697
http://shovelman.tistory.com/705
http://shovelman.tistory.com/706



이전 포스트에서는 사진에서 얼굴 위치를 찾아오는 것 까지만 진행 했습니다. 이번에는 사진 속 인물이 누구인지, 학습을 시킨 후에 전혀 다른 사진을 이용해서 해당 사용자를 찾아내는 작업을 해 보도록 하겠습니다.



1. 참고

How to Identify Faces in Image



2. 인증 과정


1) groupId를 이용해서 그룹을 생성합니다. -> CreatePersonGroupAsync

2) 그룹에 사용자를 추가해서 personId를 반환 받습니다. -> CreatePersonAsync

3) groupId와 personId를 이용해서 이미지를 추가합니다.  -> AddPersonFaceAsync

4) 한 사람당 3장 이상의 이미지를 등록 후 머신 러닝을 실행합니다. -> GetPersonGroupTrainingStatusAsync

5) 인식을 원하는 이미지를 이용해서 DetectAsync를 실행합니다.

6) 찾아낸 FaceId를 모아서 인증을 수행합니다. -> IdentifyAsync

7) 인증된 사용자의 정보를 조회 합니다. -> GetPersonAsync


3. FaceIdentify 앱 만들기 시작


File -> New -> Project


Hambuger(Template 10)을 선택하고, FaceIdentify라고 입력하고 OK를 클릭합니다.



Build 후 비주얼 스튜디오를 종료 하고, 다시 시작합니다. (제사한 사항은 Template 10에 대한 포스트 참고)

F5를 눌러서 시작합니다.


햄버거 템플릿의 기본 화면입니다. 여기에 2개의 화면을 추가할 예정입니다. (아무래도 포스트가 2개 이상 필요할 듯하네요;;)





템플릿으로 프로젝트를 만들었는데 실행 했을 때 앱의 기본 골격이 보여지니 내가 필요한 것 몇개만 추가하면 바로 앱이 완성 됩니다. 물론, 솔루션 탐색기에 여러가지 파일들에 대해서 다 알면 좋겠지만, 몰라도 괜찮습니다. 딱 필요한 것만 알고 넘어 가도록 하겠습니다.



4. GroupPage 추가


그룹을 관리하고, 사람을 추가하고, 사진을 추가할 페이지를 추가하도록 하겠습니다.


솔루션 탐색기 Views 폴더에서 마우스 오른쪽 클릭 -> Add -> New Item...을 선택



GroupPage를 입력하고 Add를 클릭합니다.




GroupPage.xaml


<Page
    x:Class="FaceIdentify.Views.GroupPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:FaceIdentify.Views"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:controls="using:Template10.Controls"
    xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
    xmlns:core="using:Microsoft.Xaml.Interactions.Core"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>
       
        <!--페이지 해더-->
        <controls:PageHeader x:Name="pageHeader" Text="Group Page">
            <controls:PageHeader.SecondaryCommands>
                <!--사람 삭제-->
                <AppBarButton Label="Remove Person" Command="{Binding RemovePersonCommand}"/>
            </controls:PageHeader.SecondaryCommands>
            <!--사람 추가-->
            <AppBarButton Icon="AddFriend" Label="Add Person" Command="{Binding AddPersonCommand}"/>
            <!--현재 선택된 사람에 사진 추가-->
            <AppBarButton Icon="Pictures" Label="Add Picture" Command="{Binding AddPictureCommand}"/>
            <!--트레이닝-->
            <AppBarButton Label="Train" Command="{Binding TrainCommand}">
                <AppBarButton.Icon>
                    <FontIcon Glyph="&#xE82E;"/>
                </AppBarButton.Icon>
            </AppBarButton>
        </controls:PageHeader>

        <Grid x:Name="InputName" Grid.Row="1" Margin="0 10 4 0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="1*"/>
            </Grid.ColumnDefinitions>
            <TextBlock Text="Person" VerticalAlignment="Center"/>

            <!--기존 등록된 사람 목록 선택용 콤보 박스-->
            <ComboBox x:Name="comboBox" Grid.Column="1" HorizontalAlignment="Stretch" Margin="10,0,0,0"
                ItemsSource="{Binding Persons}" DisplayMemberPath="Name">
                <interactivity:Interaction.Behaviors>
                    <core:EventTriggerBehavior EventName="SelectionChanged">
                        <core:InvokeCommandAction Command="{Binding SelectionChangedPersonCommand}"
                                                  CommandParameter="{Binding SelectedItem, ElementName=comboBox}"/>
                    </core:EventTriggerBehavior>
                </interactivity:Interaction.Behaviors>
            </ComboBox>

            <!--추가할 사람 이름-->
            <TextBox Grid.Column="2" Margin="6 0 0 0"
                     Text="{Binding PersonName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
        </Grid>

        <!--현재 선택된 사람-->
        <Grid x:Name="personName" Grid.Row="2" Margin="0 10 4 0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="1*"/>
            </Grid.ColumnDefinitions>
            <TextBlock Text="Current Person"/>
            <TextBlock Grid.Column="1" Margin="6 0 0 0" Text="{Binding CurrentPerson.Name}"/>
            <TextBlock Grid.Column="2" Text="Reg Pics" Margin="6 0 0 0"/>
            <TextBlock Grid.Column="3" Margin="6 0 0 0"
                       Text="{Binding CurrentPerson.PersistedFaceIds.Length}"
                       HorizontalAlignment="Right"/>
        </Grid>

        <!--현재 선택된 사람에게 추가된 이미지(나중에 다시 보는 기능은 없습니다)-->
        <ListBox Grid.Row="3"
                 Margin="0 10 4 10"
                 ItemsSource="{Binding CurrentPerson.FaceImages}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Image Source="{Binding}" Stretch="UniformToFill"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Page>


XAML 코드 한줄 한줄이 어떻게 만들어 졌는지는..따로 설명을 드리기가 어렵겠네요..ㅜㅜ 지금 진행하고 있는 앱 개발 기초 과정에서 2주간에 걸쳐서 만들어진 코드랍니다.



5. Shell.xaml에 메뉴 추가하기


솔루션 탐색기에서 Shell.xaml 파일을 더블 클릭해서 디자인 화면을 열고, 30라인에 아래 내용을 붙여 넣기 하시면 됩니다. (라인 번호가 보이지 않으면, Tool -> Options -> Text Editor -> All Languages -> Line numbers에 체크를 해주시면 됩니다.)


            <Controls:HamburgerButtonInfo PageType="views:GroupPage">
                <StackPanel Orientation="Horizontal">
                    <SymbolIcon Width="48" Height="48"
                                Symbol="People" />
                    <TextBlock Margin="12,0,0,0" VerticalAlignment="Center"
                               Text="Group" />
                </StackPanel>
            </Controls:HamburgerButtonInfo>


실행해서 디자인 확인하기


다음과 같은 화면이 나오면 정상 입니다. 이제, 내용을 넣도록 하겠습니다.




6. FaceAPIHelper 추가하기

Nuget packages에서 Microsoft.ProjectOxford.Face를 선택해서 추가합니다. (이전 포스트에서 다루었던 내용입니다.)


Services 폴더를 선택하고 마우스 오른쪽, Add -> Class를 선택 -> FaceAPIHelper입력 -> OK


Input your service key에 여러분의 키를 입력하시면 됩니다. 역시 이전 포스트에서 다루는 내용입니다.


using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.ProjectOxford.Face;
using Microsoft.ProjectOxford.Face.Contract;

namespace FaceIdentify.Services
{
    public class FaceAPIHelper
    {
        private static FaceAPIHelper _instance;

        // Create an empty person group
        private readonly string _personGroupId = "myfriends";

        private readonly IFaceServiceClient faceServiceClient =
            new FaceServiceClient("Input your service key");

        private bool _init;

        /// <summary>
        ///     생성자
        /// </summary>
        public FaceAPIHelper()
        {
            Init();
        }

        /// <summary>
        ///     인스턴스
        /// </summary>
        public static FaceAPIHelper Instance
        {
            get { return _instance = _instance ?? new FaceAPIHelper(); }
        }

        /// <summary>
        ///     초기화
        /// </summary>
        public async void Init()
        {
            if (_init) return;

            //사람그룹 목록을 조회한 후에
            var list = await faceServiceClient.ListPersonGroupsAsync();
            //myFriends란 아이디를 가지고 있지 않다면, 사람그룹을 생성한다.
            if (list.All(p => p.PersonGroupId != _personGroupId))
            {
                await faceServiceClient.CreatePersonGroupAsync(_personGroupId, "My Friends");
            }

            _init = true;
        }

        /// <summary>
        ///     사진에서 얼굴 디텍트
        /// </summary>
        /// <param name="fileStream"></param>
        /// <returns></returns>
        public async Task<Face[]> GetDetectAsync(Stream fileStream)
        {
            return await faceServiceClient.DetectAsync(fileStream);
        }

        /// <summary>
        ///     사람 추가
        /// </summary>
        /// <param name="personName"></param>
        /// <returns></returns>
        public async Task<Guid> CreatePersonAsync(string personName)
        {
            var persons = await faceServiceClient.GetPersonsAsync(_personGroupId);
            if (persons.Any(p => p.Name == personName))
            {
                var per = persons.First(p => p.Name == personName);
                return per.PersonId;
            }
            var result = await faceServiceClient.CreatePersonAsync(_personGroupId, personName);
            return result.PersonId;
        }

        /// <summary>
        ///     사람 삭제
        /// </summary>
        /// <param name="personId"></param>
        /// <returns></returns>
        public async Task DeletePersonAsync(Guid personId)
        {
            await faceServiceClient.DeletePersonAsync(_personGroupId, personId);
        }

        /// <summary>
        ///     등록된 사람들 정보 조회
        /// </summary>
        /// <returns></returns>
        public async Task<Person[]> GetPersonsAsync()
        {
            var persons = await faceServiceClient.GetPersonsAsync(_personGroupId);
            return persons;
        }

        /// <summary>
        ///     사람에 얼굴 등록하기
        /// </summary>
        /// <param name="personId"></param>
        /// <param name="fileStream"></param>
        /// <returns></returns>
        public async Task<AddPersistedFaceResult> AddPersonFaceAsync(Guid personId, Stream fileStream)
        {
            try
            {
                // Detect faces in the image and add to Anna
                var result = await faceServiceClient.AddPersonFaceAsync(
                    _personGroupId, personId, fileStream);
                return result;
            }
            catch (FaceAPIException fae)
            {
                Debug.WriteLine(fae.ErrorMessage);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
            return null;
        }

        /// <summary>
        ///     트레이닝 사람그룹
        /// </summary>
        /// <returns></returns>
        public async Task TrainPersonGroupAsync()
        {
            await faceServiceClient.TrainPersonGroupAsync(_personGroupId);

            while (true)
            {
                var trainingStatus = await faceServiceClient.GetPersonGroupTrainingStatusAsync(_personGroupId);
                if (trainingStatus.Status != Status.Running)
                {
                    break;
                }
                await Task.Delay(1000);
            }
        }

        /// <summary>
        ///     인증하기
        /// </summary>
        /// <param name="faceIds"></param>
        /// <returns></returns>
        public async Task<IdentifyResult[]> IdentifyAsync(Guid[] faceIds)
        {
            try
            {
                var result = await faceServiceClient.IdentifyAsync(_personGroupId, faceIds);
                return result;
            }
            catch (FaceAPIException fae)
            {
                Debug.WriteLine(fae.ErrorMessage);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
            return null;
        }

        /// <summary>
        ///     사람 정보 조회
        /// </summary>
        /// <param name="personId"></param>
        /// <returns></returns>
        public async Task<Person> GetPersonAsync(Guid personId)
        {
            var result = await faceServiceClient.GetPersonAsync(_personGroupId, personId);
            return result;
        }
    }
}



7. PersonModel 추가


Models 폴더를 선택하고 마우스 오른쪽, Add -> Class를 선택 -> PersonModel 입력 후 OK


Face API에 있는 Person이라는 클래스를 상속 받아서 몇개의 프로퍼티를 추가하고, 기능을 보강한 모델을 만들어 사용합니다.


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using Windows.UI.Xaml.Media.Imaging;
using Microsoft.ProjectOxford.Face.Contract;

namespace FaceIdentify.Models
{
    public class PersonModel : Person, INotifyPropertyChanged
    {
        /// <summary>
        ///     Guid -> Face Guid, BitmapSource -> 사진
        /// </summary>
        public IDictionary<Guid, BitmapSource> Faces = new Dictionary<Guid, BitmapSource>();

        /// <summary>
        ///     바인딩을 하기 위한 프로퍼티
        /// </summary>
        public IList<BitmapSource> FaceImages
        {
            get { return Faces.Select(p => p.Value).ToList(); }
        }

        /// <summary>
        ///     얼굴 추가
        /// </summary>
        /// <param name="faceId"></param>
        /// <param name="bs"></param>
        public void AddFace(Guid faceId, BitmapSource bs)
        {
            Faces.Add(faceId, bs);
            //얼굴 추가 메소드를 이용해서 프로퍼티 체인지 이벤트를 강제로 발생시킴
            OnPropertyChanged("FaceImages");
        }

        /// <summary>
        ///     얼굴 삭제
        /// </summary>
        /// <param name="faceId"></param>
        public void RemoveFace(Guid faceId)
        {
            if (!Faces.ContainsKey(faceId)) return;
            Faces.Remove(faceId);
            OnPropertyChanged("FaceImages");
        }

        #region INotifyPropertyChanged, Person 모델을 상속 받았기 때문에 인터페이스를 직접 구현

        public event PropertyChangedEventHandler PropertyChanged;

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

        #endregion
    }
}



8. GroupPageViewModel.cs 추가하기


GroupPage.xaml을 추가했던 비슷한 방법으로, ViewModels 폴더를 선택하고 마우스 오른쪽, Add -> Class를 선택 -> GroupPageViewModel 입력 후 OK



using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Input;
using Windows.ApplicationModel;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.UI.Popups;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Navigation;
using FaceIdentify.Models;
using FaceIdentify.Services;
using FaceIdentify.Views;
using Microsoft.ProjectOxford.Face.Contract;
using Template10.Mvvm;

namespace FaceIdentify.ViewModels
{
    public class GroupPageViewModel : ViewModelBase
    {
        private PersonModel _currentPerson;
        private string _personName;
        private IList<Person> _persons;

        public GroupPageViewModel()
        {
            if (DesignMode.DesignModeEnabled)
            {
                //디자인 타임 데이터
            }
            else
            {
                //런타임 데이터
                Init();
            }
        }

        /// <summary>
        ///     현재 작업 중인(추가한) 사용자
        /// </summary>
        public PersonModel CurrentPerson
        {
            get { return _currentPerson; }
            set { Set(ref _currentPerson, value); }
        }

        /// <summary>
        ///     등록된 사용자 목록
        /// </summary>
        public IList<Person> Persons
        {
            get { return _persons; }
            set { Set(ref _persons, value); }
        }

        /// <summary>
        ///     콤보박스 사용자가 선택되는 경우 실행되는 커맨드
        /// </summary>
        public ICommand SelectionChangedPersonCommand { get; set; }

        /// <summary>
        ///     트레이닝 커맨드
        /// </summary>
        public ICommand TrainCommand { get; set; }

        /// <summary>
        ///     사람 제거 커맨드
        /// </summary>
        public ICommand RemovePersonCommand { get; set; }

        /// <summary>
        ///     사람 추가
        /// </summary>
        public ICommand AddPersonCommand { get; set; }

        /// <summary>
        ///     사진 추가
        /// </summary>
        public ICommand AddPictureCommand { get; set; }

        /// <summary>
        ///     사람 이름
        /// </summary>
        public string PersonName
        {
            get { return _personName; }
            set { Set(ref _personName, value); }
        }

        /// <summary>
        ///     초기화
        /// </summary>
        private void Init()
        {
            //커맨드 생성
            AddPersonCommand = new DelegateCommand(AddPersonCommandHandler,
                () => !string.IsNullOrEmpty(PersonName));

            AddPictureCommand = new DelegateCommand(AddPictureCommandHandler);

            TrainCommand = new DelegateCommand(async () =>
            {
                //화면에 Busy 표시
                Busy.SetBusy(true, "Training....");
                await FaceAPIHelper.Instance.TrainPersonGroupAsync();
                Busy.SetBusy(false);
            });

            SelectionChangedPersonCommand = new DelegateCommand<object>(obj =>
            {
                //obj로 넘어온 데이터를 이용해서 현재 사람 표시
                var person = obj as Person;
                if (person == null) return;
                CurrentPerson = new PersonModel
                {
                    Name = person.Name,
                    PersonId = person.PersonId,
                    PersistedFaceIds = person.PersistedFaceIds
                };
            });

            RemovePersonCommand = new DelegateCommand(async () =>
            {
                if (CurrentPerson == null) return;

                Busy.SetBusy(true, "Removing...");
                await FaceAPIHelper.Instance.DeletePersonAsync(CurrentPerson.PersonId);

                Persons = await FaceAPIHelper.Instance.GetPersonsAsync();
                CurrentPerson = null;
                Busy.SetBusy(false);
            });

            PropertyChanged += (s, e) =>
            {
                switch (e.PropertyName)
                {
                    case "PersonName":
                        //커맨드 사용 가능 여부 확인
                        ((DelegateCommand) AddPersonCommand).RaiseCanExecuteChanged();
                        break;
                }
            };
        }

        private async void AddPictureCommandHandler()
        {
            //FileOpePicker 설정
            var openDlg = new FileOpenPicker();
            openDlg.FileTypeFilter.Add(".jpg");
            openDlg.FileTypeFilter.Add(".jpeg");
            openDlg.FileTypeFilter.Add(".png");
            //파일 선택 창 출력
            var file = await openDlg.PickSingleFileAsync();
            var basic = await file.GetBasicPropertiesAsync();
            //서버에 올릴 수 있는 이미지의 크기는 4MB까지입니다.
            if (basic.Size > 4*1024*1024)
            {
                var msg = new MessageDialog("It is too big image. It can only be up to 4MB.");
                await msg.ShowAsync();
                return;
            }
            //처음 화면에 출력용
            var bm = new BitmapImage();
            using (var stream = await file.OpenAsync(FileAccessMode.Read))
            {
                await bm.SetSourceAsync(stream);
            }
            //얼굴인식하고 테두리 표시하기
            using (var stream = await file.OpenStreamForReadAsync())
            {
                //얼굴 인식에 실패하는 경우 null을 반환합니다.
                var faceResult = await FaceAPIHelper.Instance.AddPersonFaceAsync(CurrentPerson.PersonId, stream);
                if (faceResult == null) return;

                CurrentPerson.AddFace(faceResult.PersistedFaceId, bm);
            }
        }


        /// <summary>
        ///     사람 추가하기
        /// </summary>
        private async void AddPersonCommandHandler()
        {
            if (string.IsNullOrEmpty(PersonName)) return;
            var personId = await FaceAPIHelper.Instance.CreatePersonAsync(PersonName);

            CurrentPerson = new PersonModel
            {
                Name = PersonName,
                PersonId = personId
            };
        }

        /// <summary>
        ///     Template 10의 기능으로 페이지 네비게이션을 통해서 페이지로 진입하면 실행되는 부분입니다.
        /// </summary>
        /// <param name="parameter"></param>
        /// <param name="mode"></param>
        /// <param name="state"></param>
        /// <returns></returns>
        public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode,
            IDictionary<string, object> state)
        {
            Persons = await FaceAPIHelper.Instance.GetPersonsAsync();
        }
    }
}



9. View와 ViewModel 연결


빌드를 한 후 GroupPage.xaml 디자인 화면으로 이동합니다.


네임스페이스를 추가하고 그 아래 Page.DataContrext에 뷰모델을 넣습니다.


참고로 이 방법은 디자인 타임과 런타임에 동일한 방식으로 뷰모델이 생성되는 방식입니다. 뷰모델을 따로 관리하지 않고, 페이지 네비게이션이 발생할 때마다 뷰모델도 생성되었다가 없어지는 구조입니다. 이런 경우 뷰모델 내부에서 저장해야할 데이터는 SessionState를 이용해서 관리합니다.


    xmlns:ViewModels="using:FaceIdentify.ViewModels"

    x:Class="FaceIdentify.Views.GroupPage"
    mc:Ignorable="d">


    <Page.DataContext>
        <ViewModels:GroupPageViewModel/>
    </Page.DataContext>



10. 실행


사람 이름을 입력하면 상단에 사람 추가 버튼이 활성화 됩니다.



정상적으로 추가가 되면 Current Person에 사람 이름이 표시 됩니다.



빨간 색의 사진 추가 버튼을 눌러서 사진을 추가합니다. 이때 사진은 단독 사진으로 하여야 합니다. (여러 사람 얼굴이 나오는 사진도 가능하기는 하지만, 현재 기능 구현이 되어있지 않습니다.)




그래서 저는 총4장을 추가했습니다. 아래는..추가한 사진들 입니다. 흐흐흐





마지막으로 트레이닝 버튼을 눌러서 학습을 시킵니다.



트레이닝은 금방 종료됩니다. ^^;;


그런데, 오늘은 트레이닝까지만... 진행 하겠습니다. 다음에 우주소녀 12명 중에 성소를 찾는 부분을 진행하도록 하겠습니다. -0-;;;; 시간이 늦어서말이죠..


후다닥 =3 =3



11. 소스 (파일이름 틀렸네요;; part2 소스 맞습니다.)


FaceIdentify_part1.zip


블로그 이미지

kaki104

/// Microsoft MVP - Windows Development 2014 ~ 2019 5ring /// twitter : @kaki104, facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

UWP 앱을 처음 개발하려고, 비주얼 스튜디오를 설치하고, 새 프로젝트 만들기를 하면 Blank App (Universal Windows) 하나만 보입니다. Hello World 앱 만드는데는 문제가 없지만...진짜 앱을 만들기 위해서는 너무나도 빈약 합니다.


간단한 햄버거 메뉴를 하나 만들려고 해도 해야할일이 참~ 많습니다. 그래서, 이런 문제를 해결하기 위한 방법으로  Template 10이라는 UWP 앱 템플릿을 소개하려고 합니다.



1. Template 10


Microsoft Jerry Nixon이라는 UWP 앱 교육 비디오에 자주 출연하는 멋진 분이 만들어서 공개한 템플릿으로, 앱을 쉽게 만들 수 있도록 Navigation, Suspension, Hamburger control, PageHeader 등의 내용들로 구성 되어 있으며, 이 내용들을 이용한다면 더 쉽게 앱을 만들 수 있습니다.


동영상 강좌를 보면 더 쉽게 이용할 수 있습니다.

Microsoft Virtual Academy Template 10 Training Videos.



2. Template 10에 적용된 규칙 일부 입니다.


1) MVVM 패턴이 기본적으로 적용되어 있습니다. 그래서, Views 폴더에 XAML 파일일 들어가 있습니다.

2) 한개의 뷰에 한개의 뷰모델이 존재합니다.

3) View-model은 ViewModels 폴더에 들어가 있습니다.

4) View-model에서 OnNavigatedTo를 사용할 수 있습니다.

5) Model은 Models 폴더에 들어가 있습니다.

6) NavigationService를 이용해서 navigate를 실행할 수 있습니다.

7) Messenger와 커뮤니케이션 할 수 있습니다.

8) Dependency Injection을 좋아합니다.



3. Template 10에서는 MVVM이 필수 인가요?


MVVM 패턴을 반드시 사용하도록 요구하지는 않지만, 대부분의 XAML app에서 사용하기 때문에 사용하는 것을 권장합니다. 그래서, 다른 MVVM 프레임웍과도 호환이 되도록 만들어져 있습니다.



4. 어떻게 사용할 수 있나요?


Visual Studio 2015를 실행 -> Tools -> Extensions and Updates -> Online -> template 입력 ->

Template 10 Template Pack version : 1.19를 선택 -> Install 클릭


설치하는데 약간 시간이 걸릴 수 있습니다. 완료될 때까지 기다려 주세요~ 이전 버전인 1.18은 템플릿을 만든 후 추가 작업을 해야 했는데..바로 어제 버전이 1.19로 올라가면서 모든 문제가 해결되었습니다.




5. 어떤 템플릿을 만들 수 있나요?


File -> New -> Project를 선택하면, 아래와 같이 3가지 템플릿이 추가된 것을 확인할 수 있습니다.



** 만약 한글 Visual Studio 2015를 사용한다면, 영문으로 Windows라고 되어있는 곳에서 찾아 보아야 합니다.

(1.19에서 수정이 되었는지 여부는 확인을 못했습니다.)




6. Hello Template 10을 만들어 보겠습니다.


Visual Studio 2015 실행 후


File -> New -> Project -> Blank (Template 10) 선택 -> 이름은 HelloT10을 입력하고 OK를 클릭합니다.



아래와 같이 Target Version과 Minimum Version을 선택하는 화면이 나옵니다.

여기서 Windows 10 Anniversary Edition (10.0; Build 14393)이 나오지 않으면, 현재 윈도우 버전이 최신 버전이 아니고, SDK가 설치되어 있지 않기 때문입니다.


포스트를 계속 보시기 위해서는 반드시 최신 윈도우 버전 1607(OS Build 14393.xxx)

Programs and Features에는

Windows Software Development Kit - Windows 10.0.14393.33

Windows Software Development Kit - Windows 10.0.10586.212

이 설치되어있는지 확인하시면 됩니다.


Windows 10 SDK




프로젝트가 만들어지면, F6 키를 눌러서 Build를 한번 꼭 해줍니다.

오른쪽 Solution Explorer에서 References를 확장해서 보았을 때 아래와 같이 나오지 않는다면, Visual Studio를 종료한 후 다시 실행해서 새로 만든 프로젝트를 불러오면 아래와 같이 보일 것입니다.


MainPage.xaml을 더블클릭하면 디자인 화면을 볼 수 있습니다.



디자인 화면이 아래와 같이 나오는지 확인해 주세요~ 화면이 아래와 같이 나오지 않는다면~



Task Manager를 열고, Details 탭을 선택하고, XDesProc.exe를 선택 한 후 End task를 클릭해서, 프로세스를 죽이고 디자인 화면을 다시 열면 잘 보일 것으로 생각됩니다~




MainPage.xaml


<TextBlock .. />을 찾아서 Text 부분을 Hello T10 World를 변경 합니다.


디자인 화면만 보이고, XAML 코드가 보이지 않는다면, 빨간 테두리가 있는 곳에 - 아이콘을 눌러서 화면을 나누어 주시면 코드를 직접 입력할 수 있습니다.




F5를 눌러서 실행 합니다.



이렇게해서 Template 10을 이용해서 UWP 앱을 만드는 과정을 간단하게 살펴 보았습니다.


요즘 계속해서 사용하고 있는데, 상단히 마음에 드는 템플릿입니다. 시간을 내서 후속 포스트를 작성해야하는데..어떻게 될지는 잘 모르겠네요..


좋아요~팍팍 눌러주시면..열심히 작성해 보겠습니다.~~~



블로그 이미지

kaki104

/// Microsoft MVP - Windows Development 2014 ~ 2019 5ring /// twitter : @kaki104, facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

티스토리 툴바