티스토리 뷰

반응형

지난 포스트에 이어서 LayoutAwarePage.cs에 대해서 살펴 보도록 하겠다.

 

Grid App analyze Part 1 - Windows 8 Release Preview

Grid App analyze Part 2 - Windows 8 Release Preview - 현재

 

 

1. 정리

LayoutAwarePage.cs는 몇개의 구분을 가지는데, 기본부분, Navigation support, Visual state switching, Process lifetime management 정도가 되겠다.

 

기본부분 :

DefaultViewModel을 만드는 부분과, 기본 생성자가 포함된다.

 

Navigation support :

GoHome, GoBack, GoForward, CoreDispatcher_AcceleratorKeyActivated, CoreWindow_PointerPressed가 포함되며, 페이지 네비게이션 기능을 구현해 놓았다.

 

Visual state switching :

StartLayoutUpdates, WindowSizeChanged, StopLayoutUpdates, DetermineVisualState, InvalidateVisualState로 구성되며, 화면의 Visual state가 변경되었을 때 어떤 처리를 할 것인지가 정의되어 있다.

* 현재 RP 버전에서는 Visual State를 알기 위해서는, WindowSizeChanged 이벤트를 사용해야 한다. 

 

Process lifetime management :

OnNavigatedTo, OnNavigatedFrom, LoadState, SaveState로 구성되며, 각 페이지의 View State를 관리한다.

 

2. 전체 소스

namespace GridSampleApp.Common
{
    [Windows.Foundation.Metadata.WebHostHidden]
    public class LayoutAwarePage : Page
    {
        /// <summary>
        /// DefaultViewModel 프로퍼티를 추가
        /// </summary>
        public static readonly DependencyProperty DefaultViewModelProperty =
            DependencyProperty.Register("DefaultViewModel", typeof(IObservableMap<String, Object>),
            typeof(LayoutAwarePage), null);

 

        private List<Control> _layoutAwareControls;

 

        /// <summary>
        /// 기본 생성자
        /// </summary>
        public LayoutAwarePage()
        {
            if (Windows.ApplicationModel.DesignMode.DesignModeEnabled) return;

 

            // DefaultViewModel을 ObservableDictionary로 만들어 주는데 이 것을 사용하면

            // View에서 디자인 타임에서 ViewModel의 프로퍼티들을 바인딩을 하기가 어렵기 때문에 간단한 용도로만 사용한다.
            this.DefaultViewModel = new ObservableDictionary<String, Object>();

 

            // Page를 상속 받은 클래스이기 때문에 Loaded 이벤트를 지정할 수 있는데, 

            // 페이지의 비주얼 스테이트 맵을 만들어 주고, 키보드나 마우스 네비게이션 처리를 한다.
            this.Loaded += (sender, e) =>
            {

                // 비주얼 스테이트 관리 시작
                this.StartLayoutUpdates(sender, e);

 

                // 키보드와 마우스 이벤트 연결
                if (this.ActualHeight == Window.Current.Bounds.Height &&
                    this.ActualWidth == Window.Current.Bounds.Width)
                {
                    // Listen to the window directly so focus isn't required
                    Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated +=
                        CoreDispatcher_AcceleratorKeyActivated;
                    Window.Current.CoreWindow.PointerPressed +=
                        this.CoreWindow_PointerPressed;
                }
            };

            // 페이지 언로드시 연결 이벤트 해제 - 페이지 네비게이션 발생시 원래 페이지는 Unloaded 됨
            this.Unloaded += (sender, e) =>
            {

                // 비주얼 스테이트 관리 종료
                this.StopLayoutUpdates(sender, e);


                Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated -=
                    CoreDispatcher_AcceleratorKeyActivated;
                Window.Current.CoreWindow.PointerPressed -=
                    this.CoreWindow_PointerPressed;
            };
        }

 

        /// <summary>
        /// VS 2012에서 새로 추가된 것으로 http://msdn.microsoft.com/en-us/library/windows/apps/br226050.aspx 참조

        /// 딕셔너리의 확장판이라고 생각하면 될 것 같다.
        /// </summary>
        protected IObservableMap<String, Object> DefaultViewModel
        {
            get
            {
                return this.GetValue(DefaultViewModelProperty) as IObservableMap<String, Object>;
            }

            set
            {
                this.SetValue(DefaultViewModelProperty, value);
            }
        }

 

        #region Navigation support

        /// <summary>
        /// 이 부분은 네비게이션 지원 부분으로 GoHome, GoBack, GoForward 처리 부분과  OnNavigatedTo, OnNavigatedFrom가 연결되는 부분을 살펴 보도록 한다.

        /// </summary>
        protected virtual void GoHome(object sender, RoutedEventArgs e)
        {
            // Use the navigation frame to return to the topmost page
            if (this.Frame != null)
            {
                while (this.Frame.CanGoBack) this.Frame.GoBack();
            }
        }

        /// <summary>
        /// Invoked as an event handler to navigate backward in the navigation stack
        /// associated with this page's <see cref="Frame"/>.
        /// </summary>
        /// <param name="sender">Instance that triggered the event.</param>
        /// <param name="e">Event data describing the conditions that led to the
        /// event.</param>
        protected virtual void GoBack(object sender, RoutedEventArgs e)
        {
            // Use the navigation frame to return to the previous page
            if (this.Frame != null && this.Frame.CanGoBack) this.Frame.GoBack();
        }

        /// <summary>
        /// Invoked as an event handler to navigate forward in the navigation stack
        /// associated with this page's <see cref="Frame"/>.
        /// </summary>
        /// <param name="sender">Instance that triggered the event.</param>
        /// <param name="e">Event data describing the conditions that led to the
        /// event.</param>
        protected virtual void GoForward(object sender, RoutedEventArgs e)
        {
            // Use the navigation frame to move to the next page
            if (this.Frame != null && this.Frame.CanGoForward) this.Frame.GoForward();
        }

 

        /// <summary>
        /// Invoked on every keystroke, including system keys such as Alt key combinations, when
        /// this page is active and occupies the entire window.  Used to detect keyboard navigation
        /// between pages even when the page itself doesn't have focus.
        /// </summary>
        /// <param name="sender">Instance that triggered the event.</param>
        /// <param name="args">Event data describing the conditions that led to the event.</param>
        private void CoreDispatcher_AcceleratorKeyActivated(CoreDispatcher sender,
            AcceleratorKeyEventArgs args)
        {

            //중략
        }

        /// <summary>
        /// Invoked on every mouse click, touch screen tap, or equivalent interaction when this
        /// page is active and occupies the entire window.  Used to detect browser-style next and
        /// previous mouse button clicks to navigate between pages.
        /// </summary>
        /// <param name="sender">Instance that triggered the event.</param>
        /// <param name="args">Event data describing the conditions that led to the event.</param>
        private void CoreWindow_PointerPressed(CoreWindow sender,
            PointerEventArgs args)
        {

            //중략
        }

        #endregion

 

        #region Visual state switching

        /// <summary>

        /// 비주얼 스테이트 업데이트 시작
        /// </summary>
        public void StartLayoutUpdates(object sender, RoutedEventArgs e)
        {

            // sender는 Page이다.
            var control = sender as Control;
            if (control == null) return;
            // 윈도우 사이즈 체인지 이벤트를 연결하고, layoutAwareControls을 초기화 해준다.

            if (this._layoutAwareControls == null)
            {
                // Start listening to view state changes when there are controls interested in updates
                Window.Current.SizeChanged += this.WindowSizeChanged;
                this._layoutAwareControls = new List<Control>();
            }

            // 컨트롤 리스트에 페이지를 넣고
            this._layoutAwareControls.Add(control);

            // 처음 비주얼 스테이트를 변경한다.
            VisualStateManager.GoToState(control, DetermineVisualState(ApplicationView.Value), false);
        }

        //윈도우 사이즈 체인지 이벤트는 화면의 상태가 변경될 때 발생한다.(가로->세로, 전체->스냅드 등)

        private void WindowSizeChanged(object sender, WindowSizeChangedEventArgs e)
        {
            this.InvalidateVisualState();
        }

        /// <summary>

        /// 비주얼 스테이트 업데이트 종료 - Unloaded 이벤트에서 호출됨
        /// </summary>
        public void StopLayoutUpdates(object sender, RoutedEventArgs e)
        {
            var control = sender as Control;
            if (control == null || this._layoutAwareControls == null) return;
            this._layoutAwareControls.Remove(control);
            if (this._layoutAwareControls.Count == 0)
            {
                // Stop listening to view state changes when no controls are interested in updates
                this._layoutAwareControls = null;
                Window.Current.SizeChanged -= this.WindowSizeChanged;
            }
        }

        /// <summary>
        /// 현재 뷰 상태를 문자로 반환한다.
        /// </summary>
        protected virtual string DetermineVisualState(ApplicationViewState viewState)
        {
            return viewState.ToString();
        }

        /// <summary>
        /// 현재 뷰 상태를 컨트롤에 반영한다.
        /// visual state.
        /// </summary>
        public void InvalidateVisualState()
        {
            if (this._layoutAwareControls != null)
            {
                string visualState = DetermineVisualState(ApplicationView.Value);
                foreach (var layoutAwareControl in this._layoutAwareControls)
                {
                    VisualStateManager.GoToState(layoutAwareControl, visualState, false);
                }
            }
        }

        #endregion

 

        #region Process lifetime management

        //SuspensionManager(SM) : 셈플에서의 사용 용도는 각 페이지의 View State를 관리하는데 사용됨

 

        private String _pageKey;

        /// <summary>
        /// 네이게이션 목적지 페이지에서 발생하는 이벤트
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.  The Parameter
        /// property provides the group to be displayed.</param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            // 처음 페이지로 네비게이션이 되어서 들어오면 해당 페이지의 _pageKey를 만들어서 SuspensionManager(SM)에 페이지

            // 에대한 스테이트를 보관하는 곳을 만들어 놓는다.
            if (this._pageKey != null) return;

            var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
            this._pageKey = "Page-" + this.Frame.BackStackDepth;

            //네비게이션 모드가 신규라면

            if (e.NavigationMode == NavigationMode.New)
            {
                // 현재 페이지를 기준으로 페이지 상태를 정리한다.
                var nextPageKey = this._pageKey;
                int nextPageIndex = this.Frame.BackStackDepth;
                while (frameState.Remove(nextPageKey))
                {
                    nextPageIndex++;
                    nextPageKey = "Page-" + nextPageIndex;
                }

                // LoadState를 실행한다, e.Parameter는 네비게이션 파라메터
                this.LoadState(e.Parameter, null);
            }
            else
            {
                // 네비게이션 모드가 신규가 아니라면, 현재 페이지의 뷰 스테이트를 이용해서 화면을 복구 시킨다.

                // (Dictionary<String, Object>)frameState[this._pageKey] 는 현재 페이지의 저장된 뷰 스테이트
                this.LoadState(e.Parameter, (Dictionary<String, Object>)frameState[this._pageKey]);
            }
        }

        /// <summary>
        /// 네비게이션 출발지 페이지에서 발생하는 이벤트 - 페이지가 종료되기 전에 뷰 스테이트를 저장해 놓는다.
        /// </summary>
        protected override void OnNavigatedFrom(NavigationEventArgs e)
        {
            var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
            var pageState = new Dictionary<String, Object>();
            this.SaveState(pageState);
            frameState[_pageKey] = pageState;
        }

 

        /// <summary>
        /// 저장되어 있는 뷰 스테이트를 이용해서 화면을 복구 시킨다.
        /// 각 페이지 내부에서 override해서 사용한다.
        /// </summary>
        protected virtual void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
        {
        }

 

        /// <summary>
        /// 현재 페이지의 뷰 스테이트를 저장한다. 각 페이지 내부에서 override해서 사용한다.
        /// </summary>
        /// <param name="pageState">An empty dictionary to be populated with serializable state.</param>
        protected virtual void SaveState(Dictionary<String, Object> pageState)
        {
        }

        #endregion

 

        /// <summary>
        /// Implementation of IObservableMap that supports reentrancy for use as a default view
        /// model.
        /// </summary>
        private class ObservableDictionary<K, V> : IObservableMap<K, V>
        {

            // 중략
        }
    }
}

 

3. GroupedItemsPage 네비게이션 프로세스

 

1) App.xaml.cs 에서 네비게이션 시작

if (!rootFrame.Navigate(typeof(GroupedItemsPage), "AllGroups"))
{
    throw new Exception("Failed to create initial page");
}

 

2) protected override void OnNavigatedTo(NavigationEventArgs e)

처음으로 들어오는 것이니 _pageKey = "Page-0"를 만들고, LoadState 호출(네비게이션 파라메터 : AllGroups)

 

3) protected virtual void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)

각 페이지에 override되어 있는 LoadState를 호출하게 됨

네비게이션 파라메터는 : AllGroups, pageState : null

 

4. ItemDetailPage 네비게이션 프로세스

 

1) GroupedItemsPage.xaml.cs -> ItemView_ItemClick

 

var itemId = ((SampleDataItem)e.ClickedItem).UniqueId;

//네비게이션 파라메터로 선택된 아이템의 UniqueId를 입력
this.Frame.Navigate(typeof(ItemDetailPage), itemId);

 

2) LayoutAwarePage.cs -> OnNavigatedFrom

 

protected override void OnNavigatedFrom(NavigationEventArgs e)
{

    //현재 프래임의 상태를 저장하는 프래임 스테이트를 조회
    var frameState = SuspensionManager.SessionStateForFrame(this.Frame);

    //페이지 스테이트를 새로 만들고
    var pageState = new Dictionary<String, Object>();

    //페이지 스테이트를 저장하는 메소드 호출(이 메소드는 각 페이지에서 오버라이드해서 사용한다)
    this.SaveState(pageState);

    //페이지 스테이트를 프래임 스테이트에 저장
    frameState[_pageKey] = pageState;
}

 

3) LayoutAwarePage.cs -> OnNavigatedTo

4) ItemDetailPage.xaml -> LoadState

 

5. 마무리

 

위에서 각 페이지 스테이트를 저장한 내용은 GoBack이 호출되었을 때 LoadState 메소드에서 사용하게 된다.

 

메트로 스타일 앱에서는 클릭 할 때 마다 계속 네비게이션이 발생하는 구조를 지향하고 있다. 하지만, 반드시 그렇게 만들어야 하는 것은 아니니 개발시 참고 사항으로 알면 될 것 같다.

 

다음 포스트는 각 페이지에서 어떻게 데이터를 보여주는지를 알아 보도록 하겠다.

반응형
댓글