using System; using System.Collections.Generic; using Windows.Foundation.Metadata; 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); /// } /// /// [WebHostHidden] public class NavigationHelper : DependencyObject { private Page Page { get; set; } private Frame Frame => 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) { 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 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.Equals(Window.Current.Bounds.Height) && Page.ActualWidth.Equals(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 += CoreWindow_PointerPressed; } #endif }; // Undo the same changes when the page is no longer visible 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 -= CoreWindow_PointerPressed; #endif }; } #region Navigation support private RelayCommand _goBackCommand; private 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 { return _goBackCommand ?? (_goBackCommand = new RelayCommand(GoBack, CanGoBack)); } 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 => _goForwardCommand ?? (_goForwardCommand = new RelayCommand(GoForward, CanGoForward)); /// /// 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 Frame != null && 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 Frame != null && Frame.CanGoForward; } /// /// Virtual method used by the property /// to invoke the method. /// public virtual void GoBack() { if (Frame != null && Frame.CanGoBack) { Frame.GoBack(); } } /// /// Virtual method used by the property /// to invoke the method. /// public virtual void GoForward() { if (Frame != null && Frame.CanGoForward) { 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; const CoreVirtualKeyStates downState = CoreVirtualKeyStates.Down; var menuKey = (coreWindow.GetKeyState(VirtualKey.Menu) & downState) == downState; var controlKey = (coreWindow.GetKeyState(VirtualKey.Control) & downState) == downState; var shiftKey = (coreWindow.GetKeyState(VirtualKey.Shift) & downState) == downState; var noModifiers = !menuKey && !controlKey && !shiftKey; var 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; 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; 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 forward are pressed (but not both) navigate appropriately var backPressed = properties.IsXButton1Pressed; var forwardPressed = properties.IsXButton2Pressed; if (backPressed ^ forwardPressed) { e.Handled = true; if (backPressed) { GoBackCommand.Execute(null); } if (forwardPressed) { 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 navigation 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(Frame); _pageKey = "Page-" + Frame.BackStackDepth; if (e.NavigationMode == NavigationMode.New) { // Clear existing state for forward navigation when adding a new page to the // navigation stack var nextPageKey = _pageKey; var nextPageIndex = Frame.BackStackDepth; while (frameState.Remove(nextPageKey)) { nextPageIndex++; nextPageKey = "Page-" + nextPageIndex; } // Pass the navigation parameter to the new page LoadState?.Invoke(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 LoadState?.Invoke(this, new LoadStateEventArgs(e.Parameter, (Dictionary) frameState[_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(Frame); var pageState = new Dictionary(); SaveState?.Invoke(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) { NavigationParameter = navigationParameter; 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) { PageState = pageState; } } }