Home > Articles > Programming > C#

  • Print
  • + Share This
This chapter is from the book

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.Current as 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.

  • + Share This
  • 🔖 Save To Your Account