1 //*********************************************************
2 //
3 // Copyright (c) Microsoft. All rights reserved.
4 // THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
5 // ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
6 // IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
7 // PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
8 //
9 //*********************************************************
10 
11 #include "pch.h"
12 #include "LayoutAwarePage.h"
13 #include "SuspensionManager.h"
14 
15 using namespace SDKSample::Common;
16 
17 using namespace Platform;
18 using namespace Platform::Collections;
19 using namespace Windows::Foundation;
20 using namespace Windows::Foundation::Collections;
21 using namespace Windows::System;
22 using namespace Windows::UI::Core;
23 using namespace Windows::UI::ViewManagement;
24 using namespace Windows::UI::Xaml;
25 using namespace Windows::UI::Xaml::Controls;
26 using namespace Windows::UI::Xaml::Interop;
27 using namespace Windows::UI::Xaml::Navigation;
28 
29 /// <summary>
30 /// Initializes a new instance of the <see cref="LayoutAwarePage"/> class.
31 /// </summary>
LayoutAwarePage()32 LayoutAwarePage::LayoutAwarePage()
33 {
34     if (Windows::ApplicationModel::DesignMode::DesignModeEnabled)
35     {
36         return;
37     }
38 
39     // Create an empty default view model
40     DefaultViewModel = ref new Map<String^, Object^>(std::less<String^>());
41 
42     // When this page is part of the visual tree make two changes:
43     // 1) Map application view state to visual state for the page
44     // 2) Handle keyboard and mouse navigation requests
45     Loaded += ref new RoutedEventHandler(this, &LayoutAwarePage::OnLoaded);
46 
47     // Undo the same changes when the page is no longer visible
48     Unloaded += ref new RoutedEventHandler(this, &LayoutAwarePage::OnUnloaded);
49 }
50 
51 static DependencyProperty^ _defaultViewModelProperty =
52     DependencyProperty::Register("DefaultViewModel",
53     TypeName(IObservableMap<String^, Object^>::typeid), TypeName(LayoutAwarePage::typeid), nullptr);
54 
55 /// <summary>
56 /// Identifies the <see cref="DefaultViewModel"/> dependency property.
57 /// </summary>
58 DependencyProperty^ LayoutAwarePage::DefaultViewModelProperty::get()
59 {
60     return _defaultViewModelProperty;
61 }
62 
63 /// <summary>
64 /// Gets an implementation of <see cref="IObservableMap&lt;String, Object&gt;"/> designed to be
65 /// used as a trivial view model.
66 /// </summary>
67 IObservableMap<String^, Object^>^ LayoutAwarePage::DefaultViewModel::get()
68 {
69     return safe_cast<IObservableMap<String^, Object^>^>(GetValue(DefaultViewModelProperty));
70 }
71 
72 /// <summary>
73 /// Sets an implementation of <see cref="IObservableMap&lt;String, Object&gt;"/> designed to be
74 /// used as a trivial view model.
75 /// </summary>
76 void LayoutAwarePage::DefaultViewModel::set(IObservableMap<String^, Object^>^ value)
77 {
78     SetValue(DefaultViewModelProperty, value);
79 }
80 
81 /// <summary>
82 /// Invoked when the page is part of the visual tree
83 /// </summary>
84 /// <param name="sender">Instance that triggered the event.</param>
85 /// <param name="e">Event data describing the conditions that led to the event.</param>
86 void LayoutAwarePage::OnLoaded(Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
87 {
88     this->StartLayoutUpdates(sender, e);
89 
90     // Keyboard and mouse navigation only apply when occupying the entire window
91     if (this->ActualHeight == Window::Current->Bounds.Height &&
92         this->ActualWidth == Window::Current->Bounds.Width)
93     {
94         // Listen to the window directly so focus isn't required
95         _acceleratorKeyEventToken = Window::Current->CoreWindow->Dispatcher->AcceleratorKeyActivated +=
96             ref new TypedEventHandler<CoreDispatcher^, AcceleratorKeyEventArgs^>(this,
97             &LayoutAwarePage::CoreDispatcher_AcceleratorKeyActivated);
98         _pointerPressedEventToken = Window::Current->CoreWindow->PointerPressed +=
99             ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this,
100             &LayoutAwarePage::CoreWindow_PointerPressed);
101         _navigationShortcutsRegistered = true;
102     }
103 }
104 
105 /// <summary>
106 /// Invoked when the page is removed from visual tree
107 /// </summary>
108 /// <param name="sender">Instance that triggered the event.</param>
109 /// <param name="e">Event data describing the conditions that led to the event.</param>
110 void LayoutAwarePage::OnUnloaded(Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
111 {
112     if (_navigationShortcutsRegistered)
113     {
114         Window::Current->CoreWindow->Dispatcher->AcceleratorKeyActivated -= _acceleratorKeyEventToken;
115         Window::Current->CoreWindow->PointerPressed -= _pointerPressedEventToken;
116         _navigationShortcutsRegistered = false;
117     }
118     StopLayoutUpdates(sender, e);
119 }
120 
121 #pragma region Navigation support
122 
123 /// <summary>
124 /// Invoked as an event handler to navigate backward in the page's associated <see cref="Frame"/>
125 /// until it reaches the top of the navigation stack.
126 /// </summary>
127 /// <param name="sender">Instance that triggered the event.</param>
128 /// <param name="e">Event data describing the conditions that led to the event.</param>
129 void LayoutAwarePage::GoHome(Object^ sender, RoutedEventArgs^ e)
130 {
131     (void) sender;	// Unused parameter
132     (void) e;	// Unused parameter
133 
134     // Use the navigation frame to return to the topmost page
135     if (Frame != nullptr)
136     {
137         while (Frame->CanGoBack)
138         {
139             Frame->GoBack();
140         }
141     }
142 }
143 
144 /// <summary>
145 /// Invoked as an event handler to navigate backward in the navigation stack
146 /// associated with this page's <see cref="Frame"/>.
147 /// </summary>
148 /// <param name="sender">Instance that triggered the event.</param>
149 /// <param name="e">Event data describing the conditions that led to the event.</param>
150 void LayoutAwarePage::GoBack(Object^ sender, RoutedEventArgs^ e)
151 {
152     (void) sender;	// Unused parameter
153     (void) e;	// Unused parameter
154 
155     // Use the navigation frame to return to the previous page
156     if (Frame != nullptr && Frame->CanGoBack)
157     {
158         Frame->GoBack();
159     }
160 }
161 
162 /// <summary>
163 /// Invoked as an event handler to navigate forward in the navigation stack
164 /// associated with this page's <see cref="Frame"/>.
165 /// </summary>
166 /// <param name="sender">Instance that triggered the event.</param>
167 /// <param name="e">Event data describing the conditions that led to the event.</param>
168 void LayoutAwarePage::GoForward(Object^ sender, RoutedEventArgs^ e)
169 {
170     (void) sender;	// Unused parameter
171     (void) e;	// Unused parameter
172 
173     // Use the navigation frame to advance to the next page
174     if (Frame != nullptr && Frame->CanGoForward)
175     {
176         Frame->GoForward();
177     }
178 }
179 
180 /// <summary>
181 /// Invoked on every keystroke, including system keys such as Alt key combinations, when
182 /// this page is active and occupies the entire window.  Used to detect keyboard navigation
183 /// between pages even when the page itself doesn't have focus.
184 /// </summary>
185 /// <param name="sender">Instance that triggered the event.</param>
186 /// <param name="args">Event data describing the conditions that led to the event.</param>
187 void LayoutAwarePage::CoreDispatcher_AcceleratorKeyActivated(CoreDispatcher^ sender, AcceleratorKeyEventArgs^ args)
188 {
189     auto virtualKey = args->VirtualKey;
190 
191     // Only investigate further when Left, Right, or the dedicated Previous or Next keys
192     // are pressed
193     if ((args->EventType == CoreAcceleratorKeyEventType::SystemKeyDown ||
194         args->EventType == CoreAcceleratorKeyEventType::KeyDown) &&
195         (virtualKey == VirtualKey::Left || virtualKey == VirtualKey::Right ||
196         (int)virtualKey == 166 || (int)virtualKey == 167))
197     {
198         auto coreWindow = Window::Current->CoreWindow;
199         auto downState = Windows::UI::Core::CoreVirtualKeyStates::Down;
200         bool menuKey = (coreWindow->GetKeyState(VirtualKey::Menu) & downState) == downState;
201         bool controlKey = (coreWindow->GetKeyState(VirtualKey::Control) & downState) == downState;
202         bool shiftKey = (coreWindow->GetKeyState(VirtualKey::Shift) & downState) == downState;
203         bool noModifiers = !menuKey && !controlKey && !shiftKey;
204         bool onlyAlt = menuKey && !controlKey && !shiftKey;
205 
206         if (((int)virtualKey == 166 && noModifiers) ||
207             (virtualKey == VirtualKey::Left && onlyAlt))
208         {
209             // When the previous key or Alt+Left are pressed navigate back
210             args->Handled = true;
211             GoBack(this, ref new RoutedEventArgs());
212         }
213         else if (((int)virtualKey == 167 && noModifiers) ||
214             (virtualKey == VirtualKey::Right && onlyAlt))
215         {
216             // When the next key or Alt+Right are pressed navigate forward
217             args->Handled = true;
218             GoForward(this, ref new RoutedEventArgs());
219         }
220     }
221 }
222 
223 /// <summary>
224 /// Invoked on every mouse click, touch screen tap, or equivalent interaction when this
225 /// page is active and occupies the entire window.  Used to detect browser-style next and
226 /// previous mouse button clicks to navigate between pages.
227 /// </summary>
228 /// <param name="sender">Instance that triggered the event.</param>
229 /// <param name="args">Event data describing the conditions that led to the event.</param>
230 void LayoutAwarePage::CoreWindow_PointerPressed(CoreWindow^ sender, PointerEventArgs^ args)
231 {
232     auto properties = args->CurrentPoint->Properties;
233 
234     // Ignore button chords with the left, right, and middle buttons
235     if (properties->IsLeftButtonPressed || properties->IsRightButtonPressed ||
236         properties->IsMiddleButtonPressed) return;
237 
238     // If back or forward are pressed (but not both) navigate appropriately
239     bool backPressed = properties->IsXButton1Pressed;
240     bool forwardPressed = properties->IsXButton2Pressed;
241     if (backPressed ^ forwardPressed)
242     {
243         args->Handled = true;
244         if (backPressed) GoBack(this, ref new RoutedEventArgs());
245         if (forwardPressed) GoForward(this, ref new RoutedEventArgs());
246     }
247 }
248 
249 #pragma endregion
250 
251 #pragma region Visual state switching
252 
253 /// <summary>
254 /// Invoked as an event handler, typically on the <see cref="Loaded"/> event of a
255 /// <see cref="Control"/> within the page, to indicate that the sender should start receiving
256 /// visual state management changes that correspond to application view state changes.
257 /// </summary>
258 /// <param name="sender">Instance of <see cref="Control"/> that supports visual state management
259 /// corresponding to view states.</param>
260 /// <param name="e">Event data that describes how the request was made.</param>
261 /// <remarks>The current view state will immediately be used to set the corresponding visual state
262 /// when layout updates are requested.  A corresponding <see cref="Unloaded"/> event handler
263 /// connected to <see cref="StopLayoutUpdates"/> is strongly encouraged.  Instances of
264 /// <see cref="LayoutAwarePage"/> automatically invoke these handlers in their Loaded and Unloaded
265 /// events.</remarks>
266 /// <seealso cref="DetermineVisualState"/>
267 /// <seealso cref="InvalidateVisualState"/>
268 void LayoutAwarePage::StartLayoutUpdates(Object^ sender, RoutedEventArgs^ e)
269 {
270     (void) e;	// Unused parameter
271 
272     auto control = safe_cast<Control^>(sender);
273     if (_layoutAwareControls == nullptr)
274     {
275         // Start listening to view state changes when there are controls interested in updates
276         _layoutAwareControls = ref new Vector<Control^>();
277         _windowSizeEventToken = Window::Current->SizeChanged += ref new WindowSizeChangedEventHandler(this, &LayoutAwarePage::WindowSizeChanged);
278 
279         // Page receives notifications for children. Protect the page until we stopped layout updates for all controls.
280         _this = this;
281     }
282     _layoutAwareControls->Append(control);
283 
284     // Set the initial visual state of the control
285     VisualStateManager::GoToState(control, DetermineVisualState(ApplicationView::Value), false);
286 }
287 
288 void LayoutAwarePage::WindowSizeChanged(Object^ sender, Windows::UI::Core::WindowSizeChangedEventArgs^ e)
289 {
290     (void) sender;	// Unused parameter
291     (void) e;	// Unused parameter
292 
293     InvalidateVisualState();
294 }
295 
296 /// <summary>
297 /// Invoked as an event handler, typically on the <see cref="Unloaded"/> event of a
298 /// <see cref="Control"/>, to indicate that the sender should start receiving visual state
299 /// management changes that correspond to application view state changes.
300 /// </summary>
301 /// <param name="sender">Instance of <see cref="Control"/> that supports visual state management
302 /// corresponding to view states.</param>
303 /// <param name="e">Event data that describes how the request was made.</param>
304 /// <remarks>The current view state will immediately be used to set the corresponding visual state
305 /// when layout updates are requested.</remarks>
306 /// <seealso cref="StartLayoutUpdates"/>
307 void LayoutAwarePage::StopLayoutUpdates(Object^ sender, RoutedEventArgs^ e)
308 {
309     (void) e;	// Unused parameter
310 
311     auto control = safe_cast<Control^>(sender);
312     unsigned int index;
313     if (_layoutAwareControls != nullptr && _layoutAwareControls->IndexOf(control, &index))
314     {
315         _layoutAwareControls->RemoveAt(index);
316         if (_layoutAwareControls->Size == 0)
317         {
318             // Stop listening to view state changes when no controls are interested in updates
319             Window::Current->SizeChanged -= _windowSizeEventToken;
320             _layoutAwareControls = nullptr;
321             // Last control has received the Unload notification.
322             _this = nullptr;
323         }
324     }
325 }
326 
327 /// <summary>
328 /// Translates <see cref="ApplicationViewState"/> values into strings for visual state management
329 /// within the page.  The default implementation uses the names of enum values.  Subclasses may
330 /// override this method to control the mapping scheme used.
331 /// </summary>
332 /// <param name="viewState">View state for which a visual state is desired.</param>
333 /// <returns>Visual state name used to drive the <see cref="VisualStateManager"/></returns>
334 /// <seealso cref="InvalidateVisualState"/>
335 String^ LayoutAwarePage::DetermineVisualState(ApplicationViewState viewState)
336 {
337     switch (viewState)
338     {
339     case ApplicationViewState::Filled:
340         return "Filled";
341     case ApplicationViewState::Snapped:
342         return "Snapped";
343     case ApplicationViewState::FullScreenPortrait:
344         return "FullScreenPortrait";
345     case ApplicationViewState::FullScreenLandscape:
346     default:
347         return "FullScreenLandscape";
348     }
349 }
350 
351 /// <summary>
352 /// Updates all controls that are listening for visual state changes with the correct visual
353 /// state.
354 /// </summary>
355 /// <remarks>
356 /// Typically used in conjunction with overriding <see cref="DetermineVisualState"/> to
357 /// signal that a different value may be returned even though the view state has not changed.
358 /// </remarks>
InvalidateVisualState()359 void LayoutAwarePage::InvalidateVisualState()
360 {
361     if (_layoutAwareControls != nullptr)
362     {
363         String^ visualState = DetermineVisualState(ApplicationView::Value);
364         auto controlIterator = _layoutAwareControls->First();
365         while (controlIterator->HasCurrent)
366         {
367             auto control = controlIterator->Current;
368             VisualStateManager::GoToState(control, visualState, false);
369             controlIterator->MoveNext();
370         }
371     }
372 }
373 
374 #pragma endregion
375 
376 #pragma region Process lifetime management
377 
378 /// <summary>
379 /// Invoked when this page is about to be displayed in a Frame.
380 /// </summary>
381 /// <param name="e">Event data that describes how this page was reached.  The Parameter
382 /// property provides the group to be displayed.</param>
383 void LayoutAwarePage::OnNavigatedTo(NavigationEventArgs^ e)
384 {
385     // Returning to a cached page through navigation shouldn't trigger state loading
386     if (_pageKey != nullptr) return;
387 
388     auto frameState = SuspensionManager::SessionStateForFrame(Frame);
389     _pageKey = "Page-" + Frame->BackStackDepth;
390 
391     if (e->NavigationMode == NavigationMode::New)
392     {
393         // Clear existing state for forward navigation when adding a new page to the
394         // navigation stack
395         auto nextPageKey = _pageKey;
396         int nextPageIndex = Frame->BackStackDepth;
397         while (frameState->HasKey(nextPageKey))
398         {
399             frameState->Remove(nextPageKey);
400             nextPageIndex++;
401             nextPageKey = "Page-" + nextPageIndex;
402         }
403 
404         // Pass the navigation parameter to the new page
405         LoadState(e->Parameter, nullptr);
406     }
407     else
408     {
409         // Pass the navigation parameter and preserved page state to the page, using
410         // the same strategy for loading suspended state and recreating pages discarded
411         // from cache
412         LoadState(e->Parameter, safe_cast<IMap<String^, Object^>^>(frameState->Lookup(_pageKey)));
413     }
414 }
415 
416 /// <summary>
417 /// Invoked when this page will no longer be displayed in a Frame.
418 /// </summary>
419 /// <param name="e">Event data that describes how this page was reached.  The Parameter
420 /// property provides the group to be displayed.</param>
421 void LayoutAwarePage::OnNavigatedFrom(NavigationEventArgs^ e)
422 {
423     auto frameState = SuspensionManager::SessionStateForFrame(Frame);
424     auto pageState = ref new Map<String^, Object^>();
425     SaveState(pageState);
426     frameState->Insert(_pageKey, pageState);
427 }
428 
429 /// <summary>
430 /// Populates the page with content passed during navigation.  Any saved state is also
431 /// provided when recreating a page from a prior session.
432 /// </summary>
433 /// <param name="navigationParameter">The parameter value passed to
434 /// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested.
435 /// </param>
436 /// <param name="pageState">A map of state preserved by this page during an earlier
437 /// session.  This will be null the first time a page is visited.</param>
438 void LayoutAwarePage::LoadState(Object^ navigationParameter, IMap<String^, Object^>^ pageState)
439 {
440 }
441 
442 /// <summary>
443 /// Preserves state associated with this page in case the application is suspended or the
444 /// page is discarded from the navigation cache.  Values must conform to the serialization
445 /// requirements of <see cref="SuspensionManager.SessionState"/>.
446 /// </summary>
447 /// <param name="pageState">An empty map to be populated with serializable state.</param>
448 void LayoutAwarePage::SaveState(IMap<String^, Object^>^ pageState)
449 {
450 }
451 
452 #pragma endregion
453