Keine Zeit? Hier geht’s zum Code.

Fast Track: Find the code here:

Mit Windows 8.1 Preview ist es möglich eine Windows Store App nicht nur mehr auf einem Bildschirm zu sehen, sondern diese über mehrere Applikationsfenster auf mehrere Bildschirme zu verteilen. Das ermöglicht ein paar schöne Szenarien: Beispielswiese ist es jetzt möglich eine echte “Second Screen” Anwendung zu bauen, die mit einem Präsentationsmodus und einem Verwaltungsmodus arbeitet. So ist es beispielsweise vorstellbar, dass man über das Tablet in der Hand während einer Teambesprechung auf der lokalen Ansicht Daten auswählt während man über den Beamer nur die Daten angezeigt bekommt, über die gerade diskutiert wird.

Ebenso wäre es möglich eine Videoplaylist auf Screen 1 zu editieren, während auf Screen 2 bereits die Videos laufen. Das Ganze wird ermöglicht über den Windows.UI.ViewManagement Namespace. Hier bietet uns die Methode ApplicationViewSwitcher.TryShowAsStandaloneAsync() relativ komfortable Möglichkeiten ein neues Fenster für die App mit Inhalten zu füllen. Für dieses Szenario gibt es auch ein Sample, das Ihr hier findet.

In diesem Blogpost stelle ich Euch eine Beispielanwendung vor, die ich basierend auf diesem Sample aufgebaut habe: Den PdfPresenter. Der PdfPresenter soll eine Anwendung sein, die ausgehend von einer ausgewählten PDF Datei alle Seiten in einer kleinen Übersichtsanzeige auf einmal auf Screen 1 darstellt. Der Anwender kann dann durch die Seiten scrollen und kann eine gewünschte Seite auswählen, die er auf dem zweiten Screen zur Ansicht bringt. Als kleines Add-on soll noch die Möglichkeit gegeben sein, in einer Voransicht auf Screen 1 Operationen wie Zoomen durchzuführen, die sich dann sofort auf den zweiten Screen auswirken.

Das wird am Ende wie folgt aussehen: Der Anwender kann eine PDF auswählen (App Bar). Das Grid links zeigt die Daten an. Der Anwender wählt eine Page. Daraufhin wird diese im Preview Bereich angezeigt und der 2. Screen geöffnet.

image

 

Kleines Schmankerl: Die Größe der Preview (rot) im Screen eins hängt von der Größe des zweiten App Fensters ab – spiegelt also dessen Relationen wieder. Außerdem führt Zooming auf Screen 1 zu Zooming auf Screen 2.

 

image

Wenn man das offizielle Sample näher beleuchtet, wird man feststellen, dass sehr viel der Basisarbeit durch das Sample abgedeckt ist. Ich habe in meinem Beispiel den Weg gewählt den Code des Samples so weit zu reduzieren, dass wirklich nur noch das notwendigste stehen bleibt – und dadurch klar ist, worauf es ankommt. Das schließt dann Exceptionhandling in vielen Fällen aus. Ich kann mir diesen Hinweis vermutlich sparen, aber bitte versteht diesen Code wirklich nur als Demo-Code und sichert ihn vor dem Einsatz in Projekten entsprechend ab.

Die wesentlichen Schritte, die zu tun sind, sind die folgenden:

1. Vorbereiten der App.cs für den Split

In der App.cs benötigen eine Referenz auf die neue dargestellte Page auf Screen 2– schließlich möchten wir darauf ja ggf. Inhalte verändern. Hierzu lege ich ein Property MyPresenterPage an.

// referenc to presenter page
public PresenterPage myPresenterPage;
public PresenterPage MyPresenterPage
{
    get { return myPresenterPage; }
    set { myPresenterPage = value; }
}

Um tatsächlich Änderungen an der PresenterPage druchführen zu können, benötigen wir Zugriff auf den Dispatcher des neuen Windows, denn jedes neue Window läuft in einem eigenen UI Thread. Den Dispatcher des neuen Windows erfahren wir im OnWindowCreated Handler, den wir in der App.cs anlegen:

// Coredispatcher of Presenter Page
public CoreDispatcher MyPresenterPageDispatcher { get; set; }

protected override void OnWindowCreated(WindowCreatedEventArgs args)
{
        // get the CoreDispatcher for the new window and register it in an app prop for later access.
        MyPresenterPageDispatcher = args.Window.Dispatcher;
}

2. Durchführen des Splits auf der MainPage.xaml

Um das zweite Fenster zu erstellen, rufen wir einfach die Statische Methode CoreApplication.CreateNewView() auf. Anschließend wird unser Eventhandler aktiv werden, sobald das neue Fenster erstellt ist. Wir können dann also davon ausgehen, dass MyPresenterPageDispatcher gesetzt ist und können diesen Dispatcher nutzen, um Content in die Seite zu laden und weitere Operationen auf der Page auszuführen. (Kleiner Hinweis: In meinem Code habe ich nicht abgefangen, was passiert, wenn der Button mehr als einmal gedrückt wird oder aber der 2nd Screen geschlossen wird! Das ist in jedem Fall ratsam!)

// create new window
var newView = CoreApplication.CreateNewView("", "");

// new MyNewDisp is no longer null --> (wurde in App.OnWindowCreated gesetzt)
if (app.MyPresenterPageDispatcher != null)
{
    await app.MyPresenterPageDispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
        // Todo 1: more to come … get id here

        // create new frame
        var rootFrame = new Frame();

        // set window content
        Window.Current.Content = rootFrame;

        // navigate to target page
        rootFrame.Navigate(targetPageType, null);
    });
    // more to come … do the split here
}

Jetzt haben wir zwar Content, aber noch nicht in einem separaten Fenster. Wir benötigen noch ein paar Zeilen mehr: Wir müssen, um die neue View wirklich separat darstellen zu können die View-ID dafür abfragen. Das tun wir über folgende Zeilen Code, die im Code oben noch an Stelle 1 ergänzt werden müssen:

// set content of new window
var currentCoreWindows = CoreWindow.GetForCurrentThread();
appViewId = ApplicationView.GetApplicationViewIdForWindow(currentCoreWindows);

Zusätzlich müssen wir natürlich die Funktion aufrufen, um ein “Standalone View” zu erhalten:

// Split Screen
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(appViewId);

Alternativ könnten wir hier auch auf den ProjectionManager zugreifen. Der Unterschied liegt darin, ob das Bild auf einen 2. angeschlossenen Monitor projeziert wird, oder ob der bestehende Bildschirmplatz auf einem Bildschirm geteilt wird.

Der Code hierfür sähe folgendermaßen aus:

// 2nd Screen
await ProjectionManager.StartProjectingAsync(appViewId, currentViewId);

3. Registrieren von Page Referenzen innerhalb des App Objektes

Um das jeweils andere Fenster, bzw dessen dargestellte Seite erreichen zu können, arbeite ich im Sample ganz einfach mit Referenzen auf die jeweiligen Page-Objekte, die ich im App Objekt hinterlege. Diese Referenzen werden im Konstruktor der Page gesetzt. Ich habe im Sample keine Vorkehrungen getroffen, was passiert, wenn man mehrere neue Fenster öffnet. In meinem Fall verwaisen die (alten) Seiten dann einfach, da ich keine Referenz mehr darauf halte.

// register this page in the app. This way other pages can access it.
 MyApp.MyPresenterPage = this;   

4. Reagieren auf Änderungen und Aufrufen von Methoden im jeweils anderen Fenster

Das war’s auch fast schon, wir haben jetzt ein zusätzliches Fenster in unserer App. Um die Fensterdarstellung zu synchronisieren , muss ich jetzt noch auf Änderungen in der Fenstergröße reagiere, bzw Änderungen in der Darstellung der Preview ausgelöst durch Zooming reagieren. Das mache ich über entsprechende Eventhandler (Size_Changed und Layout_Changed).

Innerhalb der Eventhandler muss ich dann Einfluss auf das jeweilig andere Fenster nehmen. Hier kann ich nicht einfach auf eine öffentliche Methode des anderen Fensters zugreifen – ich muss über den Dispatcher der Zielpage gehen, da ich sonst eine Thread Access Violation Exception bekomme. Den Zugriff auf den Dispatcher haben wir uns ja bereits weiter oben besorgt. Der Code für den Handler sieht dann beispielsweise ungefähr so aus:

// sync scrolling of preview & pdfpresenter
async private void pdfScrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
    var sv = ((ScrollViewer)(sender));
    var zoomfactor = sv.ZoomFactor;
    var hor = sv.HorizontalOffset;
    var ver = sv.VerticalOffset;

    await MyApp.MyPresenterPage.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        MyApp.MyPresenterPage.UpdateZoomFactor(zoomfactor, hor, ver);
    });
}

Wie Ihr seht, habe ich eine public Method für das Updaten des Zoom Factors auf der PresenterPage erstellt, die ich im entsprechenden Eventhandler unter Einbeziehung des korrekten Dispatchers aufrufe.

Den kompletten Code für das Sample findet Ihr hier. In diesem Blogpost bin ich nicht näher auf das Darstellen von PDFs eingegangen. Eventuell kommt das in einem zukünftigen Post.

Ich empfehle Euch auch, den offiziellen Sample Code aus dem DevCenter noch einmal durchzugehen, da an der ein oder anderen Stelle Dinge anders realisiert wurden – wie gesagt war mein Ziel, das Sample auf die nötigsten Codesnippets herunterzubrechen. Viel Spaß beim Präsentieren!