file360

Log | Files | Refs

NavigationHelper.cs (18723B)


      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.Text;
      5 using System.Threading.Tasks;
      6 using System.Windows.Input;
      7 using Windows.System;
      8 using Windows.UI.Core;
      9 using Windows.UI.Xaml;
     10 using Windows.UI.Xaml.Controls;
     11 using Windows.UI.Xaml.Navigation;
     12 
     13 namespace File360.Common
     14 {
     15     /// <summary>
     16     /// NavigationHelper aids in navigation between pages.  It provides commands used to 
     17     /// navigate back and forward as well as registers for standard mouse and keyboard 
     18     /// shortcuts used to go back and forward in Windows and the hardware back button in
     19     /// Windows Phone.  In addition it integrates SuspensionManger to handle process lifetime
     20     /// management and state management when navigating between pages.
     21     /// </summary>
     22     /// <example>
     23     /// To make use of NavigationHelper, follow these two steps or
     24     /// start with a BasicPage or any other Page item template other than BlankPage.
     25     /// 
     26     /// 1) Create an instance of the NavigationHelper somewhere such as in the 
     27     ///     constructor for the page and register a callback for the LoadState and 
     28     ///     SaveState events.
     29     /// <code>
     30     ///     public MyPage()
     31     ///     {
     32     ///         this.InitializeComponent();
     33     ///         var navigationHelper = new NavigationHelper(this);
     34     ///         this.navigationHelper.LoadState += navigationHelper_LoadState;
     35     ///         this.navigationHelper.SaveState += navigationHelper_SaveState;
     36     ///     }
     37     ///     
     38     ///     private async void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
     39     ///     { }
     40     ///     private async void navigationHelper_SaveState(object sender, LoadStateEventArgs e)
     41     ///     { }
     42     /// </code>
     43     /// 
     44     /// 2) Register the page to call into the NavigationHelper whenever the page participates 
     45     ///     in navigation by overriding the <see cref="Windows.UI.Xaml.Controls.Page.OnNavigatedTo"/> 
     46     ///     and <see cref="Windows.UI.Xaml.Controls.Page.OnNavigatedFrom"/> events.
     47     /// <code>
     48     ///     protected override void OnNavigatedTo(NavigationEventArgs e)
     49     ///     {
     50     ///         navigationHelper.OnNavigatedTo(e);
     51     ///     }
     52     ///     
     53     ///     protected override void OnNavigatedFrom(NavigationEventArgs e)
     54     ///     {
     55     ///         navigationHelper.OnNavigatedFrom(e);
     56     ///     }
     57     /// </code>
     58     /// </example>
     59     [Windows.Foundation.Metadata.WebHostHidden]
     60     public class NavigationHelper : DependencyObject
     61     {
     62         private Page Page { get; set; }
     63         private Frame Frame { get { return this.Page.Frame; } }
     64 
     65         /// <summary>
     66         /// Initializes a new instance of the <see cref="NavigationHelper"/> class.
     67         /// </summary>
     68         /// <param name="page">A reference to the current page used for navigation.  
     69         /// This reference allows for frame manipulation and to ensure that keyboard 
     70         /// navigation requests only occur when the page is occupying the entire window.</param>
     71         public NavigationHelper(Page page)
     72         {
     73             this.Page = page;
     74 
     75             // When this page is part of the visual tree make two changes:
     76             // 1) Map application view state to visual state for the page
     77             // 2) Handle hardware navigation requests
     78             this.Page.Loaded += (sender, e) =>
     79             {
     80 #if WINDOWS_PHONE_APP
     81                 Windows.Phone.UI.Input.HardwareButtons.BackPressed += HardwareButtons_BackPressed;
     82 #else
     83                 // Keyboard and mouse navigation only apply when occupying the entire window
     84                 if (this.Page.ActualHeight == Window.Current.Bounds.Height &&
     85                     this.Page.ActualWidth == Window.Current.Bounds.Width)
     86                 {
     87                     // Listen to the window directly so focus isn't required
     88                     Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated +=
     89                         CoreDispatcher_AcceleratorKeyActivated;
     90                     Window.Current.CoreWindow.PointerPressed +=
     91                         this.CoreWindow_PointerPressed;
     92                 }
     93 #endif
     94             };
     95 
     96             // Undo the same changes when the page is no longer visible
     97             this.Page.Unloaded += (sender, e) =>
     98             {
     99 #if WINDOWS_PHONE_APP
    100                 Windows.Phone.UI.Input.HardwareButtons.BackPressed -= HardwareButtons_BackPressed;
    101 #else
    102                 Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated -=
    103                     CoreDispatcher_AcceleratorKeyActivated;
    104                 Window.Current.CoreWindow.PointerPressed -=
    105                     this.CoreWindow_PointerPressed;
    106 #endif
    107             };
    108         }
    109 
    110         #region Navigation support
    111 
    112         RelayCommand _goBackCommand;
    113         RelayCommand _goForwardCommand;
    114 
    115         /// <summary>
    116         /// <see cref="RelayCommand"/> used to bind to the back Button's Command property
    117         /// for navigating to the most recent item in back navigation history, if a Frame
    118         /// manages its own navigation history.
    119         /// 
    120         /// The <see cref="RelayCommand"/> is set up to use the virtual method <see cref="GoBack"/>
    121         /// as the Execute Action and <see cref="CanGoBack"/> for CanExecute.
    122         /// </summary>
    123         public RelayCommand GoBackCommand
    124         {
    125             get
    126             {
    127                 if (_goBackCommand == null)
    128                 {
    129                     _goBackCommand = new RelayCommand(
    130                         () => this.GoBack(),
    131                         () => this.CanGoBack());
    132                 }
    133                 return _goBackCommand;
    134             }
    135             set
    136             {
    137                 _goBackCommand = value;
    138             }
    139         }
    140         /// <summary>
    141         /// <see cref="RelayCommand"/> used for navigating to the most recent item in 
    142         /// the forward navigation history, if a Frame manages its own navigation history.
    143         /// 
    144         /// The <see cref="RelayCommand"/> is set up to use the virtual method <see cref="GoForward"/>
    145         /// as the Execute Action and <see cref="CanGoForward"/> for CanExecute.
    146         /// </summary>
    147         public RelayCommand GoForwardCommand
    148         {
    149             get
    150             {
    151                 if (_goForwardCommand == null)
    152                 {
    153                     _goForwardCommand = new RelayCommand(
    154                         () => this.GoForward(),
    155                         () => this.CanGoForward());
    156                 }
    157                 return _goForwardCommand;
    158             }
    159         }
    160 
    161         /// <summary>
    162         /// Virtual method used by the <see cref="GoBackCommand"/> property
    163         /// to determine if the <see cref="Frame"/> can go back.
    164         /// </summary>
    165         /// <returns>
    166         /// true if the <see cref="Frame"/> has at least one entry 
    167         /// in the back navigation history.
    168         /// </returns>
    169         public virtual bool CanGoBack()
    170         {
    171             return this.Frame != null && this.Frame.CanGoBack;
    172         }
    173         /// <summary>
    174         /// Virtual method used by the <see cref="GoForwardCommand"/> property
    175         /// to determine if the <see cref="Frame"/> can go forward.
    176         /// </summary>
    177         /// <returns>
    178         /// true if the <see cref="Frame"/> has at least one entry 
    179         /// in the forward navigation history.
    180         /// </returns>
    181         public virtual bool CanGoForward()
    182         {
    183             return this.Frame != null && this.Frame.CanGoForward;
    184         }
    185 
    186         /// <summary>
    187         /// Virtual method used by the <see cref="GoBackCommand"/> property
    188         /// to invoke the <see cref="Windows.UI.Xaml.Controls.Frame.GoBack"/> method.
    189         /// </summary>
    190         public virtual void GoBack()
    191         {
    192             if (this.Frame != null && this.Frame.CanGoBack) this.Frame.GoBack();
    193         }
    194         /// <summary>
    195         /// Virtual method used by the <see cref="GoForwardCommand"/> property
    196         /// to invoke the <see cref="Windows.UI.Xaml.Controls.Frame.GoForward"/> method.
    197         /// </summary>
    198         public virtual void GoForward()
    199         {
    200             if (this.Frame != null && this.Frame.CanGoForward) this.Frame.GoForward();
    201         }
    202 
    203 #if WINDOWS_PHONE_APP
    204         /// <summary>
    205         /// Invoked when the hardware back button is pressed. For Windows Phone only.
    206         /// </summary>
    207         /// <param name="sender">Instance that triggered the event.</param>
    208         /// <param name="e">Event data describing the conditions that led to the event.</param>
    209         private void HardwareButtons_BackPressed(object sender, Windows.Phone.UI.Input.BackPressedEventArgs e)
    210         {
    211             if (this.GoBackCommand.CanExecute(null))
    212             {
    213                 e.Handled = true;
    214                 this.GoBackCommand.Execute(null);
    215             }
    216         }
    217 #else
    218         /// <summary>
    219         /// Invoked on every keystroke, including system keys such as Alt key combinations, when
    220         /// this page is active and occupies the entire window.  Used to detect keyboard navigation
    221         /// between pages even when the page itself doesn't have focus.
    222         /// </summary>
    223         /// <param name="sender">Instance that triggered the event.</param>
    224         /// <param name="e">Event data describing the conditions that led to the event.</param>
    225         private void CoreDispatcher_AcceleratorKeyActivated(CoreDispatcher sender,
    226             AcceleratorKeyEventArgs e)
    227         {
    228             var virtualKey = e.VirtualKey;
    229 
    230             // Only investigate further when Left, Right, or the dedicated Previous or Next keys
    231             // are pressed
    232             if ((e.EventType == CoreAcceleratorKeyEventType.SystemKeyDown ||
    233                 e.EventType == CoreAcceleratorKeyEventType.KeyDown) &&
    234                 (virtualKey == VirtualKey.Left || virtualKey == VirtualKey.Right ||
    235                 (int)virtualKey == 166 || (int)virtualKey == 167))
    236             {
    237                 var coreWindow = Window.Current.CoreWindow;
    238                 var downState = CoreVirtualKeyStates.Down;
    239                 bool menuKey = (coreWindow.GetKeyState(VirtualKey.Menu) & downState) == downState;
    240                 bool controlKey = (coreWindow.GetKeyState(VirtualKey.Control) & downState) == downState;
    241                 bool shiftKey = (coreWindow.GetKeyState(VirtualKey.Shift) & downState) == downState;
    242                 bool noModifiers = !menuKey && !controlKey && !shiftKey;
    243                 bool onlyAlt = menuKey && !controlKey && !shiftKey;
    244 
    245                 if (((int)virtualKey == 166 && noModifiers) ||
    246                     (virtualKey == VirtualKey.Left && onlyAlt))
    247                 {
    248                     // When the previous key or Alt+Left are pressed navigate back
    249                     e.Handled = true;
    250                     this.GoBackCommand.Execute(null);
    251                 }
    252                 else if (((int)virtualKey == 167 && noModifiers) ||
    253                     (virtualKey == VirtualKey.Right && onlyAlt))
    254                 {
    255                     // When the next key or Alt+Right are pressed navigate forward
    256                     e.Handled = true;
    257                     this.GoForwardCommand.Execute(null);
    258                 }
    259             }
    260         }
    261 
    262         /// <summary>
    263         /// Invoked on every mouse click, touch screen tap, or equivalent interaction when this
    264         /// page is active and occupies the entire window.  Used to detect browser-style next and
    265         /// previous mouse button clicks to navigate between pages.
    266         /// </summary>
    267         /// <param name="sender">Instance that triggered the event.</param>
    268         /// <param name="e">Event data describing the conditions that led to the event.</param>
    269         private void CoreWindow_PointerPressed(CoreWindow sender,
    270             PointerEventArgs e)
    271         {
    272             var properties = e.CurrentPoint.Properties;
    273 
    274             // Ignore button chords with the left, right, and middle buttons
    275             if (properties.IsLeftButtonPressed || properties.IsRightButtonPressed ||
    276                 properties.IsMiddleButtonPressed) return;
    277 
    278             // If back or foward are pressed (but not both) navigate appropriately
    279             bool backPressed = properties.IsXButton1Pressed;
    280             bool forwardPressed = properties.IsXButton2Pressed;
    281             if (backPressed ^ forwardPressed)
    282             {
    283                 e.Handled = true;
    284                 if (backPressed) this.GoBackCommand.Execute(null);
    285                 if (forwardPressed) this.GoForwardCommand.Execute(null);
    286             }
    287         }
    288 #endif
    289 
    290         #endregion
    291 
    292         #region Process lifetime management
    293 
    294         private String _pageKey;
    295 
    296         /// <summary>
    297         /// Register this event on the current page to populate the page
    298         /// with content passed during navigation as well as any saved
    299         /// state provided when recreating a page from a prior session.
    300         /// </summary>
    301         public event LoadStateEventHandler LoadState;
    302         /// <summary>
    303         /// Register this event on the current page to preserve
    304         /// state associated with the current page in case the
    305         /// application is suspended or the page is discarded from
    306         /// the navigaqtion cache.
    307         /// </summary>
    308         public event SaveStateEventHandler SaveState;
    309 
    310         /// <summary>
    311         /// Invoked when this page is about to be displayed in a Frame.  
    312         /// This method calls <see cref="LoadState"/>, where all page specific
    313         /// navigation and process lifetime management logic should be placed.
    314         /// </summary>
    315         /// <param name="e">Event data that describes how this page was reached.  The Parameter
    316         /// property provides the group to be displayed.</param>
    317         public void OnNavigatedTo(NavigationEventArgs e)
    318         {
    319             var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
    320             this._pageKey = "Page-" + this.Frame.BackStackDepth;
    321 
    322             if (e.NavigationMode == NavigationMode.New)
    323             {
    324                 // Clear existing state for forward navigation when adding a new page to the
    325                 // navigation stack
    326                 var nextPageKey = this._pageKey;
    327                 int nextPageIndex = this.Frame.BackStackDepth;
    328                 while (frameState.Remove(nextPageKey))
    329                 {
    330                     nextPageIndex++;
    331                     nextPageKey = "Page-" + nextPageIndex;
    332                 }
    333 
    334                 // Pass the navigation parameter to the new page
    335                 if (this.LoadState != null)
    336                 {
    337                     this.LoadState(this, new LoadStateEventArgs(e.Parameter, null));
    338                 }
    339             }
    340             else
    341             {
    342                 // Pass the navigation parameter and preserved page state to the page, using
    343                 // the same strategy for loading suspended state and recreating pages discarded
    344                 // from cache
    345                 if (this.LoadState != null)
    346                 {
    347                     this.LoadState(this, new LoadStateEventArgs(e.Parameter, (Dictionary<String, Object>)frameState[this._pageKey]));
    348                 }
    349             }
    350         }
    351 
    352         /// <summary>
    353         /// Invoked when this page will no longer be displayed in a Frame.
    354         /// This method calls <see cref="SaveState"/>, where all page specific
    355         /// navigation and process lifetime management logic should be placed.
    356         /// </summary>
    357         /// <param name="e">Event data that describes how this page was reached.  The Parameter
    358         /// property provides the group to be displayed.</param>
    359         public void OnNavigatedFrom(NavigationEventArgs e)
    360         {
    361             var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
    362             var pageState = new Dictionary<String, Object>();
    363             if (this.SaveState != null)
    364             {
    365                 this.SaveState(this, new SaveStateEventArgs(pageState));
    366             }
    367             frameState[_pageKey] = pageState;
    368         }
    369 
    370         #endregion
    371     }
    372 
    373     /// <summary>
    374     /// Represents the method that will handle the <see cref="NavigationHelper.LoadState"/>event
    375     /// </summary>
    376     public delegate void LoadStateEventHandler(object sender, LoadStateEventArgs e);
    377     /// <summary>
    378     /// Represents the method that will handle the <see cref="NavigationHelper.SaveState"/>event
    379     /// </summary>
    380     public delegate void SaveStateEventHandler(object sender, SaveStateEventArgs e);
    381 
    382     /// <summary>
    383     /// Class used to hold the event data required when a page attempts to load state.
    384     /// </summary>
    385     public class LoadStateEventArgs : EventArgs
    386     {
    387         /// <summary>
    388         /// The parameter value passed to <see cref="Frame.Navigate(Type, Object)"/> 
    389         /// when this page was initially requested.
    390         /// </summary>
    391         public Object NavigationParameter { get; private set; }
    392         /// <summary>
    393         /// A dictionary of state preserved by this page during an earlier
    394         /// session.  This will be null the first time a page is visited.
    395         /// </summary>
    396         public Dictionary<string, Object> PageState { get; private set; }
    397 
    398         /// <summary>
    399         /// Initializes a new instance of the <see cref="LoadStateEventArgs"/> class.
    400         /// </summary>
    401         /// <param name="navigationParameter">
    402         /// The parameter value passed to <see cref="Frame.Navigate(Type, Object)"/> 
    403         /// when this page was initially requested.
    404         /// </param>
    405         /// <param name="pageState">
    406         /// A dictionary of state preserved by this page during an earlier
    407         /// session.  This will be null the first time a page is visited.
    408         /// </param>
    409         public LoadStateEventArgs(Object navigationParameter, Dictionary<string, Object> pageState)
    410             : base()
    411         {
    412             this.NavigationParameter = navigationParameter;
    413             this.PageState = pageState;
    414         }
    415     }
    416     /// <summary>
    417     /// Class used to hold the event data required when a page attempts to save state.
    418     /// </summary>
    419     public class SaveStateEventArgs : EventArgs
    420     {
    421         /// <summary>
    422         /// An empty dictionary to be populated with serializable state.
    423         /// </summary>
    424         public Dictionary<string, Object> PageState { get; private set; }
    425 
    426         /// <summary>
    427         /// Initializes a new instance of the <see cref="SaveStateEventArgs"/> class.
    428         /// </summary>
    429         /// <param name="pageState">An empty dictionary to be populated with serializable state.</param>
    430         public SaveStateEventArgs(Dictionary<string, Object> pageState)
    431             : base()
    432         {
    433             this.PageState = pageState;
    434         }
    435     }
    436 }