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 PuzzleMetro.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
{
private List<Control> _layoutAwareControls;
private IObservableMap<String, Object> _defaultViewModel = new ObservableDictionary<String, Object>();
private bool _useFilledStateForNarrowWindow = false;
/// <summary>
/// Initializes a new instance of the <see cref="LayoutAwarePage"/> class.
/// </summary>
public LayoutAwarePage()
{
if (Windows.ApplicationModel.DesignMode.DesignModeEnabled) return;
// 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;
// Establish the default view model as the initial DataContext
this.DataContext = _defaultViewModel;
}
/// <summary>
/// Gets an implementation of <see cref="IObservableMap<String, Object>"/> set as the
/// page's default <see cref="DataContext"/>. This instance can be bound and surfaces
/// property change notifications making it suitable for use as a trivial view model.
/// </summary>
protected IObservableMap<String, Object> DefaultViewModel
{
get
{
return _defaultViewModel;
}
}
/// <summary>
/// Gets or sets a value indicating whether visual states can be a loose interpretation
/// of the actual application view state. This is often convenient when a page layout
/// is space constrained.
/// </summary>
/// <remarks>
/// The default value of false indicates that the visual state is identical to the view
/// state, meaning that Filled is only used when another application is snapped. When
/// set to true FullScreenLandscape is used to indicate that at least 1366 virtual
/// pixels of horizontal real estate are available - even if another application is
/// snapped - and Filled indicates a lesser width, even if no other application is
/// snapped. On a smaller display such as a 1024x768 panel this will result in the
/// visual state Filled whenever the device is in landscape orientation.
/// </remarks>
public bool UseFilledStateForNarrowWindow
{
get
{
return _useFilledStateForNarrowWindow;
}
set
{
_useFilledStateForNarrowWindow = value;
this.InvalidateVisualState();
}
}
/// <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
ApplicationView.GetForCurrentView().ViewStateChanged += this.ViewStateChanged;
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 ViewStateChanged(ApplicationView sender, ApplicationViewStateChangedEventArgs e)
{
this.InvalidateVisualState(e.ViewState);
}
private void WindowSizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
{
if (this._useFilledStateForNarrowWindow) 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;
ApplicationView.GetForCurrentView().ViewStateChanged -= this.ViewStateChanged;
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)
{
if (this._useFilledStateForNarrowWindow &&
(viewState == ApplicationViewState.Filled ||
viewState == ApplicationViewState.FullScreenLandscape))
{
// Allow pages to request that the Filled state be used only for landscape layouts narrower
// than 1366 virtual pixels
var windowWidth = Window.Current.Bounds.Width;
viewState = windowWidth >= 1366 ? ApplicationViewState.FullScreenLandscape : ApplicationViewState.Filled;
}
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>
/// <param name="viewState">The desired view state, or null if the current view state
/// should be used.</param>
public void InvalidateVisualState(ApplicationViewState? viewState = null)
{
if (this._layoutAwareControls != null)
{
string visualState = DetermineVisualState(viewState == null ? ApplicationView.Value : viewState.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;
}
}
}
}
}