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 }