Windows 8.1 Preview: Multiple Windows

In talking to developers in the UK about Windows 8.0, one of the things that always struck me as slightly unusual about modern, Windows Store apps was that, even on an operating system called “Windows”, those apps didn’t really have much of an opportunity to display multiple windows.

The action seemed to mostly happen inside of one window although I don’t think that’s necessarily true when you get into scenarios like your application being the target of a Share operation with UI that the system hosts for you.

In Windows 8.1, things change. It’s possible for an application to have multiple windows with perhaps the canonical examples being a mail app where you want to tap on an email and have it open in its own dedicated window or, perhaps, a browser where you want to open up multiple tabs in multiple windows.

As an aside, if an app can have multiple windows then that opens up the question of where those windows can live if the machine happens to have multiple monitors connected and, if it’s possible for an app to have multiple windows on multiple monitors then you might get into interesting scenarios such as;

  • App’s Main Window open on monitor 1 which has a DPI which causes the app to load resources at scale 1.0
  • App’s Second Window open on monitor 2 which has a DPI which causes the app to load resources at scale 1.4
  • The user drags the second window from monitor 1 to monitor 2, what happens?

(this topic of scaling is talked about on MSDN).

and I’m not sure at the time of writing that I know quite how to handle those kinds of scenarios in the sense of knowing how the “MRT” resource lookup system ( as talked about on MSDN ) helps with such dynamic changes.

In some ways, this is “just” the same as having to deal with 1 window which moves from monitor 1 to monitor 2 ( as demonstrated by the official “scaling sample” ) but I see that sample manually handling the DisplayInformation.DpiChanged event and then manually choosing images in response to the current scale factor that it gets from the DisplayInformation class. My assumption would be then that an application with multiple windows needs to do this at the window level.

That’s (potentially) quite a lot to think about – some of this was covered in the BUILD session below;

and there are also official samples in the SDK that demonstrate how to create multiple windows and also UX guidance around when multiple windows might be a sensible route to take.

Window, CoreWindow, Application, CoreApplication, ApplicationView, CoreApplicationView – Oh My.

I have to admit that I find the number of entities involved in getting a new window onto the screen a little bit over-complicated but that’s often the case when components are designed to serve many scenarios.

In my mind, I would expect something like (pseudo-code);

Window w = new Window(); // maybe pass some optional parent window?

w.Content = new Grid(); // or some other content

w.Show(); // maybe pass some detail around how that window is to be shown.

but the reality of the situation seems to be that there are quite a few classes involved in getting a window onto the screen including (in the XAML world) Window, CoreWindow, ApplicationView and CoreApplicationView.

The best docs that I can find around CoreApplication, CoreApplicationView and its friends seems to live in the C++/DirectX documentation up on MSDN and specifically this page.

I wanted to try and understand the simplest piece of code involving the least number of types that I could write to get this done. As far as I can tell it starts with a call to CoreApplication.CreateNewView() which I have to say is a little tersely documented at the time of writing as;

“Creates a new view for the app.”

Ok, so I’ve got a new view and it’s not my Main view because I ran the code below from an event handler behind a button on a page inside a window as set up by the standard blank XAML template that ships with Visual Studio 2013 Preview.

      CoreApplicationView newView = CoreApplication.CreateNewView();
      Debug.Assert(!newView.IsMain);      

Interestingly – that assertion on the 2nd line of code up there doesn’t fail. It throws! Apparently, it’s not appropriate to call newView.IsMain at that point but I didn’t find that doc’d anywhere so it came as a bit of a surprise.

In my mode as XAML developer I guess what I want to get hold of it some kind of Window because I’m familiar with the idea that I can put Content into a Window.

I already knew from watching an online session somewhere that my Application.OnWindowCreated override would get called when a Window got created so I set a breakpoint in that function and took a look at the call-stack;

image

Guessing from this, it looks like some COM machinery was waiting on a handle somewhere and got invoked to set me up a new “view”. Somewhere beyond the RPC library, that got picked up by CoreApplicationView :: SetWindowAndGetDispatcher and that looks like it is taking a window factory and expects to “return” an ICoreWindow and an ICoreDispatcher.

At that point, it looks like the XAML pieces get plugged in presumably via FrameworkViewSource being passed to the CoreApplication.Run method as an IFrameworkViewSource and it then creates a FrameworkView which gets wired up to my Application and causes it to call OnWindowCreated.

By the time things reach my application code, the ICoreWindow has been wrapped into a Window object ( in the XAML world ) which has a CoreWindow property hanging off it.

So, I have one piece of code in a page which was invoked from an event handler and it’s calling CoreApplication.CreateNewView() and getting back a CoreApplicationView and some infrastructure is setting up a new Window and passing it into code living in my Application derived class in its OnWindowCreated override.

These 2 pieces of code feel quite disjoint/disconnected to me – I’d prefer to bring them into one place if that’s possible. Having tried to interact with the CoreApplicationView returned from CreateNewView() it seemed that the object at the point that it’s returned isn’t usable – it threw on me when I asked it for its IsMain property.

But maybe that was a bit hasty, so I experimented a little more I noticed that while the IsMain property seemed to cause me a problem, the Dispatcher property seemed to be available to me at that point. For example;

      ApplicationView currentAppView = ApplicationView.GetForCurrentView();

      CoreApplicationView newCoreAppView = CoreApplication.CreateNewView();

      newCoreAppView.Dispatcher.RunAsync(
        CoreDispatcherPriority.Normal,
        async () =>
        {
          // This executes on the new dispatcher so I assume that Window.Current and so on
          // reflects the new Window associated with that dispatcher rather than the original
          // dispatcher.
          Window newWindow = Window.Current;
          ApplicationView newAppView = ApplicationView.GetForCurrentView();

          newWindow.Content = 
            new Grid() 
            { 
              Background = new SolidColorBrush(Colors.Red) 
            };

          // Making a window isn't enough. Need to put it somewhere. Adding await here
          // is a bit redundant at the moment.
          await ApplicationViewSwitcher.TryShowAsStandaloneAsync(
            newAppView.Id,
            ViewSizePreference.UseHalf,
            currentAppView.Id,
            ViewSizePreference.UseHalf);
        });

and, empirically, that seems to “work” in that I get a second window positioned next to the original one. What I’m not 100% sure on are those various calls to GetCurrentView or GetForCurrentView and whether I’m using them correctly or whether I’m just being “lucky” in the way that I’m using them in that I’m making some assumptions that I’ll get certain values back because of the dispatcher that I’m running on.

Changing the way in which I populated my new window such that it had an identifier for itself;

          Window newWindow = Window.Current;
          ApplicationView newAppView = ApplicationView.GetForCurrentView();

          Grid grid =
            new Grid()
            {
              Background = new SolidColorBrush(Colors.Red),              
            };

          grid.Children.Add(
            new Viewbox()
              {
                Child = new TextBlock()
                {
                  Text = CoreApplication.Views.Count.ToString()
                }
              });

          newWindow.Content = grid;

I found it interesting to click my “new window” button more than once. Here’s the 2nd window appearing;

image

and here’s the 3rd window;

image

and so it slides in there and replaces that 2nd window but I can easily drag them around and place them wherever I want (worth noting that I set my minimum width on this application down to 320px rather than the 500px that’s the new default);

image

What if a user closes down a window? If I drag window 3 from the top of the screen to the bottom, what happens? One of two things;

  1. If I drag the window down to the bottom of the screen and release it then the window disappears but I don’t think it’s actually closed and I don’t see that Window firing its Closed event. What I do see is an ApplicationView.Consolidated event firing which passes an event args telling me that the action was “user initiated”.
  2. If I drag the window down to the bottom of the screen, wait until it spins around to reveal its logo and then release it then I don’t see a Window.Closed event and nor do I see a ApplicationView.Consolidating event – instead I see the application exit.

If I use ALT+F4 on a secondary window then it seems to be the same effect as the first entry above – that is, the window seems to get consolidated and I guess it’s then up to my code to deal with that and close the window.

In terms of keeping track of all these windows/views, CoreApplication appears to be my friend here and I can ask it for its Views collection (of CoreApplicationView) and also its MainView.

One thing I’ll add to experimenting with these new bits is that for anyone readers that’s familiar with COM apartments is that, as you know, they are still with us – each one of these windows runs its own dispatcher thread and is an “Application Single Threaded Apartment” (ASTA).

MSDN Reference on Application Single Threaded Apartments

Which, essentially says that an ASTA is an STA that you can’t create yourself and which doesn’t allow re-entrancy.

Once you start to play around with code shared between multiple of these objects, you’ll no doubt (if you’re as careless as me) start to encounter situations where you try and take dependencies in one STA from code that needs to run in another STA and the framework looks to have protected itself against this and you’ll see an exception like;

A first chance exception of type 'System.Exception' occurred in TabbedBrowser.exe

WinRT information: A COM call (IID: {638BB2DB-451D-4661-B099-414F34FFB9F1}, method index: 6) to an ASTA (thread 9596) was blocked because the call chain originated in or passed through another ASTA (thread 8028). This call pattern is deadlock-prone and disallowed by apartment call control.

Additional information: A COM call to an ASTA was blocked because the call chain originated in or passed through another ASTA. This call pattern is deadlock-prone and disallowed by apartment call control.

A COM call (IID: {638BB2DB-451D-4661-B099-414F34FFB9F1}, method index: 6) to an ASTA (thread 9596) was blocked because the call chain originated in or passed through another ASTA (thread 8028). This call pattern is deadlock-prone and disallowed by apartment call control.

If there is a handler for this exception, the program may be safely continued.

So you need to take care to avoid situations where your code running under one STA (1) calls out to another STA (2) and that STA (2) might have to re-enter STA (1) in order to do its work. The framework throws that exception at this point to try and stop you getting into a mess with deadlocks.

I wondered what it might be like to try and put something together using this and figured I’d try and simulate a multi-window web browser with a main view that listed tabs and secondary views to show the content.

Hacking Together a Tabbed Browser

I played around with these windowing APIs a little bit to make the most basic of “tabbed browsers” – two views;

image

and then;

image

and some more tabs;

image

and when I click on one of those buttons, it activates the right secondary window and puts it back on the screen in a 50:50 split with the main view. The secondary view also communicates with the main view when its window is closed either explicitly via the “Close” button in the top right or when the user consolidates the view by dragging it off the screen.

In terms of bolting that together, I have that main view;

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:TabbedBrowser"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:ViewModels="using:TabbedBrowser.ViewModels"
      x:Class="TabbedBrowser.MainPage"
      mc:Ignorable="d">

  <Page.Resources>
    <Flyout x:Key="FlyoutBase1"
            Placement="Top">
      <Flyout.FlyoutPresenterStyle>
        <Style TargetType="FlyoutPresenter">
          <Setter Property="MaxWidth"
                  Value="Infinity" />
        </Style>
      </Flyout.FlyoutPresenterStyle>
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="Auto" />
          <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <TextBox Text="{Binding NewUrlText, Mode=TwoWay}"
                 MinWidth="640" 
                 VerticalAlignment="Center"/>
        <Viewbox Width="48"
                 Height="48"
                 Grid.Column="1">
          <AppBarButton Icon="Forward"
                        Command="{Binding AddTabCommand}" />
        </Viewbox>
      </Grid>
    </Flyout>
  </Page.Resources>
  <Page.BottomAppBar>
    <CommandBar>
      <CommandBar.SecondaryCommands>
        <AppBarButton Label="New Tab"
                      Icon="Add"
                      Flyout="{StaticResource FlyoutBase1}" />
      </CommandBar.SecondaryCommands>
    </CommandBar>
  </Page.BottomAppBar>

  <Page.DataContext>
    <ViewModels:MainViewModel />
  </Page.DataContext>

  <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
      <RowDefinition Height="100" />
      <RowDefinition Height="40" />
      <RowDefinition />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="120" />
      <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <TextBlock TextWrapping="Wrap"
               Text="browser tabs"
               Grid.Column="1"
               Style="{StaticResource HeaderTextBlockStyle}"
               VerticalAlignment="Bottom" />
    <GridView Grid.Column="1"
              Grid.Row="2"
              Margin="0,0,0,88"
              ItemsSource="{Binding Tabs}"
              SelectionMode="Multiple"
              IsItemClickEnabled="True">
      <GridView.ItemTemplate>
        <DataTemplate>
          <Button Command="{Binding InvokeItemCommand}">
            <StackPanel>
              <Image Width="320"
                     Height="240"
                     Source="Assets/ielogo.jpg" 
                     Stretch="UniformToFill"/>
              <TextBlock Text="{Binding Url}" 
                         MaxWidth="300" TextWrapping="NoWrap"/>
            </StackPanel>
          </Button>
        </DataTemplate>
      </GridView.ItemTemplate>
    </GridView>
  </Grid>
</Page>

bound onto an instance of a view model that it creates;

namespace TabbedBrowser.ViewModels
{
  using System;
  using System.Collections.Generic;
  using System.Collections.ObjectModel;
  using System.Linq;
  using System.Windows.Input;
  using TabbedBrowser.Common;
  using TabbedBrowser.Services;
  using Windows.UI.Core;
  using Windows.UI.Xaml;
  using Windows.UI.Xaml.Media.Imaging;
  using System.IO;
  class MainViewModel : ViewModelBase
  {
    public MainViewModel()
    {
      // TODO: bit of a nasty heck for a viewmodel.
      this.owningDispatcher = Window.Current.Dispatcher;

      this.initialThreadId = Environment.CurrentManagedThreadId;
      this.AddTabCommand = new RelayCommand(OnAddTabCommand);
      this.Tabs = new ObservableCollection<BrowserTabItemViewModel>();
      WindowManager.WindowRemoved += OnWindowClosed;
    }

    void OnWindowClosed(object sender, WindowEventArgs e)
    {
      this.CheckThread();

      var entry = this.Tabs.Where(
        t => t.WindowId == e.Id).FirstOrDefault();

      if (entry != null)
      {
        this.Tabs.Remove(entry);
      };
    }

    public string NewUrlText
    {
      get
      {
        this.CheckThread();
        return (this._newUrlText);
      }
      set
      {
        this.CheckThread();
        base.SetProperty(ref this._newUrlText, value);
      }
    }
    string _newUrlText;

    public ObservableCollection<BrowserTabItemViewModel> Tabs
    {
      get
      {
        this.CheckThread();
        return (this._tabs);
      }
      private set
      {
        this.CheckThread();
        base.SetProperty(ref this._tabs, value);
      }
    }
    ObservableCollection<BrowserTabItemViewModel> _tabs;

    public ICommand AddTabCommand
    {
      get
      {
        this.CheckThread();
        return (this._addTabCommand);
      }
      private set
      {
        this.CheckThread();
        base.SetProperty(ref this._addTabCommand, value);
      }
    }
    ICommand _addTabCommand;

    async void OnAddTabCommand()
    {
      this.CheckThread();

      int? newViewId = await WindowManager.CreateWindowForPage(typeof(BrowserPage),
        this.NewUrlText);

      if (newViewId.HasValue)
      {
        BrowserTabItemViewModel viewModel = new BrowserTabItemViewModel()
        {
          Url = this.NewUrlText,
          WindowId = newViewId.Value
        };
        this.Tabs.Add(viewModel);
      }
    }
    void CheckThread()
    {
      if (this.initialThreadId != Environment.CurrentManagedThreadId)
      {
        throw new InvalidOperationException("Wrong thread");
      }
    }
    CoreDispatcher owningDispatcher;
    int initialThreadId;
  }
}

and the slightly unusual thing that’s doing is capturing the Dispatcher of the owning thread to try and make sure that the methods/properties of the view model are only invoked from that thread. This view model has a collection of the view models that sit behind my secondary page;

namespace TabbedBrowser.ViewModels
{
  using System;
  using System.Windows.Input;
  using TabbedBrowser.Common;
  using TabbedBrowser.Services;
  using Windows.UI.Xaml.Media.Imaging;
  class BrowserTabItemViewModel : ViewModelBase
  {
    public BrowserTabItemViewModel()
    {
      this.CloseCommand = new RelayCommand(this.OnCloseCommand);
      this.InvokeItemCommand = new RelayCommand(this.OnInvokeItemCommand);
    }
    public string Url
    {
      get
      {
        return (this._url);
      }
      set
      {
        base.SetProperty(ref this._url, value);
      }
    }
    string _url;


    public int WindowId
    {
      get
      {
        return (this._windowId);
      }
      set
      {
        base.SetProperty(ref this._windowId, value);
      }
    }
    int _windowId;


    public BitmapSource Thumbnail
    {
      get
      {
        return (this._thumbnail);
      }
      set
      {
        base.SetProperty(ref this._thumbnail, value);
      }
    }
    BitmapSource _thumbnail;


    public override void Initialise(object parameter)
    {
      Tuple<object, int> realParameter = (Tuple<object, int>)parameter;
      this.Url = (string)realParameter.Item1;
      this.WindowId = realParameter.Item2;
    }


    public ICommand CloseCommand
    {
      get
      {
        return (this._closeCommand);
      }
      set
      {
        base.SetProperty(ref this._closeCommand, value);
      }
    }
    ICommand _closeCommand;

    public ICommand InvokeItemCommand
    {
      get
      {
        return (this._invokeItemCommand);
      }
      private set
      {
        base.SetProperty(ref this._invokeItemCommand, value);
      }
    }
    ICommand _invokeItemCommand;

    void OnInvokeItemCommand()
    {
      WindowManager.ActivateWindow(this.WindowId);
    }
    void OnCloseCommand()
    {
      WindowManager.RequestConsolidateWindow(this.WindowId);
    }
  }
}

which has a view bound to it;

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:TabbedBrowser"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:ViewModels="using:TabbedBrowser.ViewModels"
      x:Class="TabbedBrowser.BrowserPage"
      mc:Ignorable="d">
  <Page.TopAppBar>
    <CommandBar IsOpen="True"
                IsSticky="True">
      <AppBarButton Label="Close"
                    Icon="ClosePane"
                    Command="{Binding CloseCommand}"/>
    </CommandBar>
  </Page.TopAppBar>
  <Page.DataContext>
    <ViewModels:BrowserTabItemViewModel />
  </Page.DataContext>

  <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <WebView Margin="0,88,0,0"
             Source="{Binding Url}" />

  </Grid>
</Page>

and I have a class ( built as a singleton with the idea being that it could easily be moved to be an injectible service ) which does some work in managing windows and sort of becomes a means of communication for these multiple views;

namespace TabbedBrowser.Services
{
  using System;
  using System.IO;
  using System.Threading.Tasks;
  using Windows.ApplicationModel.Core;
  using Windows.UI.Core;
  using Windows.UI.ViewManagement;
  using Windows.UI.Xaml;
  using Windows.UI.Xaml.Controls;

  public class WindowEventArgs : EventArgs
  {
    public int Id { get; set; }
  }

  static class WindowManager
  {
    public static event EventHandler<WindowEventArgs> WindowRemoved;

    public static async Task<int?> CreateWindowForPage(Type pageType,
      object initialState)
    {
      if (dispatcher == null)
      {
        dispatcher = Window.Current.Dispatcher;
      }

      // Make a new view.
      CoreApplicationView newCoreView = CoreApplication.CreateNewView();

      ApplicationView newAppView = null;

      int mainViewId = ApplicationView.GetApplicationViewIdForWindow(
        CoreApplication.MainView.CoreWindow);

      await newCoreView.Dispatcher.RunAsync(
        CoreDispatcherPriority.Normal,
        () =>
        {
          newAppView = ApplicationView.GetForCurrentView();
          newAppView.Consolidated += OnAppViewConsolidated;

          Frame frame = new Frame();

          Window.Current.Content = frame;

          frame.Navigate(pageType,
            new Tuple<object, int>(initialState, newAppView.Id));
        });

      bool worked = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(
        newAppView.Id,
        ViewSizePreference.UseHalf,
        mainViewId,
        ViewSizePreference.UseHalf);

      return (newAppView.Id);
    }
    static async void OnAppViewConsolidated(ApplicationView sender,
      ApplicationViewConsolidatedEventArgs args)
    {
      await dispatcher.RunAsync(
        CoreDispatcherPriority.Normal,
        () =>
        {
          sender.Consolidated -= OnAppViewConsolidated;

          FireWindowRemoved(sender.Id);
        });

      Window.Current.Close();
    }
    internal static void ActivateWindow(int windowId)
    {
      dispatcher.RunAsync(
        CoreDispatcherPriority.Normal,
        () =>
        {
          ApplicationViewSwitcher.TryShowAsStandaloneAsync(
            windowId,
            ViewSizePreference.UseMore,
            MainViewId,
            ViewSizePreference.UseLess);
        });
    }
    internal static void RequestConsolidateWindow(int windowId)
    {
      dispatcher.RunAsync(
        CoreDispatcherPriority.Normal,
        () =>
        {
          ApplicationViewSwitcher.SwitchAsync(
            MainViewId,
            windowId,
            ApplicationViewSwitchingOptions.ConsolidateViews);
        });
    }
    private static int MainViewId
    {
      get
      {
        if (mainViewId == null)
        {
          mainViewId = ApplicationView.GetApplicationViewIdForWindow(
            CoreApplication.MainView.CoreWindow);
        }
        return ((int)mainViewId);
      }
    }

    static void FireWindowRemoved(int windowId)
    {
      if (WindowRemoved != null)
      {
        dispatcher.RunAsync(
          CoreDispatcherPriority.Normal,
          () =>
          {
            WindowRemoved(null, new WindowEventArgs() { Id = windowId });
          });
      }
    }
    static int? mainViewId;
    static CoreDispatcher dispatcher;
  }
}

the only other thing that’s in there is the idea that the main page uses the navigation framework to navigate to a new secondary page and, in doing so, it wants to pass a parameter to the view model that the second page instantiates. It needs a way of doing this so I define an interface that I can implement on that view model behind the second page;

  public interface ISupportInitialise
  {
    void Initialise(object parameter);
  }

and I can have my view code check for that interface at navigation time and pass on the parameter for initialisation if it happens to see a DataContext that implements this interface;

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
      ISupportInitialise initialise = this.DataContext as ISupportInitialise;

      if (initialise != null)
      {
        initialise.Initialise(e.Parameter);
      }
    }

I’m sure there’s lots of issues with that code as I put it together pretty quickly but it let me experiment a little with these new windowing APIs – I’ve put the bits here for download if it helps kick-start anything that you’re doing or if you just want to experiment. As an aside, I did write some more code which used the new RenderTargetBitmap to draw a preview of each browser tab’s contents onto the main view but I took that code back out as it made things too complicated.

This has been a bit of a scrappy post – what I’d like to return to is that idea of how an app shows the right resources and layout when it’s potentially on multiple, different monitors. I’ll come back to it…