Permalink
Browse files

Made some progress, we can load the main view

  • Loading branch information...
1 parent 63bc6eb commit e33d05af346bae99781b3b537df872ad03264824 @paulcbetts paulcbetts committed Sep 9, 2012
Showing with 1,361 additions and 6 deletions.
  1. 0 play-win8/{Fonts → Assets}/OpenSans-Italic.ttf
  2. 0 play-win8/{Fonts → Assets}/OpenSans-LightItalic.ttf
  3. 0 play-win8/{Fonts → Assets}/OpenSans-Regular.ttf
  4. 0 play-win8/{Fonts → Assets}/OpenSans-Semibold.ttf
  5. 0 play-win8/{Fonts → Assets}/Roboto-Black.ttf
  6. 0 play-win8/{Fonts → Assets}/Roboto-BlackItalic.ttf
  7. 0 play-win8/{Fonts → Assets}/Roboto-Bold.ttf
  8. 0 play-win8/{Fonts → Assets}/Roboto-BoldCondensed.ttf
  9. 0 play-win8/{Fonts → Assets}/Roboto-BoldCondensedItalic.ttf
  10. 0 play-win8/{Fonts → Assets}/Roboto-BoldItalic.ttf
  11. 0 play-win8/{Fonts → Assets}/Roboto-Condensed.ttf
  12. 0 play-win8/{Fonts → Assets}/Roboto-CondensedItalic.ttf
  13. 0 play-win8/{Fonts → Assets}/Roboto-Italic.ttf
  14. 0 play-win8/{Fonts → Assets}/Roboto-Light.ttf
  15. 0 play-win8/{Fonts → Assets}/Roboto-LightItalic.ttf
  16. 0 play-win8/{Fonts → Assets}/Roboto-Medium.ttf
  17. 0 play-win8/{Fonts → Assets}/Roboto-MediumItalic.ttf
  18. 0 play-win8/{Fonts → Assets}/Roboto-Regular.ttf
  19. 0 play-win8/{Fonts → Assets}/Roboto-Thin.ttf
  20. 0 play-win8/{Fonts → Assets}/Roboto-ThinItalic.ttf
  21. +55 −0 play-win8/Common/BindableBase.cs
  22. +21 −0 play-win8/Common/BooleanNegationConverter.cs
  23. +32 −0 play-win8/Common/BooleanToVisibilityConverter.cs
  24. +543 −0 play-win8/Common/LayoutAwarePage.cs
  25. +12 −0 play-win8/Common/ReadMe.txt
  26. +210 −0 play-win8/Common/RichTextColumns.cs
  27. +258 −0 play-win8/Common/SuspensionManager.cs
  28. +2 −2 play-win8/MainPage.xaml
  29. +6 −0 play-win8/MainPage.xaml.cs
  30. +1 −0 play-win8/Models/PlayApi.cs
  31. +15 −3 play-win8/ViewModels/AppBootstrapper.cs
  32. +78 −0 play-win8/Views/PlayView.xaml
  33. +53 −0 play-win8/Views/PlayView.xaml.cs
  34. +75 −1 play-win8/play-win8.csproj
View
0 play-win8/Fonts/OpenSans-Italic.ttf → play-win8/Assets/OpenSans-Italic.ttf
File renamed without changes.
View
0 play-win8/Fonts/OpenSans-LightItalic.ttf → play-win8/Assets/OpenSans-LightItalic.ttf
File renamed without changes.
View
0 play-win8/Fonts/OpenSans-Regular.ttf → play-win8/Assets/OpenSans-Regular.ttf
File renamed without changes.
View
0 play-win8/Fonts/OpenSans-Semibold.ttf → play-win8/Assets/OpenSans-Semibold.ttf
File renamed without changes.
View
0 play-win8/Fonts/Roboto-Black.ttf → play-win8/Assets/Roboto-Black.ttf
File renamed without changes.
View
0 play-win8/Fonts/Roboto-BlackItalic.ttf → play-win8/Assets/Roboto-BlackItalic.ttf
File renamed without changes.
View
0 play-win8/Fonts/Roboto-Bold.ttf → play-win8/Assets/Roboto-Bold.ttf
File renamed without changes.
View
0 play-win8/Fonts/Roboto-BoldCondensed.ttf → play-win8/Assets/Roboto-BoldCondensed.ttf
File renamed without changes.
View
0 ...win8/Fonts/Roboto-BoldCondensedItalic.ttf → ...in8/Assets/Roboto-BoldCondensedItalic.ttf
File renamed without changes.
View
0 play-win8/Fonts/Roboto-BoldItalic.ttf → play-win8/Assets/Roboto-BoldItalic.ttf
File renamed without changes.
View
0 play-win8/Fonts/Roboto-Condensed.ttf → play-win8/Assets/Roboto-Condensed.ttf
File renamed without changes.
View
0 play-win8/Fonts/Roboto-CondensedItalic.ttf → play-win8/Assets/Roboto-CondensedItalic.ttf
File renamed without changes.
View
0 play-win8/Fonts/Roboto-Italic.ttf → play-win8/Assets/Roboto-Italic.ttf
File renamed without changes.
View
0 play-win8/Fonts/Roboto-Light.ttf → play-win8/Assets/Roboto-Light.ttf
File renamed without changes.
View
0 play-win8/Fonts/Roboto-LightItalic.ttf → play-win8/Assets/Roboto-LightItalic.ttf
File renamed without changes.
View
0 play-win8/Fonts/Roboto-Medium.ttf → play-win8/Assets/Roboto-Medium.ttf
File renamed without changes.
View
0 play-win8/Fonts/Roboto-MediumItalic.ttf → play-win8/Assets/Roboto-MediumItalic.ttf
File renamed without changes.
View
0 play-win8/Fonts/Roboto-Regular.ttf → play-win8/Assets/Roboto-Regular.ttf
File renamed without changes.
View
0 play-win8/Fonts/Roboto-Thin.ttf → play-win8/Assets/Roboto-Thin.ttf
File renamed without changes.
View
0 play-win8/Fonts/Roboto-ThinItalic.ttf → play-win8/Assets/Roboto-ThinItalic.ttf
File renamed without changes.
View
55 play-win8/Common/BindableBase.cs
@@ -0,0 +1,55 @@
+using System;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using Windows.UI.Xaml.Data;
+
+namespace Play.Common
+{
+ /// <summary>
+ /// Implementation of <see cref="INotifyPropertyChanged"/> to simplify models.
+ /// </summary>
+ [Windows.Foundation.Metadata.WebHostHidden]
+ public abstract class BindableBase : INotifyPropertyChanged
+ {
+ /// <summary>
+ /// Multicast event for property change notifications.
+ /// </summary>
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ /// <summary>
+ /// Checks if a property already matches a desired value. Sets the property and
+ /// notifies listeners only when necessary.
+ /// </summary>
+ /// <typeparam name="T">Type of the property.</typeparam>
+ /// <param name="storage">Reference to a property with both getter and setter.</param>
+ /// <param name="value">Desired value for the property.</param>
+ /// <param name="propertyName">Name of the property used to notify listeners. This
+ /// value is optional and can be provided automatically when invoked from compilers that
+ /// support CallerMemberName.</param>
+ /// <returns>True if the value was changed, false if the existing value matched the
+ /// desired value.</returns>
+ protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
+ {
+ if (object.Equals(storage, value)) return false;
+
+ storage = value;
+ this.OnPropertyChanged(propertyName);
+ return true;
+ }
+
+ /// <summary>
+ /// Notifies listeners that a property value has changed.
+ /// </summary>
+ /// <param name="propertyName">Name of the property used to notify listeners. This
+ /// value is optional and can be provided automatically when invoked from compilers
+ /// that support <see cref="CallerMemberNameAttribute"/>.</param>
+ protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ var eventHandler = this.PropertyChanged;
+ if (eventHandler != null)
+ {
+ eventHandler(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+ }
+}
View
21 play-win8/Common/BooleanNegationConverter.cs
@@ -0,0 +1,21 @@
+using System;
+using Windows.UI.Xaml.Data;
+
+namespace Play.Common
+{
+ /// <summary>
+ /// Value converter that translates true to false and vice versa.
+ /// </summary>
+ public sealed class BooleanNegationConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, string language)
+ {
+ return !(value is bool && (bool)value);
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ return !(value is bool && (bool)value);
+ }
+ }
+}
View
32 play-win8/Common/BooleanToVisibilityConverter.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using Windows.Graphics.Display;
+using Windows.UI.ViewManagement;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Data;
+
+namespace Play.Common
+{
+ /// <summary>
+ /// Value converter that translates true to <see cref="Visibility.Visible"/> and false to
+ /// <see cref="Visibility.Collapsed"/>.
+ /// </summary>
+ public sealed class BooleanToVisibilityConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, string language)
+ {
+ return (value is bool && (bool)value) ? Visibility.Visible : Visibility.Collapsed;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ return value is Visibility && (Visibility)value == Visibility.Visible;
+ }
+ }
+}
View
543 play-win8/Common/LayoutAwarePage.cs
@@ -0,0 +1,543 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using Windows.System;
+using Windows.UI.Core;
+using Windows.UI.ViewManagement;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Navigation;
+
+namespace Play.Common
+{
+ /// <summary>
+ /// Typical implementation of Page that provides several important conveniences:
+ /// <list type="bullet">
+ /// <item>
+ /// <description>Application view state to visual state mapping</description>
+ /// </item>
+ /// <item>
+ /// <description>GoBack, GoForward, and GoHome event handlers</description>
+ /// </item>
+ /// <item>
+ /// <description>Mouse and keyboard shortcuts for navigation</description>
+ /// </item>
+ /// <item>
+ /// <description>State management for navigation and process lifetime management</description>
+ /// </item>
+ /// <item>
+ /// <description>A default view model</description>
+ /// </item>
+ /// </list>
+ /// </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>();
+
+ // When this page is part of the visual tree make two changes:
+ // 1) Map application view state to visual state for the page
+ // 2) Handle keyboard and mouse navigation requests
+ this.Loaded += (sender, e) =>
+ {
+ this.StartLayoutUpdates(sender, e);
+
+ // Keyboard and mouse navigation only apply when occupying the entire window
+ 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;
+ }
+ };
+
+ // Undo the same changes when the page is no longer visible
+ this.Unloaded += (sender, e) =>
+ {
+ this.StopLayoutUpdates(sender, e);
+ Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated -=
+ CoreDispatcher_AcceleratorKeyActivated;
+ Window.Current.CoreWindow.PointerPressed -=
+ this.CoreWindow_PointerPressed;
+ };
+ }
+
+ /// <summary>
+ /// An implementation of <see cref="IObservableMap&lt;String, Object&gt;"/> 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);
+ }
+ }
+
+ #region Navigation support
+
+ /// <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 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)
+ {
+ var virtualKey = args.VirtualKey;
+
+ // Only investigate further when Left, Right, or the dedicated Previous or Next keys
+ // are pressed
+ if ((args.EventType == CoreAcceleratorKeyEventType.SystemKeyDown ||
+ args.EventType == CoreAcceleratorKeyEventType.KeyDown) &&
+ (virtualKey == VirtualKey.Left || virtualKey == VirtualKey.Right ||
+ (int)virtualKey == 166 || (int)virtualKey == 167))
+ {
+ var coreWindow = Window.Current.CoreWindow;
+ var downState = CoreVirtualKeyStates.Down;
+ bool menuKey = (coreWindow.GetKeyState(VirtualKey.Menu) & downState) == downState;
+ bool controlKey = (coreWindow.GetKeyState(VirtualKey.Control) & downState) == downState;
+ bool shiftKey = (coreWindow.GetKeyState(VirtualKey.Shift) & downState) == downState;
+ bool noModifiers = !menuKey && !controlKey && !shiftKey;
+ bool onlyAlt = menuKey && !controlKey && !shiftKey;
+
+ if (((int)virtualKey == 166 && noModifiers) ||
+ (virtualKey == VirtualKey.Left && onlyAlt))
+ {
+ // When the previous key or Alt+Left are pressed navigate back
+ args.Handled = true;
+ this.GoBack(this, new RoutedEventArgs());
+ }
+ else if (((int)virtualKey == 167 && noModifiers) ||
+ (virtualKey == VirtualKey.Right && onlyAlt))
+ {
+ // When the next key or Alt+Right are pressed navigate forward
+ args.Handled = true;
+ this.GoForward(this, new RoutedEventArgs());
+ }
+ }
+ }
+
+ /// <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)
+ {
+ var properties = args.CurrentPoint.Properties;
+
+ // Ignore button chords with the left, right, and middle buttons
+ if (properties.IsLeftButtonPressed || properties.IsRightButtonPressed ||
+ properties.IsMiddleButtonPressed) return;
+
+ // If back or foward are pressed (but not both) navigate appropriately
+ bool backPressed = properties.IsXButton1Pressed;
+ bool forwardPressed = properties.IsXButton2Pressed;
+ if (backPressed ^ forwardPressed)
+ {
+ args.Handled = true;
+ if (backPressed) this.GoBack(this, new RoutedEventArgs());
+ if (forwardPressed) this.GoForward(this, new RoutedEventArgs());
+ }
+ }
+
+ #endregion
+
+ #region Visual state switching
+
+ /// <summary>
+ /// Invoked as an event handler, typically on the <see cref="FrameworkElement.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="FrameworkElement.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, WindowSizeChangedEventArgs e)
+ {
+ this.InvalidateVisualState();
+ }
+
+ /// <summary>
+ /// Invoked as an event handler, typically on the <see cref="FrameworkElement.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);
+ }
+ }
+ }
+
+ #endregion
+
+ #region Process lifetime management
+
+ private String _pageKey;
+
+ /// <summary>
+ /// Invoked when this page is about to be displayed in a Frame.
+ /// </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)
+ {
+ // Returning to a cached page through navigation shouldn't trigger state loading
+ if (this._pageKey != null) return;
+
+ var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
+ this._pageKey = "Page-" + this.Frame.BackStackDepth;
+
+ if (e.NavigationMode == NavigationMode.New)
+ {
+ // Clear existing state for forward navigation when adding a new page to the
+ // navigation stack
+ var nextPageKey = this._pageKey;
+ int nextPageIndex = this.Frame.BackStackDepth;
+ while (frameState.Remove(nextPageKey))
+ {
+ nextPageIndex++;
+ nextPageKey = "Page-" + nextPageIndex;
+ }
+
+ // Pass the navigation parameter to the new page
+ this.LoadState(e.Parameter, null);
+ }
+ else
+ {
+ // Pass the navigation parameter and preserved page state to the page, using
+ // the same strategy for loading suspended state and recreating pages discarded
+ // from cache
+ this.LoadState(e.Parameter, (Dictionary<String, Object>)frameState[this._pageKey]);
+ }
+ }
+
+ /// <summary>
+ /// Invoked when this page will no longer be displayed in a Frame.
+ /// </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 OnNavigatedFrom(NavigationEventArgs e)
+ {
+ var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
+ var pageState = new Dictionary<String, Object>();
+ this.SaveState(pageState);
+ frameState[_pageKey] = pageState;
+ }
+
+ /// <summary>
+ /// Populates the page with content passed during navigation. Any saved state is also
+ /// provided when recreating a page from a prior session.
+ /// </summary>
+ /// <param name="navigationParameter">The parameter value passed to
+ /// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested.
+ /// </param>
+ /// <param name="pageState">A dictionary of state preserved by this page during an earlier
+ /// session. This will be null the first time a page is visited.</param>
+ protected virtual void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
+ {
+ }
+
+ /// <summary>
+ /// Preserves state associated with this page in case the application is suspended or the
+ /// page is discarded from the navigation cache. Values must conform to the serialization
+ /// requirements of <see cref="SuspensionManager.SessionState"/>.
+ /// </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>
+ {
+ 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(change, 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;
+ }
+ }
+ }
+ }
+}
View
12 play-win8/Common/ReadMe.txt
@@ -0,0 +1,12 @@
+The Common directory contains classes and XAML styles that simplify application development.
+
+These are not merely convenient, but are required by most Visual Studio project and item templates.
+If you need a variation on one of the styles in StandardStyles it is recommended that you make a
+copy in your own resource dictionary. When right-clicking on a styled control in the design
+surface the context menu includes an option to Edit a Copy to simplify this process.
+
+Classes in the Common directory form part of your project and may be further enhanced to meet your
+needs. Care should be taken when altering existing methods and properties as incompatible changes
+will require corresponding changes to code included in a variety of Visual Studio templates. For
+example, additional pages added to your project are written assuming that the original methods and
+properties in Common classes are still present and that the names of the types have not changed.
View
210 play-win8/Common/RichTextColumns.cs
@@ -0,0 +1,210 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Windows.Foundation;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Xaml.Documents;
+
+namespace Play.Common
+{
+ /// <summary>
+ /// Wrapper for <see cref="RichTextBlock"/> that creates as many additional overflow
+ /// columns as needed to fit the available content.
+ /// </summary>
+ /// <example>
+ /// The following creates a collection of 400-pixel wide columns spaced 50 pixels apart
+ /// to contain arbitrary data-bound content:
+ /// <code>
+ /// <RichTextColumns>
+ /// <RichTextColumns.ColumnTemplate>
+ /// <DataTemplate>
+ /// <RichTextBlockOverflow Width="400" Margin="50,0,0,0"/>
+ /// </DataTemplate>
+ /// </RichTextColumns.ColumnTemplate>
+ ///
+ /// <RichTextBlock Width="400">
+ /// <Paragraph>
+ /// <Run Text="{Binding Content}"/>
+ /// </Paragraph>
+ /// </RichTextBlock>
+ /// </RichTextColumns>
+ /// </code>
+ /// </example>
+ /// <remarks>Typically used in a horizontally scrolling region where an unbounded amount of
+ /// space allows for all needed columns to be created. When used in a vertically scrolling
+ /// space there will never be any additional columns.</remarks>
+ [Windows.UI.Xaml.Markup.ContentProperty(Name = "RichTextContent")]
+ public sealed class RichTextColumns : Panel
+ {
+ /// <summary>
+ /// Identifies the <see cref="RichTextContent"/> dependency property.
+ /// </summary>
+ public static readonly DependencyProperty RichTextContentProperty =
+ DependencyProperty.Register("RichTextContent", typeof(RichTextBlock),
+ typeof(RichTextColumns), new PropertyMetadata(null, ResetOverflowLayout));
+
+ /// <summary>
+ /// Identifies the <see cref="ColumnTemplate"/> dependency property.
+ /// </summary>
+ public static readonly DependencyProperty ColumnTemplateProperty =
+ DependencyProperty.Register("ColumnTemplate", typeof(DataTemplate),
+ typeof(RichTextColumns), new PropertyMetadata(null, ResetOverflowLayout));
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RichTextColumns"/> class.
+ /// </summary>
+ public RichTextColumns()
+ {
+ this.HorizontalAlignment = HorizontalAlignment.Left;
+ }
+
+ /// <summary>
+ /// Gets or sets the initial rich text content to be used as the first column.
+ /// </summary>
+ public RichTextBlock RichTextContent
+ {
+ get { return (RichTextBlock)GetValue(RichTextContentProperty); }
+ set { SetValue(RichTextContentProperty, value); }
+ }
+
+ /// <summary>
+ /// Gets or sets the template used to create additional
+ /// <see cref="RichTextBlockOverflow"/> instances.
+ /// </summary>
+ public DataTemplate ColumnTemplate
+ {
+ get { return (DataTemplate)GetValue(ColumnTemplateProperty); }
+ set { SetValue(ColumnTemplateProperty, value); }
+ }
+
+ /// <summary>
+ /// Invoked when the content or overflow template is changed to recreate the column layout.
+ /// </summary>
+ /// <param name="d">Instance of <see cref="RichTextColumns"/> where the change
+ /// occurred.</param>
+ /// <param name="e">Event data describing the specific change.</param>
+ private static void ResetOverflowLayout(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ // When dramatic changes occur, rebuild the column layout from scratch
+ var target = d as RichTextColumns;
+ if (target != null)
+ {
+ target._overflowColumns = null;
+ target.Children.Clear();
+ target.InvalidateMeasure();
+ }
+ }
+
+ /// <summary>
+ /// Lists overflow columns already created. Must maintain a 1:1 relationship with
+ /// instances in the <see cref="Panel.Children"/> collection following the initial
+ /// RichTextBlock child.
+ /// </summary>
+ private List<RichTextBlockOverflow> _overflowColumns = null;
+
+ /// <summary>
+ /// Determines whether additional overflow columns are needed and if existing columns can
+ /// be removed.
+ /// </summary>
+ /// <param name="availableSize">The size of the space available, used to constrain the
+ /// number of additional columns that can be created.</param>
+ /// <returns>The resulting size of the original content plus any extra columns.</returns>
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ if (this.RichTextContent == null) return new Size(0, 0);
+
+ // Make sure the RichTextBlock is a child, using the lack of
+ // a list of additional columns as a sign that this hasn't been
+ // done yet
+ if (this._overflowColumns == null)
+ {
+ Children.Add(this.RichTextContent);
+ this._overflowColumns = new List<RichTextBlockOverflow>();
+ }
+
+ // Start by measuring the original RichTextBlock content
+ this.RichTextContent.Measure(availableSize);
+ var maxWidth = this.RichTextContent.DesiredSize.Width;
+ var maxHeight = this.RichTextContent.DesiredSize.Height;
+ var hasOverflow = this.RichTextContent.HasOverflowContent;
+
+ // Make sure there are enough overflow columns
+ int overflowIndex = 0;
+ while (hasOverflow && maxWidth < availableSize.Width && this.ColumnTemplate != null)
+ {
+ // Use existing overflow columns until we run out, then create
+ // more from the supplied template
+ RichTextBlockOverflow overflow;
+ if (this._overflowColumns.Count > overflowIndex)
+ {
+ overflow = this._overflowColumns[overflowIndex];
+ }
+ else
+ {
+ overflow = (RichTextBlockOverflow)this.ColumnTemplate.LoadContent();
+ this._overflowColumns.Add(overflow);
+ this.Children.Add(overflow);
+ if (overflowIndex == 0)
+ {
+ this.RichTextContent.OverflowContentTarget = overflow;
+ }
+ else
+ {
+ this._overflowColumns[overflowIndex - 1].OverflowContentTarget = overflow;
+ }
+ }
+
+ // Measure the new column and prepare to repeat as necessary
+ overflow.Measure(new Size(availableSize.Width - maxWidth, availableSize.Height));
+ maxWidth += overflow.DesiredSize.Width;
+ maxHeight = Math.Max(maxHeight, overflow.DesiredSize.Height);
+ hasOverflow = overflow.HasOverflowContent;
+ overflowIndex++;
+ }
+
+ // Disconnect extra columns from the overflow chain, remove them from our private list
+ // of columns, and remove them as children
+ if (this._overflowColumns.Count > overflowIndex)
+ {
+ if (overflowIndex == 0)
+ {
+ this.RichTextContent.OverflowContentTarget = null;
+ }
+ else
+ {
+ this._overflowColumns[overflowIndex - 1].OverflowContentTarget = null;
+ }
+ while (this._overflowColumns.Count > overflowIndex)
+ {
+ this._overflowColumns.RemoveAt(overflowIndex);
+ this.Children.RemoveAt(overflowIndex + 1);
+ }
+ }
+
+ // Report final determined size
+ return new Size(maxWidth, maxHeight);
+ }
+
+ /// <summary>
+ /// Arranges the original content and all extra columns.
+ /// </summary>
+ /// <param name="finalSize">Defines the size of the area the children must be arranged
+ /// within.</param>
+ /// <returns>The size of the area the children actually required.</returns>
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ double maxWidth = 0;
+ double maxHeight = 0;
+ foreach (var child in Children)
+ {
+ child.Arrange(new Rect(maxWidth, 0, child.DesiredSize.Width, finalSize.Height));
+ maxWidth += child.DesiredSize.Width;
+ maxHeight = Math.Max(maxHeight, child.DesiredSize.Height);
+ }
+ return new Size(maxWidth, maxHeight);
+ }
+ }
+}
View
258 play-win8/Common/SuspensionManager.cs
@@ -0,0 +1,258 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Threading.Tasks;
+using Windows.ApplicationModel;
+using Windows.Storage;
+using Windows.Storage.Streams;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+
+namespace Play.Common
+{
+ /// <summary>
+ /// SuspensionManager captures global session state to simplify process lifetime management
+ /// for an application. Note that session state will be automatically cleared under a variety
+ /// of conditions and should only be used to store information that would be convenient to
+ /// carry across sessions, but that should be discarded when an application crashes or is
+ /// upgraded.
+ /// </summary>
+ internal sealed class SuspensionManager
+ {
+ private static Dictionary<string, object> _sessionState = new Dictionary<string, object>();
+ private static List<Type> _knownTypes = new List<Type>();
+ private const string sessionStateFilename = "_sessionState.xml";
+
+ /// <summary>
+ /// Provides access to global session state for the current session. This state is
+ /// serialized by <see cref="SaveAsync"/> and restored by
+ /// <see cref="RestoreAsync"/>, so values must be serializable by
+ /// <see cref="DataContractSerializer"/> and should be as compact as possible. Strings
+ /// and other self-contained data types are strongly recommended.
+ /// </summary>
+ public static Dictionary<string, object> SessionState
+ {
+ get { return _sessionState; }
+ }
+
+ /// <summary>
+ /// List of custom types provided to the <see cref="DataContractSerializer"/> when
+ /// reading and writing session state. Initially empty, additional types may be
+ /// added to customize the serialization process.
+ /// </summary>
+ public static List<Type> KnownTypes
+ {
+ get { return _knownTypes; }
+ }
+
+ /// <summary>
+ /// Save the current <see cref="SessionState"/>. Any <see cref="Frame"/> instances
+ /// registered with <see cref="RegisterFrame"/> will also preserve their current
+ /// navigation stack, which in turn gives their active <see cref="Page"/> an opportunity
+ /// to save its state.
+ /// </summary>
+ /// <returns>An asynchronous task that reflects when session state has been saved.</returns>
+ public static async Task SaveAsync()
+ {
+ try
+ {
+ // Save the navigation state for all registered frames
+ foreach (var weakFrameReference in _registeredFrames)
+ {
+ Frame frame;
+ if (weakFrameReference.TryGetTarget(out frame))
+ {
+ SaveFrameNavigationState(frame);
+ }
+ }
+
+ // Serialize the session state synchronously to avoid asynchronous access to shared
+ // state
+ MemoryStream sessionData = new MemoryStream();
+ DataContractSerializer serializer = new DataContractSerializer(typeof(Dictionary<string, object>), _knownTypes);
+ serializer.WriteObject(sessionData, _sessionState);
+
+ // Get an output stream for the SessionState file and write the state asynchronously
+ StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync(sessionStateFilename, CreationCollisionOption.ReplaceExisting);
+ using (Stream fileStream = await file.OpenStreamForWriteAsync())
+ {
+ sessionData.Seek(0, SeekOrigin.Begin);
+ await sessionData.CopyToAsync(fileStream);
+ await fileStream.FlushAsync();
+ }
+ }
+ catch (Exception e)
+ {
+ throw new SuspensionManagerException(e);
+ }
+ }
+
+ /// <summary>
+ /// Restores previously saved <see cref="SessionState"/>. Any <see cref="Frame"/> instances
+ /// registered with <see cref="RegisterFrame"/> will also restore their prior navigation
+ /// state, which in turn gives their active <see cref="Page"/> an opportunity restore its
+ /// state.
+ /// </summary>
+ /// <returns>An asynchronous task that reflects when session state has been read. The
+ /// content of <see cref="SessionState"/> should not be relied upon until this task
+ /// completes.</returns>
+ public static async Task RestoreAsync()
+ {
+ _sessionState = new Dictionary<String, Object>();
+
+ try
+ {
+ // Get the input stream for the SessionState file
+ StorageFile file = await ApplicationData.Current.LocalFolder.GetFileAsync(sessionStateFilename);
+ using (IInputStream inStream = await file.OpenSequentialReadAsync())
+ {
+ // Deserialize the Session State
+ DataContractSerializer serializer = new DataContractSerializer(typeof(Dictionary<string, object>), _knownTypes);
+ _sessionState = (Dictionary<string, object>)serializer.ReadObject(inStream.AsStreamForRead());
+ }
+
+ // Restore any registered frames to their saved state
+ foreach (var weakFrameReference in _registeredFrames)
+ {
+ Frame frame;
+ if (weakFrameReference.TryGetTarget(out frame))
+ {
+ frame.ClearValue(FrameSessionStateProperty);
+ RestoreFrameNavigationState(frame);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ throw new SuspensionManagerException(e);
+ }
+ }
+
+ private static DependencyProperty FrameSessionStateKeyProperty =
+ DependencyProperty.RegisterAttached("_FrameSessionStateKey", typeof(String), typeof(SuspensionManager), null);
+ private static DependencyProperty FrameSessionStateProperty =
+ DependencyProperty.RegisterAttached("_FrameSessionState", typeof(Dictionary<String, Object>), typeof(SuspensionManager), null);
+ private static List<WeakReference<Frame>> _registeredFrames = new List<WeakReference<Frame>>();
+
+ /// <summary>
+ /// Registers a <see cref="Frame"/> instance to allow its navigation history to be saved to
+ /// and restored from <see cref="SessionState"/>. Frames should be registered once
+ /// immediately after creation if they will participate in session state management. Upon
+ /// registration if state has already been restored for the specified key
+ /// the navigation history will immediately be restored. Subsequent invocations of
+ /// <see cref="RestoreAsync"/> will also restore navigation history.
+ /// </summary>
+ /// <param name="frame">An instance whose navigation history should be managed by
+ /// <see cref="SuspensionManager"/></param>
+ /// <param name="sessionStateKey">A unique key into <see cref="SessionState"/> used to
+ /// store navigation-related information.</param>
+ public static void RegisterFrame(Frame frame, String sessionStateKey)
+ {
+ if (frame.GetValue(FrameSessionStateKeyProperty) != null)
+ {
+ throw new InvalidOperationException("Frames can only be registered to one session state key");
+ }
+
+ if (frame.GetValue(FrameSessionStateProperty) != null)
+ {
+ throw new InvalidOperationException("Frames must be either be registered before accessing frame session state, or not registered at all");
+ }
+
+ // Use a dependency property to associate the session key with a frame, and keep a list of frames whose
+ // navigation state should be managed
+ frame.SetValue(FrameSessionStateKeyProperty, sessionStateKey);
+ _registeredFrames.Add(new WeakReference<Frame>(frame));
+
+ // Check to see if navigation state can be restored
+ RestoreFrameNavigationState(frame);
+ }
+
+ /// <summary>
+ /// Disassociates a <see cref="Frame"/> previously registered by <see cref="RegisterFrame"/>
+ /// from <see cref="SessionState"/>. Any navigation state previously captured will be
+ /// removed.
+ /// </summary>
+ /// <param name="frame">An instance whose navigation history should no longer be
+ /// managed.</param>
+ public static void UnregisterFrame(Frame frame)
+ {
+ // Remove session state and remove the frame from the list of frames whose navigation
+ // state will be saved (along with any weak references that are no longer reachable)
+ SessionState.Remove((String)frame.GetValue(FrameSessionStateKeyProperty));
+ _registeredFrames.RemoveAll((weakFrameReference) =>
+ {
+ Frame testFrame;
+ return !weakFrameReference.TryGetTarget(out testFrame) || testFrame == frame;
+ });
+ }
+
+ /// <summary>
+ /// Provides storage for session state associated with the specified <see cref="Frame"/>.
+ /// Frames that have been previously registered with <see cref="RegisterFrame"/> have
+ /// their session state saved and restored automatically as a part of the global
+ /// <see cref="SessionState"/>. Frames that are not registered have transient state
+ /// that can still be useful when restoring pages that have been discarded from the
+ /// navigation cache.
+ /// </summary>
+ /// <remarks>Apps may choose to rely on <see cref="LayoutAwarePage"/> to manage
+ /// page-specific state instead of working with frame session state directly.</remarks>
+ /// <param name="frame">The instance for which session state is desired.</param>
+ /// <returns>A collection of state subject to the same serialization mechanism as
+ /// <see cref="SessionState"/>.</returns>
+ public static Dictionary<String, Object> SessionStateForFrame(Frame frame)
+ {
+ var frameState = (Dictionary<String, Object>)frame.GetValue(FrameSessionStateProperty);
+
+ if (frameState == null)
+ {
+ var frameSessionKey = (String)frame.GetValue(FrameSessionStateKeyProperty);
+ if (frameSessionKey != null)
+ {
+ // Registered frames reflect the corresponding session state
+ if (!_sessionState.ContainsKey(frameSessionKey))
+ {
+ _sessionState[frameSessionKey] = new Dictionary<String, Object>();
+ }
+ frameState = (Dictionary<String, Object>)_sessionState[frameSessionKey];
+ }
+ else
+ {
+ // Frames that aren't registered have transient state
+ frameState = new Dictionary<String, Object>();
+ }
+ frame.SetValue(FrameSessionStateProperty, frameState);
+ }
+ return frameState;
+ }
+
+ private static void RestoreFrameNavigationState(Frame frame)
+ {
+ var frameState = SessionStateForFrame(frame);
+ if (frameState.ContainsKey("Navigation"))
+ {
+ frame.SetNavigationState((String)frameState["Navigation"]);
+ }
+ }
+
+ private static void SaveFrameNavigationState(Frame frame)
+ {
+ var frameState = SessionStateForFrame(frame);
+ frameState["Navigation"] = frame.GetNavigationState();
+ }
+ }
+ public class SuspensionManagerException : Exception
+ {
+ public SuspensionManagerException()
+ {
+ }
+
+ public SuspensionManagerException(Exception e)
+ : base("SuspensionManager failed", e)
+ {
+
+ }
+ }
+}
View
4 play-win8/MainPage.xaml
@@ -3,10 +3,10 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:Routing="using:ReactiveUI.Routing"
mc:Ignorable="d">
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
- <TextBlock Text="Hello." HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="72"/>
+ <Routing:RoutedViewHost x:Name="viewHost" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" />
</Grid>
</Page>
View
6 play-win8/MainPage.xaml.cs
@@ -2,7 +2,10 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Reactive.Linq;
using Play.ViewModels;
+using ReactiveUI;
+using ReactiveUI.Routing;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
@@ -35,6 +38,9 @@ public MainPage()
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ViewModel = new AppBootstrapper();
+
+ // XXX: ReactiveUI Bug
+ viewHost.Router = (RoutingState)ViewModel.Router;
}
public AppBootstrapper ViewModel {
View
1 play-win8/Models/PlayApi.cs
@@ -87,6 +87,7 @@ public IObservable<BitmapImage> FetchImageForAlbum(Song song)
var rq = rqFactory(HttpMethod.Get, String.Format("images/art/{0}.png", song.id));
return client.RequestAsync(rq)
.SelectMany(x => x.Content.ReadAsByteArrayAsync())
+ .ObserveOn(RxApp.DeferredScheduler)
.SelectMany(x => {
var ret = new BitmapImage();
var mem = new MemoryRandomAccessStream(x);
View
18 play-win8/ViewModels/AppBootstrapper.cs
@@ -3,15 +3,18 @@
using System.Linq;
using System.Net.Http;
using System.Reactive;
+using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using System.Text;
using System.Threading.Tasks;
using Ninject;
using Play.Models;
+using Play.Views;
using ReactiveUI;
using ReactiveUI.Routing;
using Windows.Security.Credentials;
+using Windows.UI.Core;
namespace Play.ViewModels
{
@@ -28,6 +31,9 @@ public AppBootstrapper(IKernel testKernel = null, IRoutingState router = null)
Kernel.Bind<IAppBootstrapper>().ToConstant(this);
Router = router ?? new RoutingState();
+ // XXX: This is a ReactiveUI Bug
+ RxApp.DeferredScheduler = CoreDispatcherScheduler.Current;
+
apiFactory = Kernel.TryGet<Func<IObservable<IPlayApi>>>("ApiFactory");
RxApp.ConfigureServiceLocator(
@@ -78,10 +84,14 @@ public void SaveCredentials(string baseUrl, string username)
Task<IPlayApi> loadCredentials()
{
var vault = new PasswordVault();
- var baseUrl = vault.FindAllByUserName("BaseUrl").First().Password;
- var token = vault.FindAllByUserName("Token").First().Password;
- return Observable.Return((IPlayApi)createPlayApiFromCreds(baseUrl, token)).ToTask();
+ try {
+ var baseUrl = vault.FindAllByUserName("BaseUrl").First().Password;
+ var token = vault.FindAllByUserName("Token").First().Password;
+ return Observable.Return((IPlayApi)createPlayApiFromCreds(baseUrl, token)).ToTask();
+ } catch (Exception ex) {
+ return Observable.Throw<IPlayApi>(ex).ToTask();
+ }
}
PlayApi createPlayApiFromCreds(string baseUrl, string token)
@@ -106,6 +116,8 @@ IKernel createDefaultKernel()
ret.Bind<IPlayViewModel>().To<PlayViewModel>();
ret.Bind<ISearchViewModel>().To<SearchViewModel>();
+ ret.Bind<IViewFor<PlayViewModel>>().To<PlayView>();
+
return ret;
}
}
View
78 play-win8/Views/PlayView.xaml
@@ -0,0 +1,78 @@
+<common:LayoutAwarePage
+ x:Name="pageRoot"
+ x:Class="Play.Views.PlayView"
+ DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:local="using:Play.Views"
+ xmlns:common="using:Play.Common"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:Routing="using:ReactiveUI.Routing"
+ mc:Ignorable="d">
+
+ <Page.Resources>
+
+ <!-- TODO: Delete this line if the key AppName is declared in App.xaml -->
+ <x:String x:Key="AppName">My Application</x:String>
+ </Page.Resources>
+
+ <!--
+ This grid acts as a root panel for the page that defines two rows:
+ * Row 0 contains the back button and page title
+ * Row 1 contains the rest of the page layout
+ -->
+ <Grid Style="{StaticResource LayoutRootStyle}">
+ <Grid.RowDefinitions>
+ <RowDefinition Height="140"/>
+ <RowDefinition Height="*"/>
+ </Grid.RowDefinitions>
+
+ <!-- Back button and page title -->
+ <Grid>
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="Auto" MinWidth="120"/>
+ <ColumnDefinition Width="*"/>
+ </Grid.ColumnDefinitions>
+
+ <TextBlock x:Name="pageTitle" Grid.Column="1" Text="{StaticResource AppName}" Style="{StaticResource PageHeaderTextStyle}" />
+ </Grid>
+
+ <GridView x:Name="songsList" Grid.Row="1">
+ <GridView.ItemTemplate>
+ <DataTemplate>
+ <Routing:ViewModelViewHost HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" ViewModel="{Binding}" />
+ </DataTemplate>
+ </GridView.ItemTemplate>
+ </GridView>
+
+ <VisualStateManager.VisualStateGroups>
+
+ <!-- Visual states reflect the application's view state -->
+ <VisualStateGroup x:Name="ApplicationViewStates">
+ <VisualState x:Name="FullScreenLandscape"/>
+ <VisualState x:Name="Filled"/>
+
+ <!-- The entire page respects the narrower 100-pixel margin convention for portrait -->
+ <VisualState x:Name="FullScreenPortrait">
+ <Storyboard>
+ <ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton" Storyboard.TargetProperty="Style">
+ <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PortraitBackButtonStyle}"/>
+ </ObjectAnimationUsingKeyFrames>
+ </Storyboard>
+ </VisualState>
+
+ <!-- The back button and title have different styles when snapped -->
+ <VisualState x:Name="Snapped">
+ <Storyboard>
+ <ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton" Storyboard.TargetProperty="Style">
+ <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource SnappedBackButtonStyle}"/>
+ </ObjectAnimationUsingKeyFrames>
+ <ObjectAnimationUsingKeyFrames Storyboard.TargetName="pageTitle" Storyboard.TargetProperty="Style">
+ <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource SnappedPageHeaderTextStyle}"/>
+ </ObjectAnimationUsingKeyFrames>
+ </Storyboard>
+ </VisualState>
+ </VisualStateGroup>
+ </VisualStateManager.VisualStateGroups>
+ </Grid>
+</common:LayoutAwarePage>
View
53 play-win8/Views/PlayView.xaml.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Reactive.Concurrency;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Play.Common;
+using Play.ViewModels;
+using ReactiveUI;
+using ReactiveUI.Routing;
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Navigation;
+
+// The Basic Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234237
+
+namespace Play.Views
+{
+ /// <summary>
+ /// A basic page that provides characteristics common to most applications.
+ /// </summary>
+ public sealed partial class PlayView : Play.Common.LayoutAwarePage, IViewFor<PlayViewModel>
+ {
+ public PlayView()
+ {
+ this.InitializeComponent();
+
+ //this.OneWayBind(ViewModel, x => x.AllSongs, x => x.songsList.ItemsSource);
+
+ RxApp.DeferredScheduler.Schedule(() => {
+ this.WhenAny(x => x.ViewModel.AllSongs, x => x.Value)
+ .Subscribe(x => songsList.ItemsSource = x);
+ });
+ }
+
+ public PlayViewModel ViewModel {
+ get { return (PlayViewModel)GetValue(ViewModelProperty); }
+ set { SetValue(ViewModelProperty, value); }
+ }
+ public static readonly DependencyProperty ViewModelProperty =
+ DependencyProperty.Register("ViewModel", typeof(IPlayViewModel), typeof(PlayView), new PropertyMetadata(null));
+
+ object IViewFor.ViewModel {
+ get { return ViewModel; }
+ set { ViewModel = (PlayViewModel) value; }
+ }
+ }
+}
View
76 play-win8/play-win8.csproj
@@ -103,12 +103,18 @@
</PropertyGroup>
<ItemGroup>
<!-- A reference to the entire .Net Framework and Windows SDK are automatically included -->
- <Folder Include="Views\" />
+ <Folder Include="Files\" />
</ItemGroup>
<ItemGroup>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
+ <Compile Include="Common\BindableBase.cs" />
+ <Compile Include="Common\BooleanNegationConverter.cs" />
+ <Compile Include="Common\BooleanToVisibilityConverter.cs" />
+ <Compile Include="Common\LayoutAwarePage.cs" />
+ <Compile Include="Common\RichTextColumns.cs" />
+ <Compile Include="Common\SuspensionManager.cs" />
<Compile Include="HttpClientHelper.cs" />
<Compile Include="MainPage.xaml.cs">
<DependentUpon>MainPage.xaml</DependentUpon>
@@ -122,11 +128,75 @@
<Compile Include="ViewModels\SearchViewModel.cs" />
<Compile Include="ViewModels\SongTileViewModel.cs" />
<Compile Include="ViewModels\WelcomeViewModel.cs" />
+ <Compile Include="Views\PlayView.xaml.cs">
+ <DependentUpon>PlayView.xaml</DependentUpon>
+ </Compile>
</ItemGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
<SubType>Designer</SubType>
</AppxManifest>
+ <Content Include="Common\ReadMe.txt" />
+ <Content Include="Assets\OpenSans-Italic.ttf">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="Assets\OpenSans-LightItalic.ttf">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="Assets\OpenSans-Regular.ttf">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="Assets\OpenSans-Semibold.ttf">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="Assets\Roboto-Black.ttf">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="Assets\Roboto-BlackItalic.ttf">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="Assets\Roboto-Bold.ttf">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="Assets\Roboto-BoldCondensed.ttf">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="Assets\Roboto-BoldCondensedItalic.ttf">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="Assets\Roboto-BoldItalic.ttf">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="Assets\Roboto-Condensed.ttf">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="Assets\Roboto-CondensedItalic.ttf">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="Assets\Roboto-Italic.ttf">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="Assets\Roboto-Light.ttf">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="Assets\Roboto-LightItalic.ttf">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="Assets\Roboto-Medium.ttf">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="Assets\Roboto-MediumItalic.ttf">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="Assets\Roboto-Regular.ttf">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="Assets\Roboto-Thin.ttf">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="Assets\Roboto-ThinItalic.ttf">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<None Include="packages.config" />
<None Include="play-win8_TemporaryKey.pfx" />
</ItemGroup>
@@ -149,6 +219,10 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
+ <Page Include="Views\PlayView.xaml">
+ <SubType>Designer</SubType>
+ <Generator>MSBuild:Compile</Generator>
+ </Page>
</ItemGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json">

0 comments on commit e33d05a

Please sign in to comment.