PageRenderTime 53ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/BE2012/Common/LayoutAwarePage.cs

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