//********************************************************* // // Copyright (c) Microsoft. All rights reserved. // THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY // IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR // PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. // //********************************************************* using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using Windows.Foundation; using Windows.Foundation.Collections; using Windows.UI.ViewManagement; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; namespace SDKTemplate.Common { /// <summary> /// Typical implementation of Page that provides several important conveniences: /// application view state to visual state mapping, GoBack and GoHome event handlers, and /// a default view model. /// </summary> [Windows.Foundation.Metadata.WebHostHidden] public class LayoutAwarePage : Page { /// <summary> /// Identifies the <see cref="DefaultViewModel"/> dependency property. /// </summary> public static readonly DependencyProperty DefaultViewModelProperty = DependencyProperty.Register("DefaultViewModel", typeof(IObservableMap<String, Object>), typeof(LayoutAwarePage), null); private List<Control> _layoutAwareControls; /// <summary> /// Initializes a new instance of the <see cref="LayoutAwarePage"/> class. /// </summary> public LayoutAwarePage() { if (Windows.ApplicationModel.DesignMode.DesignModeEnabled) return; // Create an empty default view model this.DefaultViewModel = new ObservableDictionary<String, Object>(); // Map application view state to visual state for this page when it is part of the visual tree this.Loaded += this.StartLayoutUpdates; this.Unloaded += this.StopLayoutUpdates; } /// <summary> /// An implementation of <see cref="IObservableMap<String, Object>"/> designed to be /// used as a trivial view model. /// </summary> protected IObservableMap<String, Object> DefaultViewModel { get { return this.GetValue(DefaultViewModelProperty) as IObservableMap<String, Object>; } set { this.SetValue(DefaultViewModelProperty, value); } } /// <summary> /// Invoked as an event handler to navigate backward in the page's associated /// <see cref="Frame"/> until it reaches the top of the navigation stack. /// </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 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 page's associated /// <see cref="Frame"/> to go back one step on the navigation stack. /// </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, typically on the <see cref="Loaded"/> event of a /// <see cref="Control"/> within the page, to indicate that the sender should start /// receiving visual state management changes that correspond to application view state /// changes. /// </summary> /// <param name="sender">Instance of <see cref="Control"/> that supports visual state /// management corresponding to view states.</param> /// <param name="e">Event data that describes how the request was made.</param> /// <remarks>The current view state will immediately be used to set the corresponding /// visual state when layout updates are requested. A corresponding /// <see cref="Unloaded"/> event handler connected to <see cref="StopLayoutUpdates"/> /// is strongly encouraged. Instances of <see cref="LayoutAwarePage"/> automatically /// invoke these handlers in their Loaded and Unloaded events.</remarks> /// <seealso cref="DetermineVisualState"/> /// <seealso cref="InvalidateVisualState"/> public void StartLayoutUpdates(object sender, RoutedEventArgs e) { var control = sender as Control; if (control == null) return; 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); // Set the initial visual state of the control VisualStateManager.GoToState(control, DetermineVisualState(ApplicationView.Value), false); } private void WindowSizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e) { this.InvalidateVisualState(); } /// <summary> /// Invoked as an event handler, typically on the <see cref="Unloaded"/> event of a /// <see cref="Control"/>, to indicate that the sender should start receiving visual /// state management changes that correspond to application view state changes. /// </summary> /// <param name="sender">Instance of <see cref="Control"/> that supports visual state /// management corresponding to view states.</param> /// <param name="e">Event data that describes how the request was made.</param> /// <remarks>The current view state will immediately be used to set the corresponding /// visual state when layout updates are requested.</remarks> /// <seealso cref="StartLayoutUpdates"/> 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> /// Translates <see cref="ApplicationViewState"/> values into strings for visual state /// management within the page. The default implementation uses the names of enum values. /// Subclasses may override this method to control the mapping scheme used. /// </summary> /// <param name="viewState">View state for which a visual state is desired.</param> /// <returns>Visual state name used to drive the /// <see cref="VisualStateManager"/></returns> /// <seealso cref="InvalidateVisualState"/> protected virtual string DetermineVisualState(ApplicationViewState viewState) { return viewState.ToString(); } /// <summary> /// Updates all controls that are listening for visual state changes with the correct /// visual state. /// </summary> /// <remarks> /// Typically used in conjunction with overriding <see cref="DetermineVisualState"/> to /// signal that a different value may be returned even though the view state has not /// changed. /// </remarks> public void InvalidateVisualState() { if (this._layoutAwareControls != null) { string visualState = DetermineVisualState(ApplicationView.Value); foreach (var layoutAwareControl in this._layoutAwareControls) { VisualStateManager.GoToState(layoutAwareControl, visualState, false); } } } /// <summary> /// Implementation of IObservableMap that supports reentrancy for use as a default view /// model. /// </summary> private class ObservableDictionary<K, V> : IObservableMap<K, V> { private class ObservableDictionaryChangedEventArgs : IMapChangedEventArgs<K> { public ObservableDictionaryChangedEventArgs(CollectionChange change, K key) { this.CollectionChange = change; this.Key = key; } public CollectionChange CollectionChange { get; private set; } public K Key { get; private set; } } private Dictionary<K, V> _dictionary = new Dictionary<K, V>(); public event MapChangedEventHandler<K, V> MapChanged; private void InvokeMapChanged(CollectionChange change, K key) { var eventHandler = MapChanged; if (eventHandler != null) { eventHandler(this, new ObservableDictionaryChangedEventArgs(CollectionChange.ItemInserted, key)); } } public void Add(K key, V value) { this._dictionary.Add(key, value); this.InvokeMapChanged(CollectionChange.ItemInserted, key); } public void Add(KeyValuePair<K, V> item) { this.Add(item.Key, item.Value); } public bool Remove(K key) { if (this._dictionary.Remove(key)) { this.InvokeMapChanged(CollectionChange.ItemRemoved, key); return true; } return false; } public bool Remove(KeyValuePair<K, V> item) { V currentValue; if (this._dictionary.TryGetValue(item.Key, out currentValue) && Object.Equals(item.Value, currentValue) && this._dictionary.Remove(item.Key)) { this.InvokeMapChanged(CollectionChange.ItemRemoved, item.Key); return true; } return false; } public V this[K key] { get { return this._dictionary[key]; } set { this._dictionary[key] = value; this.InvokeMapChanged(CollectionChange.ItemChanged, key); } } public void Clear() { var priorKeys = this._dictionary.Keys.ToArray(); this._dictionary.Clear(); foreach (var key in priorKeys) { this.InvokeMapChanged(CollectionChange.ItemRemoved, key); } } public ICollection<K> Keys { get { return this._dictionary.Keys; } } public bool ContainsKey(K key) { return this._dictionary.ContainsKey(key); } public bool TryGetValue(K key, out V value) { return this._dictionary.TryGetValue(key, out value); } public ICollection<V> Values { get { return this._dictionary.Values; } } public bool Contains(KeyValuePair<K, V> item) { return this._dictionary.Contains(item); } public int Count { get { return this._dictionary.Count; } } public bool IsReadOnly { get { return false; } } public IEnumerator<KeyValuePair<K, V>> GetEnumerator() { return this._dictionary.GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this._dictionary.GetEnumerator(); } public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex) { int arraySize = array.Length; foreach (var pair in this._dictionary) { if (arrayIndex >= arraySize) break; array[arrayIndex++] = pair; } } } } }