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;
}
}
}