- Understanding the Threading Model for Universal Apps
- Displaying Multiple Windows
- Navigating Between Pages
- Summary
Displaying Multiple Windows
Universal apps, even when running on Windows 8.1, are hosted in a window. Not only that, but an app running on a PC can use multiple windows simultaneously. Although they are called windows in XAML-specific APIs, windows are often called views in Windows Runtime APIs. In Windows Runtime terminology, a view is the union of a window and its UI thread.
Apps show a primary window when activated, but you can create and show any number of secondary windows on a PC. You create a secondary window by calling CoreApplicationView.CreateNewView. This returns a CoreApplicationView instance representing the new window and its UI thread, but you can’t interact with it yet. You must wait for Application.OnWindowCreated to be called, which occurs on the new UI thread. On this thread, you can initialize the window much like you would initialize your primary window. Once it is initialized, you can show it with a PC-only ApplicationViewSwitcher class—back on the original UI thread.
Because of the convoluted control flow, this is a perfect opportunity to use the TaskCompletionSource type mentioned earlier in this chapter. Listing 7.1 adds an await-friendly CreateWindowAsync method to App.xaml.cs, inspired by the Multiple Views Sample project provided by the Windows SDK. This portion of the code compiles for both PC and phone.
LISTING 7.1 App.xaml.cs: Providing an await-Friendly CreateWindowAsync Method
using
System;using
System.Collections.Concurrent;using
System.Threading.Tasks;using
Windows.ApplicationModel;using
Windows.ApplicationModel.Activation;using
Windows.ApplicationModel.Core;using
Windows.UI.Xaml;using
Windows.UI.Xaml.Controls;namespace
MultipleWindows {sealed partial class
{App
:Application
{// The pending tasks created by CreateWindowAsync
ConcurrentQueue
<TaskCompletionSource
<Window
>> taskWrappers =new
ConcurrentQueue
<TaskCompletionSource
<Window
>>();// Create a new window.
// This wrapper method enables awaiting.
public
Task
<Window
> CreateWindowAsync()// Create a Task that the caller can await
TaskCompletionSource
<Window
> taskWrapper =new
TaskCompletionSource
<Window
>();this
.taskWrappers.Enqueue(taskWrapper);// Create the secondary window, which calls Application.OnWindowCreated
// on its own UI thread
CoreApplication
.CreateNewView(null
,null
);// Return the Task
return
taskWrapper.Task; }protected override void
OnWindowCreated(WindowCreatedEventArgs
args) {CoreApplicationView
view =CoreApplication
.GetCurrentView();if
(!view.IsMain) {// This is a secondary window, so mark the in-progress Task as complete
// and "return" the relevant XAML-specific Window object
TaskCompletionSource
<Window
> taskWrapper;if
(!taskWrappers.TryDequeue(out
taskWrapper) || !taskWrapper.TrySetResult(args.Window)) taskWrapper.SetException(new
InvalidOperationException
()); } } ... } }
The code inside OnWindowCreated can easily check whether it is being invoked for the main window or a secondary window by obtaining the current CoreApplicationView and examining its IsMain property.
Listing 7.2 shows the code-behind for the following MainPage.xaml that leverages CreateWindowAsync to show a new window every time its Button is clicked:
<
Page
x
:
Class
="MultipleWindows.MainPage"
...>
<
Viewbox
>
<
Button
Click
="Button_Click">
Show a New Window</
Button
>
</
Viewbox
>
</
Page
>
LISTING 7.2 MainPage.xaml.cs: Using CreateWindowAsync to Create Then Show a New Window
using
System;using
Windows.UI.Core;using
Windows.UI.ViewManagement;using
Windows.UI.Xaml;using
Windows.UI.Xaml.Controls;namespace
MultipleWindows {public sealed partial class
MainPage
:Page
{public
MainPage() { InitializeComponent(); }async void
Button_Click(object
sender,RoutedEventArgs
e) {int
newWindowId = 0;
// Create the new window with our handy helper method
Window
newWindow =await
(App
.Currentas
App
).CreateWindowAsync();
// Initialize the new window on its UI thread
{
await
newWindow.Dispatcher.RunAsync(CoreDispatcherPriority
.Normal, () =>// In this context, Window.Current is the new window.
// Navigate its content to a different page.
Frame
frame =new
Frame();
frame.Navigate(typeof
(SecondPage
));Window
.Current.Content = frame;// Set a different title
ApplicationView
.GetForCurrentView().Title ="NEW"
; newWindowId =ApplicationView
.GetApplicationViewIdForWindow( newWindow.CoreWindow); });
// Back on the original UI thread, show the new window alongside this one
// (PC only)
bool
success =} } }
await
ApplicationViewSwitcher
.TryShowAsStandaloneAsync(newWindowId);
Once Button_Click retrieves the new Window instance, which actually came from App’s OnWindowCreated method, it can schedule its initialization on its UI thread. It awaits this work’s completion, because the next step requires a window ID that must be retrieved from that window’s UI thread. With the ID, the original code can then call ApplicationViewSwitcher.TryShowAsStandaloneAsync to show the new window.
Each new window is a top-level window to be managed by the user, just like the app’s main window. TryShowAsStandaloneAsync has overloads that enable you to specify a ViewSizePreference for the target window or for both windows, just like when launching an app. You can also swap one window with another in-place by calling SwitchAsync instead of TryShowAsStandaloneAsync.