In writing up a few experiments with multiple windows on Windows 8.1 ( post one and two ) there was an aspect that completely passed me by and it’s one that my colleague Mike pointed out to me.
There’s a class hiding away in the WinRT libraries called ProjectionManager and it deals with scenarios where you might want to project content from your app onto a secondary display.
I guess the most common example of this would be if you were building a Windows Store version of an app like PowerPoint.
Note – that’s not a suggestion that you should go off and build a Store app to compete with PowerPoint and nor is it an indication that there’s a Store version of PowerPoint being built anywhere by anybody. It’s just a “for instance”, ok?
By the way – there’s an official sample for the projection manager which you can find on the web.
The first thing that the projection manager can do for me is it can tell me whether I have a secondary display for projection available. This is “relatively easy” in that it looks like;
bool available = ProjectionManager.ProjectionDisplayAvailable; ProjectionManager.ProjectionDisplayAvailableChanged += (s, e) => { available = ProjectionManager.ProjectionDisplayAvailable; };
That’s my kind of API
If I run this on my laptop which is “extending” to an external monitor then I get a value of True back from that API call and if I then use the charms bar and its “Devices” charm to disconnect the display the event that I’m handling above fires pretty quickly to tell me that projection is no longer available and, likewise, re-extending onto the external monitor does the right thing too.
Nice.
What about putting content onto that display? The first thing to do is to create a view, here’s some code which is running in response to a button click on my main window and is trying to create and project a second view to a second display (if there is one or to fall back to the main display if not);
// This is running on my original UI thread (I hope). ApplicationView appView = ApplicationView.GetForCurrentView(); CoreApplicationView view = CoreApplication.CreateNewView(); // This, I want to run on the dispatcher associated with the new view. view.Dispatcher.RunAsync( CoreDispatcherPriority.Normal, () => { Window newWindow = Window.Current; ApplicationView newAppView = ApplicationView.GetForCurrentView(); // Want content for the new view, creating it in code. Grid grid = new Grid() { Background = new SolidColorBrush(Colors.Red) }; TextBlock text = new TextBlock() { Text = "Projection", FontSize = 36, VerticalAlignment = VerticalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Center }; grid.Children.Add(text); newWindow.Content = grid; ProjectionManager.StartProjectingAsync(newAppView.Id, appView.Id); } );
That works pretty well for me but it’s based on the system’s opinion of which of my screens might be the “main” display and which might be the “projection” display so it’d perhaps be good to offer a nice button that’d toggle between the two so adapting the code to something like;
// This is running on my original UI thread (I hope). ApplicationView appView = ApplicationView.GetForCurrentView(); CoreApplicationView view = CoreApplication.CreateNewView(); // This, I want to run on the dispatcher associated with the new view. view.Dispatcher.RunAsync( CoreDispatcherPriority.Normal, () => { Window newWindow = Window.Current; ApplicationView newAppView = ApplicationView.GetForCurrentView(); // Want content for the new view, creating it in code. Grid grid = new Grid() { Background = new SolidColorBrush(Colors.Red) }; Button button = new Button() { Content = "Projection", FontSize = 36, VerticalAlignment = VerticalAlignment.Stretch, HorizontalAlignment = HorizontalAlignment.Stretch }; button.Click += (s,e) => { ProjectionManager.SwapDisplaysForViewsAsync(newAppView.Id, appView.Id); }; grid.Children.Add(button); newWindow.Content = grid; ProjectionManager.StartProjectingAsync(newAppView.Id, appView.Id); } );
and that works very nicely for me too, swapping between my main monitor and my secondary monitor very quickly and then there’s a call to StopProjectingAsync() to take away the projected UI when you’re done with it.