using System; using System.Collections.Generic; using Windows.System; using Windows.UI.Core; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Navigation; using GalaSoft.MvvmLight.Command; namespace ModernKeePass.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 (Page.ActualHeight == Window.Current.Bounds.Height && 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; } } }