Advertisement
garfbradaz

LayoutAwarePage Code

Nov 19th, 2012
782
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 22.78 KB | None | 0 0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Linq;
  5. using Windows.Foundation;
  6. using Windows.Foundation.Collections;
  7. using Windows.System;
  8. using Windows.UI.Core;
  9. using Windows.UI.ViewManagement;
  10. using Windows.UI.Xaml;
  11. using Windows.UI.Xaml.Controls;
  12. using Windows.UI.Xaml.Navigation;
  13.  
  14. namespace Accountable.Common
  15. {
  16.     /// <summary>
  17.     /// Typical implementation of Page that provides several important conveniences:
  18.     /// <list type="bullet">
  19.     /// <item>
  20.     /// <description>Application view state to visual state mapping</description>
  21.     /// </item>
  22.     /// <item>
  23.     /// <description>GoBack, GoForward, and GoHome event handlers</description>
  24.     /// </item>
  25.     /// <item>
  26.     /// <description>Mouse and keyboard shortcuts for navigation</description>
  27.     /// </item>
  28.     /// <item>
  29.     /// <description>State management for navigation and process lifetime management</description>
  30.     /// </item>
  31.     /// <item>
  32.     /// <description>A default view model</description>
  33.     /// </item>
  34.     /// </list>
  35.     /// </summary>
  36.     [Windows.Foundation.Metadata.WebHostHidden]
  37.     public class LayoutAwarePage : Page
  38.     {
  39.         /// <summary>
  40.         /// Identifies the <see cref="DefaultViewModel"/> dependency property.
  41.         /// </summary>
  42.         public static readonly DependencyProperty DefaultViewModelProperty =
  43.             DependencyProperty.Register("DefaultViewModel", typeof(IObservableMap<String, Object>),
  44.             typeof(LayoutAwarePage), null);
  45.  
  46.         private List<Control> _layoutAwareControls;
  47.  
  48.         /// <summary>
  49.         /// Initializes a new instance of the <see cref="LayoutAwarePage"/> class.
  50.         /// </summary>
  51.         public LayoutAwarePage()
  52.         {
  53.             if (Windows.ApplicationModel.DesignMode.DesignModeEnabled) return;
  54.  
  55.             // Create an empty default view model
  56.             this.DefaultViewModel = new ObservableDictionary<String, Object>();
  57.  
  58.             // When this page is part of the visual tree make two changes:
  59.             // 1) Map application view state to visual state for the page
  60.             // 2) Handle keyboard and mouse navigation requests
  61.             this.Loaded += (sender, e) =>
  62.             {
  63.                 this.StartLayoutUpdates(sender, e);
  64.  
  65.                 // Keyboard and mouse navigation only apply when occupying the entire window
  66.                 if (this.ActualHeight == Window.Current.Bounds.Height &&
  67.                     this.ActualWidth == Window.Current.Bounds.Width)
  68.                 {
  69.                     // Listen to the window directly so focus isn't required
  70.                     Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated +=
  71.                         CoreDispatcher_AcceleratorKeyActivated;
  72.                     Window.Current.CoreWindow.PointerPressed +=
  73.                         this.CoreWindow_PointerPressed;
  74.                 }
  75.             };
  76.  
  77.             // Undo the same changes when the page is no longer visible
  78.             this.Unloaded += (sender, e) =>
  79.             {
  80.                 this.StopLayoutUpdates(sender, e);
  81.                 Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated -=
  82.                     CoreDispatcher_AcceleratorKeyActivated;
  83.                 Window.Current.CoreWindow.PointerPressed -=
  84.                     this.CoreWindow_PointerPressed;
  85.             };
  86.         }
  87.  
  88.         /// <summary>
  89.         /// An implementation of <see cref="IObservableMap&lt;String, Object&gt;"/> designed to be
  90.         /// used as a trivial view model.
  91.         /// </summary>
  92.         protected IObservableMap<String, Object> DefaultViewModel
  93.         {
  94.             get
  95.             {
  96.                 return this.GetValue(DefaultViewModelProperty) as IObservableMap<String, Object>;
  97.             }
  98.  
  99.             set
  100.             {
  101.                 this.SetValue(DefaultViewModelProperty, value);
  102.             }
  103.         }
  104.  
  105.         #region Navigation support
  106.  
  107.         /// <summary>
  108.         /// Invoked as an event handler to navigate backward in the page's associated
  109.         /// <see cref="Frame"/> until it reaches the top of the navigation stack.
  110.         /// </summary>
  111.         /// <param name="sender">Instance that triggered the event.</param>
  112.         /// <param name="e">Event data describing the conditions that led to the event.</param>
  113.         protected virtual void GoHome(object sender, RoutedEventArgs e)
  114.         {
  115.             // Use the navigation frame to return to the topmost page
  116.             if (this.Frame != null)
  117.             {
  118.                 while (this.Frame.CanGoBack) this.Frame.GoBack();
  119.             }
  120.         }
  121.  
  122.         /// <summary>
  123.         /// Invoked as an event handler to navigate backward in the navigation stack
  124.         /// associated with this page's <see cref="Frame"/>.
  125.         /// </summary>
  126.         /// <param name="sender">Instance that triggered the event.</param>
  127.         /// <param name="e">Event data describing the conditions that led to the
  128.         /// event.</param>
  129.         protected virtual void GoBack(object sender, RoutedEventArgs e)
  130.         {
  131.             // Use the navigation frame to return to the previous page
  132.             if (this.Frame != null && this.Frame.CanGoBack) this.Frame.GoBack();
  133.         }
  134.  
  135.         /// <summary>
  136.         /// Invoked as an event handler to navigate forward in the navigation stack
  137.         /// associated with this page's <see cref="Frame"/>.
  138.         /// </summary>
  139.         /// <param name="sender">Instance that triggered the event.</param>
  140.         /// <param name="e">Event data describing the conditions that led to the
  141.         /// event.</param>
  142.         protected virtual void GoForward(object sender, RoutedEventArgs e)
  143.         {
  144.             // Use the navigation frame to move to the next page
  145.             if (this.Frame != null && this.Frame.CanGoForward) this.Frame.GoForward();
  146.         }
  147.  
  148.         /// <summary>
  149.         /// Invoked on every keystroke, including system keys such as Alt key combinations, when
  150.         /// this page is active and occupies the entire window.  Used to detect keyboard navigation
  151.         /// between pages even when the page itself doesn't have focus.
  152.         /// </summary>
  153.         /// <param name="sender">Instance that triggered the event.</param>
  154.         /// <param name="args">Event data describing the conditions that led to the event.</param>
  155.         private void CoreDispatcher_AcceleratorKeyActivated(CoreDispatcher sender,
  156.             AcceleratorKeyEventArgs args)
  157.         {
  158.             var virtualKey = args.VirtualKey;
  159.  
  160.             // Only investigate further when Left, Right, or the dedicated Previous or Next keys
  161.             // are pressed
  162.             if ((args.EventType == CoreAcceleratorKeyEventType.SystemKeyDown ||
  163.                 args.EventType == CoreAcceleratorKeyEventType.KeyDown) &&
  164.                 (virtualKey == VirtualKey.Left || virtualKey == VirtualKey.Right ||
  165.                 (int)virtualKey == 166 || (int)virtualKey == 167))
  166.             {
  167.                 var coreWindow = Window.Current.CoreWindow;
  168.                 var downState = CoreVirtualKeyStates.Down;
  169.                 bool menuKey = (coreWindow.GetKeyState(VirtualKey.Menu) & downState) == downState;
  170.                 bool controlKey = (coreWindow.GetKeyState(VirtualKey.Control) & downState) == downState;
  171.                 bool shiftKey = (coreWindow.GetKeyState(VirtualKey.Shift) & downState) == downState;
  172.                 bool noModifiers = !menuKey && !controlKey && !shiftKey;
  173.                 bool onlyAlt = menuKey && !controlKey && !shiftKey;
  174.  
  175.                 if (((int)virtualKey == 166 && noModifiers) ||
  176.                     (virtualKey == VirtualKey.Left && onlyAlt))
  177.                 {
  178.                     // When the previous key or Alt+Left are pressed navigate back
  179.                     args.Handled = true;
  180.                     this.GoBack(this, new RoutedEventArgs());
  181.                 }
  182.                 else if (((int)virtualKey == 167 && noModifiers) ||
  183.                     (virtualKey == VirtualKey.Right && onlyAlt))
  184.                 {
  185.                     // When the next key or Alt+Right are pressed navigate forward
  186.                     args.Handled = true;
  187.                     this.GoForward(this, new RoutedEventArgs());
  188.                 }
  189.             }
  190.         }
  191.  
  192.         /// <summary>
  193.         /// Invoked on every mouse click, touch screen tap, or equivalent interaction when this
  194.         /// page is active and occupies the entire window.  Used to detect browser-style next and
  195.         /// previous mouse button clicks to navigate between pages.
  196.         /// </summary>
  197.         /// <param name="sender">Instance that triggered the event.</param>
  198.         /// <param name="args">Event data describing the conditions that led to the event.</param>
  199.         private void CoreWindow_PointerPressed(CoreWindow sender,
  200.             PointerEventArgs args)
  201.         {
  202.             var properties = args.CurrentPoint.Properties;
  203.  
  204.             // Ignore button chords with the left, right, and middle buttons
  205.             if (properties.IsLeftButtonPressed || properties.IsRightButtonPressed ||
  206.                 properties.IsMiddleButtonPressed) return;
  207.  
  208.             // If back or foward are pressed (but not both) navigate appropriately
  209.             bool backPressed = properties.IsXButton1Pressed;
  210.             bool forwardPressed = properties.IsXButton2Pressed;
  211.             if (backPressed ^ forwardPressed)
  212.             {
  213.                 args.Handled = true;
  214.                 if (backPressed) this.GoBack(this, new RoutedEventArgs());
  215.                 if (forwardPressed) this.GoForward(this, new RoutedEventArgs());
  216.             }
  217.         }
  218.  
  219.         #endregion
  220.  
  221.         #region Visual state switching
  222.  
  223.         /// <summary>
  224.         /// Invoked as an event handler, typically on the <see cref="FrameworkElement.Loaded"/>
  225.         /// event of a <see cref="Control"/> within the page, to indicate that the sender should
  226.         /// start receiving visual state management changes that correspond to application view
  227.         /// state changes.
  228.         /// </summary>
  229.         /// <param name="sender">Instance of <see cref="Control"/> that supports visual state
  230.         /// management corresponding to view states.</param>
  231.         /// <param name="e">Event data that describes how the request was made.</param>
  232.         /// <remarks>The current view state will immediately be used to set the corresponding
  233.         /// visual state when layout updates are requested.  A corresponding
  234.         /// <see cref="FrameworkElement.Unloaded"/> event handler connected to
  235.         /// <see cref="StopLayoutUpdates"/> is strongly encouraged.  Instances of
  236.         /// <see cref="LayoutAwarePage"/> automatically invoke these handlers in their Loaded and
  237.         /// Unloaded events.</remarks>
  238.         /// <seealso cref="DetermineVisualState"/>
  239.         /// <seealso cref="InvalidateVisualState"/>
  240.         public void StartLayoutUpdates(object sender, RoutedEventArgs e)
  241.         {
  242.             var control = sender as Control;
  243.             if (control == null) return;
  244.             if (this._layoutAwareControls == null)
  245.             {
  246.                 // Start listening to view state changes when there are controls interested in updates
  247.                 Window.Current.SizeChanged += this.WindowSizeChanged;
  248.                 this._layoutAwareControls = new List<Control>();
  249.             }
  250.             this._layoutAwareControls.Add(control);
  251.  
  252.             // Set the initial visual state of the control
  253.             VisualStateManager.GoToState(control, DetermineVisualState(ApplicationView.Value), false);
  254.         }
  255.  
  256.         private void WindowSizeChanged(object sender, WindowSizeChangedEventArgs e)
  257.         {
  258.             this.InvalidateVisualState();
  259.         }
  260.  
  261.         /// <summary>
  262.         /// Invoked as an event handler, typically on the <see cref="FrameworkElement.Unloaded"/>
  263.         /// event of a <see cref="Control"/>, to indicate that the sender should start receiving
  264.         /// visual state management changes that correspond to application view state changes.
  265.         /// </summary>
  266.         /// <param name="sender">Instance of <see cref="Control"/> that supports visual state
  267.         /// management corresponding to view states.</param>
  268.         /// <param name="e">Event data that describes how the request was made.</param>
  269.         /// <remarks>The current view state will immediately be used to set the corresponding
  270.         /// visual state when layout updates are requested.</remarks>
  271.         /// <seealso cref="StartLayoutUpdates"/>
  272.         public void StopLayoutUpdates(object sender, RoutedEventArgs e)
  273.         {
  274.             var control = sender as Control;
  275.             if (control == null || this._layoutAwareControls == null) return;
  276.             this._layoutAwareControls.Remove(control);
  277.             if (this._layoutAwareControls.Count == 0)
  278.             {
  279.                 // Stop listening to view state changes when no controls are interested in updates
  280.                 this._layoutAwareControls = null;
  281.                 Window.Current.SizeChanged -= this.WindowSizeChanged;
  282.             }
  283.         }
  284.  
  285.         /// <summary>
  286.         /// Translates <see cref="ApplicationViewState"/> values into strings for visual state
  287.         /// management within the page.  The default implementation uses the names of enum values.
  288.         /// Subclasses may override this method to control the mapping scheme used.
  289.         /// </summary>
  290.         /// <param name="viewState">View state for which a visual state is desired.</param>
  291.         /// <returns>Visual state name used to drive the
  292.         /// <see cref="VisualStateManager"/></returns>
  293.         /// <seealso cref="InvalidateVisualState"/>
  294.         protected virtual string DetermineVisualState(ApplicationViewState viewState)
  295.         {
  296.             return viewState.ToString();
  297.         }
  298.  
  299.         /// <summary>
  300.         /// Updates all controls that are listening for visual state changes with the correct
  301.         /// visual state.
  302.         /// </summary>
  303.         /// <remarks>
  304.         /// Typically used in conjunction with overriding <see cref="DetermineVisualState"/> to
  305.         /// signal that a different value may be returned even though the view state has not
  306.         /// changed.
  307.         /// </remarks>
  308.         public void InvalidateVisualState()
  309.         {
  310.             if (this._layoutAwareControls != null)
  311.             {
  312.                 string visualState = DetermineVisualState(ApplicationView.Value);
  313.                 foreach (var layoutAwareControl in this._layoutAwareControls)
  314.                 {
  315.                     VisualStateManager.GoToState(layoutAwareControl, visualState, false);
  316.                 }
  317.             }
  318.         }
  319.  
  320.         #endregion
  321.  
  322.         #region Process lifetime management
  323.  
  324.         private String _pageKey;
  325.  
  326.         /// <summary>
  327.         /// Invoked when this page is about to be displayed in a Frame.
  328.         /// </summary>
  329.         /// <param name="e">Event data that describes how this page was reached.  The Parameter
  330.         /// property provides the group to be displayed.</param>
  331.         protected override void OnNavigatedTo(NavigationEventArgs e)
  332.         {
  333.             // Returning to a cached page through navigation shouldn't trigger state loading
  334.             if (this._pageKey != null) return;
  335.  
  336.             var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
  337.             this._pageKey = "Page-" + this.Frame.BackStackDepth;
  338.  
  339.             if (e.NavigationMode == NavigationMode.New)
  340.             {
  341.                 // Clear existing state for forward navigation when adding a new page to the
  342.                 // navigation stack
  343.                 var nextPageKey = this._pageKey;
  344.                 int nextPageIndex = this.Frame.BackStackDepth;
  345.                 while (frameState.Remove(nextPageKey))
  346.                 {
  347.                     nextPageIndex++;
  348.                     nextPageKey = "Page-" + nextPageIndex;
  349.                 }
  350.  
  351.                 // Pass the navigation parameter to the new page
  352.                 this.LoadState(e.Parameter, null);
  353.             }
  354.             else
  355.             {
  356.                 // Pass the navigation parameter and preserved page state to the page, using
  357.                 // the same strategy for loading suspended state and recreating pages discarded
  358.                 // from cache
  359.                 this.LoadState(e.Parameter, (Dictionary<String, Object>)frameState[this._pageKey]);
  360.             }
  361.         }
  362.  
  363.         /// <summary>
  364.         /// Invoked when this page will no longer be displayed in a Frame.
  365.         /// </summary>
  366.         /// <param name="e">Event data that describes how this page was reached.  The Parameter
  367.         /// property provides the group to be displayed.</param>
  368.         protected override void OnNavigatedFrom(NavigationEventArgs e)
  369.         {
  370.             var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
  371.             var pageState = new Dictionary<String, Object>();
  372.             this.SaveState(pageState);
  373.             frameState[_pageKey] = pageState;
  374.         }
  375.  
  376.         /// <summary>
  377.         /// Populates the page with content passed during navigation.  Any saved state is also
  378.         /// provided when recreating a page from a prior session.
  379.         /// </summary>
  380.         /// <param name="navigationParameter">The parameter value passed to
  381.         /// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested.
  382.         /// </param>
  383.         /// <param name="pageState">A dictionary of state preserved by this page during an earlier
  384.         /// session.  This will be null the first time a page is visited.</param>
  385.         protected virtual void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
  386.         {
  387.         }
  388.  
  389.         /// <summary>
  390.         /// Preserves state associated with this page in case the application is suspended or the
  391.         /// page is discarded from the navigation cache.  Values must conform to the serialization
  392.         /// requirements of <see cref="SuspensionManager.SessionState"/>.
  393.         /// </summary>
  394.         /// <param name="pageState">An empty dictionary to be populated with serializable state.</param>
  395.         protected virtual void SaveState(Dictionary<String, Object> pageState)
  396.         {
  397.         }
  398.  
  399.         #endregion
  400.  
  401.         /// <summary>
  402.         /// Implementation of IObservableMap that supports reentrancy for use as a default view
  403.         /// model.
  404.         /// </summary>
  405.         private class ObservableDictionary<K, V> : IObservableMap<K, V>
  406.         {
  407.             private class ObservableDictionaryChangedEventArgs : IMapChangedEventArgs<K>
  408.             {
  409.                 public ObservableDictionaryChangedEventArgs(CollectionChange change, K key)
  410.                 {
  411.                     this.CollectionChange = change;
  412.                     this.Key = key;
  413.                 }
  414.  
  415.                 public CollectionChange CollectionChange { get; private set; }
  416.                 public K Key { get; private set; }
  417.             }
  418.  
  419.             private Dictionary<K, V> _dictionary = new Dictionary<K, V>();
  420.             public event MapChangedEventHandler<K, V> MapChanged;
  421.  
  422.             private void InvokeMapChanged(CollectionChange change, K key)
  423.             {
  424.                 var eventHandler = MapChanged;
  425.                 if (eventHandler != null)
  426.                 {
  427.                     eventHandler(this, new ObservableDictionaryChangedEventArgs(change, key));
  428.                 }
  429.             }
  430.  
  431.             public void Add(K key, V value)
  432.             {
  433.                 this._dictionary.Add(key, value);
  434.                 this.InvokeMapChanged(CollectionChange.ItemInserted, key);
  435.             }
  436.  
  437.             public void Add(KeyValuePair<K, V> item)
  438.             {
  439.                 this.Add(item.Key, item.Value);
  440.             }
  441.  
  442.             public bool Remove(K key)
  443.             {
  444.                 if (this._dictionary.Remove(key))
  445.                 {
  446.                     this.InvokeMapChanged(CollectionChange.ItemRemoved, key);
  447.                     return true;
  448.                 }
  449.                 return false;
  450.             }
  451.  
  452.             public bool Remove(KeyValuePair<K, V> item)
  453.             {
  454.                 V currentValue;
  455.                 if (this._dictionary.TryGetValue(item.Key, out currentValue) &&
  456.                     Object.Equals(item.Value, currentValue) && this._dictionary.Remove(item.Key))
  457.                 {
  458.                     this.InvokeMapChanged(CollectionChange.ItemRemoved, item.Key);
  459.                     return true;
  460.                 }
  461.                 return false;
  462.             }
  463.  
  464.             public V this[K key]
  465.             {
  466.                 get
  467.                 {
  468.                     return this._dictionary[key];
  469.                 }
  470.                 set
  471.                 {
  472.                     this._dictionary[key] = value;
  473.                     this.InvokeMapChanged(CollectionChange.ItemChanged, key);
  474.                 }
  475.             }
  476.  
  477.             public void Clear()
  478.             {
  479.                 var priorKeys = this._dictionary.Keys.ToArray();
  480.                 this._dictionary.Clear();
  481.                 foreach (var key in priorKeys)
  482.                 {
  483.                     this.InvokeMapChanged(CollectionChange.ItemRemoved, key);
  484.                 }
  485.             }
  486.  
  487.             public ICollection<K> Keys
  488.             {
  489.                 get { return this._dictionary.Keys; }
  490.             }
  491.  
  492.             public bool ContainsKey(K key)
  493.             {
  494.                 return this._dictionary.ContainsKey(key);
  495.             }
  496.  
  497.             public bool TryGetValue(K key, out V value)
  498.             {
  499.                 return this._dictionary.TryGetValue(key, out value);
  500.             }
  501.  
  502.             public ICollection<V> Values
  503.             {
  504.                 get { return this._dictionary.Values; }
  505.             }
  506.  
  507.             public bool Contains(KeyValuePair<K, V> item)
  508.             {
  509.                 return this._dictionary.Contains(item);
  510.             }
  511.  
  512.             public int Count
  513.             {
  514.                 get { return this._dictionary.Count; }
  515.             }
  516.  
  517.             public bool IsReadOnly
  518.             {
  519.                 get { return false; }
  520.             }
  521.  
  522.             public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
  523.             {
  524.                 return this._dictionary.GetEnumerator();
  525.             }
  526.  
  527.             System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  528.             {
  529.                 return this._dictionary.GetEnumerator();
  530.             }
  531.  
  532.             public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex)
  533.             {
  534.                 int arraySize = array.Length;
  535.                 foreach (var pair in this._dictionary)
  536.                 {
  537.                     if (arrayIndex >= arraySize) break;
  538.                     array[arrayIndex++] = pair;
  539.                 }
  540.             }
  541.         }
  542.     }
  543. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement