diff --git a/Pyramid2000/Pyramid2000.Windows/Pyramid2000.Windows.csproj b/Pyramid2000/Pyramid2000.Windows/Pyramid2000.Windows.csproj index 4f01783..55e75bc 100644 --- a/Pyramid2000/Pyramid2000.Windows/Pyramid2000.Windows.csproj +++ b/Pyramid2000/Pyramid2000.Windows/Pyramid2000.Windows.csproj @@ -16,6 +16,8 @@ true {BC8A1FFA-BEE3-4634-8014-F334798102B3};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} Pyramid2000.Windows_TemporaryKey.pfx + True + neutral AnyCPU diff --git a/Pyramid2000/Pyramid2000.WindowsPhone/About.xaml b/Pyramid2000/Pyramid2000.WindowsPhone/About.xaml new file mode 100644 index 0000000..7f183e5 --- /dev/null +++ b/Pyramid2000/Pyramid2000.WindowsPhone/About.xaml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + Pyramid 2000Build 1.0.0.0 + This app is an emulator/interpreter for a 'classic' text adventure game originally released for the Radio Shack Model I Computer. + + Pyramid 2000 was written by Robert Arnstein and released by Radio Shack in 1979. + + This project would not have been possible without the information and code found on Christopher Cantrell's website: + http://www.computerarcheology.com + + You can find a wealth of information about this game including a walk through solution on Sean Murphy's website: + http://www.figmentfly.com + + + + diff --git a/Pyramid2000/Pyramid2000.WindowsPhone/About.xaml.cs b/Pyramid2000/Pyramid2000.WindowsPhone/About.xaml.cs new file mode 100644 index 0000000..bf7fc0a --- /dev/null +++ b/Pyramid2000/Pyramid2000.WindowsPhone/About.xaml.cs @@ -0,0 +1,111 @@ +using Pyramid2000.Common; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +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.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=390556 + +namespace Pyramid2000 +{ + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + public sealed partial class About : Page + { + private NavigationHelper navigationHelper; + private ObservableDictionary defaultViewModel = new ObservableDictionary(); + + public About() + { + this.InitializeComponent(); + + this.navigationHelper = new NavigationHelper(this); + this.navigationHelper.LoadState += this.NavigationHelper_LoadState; + this.navigationHelper.SaveState += this.NavigationHelper_SaveState; + } + + /// + /// Gets the associated with this . + /// + public NavigationHelper NavigationHelper + { + get { return this.navigationHelper; } + } + + /// + /// Gets the view model for this . + /// This can be changed to a strongly typed view model. + /// + public ObservableDictionary DefaultViewModel + { + get { return this.defaultViewModel; } + } + + /// + /// Populates the page with content passed during navigation. Any saved state is also + /// provided when recreating a page from a prior session. + /// + /// + /// The source of the event; typically + /// + /// Event data that provides both the navigation parameter passed to + /// when this page was initially requested and + /// a dictionary of state preserved by this page during an earlier + /// session. The state will be null the first time a page is visited. + private void NavigationHelper_LoadState(object sender, LoadStateEventArgs e) + { + } + + /// + /// 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 . + /// + /// The source of the event; typically + /// Event data that provides an empty dictionary to be populated with + /// serializable state. + private void NavigationHelper_SaveState(object sender, SaveStateEventArgs e) + { + } + + #region NavigationHelper registration + + /// + /// The methods provided in this section are simply used to allow + /// NavigationHelper to respond to the page's navigation methods. + /// + /// Page specific logic should be placed in event handlers for the + /// + /// and . + /// The navigation parameter is available in the LoadState method + /// in addition to page state preserved during an earlier session. + /// + /// + /// Provides data for navigation methods and event + /// handlers that cannot cancel the navigation request. + protected override void OnNavigatedTo(NavigationEventArgs e) + { + this.navigationHelper.OnNavigatedTo(e); + } + + protected override void OnNavigatedFrom(NavigationEventArgs e) + { + this.navigationHelper.OnNavigatedFrom(e); + } + + #endregion + } +} diff --git a/Pyramid2000/Pyramid2000.WindowsPhone/Assets/Logo.scale-240.png b/Pyramid2000/Pyramid2000.WindowsPhone/Assets/Logo.scale-240.png index 3434660..b67524a 100644 Binary files a/Pyramid2000/Pyramid2000.WindowsPhone/Assets/Logo.scale-240.png and b/Pyramid2000/Pyramid2000.WindowsPhone/Assets/Logo.scale-240.png differ diff --git a/Pyramid2000/Pyramid2000.WindowsPhone/Assets/SmallLogo.scale-240.png b/Pyramid2000/Pyramid2000.WindowsPhone/Assets/SmallLogo.scale-240.png index 82c14e1..d666c5c 100644 Binary files a/Pyramid2000/Pyramid2000.WindowsPhone/Assets/SmallLogo.scale-240.png and b/Pyramid2000/Pyramid2000.WindowsPhone/Assets/SmallLogo.scale-240.png differ diff --git a/Pyramid2000/Pyramid2000.WindowsPhone/Assets/SplashScreen.scale-240.png b/Pyramid2000/Pyramid2000.WindowsPhone/Assets/SplashScreen.scale-240.png index 757064b..797dd01 100644 Binary files a/Pyramid2000/Pyramid2000.WindowsPhone/Assets/SplashScreen.scale-240.png and b/Pyramid2000/Pyramid2000.WindowsPhone/Assets/SplashScreen.scale-240.png differ diff --git a/Pyramid2000/Pyramid2000.WindowsPhone/Assets/Square71x71Logo.scale-240.png b/Pyramid2000/Pyramid2000.WindowsPhone/Assets/Square71x71Logo.scale-240.png index 622d7c8..ccfa8e4 100644 Binary files a/Pyramid2000/Pyramid2000.WindowsPhone/Assets/Square71x71Logo.scale-240.png and b/Pyramid2000/Pyramid2000.WindowsPhone/Assets/Square71x71Logo.scale-240.png differ diff --git a/Pyramid2000/Pyramid2000.WindowsPhone/Assets/StoreLogo.scale-240.png b/Pyramid2000/Pyramid2000.WindowsPhone/Assets/StoreLogo.scale-240.png index e72dc47..34d5b72 100644 Binary files a/Pyramid2000/Pyramid2000.WindowsPhone/Assets/StoreLogo.scale-240.png and b/Pyramid2000/Pyramid2000.WindowsPhone/Assets/StoreLogo.scale-240.png differ diff --git a/Pyramid2000/Pyramid2000.WindowsPhone/Assets/WideLogo.scale-240.png b/Pyramid2000/Pyramid2000.WindowsPhone/Assets/WideLogo.scale-240.png index 4f24ce2..8506067 100644 Binary files a/Pyramid2000/Pyramid2000.WindowsPhone/Assets/WideLogo.scale-240.png and b/Pyramid2000/Pyramid2000.WindowsPhone/Assets/WideLogo.scale-240.png differ diff --git a/Pyramid2000/Pyramid2000.WindowsPhone/Common/NavigationHelper.cs b/Pyramid2000/Pyramid2000.WindowsPhone/Common/NavigationHelper.cs new file mode 100644 index 0000000..42084f2 --- /dev/null +++ b/Pyramid2000/Pyramid2000.WindowsPhone/Common/NavigationHelper.cs @@ -0,0 +1,437 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; +using Windows.System; +using Windows.UI.Core; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Navigation; + +namespace Pyramid2000.Common +{ + /// + /// NavigationHelper aids in navigation between pages. It provides commands used to + /// navigate back and forward as well as registers for standard mouse and keyboard + /// shortcuts used to go back and forward in Windows and the hardware back button in + /// Windows Phone. In addition it integrates SuspensionManger to handle process lifetime + /// management and state management when navigating between pages. + /// + /// + /// To make use of NavigationHelper, follow these two steps or + /// start with a BasicPage or any other Page item template other than BlankPage. + /// + /// 1) Create an instance of the NavigationHelper somewhere such as in the + /// constructor for the page and register a callback for the LoadState and + /// SaveState events. + /// + /// public MyPage() + /// { + /// this.InitializeComponent(); + /// var navigationHelper = new NavigationHelper(this); + /// this.navigationHelper.LoadState += navigationHelper_LoadState; + /// this.navigationHelper.SaveState += navigationHelper_SaveState; + /// } + /// + /// private async void navigationHelper_LoadState(object sender, LoadStateEventArgs e) + /// { } + /// private async void navigationHelper_SaveState(object sender, LoadStateEventArgs e) + /// { } + /// + /// + /// 2) Register the page to call into the NavigationHelper whenever the page participates + /// in navigation by overriding the + /// and events. + /// + /// protected override void OnNavigatedTo(NavigationEventArgs e) + /// { + /// navigationHelper.OnNavigatedTo(e); + /// } + /// + /// protected override void OnNavigatedFrom(NavigationEventArgs e) + /// { + /// navigationHelper.OnNavigatedFrom(e); + /// } + /// + /// + [Windows.Foundation.Metadata.WebHostHidden] + public class NavigationHelper : DependencyObject + { + private Page Page { get; set; } + private Frame Frame { get { return this.Page.Frame; } } + + /// + /// Initializes a new instance of the class. + /// + /// A reference to the current page used for navigation. + /// This reference allows for frame manipulation and to ensure that keyboard + /// navigation requests only occur when the page is occupying the entire window. + public NavigationHelper(Page page) + { + this.Page = page; + + // 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 hardware navigation requests + this.Page.Loaded += (sender, e) => + { +#if WINDOWS_PHONE_APP + Windows.Phone.UI.Input.HardwareButtons.BackPressed += HardwareButtons_BackPressed; +#else + // Keyboard and mouse navigation only apply when occupying the entire window + if (this.Page.ActualHeight == Window.Current.Bounds.Height && + this.Page.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; + } +#endif + }; + + // Undo the same changes when the page is no longer visible + this.Page.Unloaded += (sender, e) => + { +#if WINDOWS_PHONE_APP + Windows.Phone.UI.Input.HardwareButtons.BackPressed -= HardwareButtons_BackPressed; +#else + Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated -= + CoreDispatcher_AcceleratorKeyActivated; + Window.Current.CoreWindow.PointerPressed -= + this.CoreWindow_PointerPressed; +#endif + }; + } + + #region Navigation support + + RelayCommand _goBackCommand; + RelayCommand _goForwardCommand; + + /// + /// used to bind to the back Button's Command property + /// for navigating to the most recent item in back navigation history, if a Frame + /// manages its own navigation history. + /// + /// The is set up to use the virtual method + /// as the Execute Action and for CanExecute. + /// + public RelayCommand GoBackCommand + { + get + { + if (_goBackCommand == null) + { + _goBackCommand = new RelayCommand( + () => this.GoBack(), + () => this.CanGoBack()); + } + return _goBackCommand; + } + set + { + _goBackCommand = value; + } + } + /// + /// used for navigating to the most recent item in + /// the forward navigation history, if a Frame manages its own navigation history. + /// + /// The is set up to use the virtual method + /// as the Execute Action and for CanExecute. + /// + public RelayCommand GoForwardCommand + { + get + { + if (_goForwardCommand == null) + { + _goForwardCommand = new RelayCommand( + () => this.GoForward(), + () => this.CanGoForward()); + } + return _goForwardCommand; + } + } + + /// + /// Virtual method used by the property + /// to determine if the can go back. + /// + /// + /// true if the has at least one entry + /// in the back navigation history. + /// + public virtual bool CanGoBack() + { + return this.Frame != null && this.Frame.CanGoBack; + } + /// + /// Virtual method used by the property + /// to determine if the can go forward. + /// + /// + /// true if the has at least one entry + /// in the forward navigation history. + /// + public virtual bool CanGoForward() + { + return this.Frame != null && this.Frame.CanGoForward; + } + + /// + /// Virtual method used by the property + /// to invoke the method. + /// + public virtual void GoBack() + { + if (this.Frame != null && this.Frame.CanGoBack) this.Frame.GoBack(); + } + /// + /// Virtual method used by the property + /// to invoke the method. + /// + public virtual void GoForward() + { + if (this.Frame != null && this.Frame.CanGoForward) this.Frame.GoForward(); + } + +#if WINDOWS_PHONE_APP + /// + /// Invoked when the hardware back button is pressed. For Windows Phone only. + /// + /// Instance that triggered the event. + /// Event data describing the conditions that led to the event. + private void HardwareButtons_BackPressed(object sender, Windows.Phone.UI.Input.BackPressedEventArgs e) + { + if (this.GoBackCommand.CanExecute(null)) + { + e.Handled = true; + this.GoBackCommand.Execute(null); + } + } +#else + /// + /// 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. + /// + /// Instance that triggered the event. + /// Event data describing the conditions that led to the event. + private void CoreDispatcher_AcceleratorKeyActivated(CoreDispatcher sender, + AcceleratorKeyEventArgs e) + { + var virtualKey = e.VirtualKey; + + // Only investigate further when Left, Right, or the dedicated Previous or Next keys + // are pressed + if ((e.EventType == CoreAcceleratorKeyEventType.SystemKeyDown || + e.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 + e.Handled = true; + this.GoBackCommand.Execute(null); + } + else if (((int)virtualKey == 167 && noModifiers) || + (virtualKey == VirtualKey.Right && onlyAlt)) + { + // When the next key or Alt+Right are pressed navigate forward + e.Handled = true; + this.GoForwardCommand.Execute(null); + } + } + } + + /// + /// 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. + /// + /// Instance that triggered the event. + /// Event data describing the conditions that led to the event. + private void CoreWindow_PointerPressed(CoreWindow sender, + PointerEventArgs e) + { + var properties = e.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) + { + e.Handled = true; + if (backPressed) this.GoBackCommand.Execute(null); + if (forwardPressed) this.GoForwardCommand.Execute(null); + } + } +#endif + + #endregion + + #region Process lifetime management + + private String _pageKey; + + /// + /// Register this event on the current page to populate the page + /// with content passed during navigation as well as any saved + /// state provided when recreating a page from a prior session. + /// + public event LoadStateEventHandler LoadState; + /// + /// Register this event on the current page to preserve + /// state associated with the current page in case the + /// application is suspended or the page is discarded from + /// the navigaqtion cache. + /// + public event SaveStateEventHandler SaveState; + + /// + /// Invoked when this page is about to be displayed in a Frame. + /// This method calls , where all page specific + /// navigation and process lifetime management logic should be placed. + /// + /// Event data that describes how this page was reached. The Parameter + /// property provides the group to be displayed. + public void OnNavigatedTo(NavigationEventArgs e) + { + 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 + if (this.LoadState != null) + { + this.LoadState(this, new LoadStateEventArgs(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 + if (this.LoadState != null) + { + this.LoadState(this, new LoadStateEventArgs(e.Parameter, (Dictionary)frameState[this._pageKey])); + } + } + } + + /// + /// Invoked when this page will no longer be displayed in a Frame. + /// This method calls , where all page specific + /// navigation and process lifetime management logic should be placed. + /// + /// Event data that describes how this page was reached. The Parameter + /// property provides the group to be displayed. + public void OnNavigatedFrom(NavigationEventArgs e) + { + var frameState = SuspensionManager.SessionStateForFrame(this.Frame); + var pageState = new Dictionary(); + if (this.SaveState != null) + { + this.SaveState(this, new SaveStateEventArgs(pageState)); + } + frameState[_pageKey] = pageState; + } + + #endregion + } + + /// + /// Represents the method that will handle the event + /// + public delegate void LoadStateEventHandler(object sender, LoadStateEventArgs e); + /// + /// Represents the method that will handle the event + /// + public delegate void SaveStateEventHandler(object sender, SaveStateEventArgs e); + + /// + /// Class used to hold the event data required when a page attempts to load state. + /// + public class LoadStateEventArgs : EventArgs + { + /// + /// The parameter value passed to + /// when this page was initially requested. + /// + public Object NavigationParameter { get; private set; } + /// + /// A dictionary of state preserved by this page during an earlier + /// session. This will be null the first time a page is visited. + /// + public Dictionary PageState { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The parameter value passed to + /// when this page was initially requested. + /// + /// + /// A dictionary of state preserved by this page during an earlier + /// session. This will be null the first time a page is visited. + /// + public LoadStateEventArgs(Object navigationParameter, Dictionary pageState) + : base() + { + this.NavigationParameter = navigationParameter; + this.PageState = pageState; + } + } + /// + /// Class used to hold the event data required when a page attempts to save state. + /// + public class SaveStateEventArgs : EventArgs + { + /// + /// An empty dictionary to be populated with serializable state. + /// + public Dictionary PageState { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// An empty dictionary to be populated with serializable state. + public SaveStateEventArgs(Dictionary pageState) + : base() + { + this.PageState = pageState; + } + } +} diff --git a/Pyramid2000/Pyramid2000.WindowsPhone/Common/ObservableDictionary.cs b/Pyramid2000/Pyramid2000.WindowsPhone/Common/ObservableDictionary.cs new file mode 100644 index 0000000..6783007 --- /dev/null +++ b/Pyramid2000/Pyramid2000.WindowsPhone/Common/ObservableDictionary.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Windows.Foundation.Collections; + +namespace Pyramid2000.Common +{ + /// + /// Implementation of IObservableMap that supports reentrancy for use as a default view + /// model. + /// + public class ObservableDictionary : IObservableMap + { + private class ObservableDictionaryChangedEventArgs : IMapChangedEventArgs + { + public ObservableDictionaryChangedEventArgs(CollectionChange change, string key) + { + this.CollectionChange = change; + this.Key = key; + } + + public CollectionChange CollectionChange { get; private set; } + public string Key { get; private set; } + } + + private Dictionary _dictionary = new Dictionary(); + public event MapChangedEventHandler MapChanged; + + private void InvokeMapChanged(CollectionChange change, string key) + { + var eventHandler = MapChanged; + if (eventHandler != null) + { + eventHandler(this, new ObservableDictionaryChangedEventArgs(change, key)); + } + } + + public void Add(string key, object value) + { + this._dictionary.Add(key, value); + this.InvokeMapChanged(CollectionChange.ItemInserted, key); + } + + public void Add(KeyValuePair item) + { + this.Add(item.Key, item.Value); + } + + public bool Remove(string key) + { + if (this._dictionary.Remove(key)) + { + this.InvokeMapChanged(CollectionChange.ItemRemoved, key); + return true; + } + return false; + } + + public bool Remove(KeyValuePair item) + { + object 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 object this[string 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 Keys + { + get { return this._dictionary.Keys; } + } + + public bool ContainsKey(string key) + { + return this._dictionary.ContainsKey(key); + } + + public bool TryGetValue(string key, out object value) + { + return this._dictionary.TryGetValue(key, out value); + } + + public ICollection Values + { + get { return this._dictionary.Values; } + } + + public bool Contains(KeyValuePair item) + { + return this._dictionary.Contains(item); + } + + public int Count + { + get { return this._dictionary.Count; } + } + + public bool IsReadOnly + { + get { return false; } + } + + public IEnumerator> GetEnumerator() + { + return this._dictionary.GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return this._dictionary.GetEnumerator(); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + int arraySize = array.Length; + foreach (var pair in this._dictionary) + { + if (arrayIndex >= arraySize) break; + array[arrayIndex++] = pair; + } + } + } +} diff --git a/Pyramid2000/Pyramid2000.WindowsPhone/Common/RelayCommand.cs b/Pyramid2000/Pyramid2000.WindowsPhone/Common/RelayCommand.cs new file mode 100644 index 0000000..3c51055 --- /dev/null +++ b/Pyramid2000/Pyramid2000.WindowsPhone/Common/RelayCommand.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace Pyramid2000.Common +{ + /// + /// A command whose sole purpose is to relay its functionality + /// to other objects by invoking delegates. + /// The default return value for the CanExecute method is 'true'. + /// needs to be called whenever + /// is expected to return a different value. + /// + public class RelayCommand : ICommand + { + private readonly Action _execute; + private readonly Func _canExecute; + + /// + /// Raised when RaiseCanExecuteChanged is called. + /// + public event EventHandler CanExecuteChanged; + + /// + /// Creates a new command that can always execute. + /// + /// The execution logic. + public RelayCommand(Action execute) + : this(execute, null) + { + } + + /// + /// Creates a new command. + /// + /// The execution logic. + /// The execution status logic. + public RelayCommand(Action execute, Func canExecute) + { + if (execute == null) + throw new ArgumentNullException("execute"); + _execute = execute; + _canExecute = canExecute; + } + + /// + /// Determines whether this can execute in its current state. + /// + /// + /// Data used by the command. If the command does not require data to be passed, this object can be set to null. + /// + /// true if this command can be executed; otherwise, false. + public bool CanExecute(object parameter) + { + return _canExecute == null ? true : _canExecute(); + } + + /// + /// Executes the on the current command target. + /// + /// + /// Data used by the command. If the command does not require data to be passed, this object can be set to null. + /// + public void Execute(object parameter) + { + _execute(); + } + + /// + /// Method used to raise the event + /// to indicate that the return value of the + /// method has changed. + /// + public void RaiseCanExecuteChanged() + { + var handler = CanExecuteChanged; + if (handler != null) + { + handler(this, EventArgs.Empty); + } + } + } +} \ No newline at end of file diff --git a/Pyramid2000/Pyramid2000.WindowsPhone/Common/SuspensionManager.cs b/Pyramid2000/Pyramid2000.WindowsPhone/Common/SuspensionManager.cs new file mode 100644 index 0000000..4f7e3d6 --- /dev/null +++ b/Pyramid2000/Pyramid2000.WindowsPhone/Common/SuspensionManager.cs @@ -0,0 +1,269 @@ +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 Pyramid2000.Common +{ + /// + /// 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. + /// + internal sealed class SuspensionManager + { + private static Dictionary _sessionState = new Dictionary(); + private static List _knownTypes = new List(); + private const string sessionStateFilename = "_sessionState.xml"; + + /// + /// Provides access to global session state for the current session. This state is + /// serialized by and restored by + /// , so values must be serializable by + /// and should be as compact as possible. Strings + /// and other self-contained data types are strongly recommended. + /// + public static Dictionary SessionState + { + get { return _sessionState; } + } + + /// + /// List of custom types provided to the when + /// reading and writing session state. Initially empty, additional types may be + /// added to customize the serialization process. + /// + public static List KnownTypes + { + get { return _knownTypes; } + } + + /// + /// Save the current . Any instances + /// registered with will also preserve their current + /// navigation stack, which in turn gives their active an opportunity + /// to save its state. + /// + /// An asynchronous task that reflects when session state has been saved. + 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), _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); + } + } + catch (Exception e) + { + throw new SuspensionManagerException(e); + } + } + + /// + /// Restores previously saved . Any instances + /// registered with will also restore their prior navigation + /// state, which in turn gives their active an opportunity restore its + /// state. + /// + /// An optional key that identifies the type of session. + /// This can be used to distinguish between multiple application launch scenarios. + /// An asynchronous task that reflects when session state has been read. The + /// content of should not be relied upon until this task + /// completes. + public static async Task RestoreAsync(String sessionBaseKey = null) + { + _sessionState = new Dictionary(); + + 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), _knownTypes); + _sessionState = (Dictionary)serializer.ReadObject(inStream.AsStreamForRead()); + } + + // Restore any registered frames to their saved state + foreach (var weakFrameReference in _registeredFrames) + { + Frame frame; + if (weakFrameReference.TryGetTarget(out frame) && (string)frame.GetValue(FrameSessionBaseKeyProperty) == sessionBaseKey) + { + 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 FrameSessionBaseKeyProperty = + DependencyProperty.RegisterAttached("_FrameSessionBaseKeyParams", typeof(String), typeof(SuspensionManager), null); + private static DependencyProperty FrameSessionStateProperty = + DependencyProperty.RegisterAttached("_FrameSessionState", typeof(Dictionary), typeof(SuspensionManager), null); + private static List> _registeredFrames = new List>(); + + /// + /// Registers a instance to allow its navigation history to be saved to + /// and restored from . 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 + /// will also restore navigation history. + /// + /// An instance whose navigation history should be managed by + /// + /// A unique key into used to + /// store navigation-related information. + /// An optional key that identifies the type of session. + /// This can be used to distinguish between multiple application launch scenarios. + public static void RegisterFrame(Frame frame, String sessionStateKey, String sessionBaseKey = null) + { + 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"); + } + + if (!string.IsNullOrEmpty(sessionBaseKey)) + { + frame.SetValue(FrameSessionBaseKeyProperty, sessionBaseKey); + sessionStateKey = sessionBaseKey + "_" + sessionStateKey; + } + + // 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)); + + // Check to see if navigation state can be restored + RestoreFrameNavigationState(frame); + } + + /// + /// Disassociates a previously registered by + /// from . Any navigation state previously captured will be + /// removed. + /// + /// An instance whose navigation history should no longer be + /// managed. + 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; + }); + } + + /// + /// Provides storage for session state associated with the specified . + /// Frames that have been previously registered with have + /// their session state saved and restored automatically as a part of the global + /// . Frames that are not registered have transient state + /// that can still be useful when restoring pages that have been discarded from the + /// navigation cache. + /// + /// Apps may choose to rely on to manage + /// page-specific state instead of working with frame session state directly. + /// The instance for which session state is desired. + /// A collection of state subject to the same serialization mechanism as + /// . + public static Dictionary SessionStateForFrame(Frame frame) + { + var frameState = (Dictionary)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(); + } + frameState = (Dictionary)_sessionState[frameSessionKey]; + } + else + { + // Frames that aren't registered have transient state + frameState = new Dictionary(); + } + 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) + { + + } + } +} diff --git a/Pyramid2000/Pyramid2000.WindowsPhone/MainPage.xaml b/Pyramid2000/Pyramid2000.WindowsPhone/MainPage.xaml index 9b9bf66..1c839c0 100644 --- a/Pyramid2000/Pyramid2000.WindowsPhone/MainPage.xaml +++ b/Pyramid2000/Pyramid2000.WindowsPhone/MainPage.xaml @@ -9,10 +9,31 @@ Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> - -