블로그 이미지
* Microsoft MVP - Windows Development 2014 ~ 2020 http://youtube.com/FutureOfDotNet kaki104

카테고리

List All (601)
Visual Studio (6)
Blazor (2)
Windows App(Universa.. (100)
Xamarin Forms (4)
Bot Framework (19)
Azure (9)
Windows 10 (52)
WPF (7)
Facebook News & Tips (158)
Windows 8&8.1 (113)
Windows Phone 8 (42)
Silverlight (37)
HTML5 & MVC4 (16)
Portable Class Library (2)
Uncategorised Tips a.. (3)
Kinect for Windows (2)
ETC (12)
kaki104 Scrap (4)
App News (11)
Total543,763
Today11
Yesterday62

앱 기능 추가 작업을 하면서 OneDrive에 올라간 파일에 대한 공유링크를 만드는 것까지는 쉽게했는데.. 그 링크가 너무 길어서 그걸 짧게 만드는 URL Shorten 작업이 필요해 졌다. 이게 4일전의 일이다. 그리고....... 오늘이 url 짧게 변환하는 작업을 시작한지 3일째다. 


첫날은 어떻게 해야하는지 알아보느랴고 보내면서 왜 MS는 웹페이지에서는 shorten link를 만들 수 있게 해주면서 Live SDK에는 그와 관련된 API가 없는 것인지에 대해 찾아 보며 @#$@#$ 했다. 


어제는 Nuget package에서 적당한 것을 찾아보고 적용해 보았는데.. 앱에서 사용할 수 있는 적당한 것을 찾을 수 없어서 오픈 소스로 되어 있는 걸 포크로 찍어서 수정해서 사용해야하나를 고민하며 대충 포기할까 하고 마음 먹고 있다. 


하지만, 아무리 생각해도 방법이 있을 텐데..라는 마음으로 마지막으로 지푸라기라도 잡아보자하고 다시 시작했고..이제서야 원하는 결과를 얻게 되었다. 최종적으로는 PCL로 만들어서 앱에 적용해야하지만, 지금 한 90%는 알아 낸 것 같다.


앱에 적용할 것이 아니라 wpf정도에 적용하는 것이였다면 아마 Nuget에 있는것을 간단하게 추가해서 사용하면 되었을 텐데..쩝 한 몇일 찾는다고.. 흑흑 찾고나서 항상 생각하는 내용이지만, 모르면 D고생한다. 된장! 쌈장! 꼬추장!


1. 참고자료

빠르게 훝어 보는 node.js - #15 - Passport를 이용한 OAuth 2.0 API 인증 (Facebook 1/2)

http://bcho.tistory.com/913

OAuth 2.0에 대한 설명을 간단하게 적어 놓은 포스트인데 도움이 되었다.


Encode to Base64 format

http://www.base64encode.org/

base64엔코딩을 할때 사용한 사이트다.


Bitly

https://bitly.com/

url단축 서비스 및 여러가지 서비스를 제공하는 사이트!! 이 사이트에서 제공하는 API를 사용하는 것이 핵심이다.


2. Bitly 가입

뭐가 되었든 가입을 해야 단축링크를 만들던 단추를 만들던 할 수 있다. 회원 가입은 간단하게 할 수 있으니, 직접 찾아서 하기를 바란다. 그 화면까지 스크린샷을 찍지는 않았다. 


"너무 불친절 한데?"


"컨셉이다"


"..."


"그래도 스크린샷은 11장이나 찍어 놨으니 그걸로 만족하기 바란다"


"....." 퍽!퍽!퍽!


3. Setting page



로그인 후 첫 페이지 오른쪽 상단에 있는 kaki104를 클릭하면 Setting이라는 메뉴가 보이고 그 메뉴를 클릭하면 위의 페이지로 이동한다. 


이름 입력은 옵션이니 편한데로 하면되는데, 이름을 입력 후에 오른쪽에 Save버튼이 나오니 지긋이 눌러준다. 않누르고 다른데로 가면 지워져 버리니 지워져도 원망하지 않도록 한다.


중요한건 Email이다. Email 인증을 반드시 받아야 한다. 만약 가짜 이메일로 가입했다면, 다시 가입해라.


Click here라는 곳을 눌러서 메일을 전송한다.


그리고, 바로 메일 함을 열어보면 인증 메일이 도착해 있을 것이다. 아니면, 스팸편지함으로 들어갔거나.. 네이트로 받았을 때는 바로 스팸편지함으로 들어갔다. 

된장! 도착않해서 메일함 다 비우고나서 재발송 해서 찾아 냈다. 망할 네이트



참 여기 나오는 메일주소로 감사하다거나 격려의 내용을 담는 메일은 보내도 괜찮다.


"?"


"물론 기대는 않한다."


도착한 메일을 열어서 링크를 눌러서 메일 인증을 완료한다. 그러면, 인증에 성공했다는 화면이 나온다.


"스크린샷은?"


"없다"


"....." 퍽!폭!퍽!


4. API page & My Apps


Bitly 페이지에서 하단으로 내리면 왼쪽 하단에 몇개의 링크가 있는데 API라는 글씨를 클릭해서 이동한다.


화면에서 My Apps를 찾아서 클릭한다.


심플한 페이지가 나오면서 Manage My Apps라는 버튼이 주황색으로 보인다. 그걸 클릭해서 아래 화면으로 이동한다.


그런데 처음에 이 화면 들어왔을 때는 client id도 있고 뭐도 있고 몇개가 더 있었는데, 이렇게 심플하게 나와서 어떻게 해야하는지 고민했다.


고민하지 말고, Register an application을 클릭한다.



Application을 등록해야 API를 이용해서 앱에서 바로 단축링크를 만들 수 있으니 Get Registration Code 버튼을 클릭해서 등록 코드를 다시 받는다.


편지함을 다시 뒤지면, 등록코드 메일이 있고, 링크를 다시 눌러서 작업을 완료한다.



이제 Application Name, Application Link, Redirect URIs등의 정보를 입력한다. 그리고 화면에 보이지는 않치만, Description도 입력한다. 


저장하고 다음으로 넘어간다.



대충 위와 같이 입력해서 저장했다. 여기서 중요한 부분은 Client ID, Client Secret이다.


이제 API를 사용할 준비가 완료됐다.


"음 간단한데?"


ㅡ,.ㅡ;; '이걸 알아 제대로 정리할려고 몇 시간 보냈고, 우쉬!!@!@'


"왜 그런 똥씹은 표정이야?"


"알면 다친다"


퍽퍽!!


"말로 해라"


퍼퍼퍼퍼퍼펑!!!!!


5. Post man으로 테스트하기


Authentication page

http://dev.bitly.com/authentication.html

위의 페이지 중간 쯤에 있는 Resource Owner Credentials Grants에 있는 인증 방법을 사용해서 테스트를 진행했다.


API를 사용하려면 인증과정을 거쳐서 access token을 얻은 후 그걸 이용해서 여러가지 서비스를 사용하는 구조이다. 그 과정을 쉽게 테스트 하기 위해서 크롬과 Post man이라는 크롬앱을 설치해서 사용한다.


아니면, 그냥 스크린샷을 보고 넘어가도 된다.





요청주소 : https://api-ssl.bitly.com/oauth/access_token

방법 : POST

Header에 Authorization추가 value에 값 넣는 방법은 위에 있는 Authentication page의 설명을 참고해서 만든다.

각 파라메터를 넣고 Send 버튼을 누르면 아래처럼 access_token이 반환된다.


이제 이걸 이용해서 각종 서비스를 호출하면 된다.


/v3/shorten 

http://dev.bitly.com/links.html#v3_shorten

단축링크 만드는 서비스이다. 이 서비스를 호출하기 위해서는 access_token과 longUrl을 파라메터로 던져주면 된다.



그런 내용을 Get으로 던져주면 위와 같은 결과가 나오는데 url 항목을 보면 짧은 링크가 반환된 것이 보인다.


바로 이 맛이다. 먹어도 먹어도 배가 부르지 않으면서, 만족감만 올라가는 이런 맛이..


이제 내일은 이런 내용을 PCL에 HttpClient로 구현해 넣고 사용해야겠다.


"구현 끝나고 소스 올려줘~"


"생각해 보겠다"


"좋은 말로 할 때 해주지?"


ㅡ,.ㅡ;;;


퍽퍽퍽!!!


윈도8 & 윈도폰8 앱 개발 모임 그룹

https://www.facebook.com/groups/vapps/



Posted by MVP kaki104

댓글을 달아 주세요

얼마전 개발 모임에서 FileOpenPicker 사용법이 Windows 8.1과 Windows Phone 8.1이 서로 달라서, 상당히 불편하다는 이야기를 듣게 되었다. 그래서 샘플작업을 진행하다가 그 부분을 동일하게 사용할 수 있는 방법이 있을까하고 찾아보니 적당한 내용이 있어서 심플하게 수정해 보았다.

 

1. 참고 포스트

Mike Taulty's Blog : 좋은 포스트를 많이 해주는 분이다.

Windows/Phone 8.1–Returning to the File Open Picker Abstraction

http://mtaulty.com/CommunityServer/blogs/mike_taultys_blog/archive/2014/05/28/windows-phone-8-1-returning-to-the-file-open-picker-abstraction.aspx

 

이 포스트에서는 PCL과 TaskCompletionSource를 이용해서 처리를 했는데, PCL을 사용하지 않는 분들을 위해 Shared 프로젝트에서 사용이 가능하도록 변경했다.

 

2. StorageHelper.cs

 

using System.Linq;
using System.Threading.Tasks;
using Windows.ApplicationModel.Activation;
using Windows.ApplicationModel.Core;
using Windows.Storage;
using Windows.Storage.Pickers;

 

namespace UniversalSample.Commons
{
    /// <summary>
    /// StorageHelper
    /// </summary>
    public class StorageHelper
    {
        private static StorageHelper _instance;

        /// <summary>
        /// Instance
        /// </summary>
        public static StorageHelper Instance
        {
            get { return _instance = _instance ?? new StorageHelper(); }
        }

#if WINDOWS_APP
        public async Task<StorageFile> PickSingleFileAsync(FileOpenPicker picker)
        {
            StorageFile file = await picker.PickSingleFileAsync();
            return file;
        }
#else
        //Windows Phone 8.1

        /// <summary>
        /// completionSource
        /// </summary>
        private TaskCompletionSource<StorageFile> completionSource;

 

        /// <summary>
        /// PickSingleFileAsync
        /// </summary>
        /// <param name="picker"></param>
        /// <returns></returns>
        public async Task<StorageFile> PickSingleFileAsync(FileOpenPicker picker)
        {
            //TaskCompletionSource 생성
            completionSource = new TaskCompletionSource<StorageFile>();

            //앱의 Activated 이벤트 연결
            CoreApplication.GetCurrentView().Activated += OnApplicationActivated;

            //PickSingleFileAndContinue 메소드 실행
            picker.PickSingleFileAndContinue();

            //TaskCompletionSource가 완료될 때까지 기다림, 결과는 StorageFile
            var file = await completionSource.Task;

            //file 리턴
            return file;
        }

 

        /// <summary>
        /// OnApplicationActivated
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void OnApplicationActivated(CoreApplicationView sender, IActivatedEventArgs args)
        {
            //Args확인
            var continueArgs = args as FileOpenPickerContinuationEventArgs;
           
            if (continueArgs != null && continueArgs.Files.Any())
            {
                //completionSource가 존재하면
                if (completionSource != null)
                {
                    //결과 세팅
                    completionSource.TrySetResult(continueArgs.Files.First());
                    //completionSource 삭제
                    completionSource = null;
                }

            }
            else
            {
                //선택한 파일이 존재하지 않으면
                if (completionSource != null)
                {
                    //취소 세팅
                    completionSource.TrySetCanceled();
                    completionSource = null;
                }

            }
            //이벤트 연결 해제
            CoreApplication.GetCurrentView().Activated -= OnApplicationActivated;
        }
#endif
    }
}

 

작업의 핵심 기능은 TaskCompletionSource을 이용해서 Task를 지연 시키는 것이다. PickSingleFileAsync가 호출되었을 때 하나의 Task를 생성하고 PickSingleFileAndContinue() 메소드를 호출 후 Task가 StorageFile을 반환할 때까지 무한정 기다리도록 한다. 그러면, 사용자가 파일을 선택하고, Activated 이벤트가 발생하면, 파일이 있는지 확인해 보고 completionSource에 결과를 세팅하거나, 취소를 세팅해서 Task를 완료 시킨다. Task가 완료되면 대기하고 있던 내용을 계속 진행하게 되는 것이다.

 

3. Using sample

 

        /// <summary>
        /// Open Image Command
        /// </summary>
        public RelayCommand OpenImageCommand
        {
            get { return _openImageCommand = _openImageCommand ?? new RelayCommand(async args =>
            {
                var openPicker = new FileOpenPicker();
                openPicker.ViewMode = PickerViewMode.Thumbnail;
                openPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
                openPicker.FileTypeFilter.Add(".jpg");
                openPicker.FileTypeFilter.Add(".jpeg");
                openPicker.FileTypeFilter.Add(".png");
                TestText = "FileOpen Start";
                StorageFile file = await StorageHelper.Instance.PickSingleFileAsync(openPicker);
                if (file != null)
                {
                    var bitmapImage = new BitmapImage();
                    using (IRandomAccessStream fileStream = await file.OpenAsync(FileAccessMode.Read))
                    {
                        // Set the image source to the selected bitmap
                        ImageProperties property = await file.Properties.GetImagePropertiesAsync();
                        bitmapImage.DecodePixelHeight = (int)property.Height;
                        bitmapImage.DecodePixelWidth = (int)property.Width;
                        await bitmapImage.SetSourceAsync(fileStream);
                    }
                    SelectedImageSource = bitmapImage;
                    TestText = "FileOpen End";
                }
            }); }
        }

 

뷰모델에서는 Windows 8.1인지, Windows Phone 8.1인지 상관하지 않고 위와 같이 사용하면 동일한 동작이 가능하다.

 

* Open Image button click

 

* FileOpenPicker 화면

 

* 선택한 이미지 출력

 

 

4. 파일 피커와 웹브로커를 위와 같은 방법으로 구현하면 쉽게 사용이 가능할 것이라 생각한다. 나중에 다른 내용들도 구현하게 되면 올리도록 하겠다.

 

전체 소스 

UniversalSample_20140618.zip

 

Posted by MVP kaki104

댓글을 달아 주세요

Page navigation part2

 

지난번 소스에서 많은 부분을 수정해서 실제 프로젝트에서 사용이 가능할 정도까지 만들었고, 소스는 모두 ReSharper를 이용해서 정리했다. 또한, 이 소스를 이용해서 개발을 시작 할 수 있도록 좀 자세하게 설명을 추가 하니 좀 길더라도 이해하기 바란다.

 

0. 환경

 

Visual Studio 2013 Update 2

Windows 8.1

지난번 사용한 UniversalSample 소스 - 참고 정도?

 

1. 기본

프로젝트는 UniversalSample.Windows(Windows 8.1 store app), UniversalSample.WindowsPhone(Windows Phone 8.1 app), UniversalSample.Shared 3개의 프로젝트로 구성된다.

 

1-1. UniversalSample.Shared

W8.1과 WP8.1 프로젝트에서 공통으로 사용되는 대부분의 코드가 이 프로젝트에 위치하고 있다.

 

. Commons folder

- BindableBase.cs : 바인딩 클래스

- FrameHelper.cs : 프레임 헬퍼 클래스, 네비게이션을 담당한다.

- RelayCommand.cs : 커맨드 클래스(ICommand를 상속받은 커맨드 생성용 클래스)

- SuspensionManager.cs : 서스팬션 메니저로 앱이 서스팬드 모드로 돌입하거나 나올때 필요한 데이터를 저장하고 복구시킨다.

- ViewModelBase.cs : 뷰모델 베이스 클래스

 

. Design folder : 디자인 데이터 폴더

. Models folder : 모델 폴더

. ViewModels folder : 뷰모델 폴더

App.xaml : 앱 시작시에 필요한 내용이 들어있음

 

1-2. UniversalSample.Windows

. Assets folder : 이미지 리소스가 포함되어 있다.

. Views : 뷰 폴더

 

1-3. UniversalSample.WindowsPhone

. Assets folder : 이미지 리소스가 포함되어 있다.

. Views : 뷰 폴더

 

* 주의할 사항은 Windows프로젝트와 WindowsPhone 프로젝트는 모두 동일한 폴더 구조와 동일한 뷰 이름을 가져야 한다.

 

2. App.xaml과 App.xaml.cs를 살펴 본다.

앱이 시작될 때 어떤 동작들이 실행되는지 살펴 보도록 하자.

 

App.xaml

 

<Application
    x:Class="UniversalSample.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:viewModel="using:UniversalSample.ViewModels">

    <Application.Resources>
        <x:String x:Key="AppName">Universal Sample</x:String>
        <viewModel:ViewModelLocator x:Key="Locator" />
    </Application.Resources>
</Application> 

 

앱이 시작 될 때 앱 전체에서 사용할 리소스를 이곳에 정의한다.

AppName이라는 문자열 리소스를 하나 만들고, ViewModelLocator라는 클래스를 생성해서 리소스로 사용한다.

뷰모델로케이터는 두가지 용도로 사용되는데, 하나는 앱 전체에서 하나의 뷰모델만 유지 하기 위함이고, 또 하나는 템플릿 내부에서 뷰모델에 있는 내용을 바인딩(주로 커맨드)할 때 사용한다.

 

App.xaml.cs

 

        protected override void OnLaunched(LaunchActivatedEventArgs e)
        {
#if DEBUG
            if (System.Diagnostics.Debugger.IsAttached)
            {
                DebugSettings.EnableFrameRateCounter = true;
            }
#endif

            var rootFrame = Window.Current.Content as Frame;

            // Do not repeat app initialization when the Window already has content,
            // just ensure that the window is active
            if (rootFrame == null)
            {
                // Create a Frame to act as the navigation context and navigate to the first page
                rootFrame = new Frame {CacheSize = 1};

                // TODO: change this value to a cache size that is appropriate for your application

                if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
                {
                    // TODO: Load state from previously suspended application
                }

                // Place the frame in the current Window
                Window.Current.Content = rootFrame;
                // 루트 프레임을 FrameHelper에 등록한다.
                FrameHelper.Instance.RegisterFrame(rootFrame);
            }

            //루트 프레임의 컨텐츠가 null이면 MainPage로 네비게이션한다.
            if (rootFrame.Content == null)
            {
#if WINDOWS_PHONE_APP
                // Removes the turnstile navigation for startup.
                if (rootFrame.ContentTransitions != null)
                {
                    this.transitions = new TransitionCollection();
                    foreach (var c in rootFrame.ContentTransitions)
                    {
                        this.transitions.Add(c);
                    }
                }

                rootFrame.ContentTransitions = null;
                rootFrame.Navigated += this.RootFrame_FirstNavigated;
#endif

                // When the navigation stack isn't restored navigate to the first page,
                // configuring the new page by passing required information as a navigation
                // parameter
                if (!FrameHelper.Instance.Navigation(typeof(MainPage), e.Arguments))
                {
                    throw new Exception("Failed to create initial page");
                }

            }

            // Ensure the current window is active
            Window.Current.Activate();
        }

 

* 기존 유니버셜앱 템플릿에 있는 NavigationHelper는 MVVM 패턴에서는 사용하기가 좀 그렇다, 그래서 ViewModel에서 네비게이션 관련 처리를 모두 할 수 있도록 수정한 것이 이 셈플이다.

 

위에서 앱이 시작되면 MainPage로 네비게이션을 시키도록 되어있다. 이제 MainPage.xaml.cs를 보도록 하자.

 

3. MainPage.xaml.cs

 

using Windows.ApplicationModel;
using Windows.UI.Xaml.Controls;
using UniversalSample.ViewModels;

namespace UniversalSample.Views
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            InitializeComponent();

            if (DesignMode.DesignModeEnabled == false)
            {
                var locator = App.Current.Resources["Locator"] as ViewModelLocator;
                if (locator != null) DataContext = DataContext ?? locator.MainPageVM;
            }

        }
    }
}

 

간단하게 코딩 했다. 런타임이라면 앱의 리소스에서 ViewModelLocator를 찾고, 뷰모델로케이터에 있는 MainPageVM을 가지고 와서 DataContext에 넣어 준다. 그렇다면 ViewModelLocator는 어떤 내용이 있는지 보아야 할 때다.

 

4. ViewModelLocator.cs

 

namespace UniversalSample.ViewModels
{
    public class ViewModelLocator
    {
        private ClassDetailPageVM _classDetailPageVM;
        private MainPageVM _mainPageVM;

        public MainPageVM MainPageVM
        {
            get { return _mainPageVM = _mainPageVM ?? new MainPageVM(); }
        }

        public ClassDetailPageVM ClassDetailPageVM
        {
            get { return _classDetailPageVM = _classDetailPageVM ?? new ClassDetailPageVM(); }
        }
    }
}

 

ViewModelLocator는 2개의 뷰모델을 프로퍼티로 가지고 있으며, 각 프로퍼티가 사용이 되는 시점에 인스턴스가 되도록 되어 있다. 아주 특별한 내용은 없다. 그러면, MainPageVM.cs의 내용을 살펴보자

 

5. MainPageVM.cs

 

using System.Collections.Generic;
using Windows.ApplicationModel;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Navigation;
using UniversalSample.Commons;
using UniversalSample.Design;
using UniversalSample.Models;
using UniversalSample.Views;

 

namespace UniversalSample.ViewModels
{
    /// <summary>
    ///     MainPage ViewModel
    /// </summary>
    public class MainPageVM : ViewModelBase
    {
        private IList<ClassM> _classList;
        private RelayCommand _itemClickCommand;
        private IList<PersonM> _people;

        /// <summary>
        ///     Constructor
        /// </summary>
        public MainPageVM()
        {
            Title = "Kaki education center";

            ClassList = new List<ClassM>();
            People = new List<PersonM>();

            if (DesignMode.DesignModeEnabled)
            {
                //디자인 타임 데이터
                ClassList = DesignDatas.GetClassList();
                People = DesignDatas.GetPeople();
            }
        }

 

        /// <summary>
        ///     Class List
        /// </summary>
        public IList<ClassM> ClassList
        {
            get { return _classList; }
            set
            {
                _classList = value;
                OnPropertyChanged();
            }
        }

 

        /// <summary>
        ///     People
        /// </summary>
        public IList<PersonM> People
        {
            get { return _people; }
            set
            {
                _people = value;
                OnPropertyChanged();
            }
        }

 

        /// <summary>
        ///     ItemClick Command
        /// </summary>
        public RelayCommand ItemClickCommand
        {
            get
            {
                return _itemClickCommand = _itemClickCommand ?? new RelayCommand(
                    args =>
                    {
                        if (!(args is ItemClickEventArgs)) return;

                        var item = ((ItemClickEventArgs) args).ClickedItem as ClassM;
                        if (item == null) return;

                        FrameHelper.Instance.Navigation(typeof (ClassDetailPage), item);
                    });
            }
        }

 

        /// <summary>
        ///     OnNavigated
        /// </summary>
        /// <param name="e"></param>
        protected override void OnNavigated(NavigationEventArgs e)
        {
            //네비게이션이 완료 된 후 실행되는 부분, 지금은 디자인 타임과 동일한 데이터를 보여준다.
            ClassList = DesignDatas.GetClassList();
            People = DesignDatas.GetPeople();
        }
    }
}

 

public RelayCommand ItemClickCommand

커맨드 프로퍼티로 ListView의 ItemClick이벤트를 처리하며, args에는 ItemClick 이벤트의 아규먼트인 ItemClickEventArgs가 반환되는데, 이 것은 InvokeCommandAction에서 CommandParameter를 지정하지 않으면 반환되는 것이다.

 

FrameHelper.Instance.Navigation(typeof (ClassDetailPage), item);
ClassDetailPage로 item object를 가지고 네비게이션 한다. 네비게이션 파라메터는 기본적으로는 string을 넘기도록 하지만, object를 넘겨도 큰 문제는 없다. 대신 네비게이션 파라메터를 serialization해서 저장을 하는 것은 할 수 없다. 만약 그렇게 해야만 한다면, string만을 넘기도록 한다.

 

protected override void OnNavigated(NavigationEventArgs e)

MainPage가 Navigated가 된후 실행되는 가상 메소드이다. 네비게이션과 관련된 가상 메소드들은 ViewModelBase.cs에 선언되어 있다. 네비게이션 과정이 어떻체 처리되는지 더 자세하게 알려면, FrameHelper.cs와 ViewModelBase.cs를 자세히 보면 된다.

 

6. MainPage.xaml - Windows 8.1

 

<Page
    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:ViewModels="using:UniversalSample.ViewModels"
    xmlns:Interactivity="using:Microsoft.Xaml.Interactivity" xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
    x:Class="UniversalSample.Views.MainPage"
    mc:Ignorable="d">


    <Page.Resources>
        <!--ListViewItem의 가로 너비를 Stretch하도록 한다-->
        <Style TargetType="ListViewItem">
            <Setter Property="HorizontalContentAlignment" Value="Stretch" />
        </Style>
    </Page.Resources>

    <!--디자인타임 뷰모델, Blend를 이용해서 작업하면 이렇게 저장된다. Visual Studio에서는 <d:Page.DataContext>로 사용하면 된다. -->
    <d:DataContext>
        <ViewModels:MainPageVM />
    </d:DataContext>

 

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.ChildrenTransitions>
            <TransitionCollection>
                <EntranceThemeTransition />
            </TransitionCollection>
        </Grid.ChildrenTransitions>

 

        <Hub>
            <Hub.Header>
                <!-- Back button and page title -->
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="80" />
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>
                    <!--GoBackCommand로 변경, ViewModelBase에 있는 커맨드-->
                    <Button Margin="-1,-1,39,0"
                            Command="{Binding GoBackCommand, Mode=OneWay}"
                            Style="{StaticResource NavigationBackButtonNormalStyle}"
                            VerticalAlignment="Top"
                            AutomationProperties.Name="Back"
                            AutomationProperties.AutomationId="BackButton"
                            AutomationProperties.ItemType="Navigation Button" />
                    <!--App.xaml에서 추가해 놓았던 AppName을 여기서 사용-->
                    <TextBlock
                        Style="{StaticResource HeaderTextBlockStyle}" Grid.Column="1"
                        IsHitTestVisible="false" TextWrapping="NoWrap" VerticalAlignment="Top"
                        Text="{StaticResource AppName}" />
                </Grid>
            </Hub.Header>

 

            <!--앱에서 사용하던 이미지를 셈플로 사용-->
            <HubSection Width="500" Margin="0,0,80,0">
                <HubSection.Background>
                    <ImageBrush Stretch="UniformToFill" ImageSource="ms-appx:///Assets/storelogo558_756.png" />
                </HubSection.Background>
            </HubSection>

 

            <!--클래스 목록-->
            <HubSection Header="Class List" MinWidth="400" HorizontalContentAlignment="Stretch">
                <DataTemplate>
                    <Grid>
                        <!--ItemClick이벤트를 사용하기 위해서는 IsItemClickEnabled가 True여야 한다-->
                        <ListView ItemsSource="{Binding ClassList}" HorizontalContentAlignment="Stretch"
                                  SelectionMode="None" IsItemClickEnabled="True">
                            <!--리스트뷰에 헤더 템플릿을 이용-->
                            <ListView.HeaderTemplate>
                                <DataTemplate>
                                    <Grid>
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="0.4*" />
                                            <ColumnDefinition Width="0.6*" />
                                            <ColumnDefinition Width="60" />
                                            <ColumnDefinition Width="15" />
                                        </Grid.ColumnDefinitions>
                                        <TextBlock Style="{StaticResource BodyTextBlockStyle}">
                                            <Run Text="Name" />
                                        </TextBlock>
                                        <TextBlock Grid.Column="1" Style="{StaticResource BodyTextBlockStyle}">
                                            <Run Text="Description" />
                                        </TextBlock>
                                        <TextBlock Grid.Column="2" Style="{StaticResource BodyTextBlockStyle}">
                                            <Run Text="Count" />
                                        </TextBlock>
                                    </Grid>
                                </DataTemplate>
                            </ListView.HeaderTemplate>
                            <!--아이템 템플릿-->
                            <ListView.ItemTemplate>
                                <DataTemplate>
                                    <Grid>
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="0.4*" />
                                            <ColumnDefinition Width="0.6*" />
                                            <ColumnDefinition Width="60" />
                                        </Grid.ColumnDefinitions>

                                        <TextBlock Text="{Binding Name}" Style="{StaticResource BodyTextBlockStyle}"
                                                   Margin="0,0,4,0" />
                                        <TextBlock Grid.Column="1" Text="{Binding Description}"
                                                   Style="{StaticResource BodyTextBlockStyle}" />
                                        <TextBlock Grid.Column="2" Text="{Binding People.Count, Mode=OneWay}"
                                                   Style="{StaticResource BodyTextBlockStyle}"
                                                   HorizontalAlignment="Center" />
                                    </Grid>
                                </DataTemplate>
                            </ListView.ItemTemplate>

                            <!--인터렉티비티-->
                            <Interactivity:Interaction.Behaviors>
                                <!--아이템클릭이벤트에 반응-->

                                <Core:EventTriggerBehavior EventName="ItemClick">
                                    <!--ItemClickCommand를 실행시키는데 CommandParameter는 ItemClickEventArgs가 자동으로 입력된다.-->
                                    <Core:InvokeCommandAction Command="{Binding ItemClickCommand, Mode=OneWay}" />
                                </Core:EventTriggerBehavior>
                            </Interactivity:Interaction.Behaviors>

                        </ListView>
                    </Grid>
                </DataTemplate>
            </HubSection>

 

            <!--People 프로퍼티 내용 출력용으로 중요한 부분은 없다-->
            <HubSection MinWidth="400" Header="People" HorizontalAlignment="Stretch"
                        HorizontalContentAlignment="Stretch">
                <DataTemplate>
                    <Grid>
                        <ListView ItemsSource="{Binding People}" HorizontalContentAlignment="Stretch"
                                  SelectionMode="None">
                            <ListView.HeaderTemplate>
                                <DataTemplate>
                                    <Grid>
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="0.3*" />
                                            <ColumnDefinition Width="0.3*" />
                                            <ColumnDefinition Width="0.3*" />
                                            <ColumnDefinition Width="15" />
                                        </Grid.ColumnDefinitions>
                                        <TextBlock Style="{StaticResource BodyTextBlockStyle}" Text="Name" />
                                        <TextBlock Grid.Column="1" Style="{StaticResource BodyTextBlockStyle}"
                                                   Text="Age" />
                                        <TextBlock Grid.Column="2" Style="{StaticResource BodyTextBlockStyle}"
                                                   Text="Sex" />
                                    </Grid>
                                </DataTemplate>
                            </ListView.HeaderTemplate>
                            <ListView.ItemTemplate>
                                <DataTemplate>
                                    <Grid>
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="0.3*" />
                                            <ColumnDefinition Width="0.3*" />
                                            <ColumnDefinition Width="0.3*" />
                                        </Grid.ColumnDefinitions>

                                        <TextBlock Text="{Binding Name}" Style="{StaticResource BodyTextBlockStyle}"
                                                   Margin="0,0,4,0" />
                                        <TextBlock Grid.Column="1" Text="{Binding Age}"
                                                   Style="{StaticResource BodyTextBlockStyle}" />
                                        <TextBlock Grid.Column="2" Text="{Binding Sex}"
                                                   Style="{StaticResource BodyTextBlockStyle}" />
                                    </Grid>
                                </DataTemplate>
                            </ListView.ItemTemplate>
                        </ListView>
                    </Grid>
                </DataTemplate>
            </HubSection>
        </Hub>
    </Grid>
</Page>

 

7. MainPage.xaml - Windows Phone 8.1

 

<Page
    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:ViewModels="using:UniversalSample.ViewModels"
    xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
    xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
    x:Class="UniversalSample.Views.MainPage"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">


    <Page.Resources>
        <!--ListViewItem의 가로 너비를 Stretch하도록 한다-->
        <Style TargetType="ListViewItem">
            <Setter Property="HorizontalContentAlignment" Value="Stretch" />
        </Style>

    </Page.Resources>

 

    <!--디자인타임 뷰모델, Blend를 이용해서 작업하면 이렇게 저장된다.-->
    <d:DataContext>
        <ViewModels:MainPageVM />
    </d:DataContext>

 

    <Grid>
        <!--App.xaml에서 추가해 놓았던 AppName을 여기서 사용-->
        <Hub Header="{StaticResource AppName}">
            <!--앱에서 사용하던 이미지를 셈플로 사용, 이미지는 Windows 8.1 프로젝트에 있는 이미지의 링크이미지임-->
            <HubSection>
                <HubSection.Background>
                    <ImageBrush Stretch="UniformToFill" ImageSource="ms-appx:///Assets/storelogo558_756.png" />
                </HubSection.Background>
            </HubSection>

 

            <!--클래스 목록, 너비를 지정하지 않는다-->
            <HubSection Header="Class List">
                <DataTemplate>
                    <Grid>
                        <!--ItemClick이벤트를 사용하기 위해서는 IsItemClickEnabled가 True여야 한다-->
                        <ListView ItemsSource="{Binding ClassList}" HorizontalContentAlignment="Stretch"
                                  SelectionMode="None" IsItemClickEnabled="True">
                            <!--리스트뷰에 헤더 템플릿을 이용-->
                            <ListView.HeaderTemplate>
                                <DataTemplate>
                                    <Grid>
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="0.4*" />
                                            <ColumnDefinition Width="0.6*" />
                                            <ColumnDefinition Width="60" />
                                        </Grid.ColumnDefinitions>
                                        <TextBlock Style="{StaticResource BodyTextBlockStyle}">
                                            <Run Text="Name" />
                                        </TextBlock>
                                        <TextBlock Grid.Column="1" Style="{StaticResource BodyTextBlockStyle}">
                                            <Run Text="Description" />
                                        </TextBlock>
                                        <TextBlock Grid.Column="2" Style="{StaticResource BodyTextBlockStyle}">
                                            <Run Text="Count" />
                                        </TextBlock>
                                    </Grid>
                                </DataTemplate>
                            </ListView.HeaderTemplate>
                            <!--아이템 템플릿-->
                            <ListView.ItemTemplate>
                                <DataTemplate>
                                    <Grid>
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="0.4*" />
                                            <ColumnDefinition Width="0.6*" />
                                            <ColumnDefinition Width="60" />
                                        </Grid.ColumnDefinitions>

                                        <TextBlock Text="{Binding Name}" Style="{StaticResource BodyTextBlockStyle}"
                                                   Margin="0,0,4,0" />
                                        <TextBlock Grid.Column="1" Text="{Binding Description}"
                                                   Style="{StaticResource BodyTextBlockStyle}" />
                                        <TextBlock Grid.Column="2" Text="{Binding People.Count, Mode=OneWay}"
                                                   Style="{StaticResource BodyTextBlockStyle}"
                                                   HorizontalAlignment="Center" />
                                    </Grid>
                                </DataTemplate>
                            </ListView.ItemTemplate>
                            <!--인터렉티비티-->
                            <Interactivity:Interaction.Behaviors>
                                <!--아이템클릭이벤트에 반응-->
                                <Core:EventTriggerBehavior EventName="ItemClick">
                                    <!--ItemClickCommand를 실행시키는데 CommandParameter는 ItemClickEventArgs가 자동으로 입력된다.-->
                                    <Core:InvokeCommandAction Command="{Binding ItemClickCommand, Mode=OneWay}" />
                                </Core:EventTriggerBehavior>
                            </Interactivity:Interaction.Behaviors>

                        </ListView>
                    </Grid>
                </DataTemplate>
            </HubSection>

 

            <!--People 프로퍼티 내용 출력용으로 중요한 부분은 없다-->
            <HubSection Header="People">
                <DataTemplate>
                    <Grid>
                        <ListView ItemsSource="{Binding People}" HorizontalContentAlignment="Stretch"
                                  SelectionMode="None">
                            <ListView.HeaderTemplate>
                                <DataTemplate>
                                    <Grid>
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="0.3*" />
                                            <ColumnDefinition Width="0.3*" />
                                            <ColumnDefinition Width="0.3*" />
                                        </Grid.ColumnDefinitions>
                                        <TextBlock Style="{StaticResource BodyTextBlockStyle}" Text="Name" />
                                        <TextBlock Grid.Column="1" Style="{StaticResource BodyTextBlockStyle}"
                                                   Text="Age" />
                                        <TextBlock Grid.Column="2" Style="{StaticResource BodyTextBlockStyle}"
                                                   Text="Sex" />
                                    </Grid>
                                </DataTemplate>
                            </ListView.HeaderTemplate>
                            <ListView.ItemTemplate>
                                <DataTemplate>
                                    <Grid>
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="0.3*" />
                                            <ColumnDefinition Width="0.3*" />
                                            <ColumnDefinition Width="0.3*" />
                                        </Grid.ColumnDefinitions>

                                        <TextBlock Text="{Binding Name}" Style="{StaticResource BodyTextBlockStyle}"
                                                   Margin="0,0,4,0" />
                                        <TextBlock Grid.Column="1" Text="{Binding Age}"
                                                   Style="{StaticResource BodyTextBlockStyle}" />
                                        <TextBlock Grid.Column="2" Text="{Binding Sex}"
                                                   Style="{StaticResource BodyTextBlockStyle}" />
                                    </Grid>
                                </DataTemplate>
                            </ListView.ItemTemplate>
                        </ListView>
                    </Grid>
                </DataTemplate>
            </HubSection>
        </Hub>
    </Grid>
</Page>

 

* 6번 MainPage.xaml과 7번 MainPage.xaml은 작은 차이가 몇가지가 있다. 우선은 헤더 부분이 다르고, 허브 Section에 너비를 지정하지 않아야 하고, ItemTemplate이 약간 다르다. 이런 작은 차이가 있기 때문에 하나의 xaml을 두 프로젝트에서 공통으로 사용하기에는 문제가 있을 것 같아 그냥 2개로 분리하는 방향으로 했다. 이 방법말고 VisualState를 이용하는 방법도 있기는 한데 파일은 하나지만 관리하기가 더 어렵지 않을까 생각된다.

 

이제 두번째 페이지를 살펴보자

 

8. ClassDetailPageVM.cs

 

using System.Linq;
using Windows.ApplicationModel;
using Windows.UI.Xaml.Navigation;
using UniversalSample.Commons;
using UniversalSample.Design;
using UniversalSample.Models;

 

namespace UniversalSample.ViewModels
{
    /// <summary>
    ///     ClassDetailPage ViewModel
    /// </summary>
    public class ClassDetailPageVM : ViewModelBase
    {
        private ClassM _currentClass;

        /// <summary>
        ///     Constructor
        /// </summary>
        public ClassDetailPageVM()
        {
            Title = "Class detail page";

            if (DesignMode.DesignModeEnabled)
            {
                //디자인 타임에서는 첫번째 데이터를 이용한다.
                CurrentClass = DesignDatas.GetClassList().First();
            }
        }

        /// <summary>
        ///     Current Class
        /// </summary>
        public ClassM CurrentClass
        {
            get { return _currentClass; }
            set
            {
                _currentClass = value;
                OnPropertyChanged();
            }
        }

        /// <summary>
        ///     Navigating
        /// </summary>
        /// <param name="e"></param>
        protected override void OnNavigating(NavigatingCancelEventArgs e)
        {
            CurrentClass = null;
        }

        /// <summary>
        ///     Navigated
        /// </summary>
        /// <param name="e"></param>
        protected override void OnNavigated(NavigationEventArgs e)
        {
            if (e.NavigationMode == NavigationMode.New)
            {
                CurrentClass = e.Parameter as ClassM;
            }
        }
    }
}

 

protected override void OnNavigated(NavigationEventArgs e)

네비게이션이 완료된 후 호출되는 가상 메소드로 NavigationMode가 New인 경우에 CurrentClass에 네비게이션 파라메터를 입력한다.

 

protected override void OnNavigating(NavigatingCancelEventArgs e)

네비게이션이 발생하기 전에 호출되는 가상 메소드로(여기서는 GoBack) CurrentClass = null을 실행한다. 특별히 네비게이션이 되기전에 처리할 내용이 있으면 이곳에서 처리하면 된다. 위의 내용은 예로 넣어 놓은 것 뿐이다.

 

뷰를 살펴보자.

 

9. ClassDetailPage.xaml - Windows 8.1

 

<Page
    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:ViewModels="using:UniversalSample.ViewModels"
    x:Class="UniversalSample.Views.ClassDetailPage"
    mc:Ignorable="d">

 

    <Page.Resources>
        <!--디자인 리소스들-->
        <Style TargetType="ListViewItem">
            <Setter Property="HorizontalContentAlignment" Value="Stretch" />
        </Style>

        <Style x:Key="HeadBorderStyle" TargetType="Border">
            <Setter Property="Background" Value="Gray" />
            <Setter Property="BorderBrush" Value="White" />
            <Setter Property="BorderThickness" Value="2" />
        </Style>
        <Style x:Key="DetailBorderStyle" TargetType="Border">
            <Setter Property="BorderBrush" Value="DarkGray" />
            <Setter Property="BorderThickness" Value="2" />
            <Setter Property="Padding" Value="10" />
        </Style>
    </Page.Resources>

 

    <!--디자인타임 뷰모델-->
    <d:Page.DataContext>
        <ViewModels:ClassDetailPageVM />
    </d:Page.DataContext>

 

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <!--에니메이션 효과-->
        <Grid.ChildrenTransitions>
            <TransitionCollection>
                <EntranceThemeTransition />
            </TransitionCollection>
        </Grid.ChildrenTransitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="140" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

 

        <Grid Grid.Row="1" Margin="120,0,120,90">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="0.5*" />
                <ColumnDefinition Width="0.5*" />
            </Grid.ColumnDefinitions>

            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="193*" />
                    <ColumnDefinition Width="490*" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="31*" />
                    <RowDefinition Height="126*" />
                </Grid.RowDefinitions>

                <Border Style="{StaticResource HeadBorderStyle}">
                    <TextBlock Text="Name" Style="{StaticResource TitleTextBlockStyle}" HorizontalAlignment="Center"
                               VerticalAlignment="Center" />
                </Border>
                <Border Grid.Row="1" Grid.Column="0" Style="{StaticResource HeadBorderStyle}">
                    <TextBlock Text="Description" Style="{StaticResource TitleTextBlockStyle}"
                               VerticalAlignment="Center" HorizontalAlignment="Center" />
                </Border>

                <!--CurrentClass.Name 프로퍼티 바인딩-->
                <Border Grid.Row="0" Grid.Column="1" Style="{StaticResource DetailBorderStyle}">
                    <TextBlock Text="{Binding CurrentClass.Name}" Style="{StaticResource BodyTextBlockStyle}"
                               VerticalAlignment="Center" />
                </Border>
                <!--CurrentClass.Description 프로퍼티 바인딩-->
                <Border Grid.Row="1" Grid.Column="1" Style="{StaticResource DetailBorderStyle}">
                    <TextBlock Text="{Binding CurrentClass.Description}" Style="{StaticResource BodyTextBlockStyle}"
                               VerticalAlignment="Center" />
                </Border>
            </Grid>

 

            <!--CurrentClass.People 프로퍼티 바인딩 HeaderTemplate, ItemTemplate는 MainPage에 있는 내용과 동일하다-->
            <ListView Grid.Column="1" ItemsSource="{Binding CurrentClass.People}" HorizontalContentAlignment="Stretch"
                      BorderBrush="White" BorderThickness="2" Margin="20,0,0,0">
                <ListView.HeaderTemplate>
                    <DataTemplate>
                        <Grid Height="40">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="0.3*" />
                                <ColumnDefinition Width="0.3*" />
                                <ColumnDefinition Width="0.3*" />
                                <ColumnDefinition Width="15" />
                            </Grid.ColumnDefinitions>
                            <TextBlock Style="{StaticResource BodyTextBlockStyle}" Text="Name" />
                            <TextBlock Grid.Column="1" Style="{StaticResource BodyTextBlockStyle}" Text="Age" />
                            <TextBlock Grid.Column="2" Style="{StaticResource BodyTextBlockStyle}" Text="Sex" />
                        </Grid>
                    </DataTemplate>
                </ListView.HeaderTemplate>
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="0.3*" />
                                <ColumnDefinition Width="0.3*" />
                                <ColumnDefinition Width="0.3*" />
                            </Grid.ColumnDefinitions>

                            <TextBlock Text="{Binding Name}" Style="{StaticResource BodyTextBlockStyle}"
                                       Margin="0,0,4,0" />
                            <TextBlock Grid.Column="1" Text="{Binding Age}" Style="{StaticResource BodyTextBlockStyle}" />
                            <TextBlock Grid.Column="2" Text="{Binding Sex}" Style="{StaticResource BodyTextBlockStyle}" />
                        </Grid>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>

        </Grid>

        <!-- Back button and page title -->
        <Grid Grid.Row="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="120" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>

            <!--GoBackCommand로 변경, ViewModelBase에 있는 커맨드-->
            <Button Margin="39,59,39,0"
                    Command="{Binding GoBackCommand, Mode=OneWay}"
                    Style="{StaticResource NavigationBackButtonNormalStyle}"
                    VerticalAlignment="Top"
                    AutomationProperties.Name="Back"
                    AutomationProperties.AutomationId="BackButton"
                    AutomationProperties.ItemType="Navigation Button" />
            <TextBlock Text="{Binding Title}" Style="{StaticResource HeaderTextBlockStyle}"
                       Grid.Column="1"
                       IsHitTestVisible="false" TextWrapping="NoWrap" VerticalAlignment="Bottom" Margin="0,0,30,40" />
        </Grid>
    </Grid>
</Page>

 

10. ClassDetailPage.xaml - Windows Phone 8.1

 

<Page
    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:ViewModels="using:UniversalSample.ViewModels"
    x:Class="UniversalSample.Views.ClassDetailPage"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

 

    <Page.Transitions>
        <TransitionCollection>
            <NavigationThemeTransition>
                <ContinuumNavigationTransitionInfo />
            </NavigationThemeTransition>
        </TransitionCollection>
    </Page.Transitions>

 

    <Page.Resources>
        <!--디자인 리소스들-->
        <Style TargetType="ListViewItem">
            <Setter Property="HorizontalContentAlignment" Value="Stretch" />
        </Style>

        <Style x:Key="HeadBorderStyle" TargetType="Border">
            <Setter Property="Background" Value="Gray" />
            <Setter Property="BorderBrush" Value="White" />
            <Setter Property="BorderThickness" Value="2" />
        </Style>
        <Style x:Key="DetailBorderStyle" TargetType="Border">
            <Setter Property="BorderBrush" Value="DarkGray" />
            <Setter Property="BorderThickness" Value="2" />
            <Setter Property="Padding" Value="10" />
        </Style>
    </Page.Resources>

 

    <!--디자인타임 뷰모델-->
    <d:Page.DataContext>
        <ViewModels:ClassDetailPageVM />
    </d:Page.DataContext>

 

    <Grid>
        <!--에니메이션 효과-->
        <Grid.ChildrenTransitions>
            <TransitionCollection>
                <EntranceThemeTransition />
            </TransitionCollection>
        </Grid.ChildrenTransitions>


        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

 

        <!-- TitlePanel -->
        <StackPanel Grid.Row="0" Margin="24,17,0,28">
            <TextBlock x:Uid="Header" Text="{StaticResource AppName}" Style="{ThemeResource TitleTextBlockStyle}"
                       Typography.Capitals="SmallCaps" />
            <TextBlock Text="{Binding Title}" Margin="0,12,0,0" Style="{ThemeResource HeaderTextBlockStyle}" />
        </StackPanel>

 

        <Grid Grid.Row="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="1*" />
            </Grid.RowDefinitions>

            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="193*" />
                    <ColumnDefinition Width="490*" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
                <Border Style="{StaticResource HeadBorderStyle}">
                    <TextBlock Text="Name" Style="{StaticResource TitleTextBlockStyle}" HorizontalAlignment="Center"
                               VerticalAlignment="Center" />
                </Border>
                <Border Grid.Row="1" Grid.Column="0" Style="{StaticResource HeadBorderStyle}">
                    <TextBlock Text="Description" Style="{StaticResource TitleTextBlockStyle}"
                               VerticalAlignment="Center" HorizontalAlignment="Center" />
                </Border>

                <!--CurrentClass.Name 프로퍼티 바인딩-->
                <Border Grid.Row="0" Grid.Column="1" Style="{StaticResource DetailBorderStyle}">
                    <TextBlock Text="{Binding CurrentClass.Name}" Style="{StaticResource BodyTextBlockStyle}"
                               VerticalAlignment="Center" />
                </Border>

                <!--CurrentClass.Description 프로퍼티 바인딩-->
                <Border Grid.Row="1" Grid.Column="1" Style="{StaticResource DetailBorderStyle}">
                    <TextBlock Text="{Binding CurrentClass.Description}" Style="{StaticResource BodyTextBlockStyle}"
                               VerticalAlignment="Center" />
                </Border>

            </Grid>

            <!--CurrentClass.People 프로퍼티 바인딩 HeaderTemplate, ItemTemplate는 MainPage에 있는 내용과 동일하다-->
            <ListView Grid.Row="1" ItemsSource="{Binding CurrentClass.People}" HorizontalContentAlignment="Stretch"
                      BorderBrush="White" BorderThickness="2" Margin="0,10,0,0">
                <ListView.HeaderTemplate>
                    <DataTemplate>
                        <Grid Height="40">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="0.3*" />
                                <ColumnDefinition Width="0.3*" />
                                <ColumnDefinition Width="0.3*" />
                            </Grid.ColumnDefinitions>
                            <TextBlock Style="{StaticResource BodyTextBlockStyle}" Text="Name" />
                            <TextBlock Grid.Column="1" Style="{StaticResource BodyTextBlockStyle}" Text="Age" />
                            <TextBlock Grid.Column="2" Style="{StaticResource BodyTextBlockStyle}" Text="Sex" />
                        </Grid>
                    </DataTemplate>
                </ListView.HeaderTemplate>
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="0.3*" />
                                <ColumnDefinition Width="0.3*" />
                                <ColumnDefinition Width="0.3*" />
                            </Grid.ColumnDefinitions>

                            <TextBlock Text="{Binding Name}" Style="{StaticResource BodyTextBlockStyle}"
                                       Margin="0,0,4,0" />
                            <TextBlock Grid.Column="1" Text="{Binding Age}" Style="{StaticResource BodyTextBlockStyle}" />
                            <TextBlock Grid.Column="2" Text="{Binding Sex}" Style="{StaticResource BodyTextBlockStyle}" />
                        </Grid>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </Grid>
    </Grid>
</Page>

 

9번과 10번도 몇개의 차이점이 있는데, 우선 타이틀 부분이 버튼의 존재 여부 때문에 다르고, 컨텐츠의 배치를 윈폰은 세로로 만들었다.

 

 

 

 

이렇게 기본 셈플을 살펴보았다. 전체 소스를 받아서 살펴보면 더 쉽게 차이점을 알 수 있을 것이다.

 

전체 소스 

UniversalSample_20140617.zip

 

 

Posted by MVP kaki104

댓글을 달아 주세요

처음 시작은 매우 간단했지만, 이제 본격적으로 작업을 시작해 보려고 한다. 처음 시작이 네비게이션인데 작업을 하고 보니, 추가된 코드나 개념들에 대한 이해가 필요할 것 같다. 하지만, MVVM 패턴이나 여기 작성된 모든 내용을 반드시 알아야지만 앱을 만들 수 있는 것은 아님을 미리 이야기한다.

 

0. 환경

 

Visual Studio 2013 Update 2

Windows 8.1

지난번 사용한 UniversalSample 소스

 

1. 기본 템플릿에 존재하는 NavigationHelper를 직접 구현

기본 템플릿에 있는 NavigationHelper는 뷰모델에서 사용하기에 적합하지 않은 구조를 가지고 있다. 그래서 난 이전부터 만들어서 사용하고 있던 FrameHelper를 수정해서 구현했다.

 

기초적인 내용만 구현해 놓았다. 다음에는 여기에서 더 수정을 해야 완성된다.

 

    /// <summary>
    /// 네비게이션 관련 헬퍼
    /// </summary>
    public class FrameHelper
    {
        private static FrameHelper _instance;
        /// <summary>
        /// Singleton
        /// </summary>
        public static FrameHelper Instance
        {
            get
            {
                return _instance = _instance ?? new FrameHelper();
            }
        }

        /// <summary>
        /// Frame object
        /// </summary>
        internal Frame _frame;

        /// <summary>
        /// Navigated Command
        /// </summary>
        public ICommand NavigatedCommand { get; set; }
   
        /// <summary>
        /// Navigating Command
        /// </summary>
        public ICommand NavigatingCommand { get; set; }

        /// <summary>
        /// RegisterFrame
        /// </summary>
        /// <param name="frame"></param>
        public void RegisterFrame(Frame frame)
        {
            _frame = frame;
            if (_frame == null) return;

            _frame.Navigated -= Frame_Navigated;
            _frame.Navigating -= Frame_Navigating;

            _frame.Navigated += Frame_Navigated;
            _frame.Navigating += Frame_Navigating;
        }

        /// <summary>
        /// Navigation Type
        /// </summary>
        /// <param name="navigationType"></param>
        /// <param name="navigationParameter"></param>
        /// <returns></returns>
        public bool Navigation(Type navigationType, object navigationParameter)
        {
            var returnValue = false;
            if (_frame == null) return false;

            returnValue = _frame.Navigate(navigationType, navigationParameter);

            return returnValue;
        }

        /// <summary>
        /// GoBack
        /// </summary>
        /// <returns></returns>
        public bool GoBack()
        {
            if (_frame.CanGoBack == true)
            {
                _frame.GoBack();
            }

            return true;
        }

        /// <summary>
        /// Navigation From
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Frame_Navigating(object sender, NavigatingCancelEventArgs e)
        {
            if(NavigatingCommand != null)
            {
                NavigatingCommand.Execute(e);
            }
           
        }

        /// <summary>
        /// Navigation To
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Frame_Navigated(object sender, NavigationEventArgs e)
        {
            if (NavigatedCommand != null)
            {
                NavigatedCommand.Execute(e);
            }
        }

    }

 

2. 프레임 등록

윈8.1이나 윈폰8.1이나 모두 페이지 네비게이션은 Frame위에서 이루어 진다. 그래서 앱이 생성될 때 App.xaml.cs를 보면 Frame을 생성하는 곳이 존재한다.

 

                // Place the frame in the current Window
                Window.Current.Content = rootFrame;

 

                FrameHelper.Instance.RegisterFrame(rootFrame);

 

위의 코드를 추가해서 프레임을 FrameHelper에 등록한다.

 

3. ViewModel 추가

이전 소스는 MainPage.xaml, MainPage.xaml.cs 2개의 파일을 사용했지만, MVVM 패턴을 사용하기 위해서 MainPageVM.cs 파일을 추가하고 작업을 진행한다.

 

ViewModel을 추가하기 위해서 ViewModelBase.cs를 추가하고 여러가지 클래스들을 추가했다.

 

Commons 폴더에 추가된 내용(더 자세한 내용은 소스 파일을 참고한다.)

* BindableBase.cs

* RelayCommand.cs

* ViewModelBase.cs : 기본적으로 사용하는 여러가지 커맨드를 가지고 있다.

여기에 적지 않는 내용들은 차후에 사용 예정이거나, 참고용으로 추가해 놓았다.

 

4. MainPageVM.cs

뷰모델이 생성되면서 프레임헬퍼에 NavigatingCommand, NavigatedCommand 두개의 커맨드에 뷰모델베이스에 있는 커맨드를 연결한다.

 

    public class MainPageVM : ViewModelBase
    {
        public MainPageVM()
        {
            Title = "Hello World First page";

            FrameHelper.Instance.NavigatingCommand = NavigatingCommand;
            FrameHelper.Instance.NavigatedCommand = NavigatedCommand;
        }

        /// <summary>
        /// Navigating
        /// </summary>
        /// <param name="args"></param>
        protected override void Navigating(object args)
        {
            System.Diagnostics.Debug.WriteLine(Title + " Navigating");
        }

        /// <summary>
        /// Navigated
        /// </summary>
        /// <param name="arg"></param>
        protected override void Navigated(object arg)
        {
            System.Diagnostics.Debug.WriteLine(Title + " Navigated");
        }

        private RelayCommand _nextPageCommand;
        /// <summary>
        /// 다음 페이지로 이동
        /// </summary>
        public RelayCommand NextPageCommand
        {
            get { return _nextPageCommand = _nextPageCommand ?? new RelayCommand(
                args =>
                {
                    if (FrameHelper.Instance.Navigation(typeof (SecondPage), null) == false)
                    {
                        var msg = new MessageDialog("Error");
                        msg.ShowAsync();
                    };
                }); }
        }

    }
}

 

5. MainPage.xaml.cs

 

앱에서 사용하는 네비게이션 방식이 Type네비게이션이므로, 페이지로 네비게이션이나 네비게이션백이 되었을 때 항상 새로 페이지를 생성한다.

 

    public sealed partial class MainPage : Page
    {
        public MainPageVM ViewModel
        {
            get { return DataContext as MainPageVM; }
            set { DataContext = value; }
        }

        public MainPage()
        {
            this.InitializeComponent();

            ViewModel = ViewModel ?? new MainPageVM();        //페이지가 생성되면 뷰모델도 생성한다. 다음에 개선할 부분이다.
        }
    }

 

6. MainPage.xaml

 

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
            <TextBlock Text="{Binding Title}" HorizontalAlignment="Center" VerticalAlignment="Center"
       Style="{StaticResource HeaderTextBlockStyle}" FontFamily="Global User Interface" TextWrapping="Wrap"/>

            <Button Content="Navigation NextPage" HorizontalAlignment="Center" Margin="0,20,0,0" Command="{Binding NextPageCommand, Mode=OneWay}"/>
        </StackPanel>
    </Grid>

 

첫번째 페이지의 수정이 완료되었다. 두번째 페이지에 대한 작업도 진행해보자

 

7. SecondPage.xaml

Page를 추가하고, 아래와 같이 작업한다. 첫번째 페이지와 크게 다르지 않다.

 

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
            <TextBlock Text="{Binding Title}" HorizontalAlignment="Center" VerticalAlignment="Center"
    Style="{StaticResource HeaderTextBlockStyle}" FontFamily="Global User Interface" TextWrapping="Wrap"/>
            <Button Content="Navigation Back" HorizontalAlignment="Center" Margin="0,20,0,0" Command="{Binding GoBackCommand, Mode=OneWay}"/>
        </StackPanel>
    </Grid>

 

8. SecondPage.xaml.cs

    public sealed partial class SecondPage : Page
    {
        public SecondPageVM ViewModel
        {
            get { return DataContext as SecondPageVM; }
            set { DataContext = value; }
        }

        public SecondPage()
        {
            this.InitializeComponent();

            ViewModel = ViewModel ?? new SecondPageVM();
        }
    }

 

9. SecondPageVM.cs

 

    public class SecondPageVM : ViewModelBase
    {
        public SecondPageVM()
        {
            Title = "Hello World, Second page";

            FrameHelper.Instance.NavigatingCommand = NavigatingCommand;
            FrameHelper.Instance.NavigatedCommand = NavigatedCommand;

        }

        protected override void Navigating(object args)
        {
            System.Diagnostics.Debug.WriteLine(Title + " Navigating");
        }

        protected override void Navigated(object arg)
        {
            System.Diagnostics.Debug.WriteLine(Title + " Navigated");
        }

    }

 

10. 실행

 

 

 

 

* Debug - Output창 내용

Hello World Fist page Navigated

 

첫 페이지로 Navigated되어서 표시가 된다.

Navigation Next Page 버튼을 클릭해보자

 

 

* Debug - Output창 내용

Hello World Fist page Navigating
Hello World, Second page Navigated

 

첫번째 페이지에서 Navigating이 발생하고, 두번째 페이지에서 Navigated가 발생한다.

 

Navigation Back버튼을 클릭해보자

 

Hello World, Second page Navigating
Hello World Fist page Navigated

 

두번째 페이지에서 Navigating이 발생하고, 첫번째 페이지에서 Navigated가 발생한다. 이 경우 네비게이션 파라메터의 Mode는 Back이 된다.

 

폰 에물레이터에서도 확인 결과 동일한 동작을한다. 그런데 Back키를 누른 경우에 앱 전환이 일어나는데 이부분은 추가 작업이 필요한 것 같다.

 

11. 매우 간단한 작업이지만, 기본적인 내용이기 때문에 네비게이션의 동작 원리를 눈여겨서 보아야 한다.

 

12. 소스 

UniversalSample_20140519.zip

 

 

 

Posted by MVP kaki104

댓글을 달아 주세요

지난 Microsoft 컨퍼런스에서 발표한 Universal App에 대해서 내용 확인 하는 과정을 하나하나 정리해서 포스트를 할 예정인데, 우선 가장 처음으로 만드는 Hello World를 만들어보자.

 

참고

Building Apps for Windows Phone 8.1: (22) Universal Apps for Windows

http://channel9.msdn.com/Series/Building-Apps-for-Windows-Phone-8-1/22

 

1. 준비물

Visual Studio 2013 Update 2 RC

Hyper-V를 지원하는 PC or Windows Phone 8.1 device

Windows 8.1

 

2. 프로젝트 생성

File -> New -> Project -> Templates -> Visual C# -> Store Apps -> Universal Apps -> Blank App(Universal Apps)

Name : UniversalSample

OK

 

여기까지 작업을 하면 3개의 프로젝트가 생성된다.

UniversalSample.Windows (Windows 8.1)

UniversalSample.WindowsPhone (Windows Phone 8.1)

UniversalSample.Shared

각 프로젝트에 대해서는 대충 보면 사용용도를 알 수 있는데, xaml코드를 공유하는 방법에 대해서 살펴 보려고 한다.

 

3. UniversalSample.Windows (Windows 8.1)

MainPage.xaml을 열고 아래 코드를 추가한다.

 

...

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock Text="Hello World! Universal App" HorizontalAlignment="Center" VerticalAlignment="Center" Style="{StaticResource HeaderTextBlockStyle}" FontFamily="Global User Interface" />
    </Grid>
...

 

4. 앱을 실행하기 전에 솔루션에서 오늘쪽 마우스 버튼을 눌러서 프로퍼티 화면으로 이동한다.

 

 

Startup Project를 Crrent selection으로 변경한다. 그렇게하면 현재 선택한 프로젝트가 실행된다.

OK를 누른 후 F5를 눌러 실행한다.

 

 

일단 Windows 8.1 앱은 실행됐다. 그럼 이제 Windows Phone 8.1을 실행해보자. 아마 빈 화면이 보일 것이다. 이는 MainPage.xaml이 각 각의 프로젝트에 존재하기 때문에 서로 다른 MainPage.xaml을 실행하기 때문이다.

 

5. Shared 프로젝트 사용하기

Windows 8.1 프로젝트에 있던 MainPage.xaml을 솔루션 탐색기에서 자르기 한 후에 Shared 프로젝트에 붙여 넣고 Windows Phone 8.1 프로젝트에 있던 MainPage.xaml을 삭제한다.

 

최종 결과는 아래와 같다.

 

 

이렇게 수정 한 후 Windows Phone 8.1 프로젝트를 선택 하고 F5를 눌러서 실행한다.

 

 

 

Hello World라는 글씨가 보인다.

그래서, Shared 프로젝트에 xaml 코드는 2개의 프로젝트가 동시에 사용이 가능하다는 것을 알 수 있다.

 

6. 차이점에 대한 처리

그런데 윈8에서는 글씨가 전부 표시가 되었는데 윈폰8에서는 글씨가 오른쪽 왼쪽이 잘려서 보인다. 표시되는 영역 보다 내용이 더 클 경우에 TextBlock에는 TextWrapping이라는 프로퍼티에서 Wrap이라고 지정이 되어있으면 자동 줄 바꿈을 실행하고, NoWrap이라고 하면 자동 줄 바꿈을 실행하지 않는다.

 

 

위의 내용을 잘 보면 현재 화면은 윈8용 디자인 화면으로 TextBlock를 선택한 상태이다. 오른쪽 프로퍼티를 보면 TextWrapping이라는 프로퍼티가 Wrap으로 표시되어있다. 이렇게 되었다면, 윈폰8에서도 자동 줄 바꿈이 일어나야하는 것이 아닐까?라는 생각을 해봤지만 확인을 다시 해야한다.

 

왼쪽 상단 빨간 테두리에 있는 UniversalSample.Windows를 UniversalSample.WindowsPhone로 변경한다.

 

 

윈폰8 디자인 상태로 변경 후 TextBlock을 선택해 보면 TextWrapping이 NoWrap이라는 것을 확인 할 수 있다.

그래서 윈폰8일 때는 자동 줄 바꿈이 발생하지 않은 것이다.

 

이 TextBlock은 무조건 자동 줄 바꿈이 가능하도록 TextWrapping을 Wrap으로 변경한다.

최종 결과를 확인한다.

 

 

 

UniversalSample.zip

 

 

Posted by MVP kaki104

댓글을 달아 주세요

Microsoft에서 얼마전에 발표한 Windows Phone 8.1 app 개발 강좌 링크 공유

 

http://channel9.msdn.com/Series/Building-Apps-for-Windows-Phone-8-1

 

카테고리를 Windows Phone 8.1로 만들지 않고, Universal Apps로 만든 것은 앞으로 이 쪽으로 앱 개발 방향이 흘러갈 것 같아서 정하게 되었다. 아무래도 윈8.1이나 윈폰8.1 앱이나 어느 한가지만 만들기에는 조금 부족한 것 같기 때문이다.

 

얼마간은 유니버셜앱을 개발하는 방법에 대해서 연구를 좀 하고 연구 과정에 대해 포스트를 올리도록 하겠다.

 

Posted by MVP kaki104

댓글을 달아 주세요