Windows 8.1 Preview: Multiple Windows (Part 2)

Yes, I’m still on the Windows 8.1 preview even though Windows 8.1 has gone out of the door and is generally available as per posts below;

Windows 8.1 now available!

Now ready for you- Windows 8.1 and the Windows Store

Visual Studio 2013 released to web!

I just need to get around to installing it Smile

Meanwhile, I wanted to experiment a little more with what I started on in my previous post Windows 8.1 Preview- Multiple Windows and specifically I wanted to see what the experience was like for an application that had multiple windows on a machine where a user may have multiple monitors with different display settings and, further, might drag windows from one monitor to another. I’d been kind of curious as to how that would work and especially in the case where those windows contained content that had loaded resources using the standard mechanisms – how dynamic are those mechanisms with respect to display settings changing while the app is running?

Experimenting with DisplayInformation

I knocked together a sort of skeleton which would allow me to create 2 windows. It took about 5 minutes to add a button to the first screen;

image

and stuck a little bit of code behind that to create my secondary window;

  public sealed partial class MainPage : Page
  {
    public MainPage()
    {
      this.InitializeComponent();
    }
    async void OnSecondWindow(object sender, RoutedEventArgs e)
    {
      if (!this.secondWindowShown)
      {
        var view = CoreApplication.CreateNewView();
        var viewId = 0;

        await view.Dispatcher.RunAsync(
          CoreDispatcherPriority.Normal,
          () =>
          {
            viewId = ApplicationView.GetForCurrentView().Id;

            Window w = Window.Current;
            Frame f = new Frame();
            w.Content = f;
            f.Navigate(typeof(SecondWindow));
          });

        await ApplicationViewSwitcher.TryShowAsStandaloneAsync(viewId,
          ViewSizePreference.UseHalf,
          ApplicationView.GetForCurrentView().Id,
          ViewSizePreference.UseHalf);

        this.secondWindowShown = true;
      }
    }
    bool secondWindowShown;
  }

which is just a bit of XAML to set up a view model and display some information from it;

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

  <Grid Background="Gray">
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="Auto" />
      <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <TextBlock Text="Resolution Scale" />
    <TextBlock Grid.Row="1" Text="DPI X" />
    <TextBlock Grid.Row="2" Text="DPI Y" />
    <TextBlock Grid.Row="3" Text="Current Orientation" />
    <TextBlock Grid.Row="4" Text="Native Orientation" />
    <TextBlock Grid.Row="5" Text="Auto Rotation Preferences" />
    <TextBlock Grid.Column="1" Text="{Binding DisplayInformation.ResolutionScale}" />
    <TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding DisplayInformation.RawDpiX}" />
    <TextBlock Grid.Column="1" Grid.Row="2" Text="{Binding DisplayInformation.RawDpiY}" />
    <TextBlock Grid.Column="1" Grid.Row="3" Text="{Binding DisplayInformation.CurrentOrientation}" />
    <TextBlock Grid.Column="1" Grid.Row="4" Text="{Binding DisplayInformation.NativeOrientation}" />
    <TextBlock Grid.Column="1" Grid.Row="5" Text="{Binding DisplayInformation.AutoRotationPreferences}" />
  </Grid>
</Page>

and then there’s just a simple view model class which that second window instantiates declaratively (i.e. default constructor time) via its XAML as you can see up there in the Page.DataContext setter on line 8 above. That view model class is largely just surfacing up the DisplayInformation class from WinRT;

  class WindowDetailsViewModel : ViewModelBase
  {
    public WindowDetailsViewModel()
    {
      DisplayInformation di = DisplayInformation.GetForCurrentView();
      di.DpiChanged += OnDpiChanged;
      di.OrientationChanged += OnOrientationChanged;
    }
    public DisplayInformation DisplayInformation 
    { 
      get
      {
        return (DisplayInformation.GetForCurrentView());
      }
    }
    void OnOrientationChanged(DisplayInformation sender, object args)
    {
      this.ChangeDisplayInformation();
    }
    void OnDpiChanged(DisplayInformation sender, object args)
    {
      this.ChangeDisplayInformation();
    }
    void ChangeDisplayInformation()
    {
      base.OnPropertyChanged("DisplayInformation");
    }
  }

and that ViewModelBase class just implements IPropertyChangedNotification so I won’t include that here.

I thought I’d try out that code across the system that I have set up here. I have a Dell XPS 12 which has a 12” monitor (I think – the name would suggest so but I haven’t measured it) running at 1920×1080 and, over DisplayPort, I have a Dell U2412M monitor which is 24” and is running at 1920×1200.

You’d have to assume that there were differences in pixel density across those 2 screens.

So, I ran up the app and pressed the button to create the second window side-by-side with the first window on my 12” laptop;

image

So, that looks like I have a 175ish DPI screen and it’s in Landscape (it is) and it would usually be landscape (it would, it’s a laptop) and Windows would apply 140% scaling to this UI. Fair enough.

I dragged this window to my second monitor, crossed my fingers and waited to see if things updated. They did;

image

and so on this monitor, the DPI is closer to the regular 96 and Windows would apply 100% scaling to this UI.

Clearly, at an API level then Windows “knows” that my window has moved monitors and it fires an event such that my app code can pick up that event and do “something”. What I wondered next was whether Windows could do anything to automatically resolve resources based on this knowledge.

Experimenting with Scaling

I’ll admit that I find I have to think hard about scaling. I find it easier to think in terms of logical pixels being 1/96th of an inch and then thinking about designing in terms of logical pixels.

For instance, if I temporarily change my secondary page to be a Grid which is defined as 96×96;

<Grid Width="96" Height="96" Background="Red">

Then when I run that on my 12” screen with high DPI, it presents a rectangle that’s around 1 inch square and if I drag the window to my 24” screen with lower DPI it presents a rectangle that around 1 inch square and that, I guess, is the point Smile

Similarly, if I use a piece of text;

    <TextBlock FontSize="96"
      Text="Hello World" Foreground="White"/>

then that looks pretty much the same size on my 2 monitors as I drag the window backwards and forwards. The problem is that some resources (particularly) images just aren’t going to play ball nicely and that’s why Windows has its scaling system to provide alternate images based on scale factors.

I went out and find an image of the Surface 2 on the web that was 485px by 323px (not a great size – should really be a multiple of 5 pixels) and dropped it into my UI with a simple image tag;

    <Image Width="485"
           Height="323"
           Stretch="Fill"
           Source="Assets/surface2.jpg" />

but I also found an image that was twice the size in terms of pixels and I attributed the 2 images so that I could tell them apart;

imageimage

and added them to my project using the regular naming convention;

image

Now, I may have gone a little over the top in terms of the size of my 140% image but I ran up the app and, sure enough on my 24” inch monitor the second window displayed;

image

and on my 12” monitor the second window displayed;

image

and so the resource-resolution system is clearing listening to the right events and is refreshing (in this case) my image for me as it realises that the surrounding graphic capabilities have changed as the window moved from one monitor to another – very neat.

Experimenting with Data-Binding

That made me wonder. What if the picture to display was actually hidden away inside of a value that was data-bound. As a simple example, what if I had this “ViewModel”;

  class ViewModel
  {
    public string ImageUrl
    {
      get
      {
        return ("Assets/surface2.png");
      }
    }
  }

and I change my UI such that it sets up one of those objects as its DataContext and then binds to it;

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

  <Grid>
    <Image Width="485"
           Height="323"
           Stretch="Fill"
           Source="{Binding ImageUrl}" />

then does that still work? Does my image still resolve to a different image as I move the window from my 12” screen to my 24” screen?

Not surprisingly, yes, it does Smile 

Local/Web Images

Naturally, if you’re loading up images that your app has stored in local storage (i.e. local/roaming/temp storage hanging off ApplicationData.Current) then the system isn’t magically going to swap those images around for you as and when the Window transitions from one monitor to another and the DPI changes.

As an aside, one of the things that I’m not 100% sure on is whether there is a way to use the resource system to resolve images in data folders using the same naming system that it uses when loading up packaged resources. For example, if I have a path like ms-appdata:///local/surface2.jpg then can I get the system to resolve that for me to ms-appdata:///local/surface2.scale-100.jpg and follow the same naming convention as for resources? I’m not sure whether I can but I’ll return to that if I plug that gap in my knowledge.

Equally, if you’ve loaded images from the web then the system is not usually going to be able to magically pick up display changes and go back to the web for “the right image”.

I think in both of those cases, you’re going to have to do some work yourself and make use of the DisplayInformation.ResolutionScale property to figure out what images to load or, if you’re in the JavaScript world, you can use something like the CSS media-resolution feature in order to specify different background-image values for various media queries and hit the problem that way.

App Resource Packages and Bundles

This is one I haven’t tried yet. I wrote a little blog post about Windows 8.1–Resource Packages and App Bundles where I took a basic look at how for Windows 8.1 it’s possible to build resource packages – e.g. it’s possible to build a package of resources that only apply to the 140% scale so that all those 140% scale images are in that package and that package would not need downloading onto a system running at 100% scale.

So…what happens in the circumstance where a user has a system with a DPI that would resolve to 100% scale but then plugs in a monitor that resolves to 140% or 180% scale and then drags a window from their 100% scale monitor to their 140% scale monitor?

Does the system go back to the Store and magically get the additional resources required and, if so, how does that affect the running app package?

As far as I know, the answer is “yes, the system does do that” but given that the Windows 8.1 Store only opened yesterday I’ve not tried it yet but I’ll update the post as/when I have any more detail.

Update: I checked on what happens in the above situation where the app has downloaded its “100%” resources and then one of its windows is dragged to a monitor which would require “140%” resources. In this case, this will prompt the system to realise that it needs to get hold of those “140%” resources the next time it services that app. That would mean that the user might get a slightly different experience when they first run the app and it is running with “100%” resources versus a later run of the app where it has been serviced and has downloaded those “140%” resources and so can now use them.