Erstellen einer Blogleser-App (C++)

Applies to Windows only

Hier wird der gesamte Prozess erläutert, der beschreibt, wie Sie mithilfe von C++ und XAML eine Windows Store-App entwickeln, mit der Sie Blogs über RSS 2.0- oder Atom 1.0-Feeds finden und lesen können.

In diesem Lernprogramm wird vorausgesetzt, dass Sie bereits mit den Konzepten vertraut sind, die in den Lektionen 1 bis 4 unter Erstellen Ihrer ersten Windows Store-App mit C++ behandelt wurden.

Wenn Sie sich die fertige Version dieser App ansehen möchten, können Sie diese auf der Website der MSDN Code Gallery herunterladen.

Wir empfehlen Ihnen nach dem Durcharbeiten dieses Lernprogramms das Thema Umfassende Entwicklung einer Windows Store-App mit C++ und XAML: Hilo, um weitere Informationen zur Verwendung von modernem C++, der Windows-Runtime, der asynchronen Programmierung, XAML und von Entwicklungsmustern wie Model-View-ViewModel (MVVM) in Windows Store-Apps, die mit C++ entwickelt wurden, zu erhalten.

Falls Sie doch eine andere Sprache bevorzugen:

Lernziele

In diesem Lernprogramm wird erläutert, wie Sie eine mehrseitige Windows Store-App erstellen und wann und wie die Visual C++-Komponentenerweiterungen (C++/CX) verwendet werden können, um die Codierung für die Windows-Runtime zu vereinfachen. Außerdem erfahren Sie im Rahmen dieses Lernprogramms, wie die concurrency::task-Klasse verwendet wird, um asynchrone Windows-Runtime-APIs zu nutzen.

Die SimpleBlogReader-App verfügt über die folgenden Features:

  • Sie unterstützt mehrere Blogfeeds.
  • Sie unterstützt die Prozessverwaltung für Lebensdauer (Process Lifetime Management, PLM). Außerdem wird der Status von der App korrekt gespeichert und erneut geladen, wenn der Blogleser vom System heruntergefahren wird, während eine andere Aufgabe im Vordergrund ausgeführt wurde.
  • Sie passt sich an verschiedene Fenstergrößen und Geräteausrichtungen (Quer- oder Hochformat) an.
  • Sie verwendet einfache Animationen und Übergänge, um die Benutzeroberfläche abwechslungsreicher zu gestalten.
  • Sie verwendet Formatvorlagen, um die App attraktiver zu gestalten.

Teil 1: Erstellen des Projekts

Zuerst verwenden wir die Windows Store-C++-Vorlage "Leere App", um ein Projekt zu erstellen.

Hh465045.wedge(de-de,WIN.10).gifSo erstellen Sie das Projekt

  1. Wählen Sie in Visual Studio die Optionen Datei > Neu > Projekt und Installiert > Visual C++ > Windows Store und anschließend die Vorlage Leere App (XAML). Eine ausführliche Anleitung finden Sie unter Hello World in C++.

  2. Geben Sie dem Projekt den Namen "SimpleBlogReader".

Angeben von App-Funktionen

Eine Windows Store-App wird in einem Sicherheitscontainer mit minimalem Zugriff auf Dateisystem, Netzwerkressourcen und Hardware ausgeführt. Sobald ein Benutzer eine App aus dem Windows Store installiert, untersucht Windows die Metadaten in der Datei Package.appxmanifest, um die Funktionen zu ermitteln, die für die App benötigt werden. Beispielsweise muss eine App vielleicht auf Daten im Internet, auf Dokumente in der Dokumentbibliothek oder die Webcam und das Mikrofon des Benutzers zugreifen können. Bei der Installation der App werden dem Benutzer die benötigten Funktionen angezeigt. Der Benutzer muss dann die Berechtigung für den Zugriff auf diese Ressourcen erteilen. Wenn eine App den Zugriff auf eine benötige Ressource nicht erhält, wird der Zugriff auf die Ressource während der Laufzeit verweigert. Unser Blogleser erfordert Internetzugriff. Daher müssen wir sicherstellen, dass dies angefordert wird.

Hh465045.wedge(de-de,WIN.10).gifSo überprüfen Sie, ob die grundlegende Internetfunktion angefordert wurde

  1. Öffnen Sie im Projektmappen-Explorer die Datei Package.appxmanifest. Die Datei wird im Application Manifest Designer (Anwendungsmanifest-Designer) geöffnet.

  2. Wählen Sie die Registerkarte Capabilities (Funktionen) aus.

    Beachten Sie, dass das Kontrollkästchen Internet (Client) basierend auf den Einstellungen der Vorlage "Leere App" bereits aktiviert ist. Falls andere Funktionen benötigt werden, können sie in diesem Designer ausgewählt werden.

  3. Schließen Sie den Manifest-Designer.

Normalerweise nutzen Sie den Anwendungsmanifest-Designer zum Angeben von Funktionen. Der Designer schreibt die Spezifikationen dann in das Capabilities-Element in der Datei Package.appxmanifest.xml, damit sie diese nicht manuell ändern müssen. Wir öffnen die Datei Package.appxmanifest.xml im XML-Text-Editor, um zu untersuchen, wie das Capabilities-Element strukturiert ist.


<Capabilities>    
    <Capability Name="internetClient" />
</Capabilities>


Weitere Informationen zu App-Funktionen finden Sie unter Manifest-Designer.

Teil 2: Abrufen von Daten in eine App

Jetzt schreiben wir den Code, um den Blogfeed in die App zu integrieren. Der Blog "Developing for Windows" stellt den Volltext seiner Beiträge in RSS- und Atom-Form bereit. In unserer App möchten wir Titel, Autor, Datum und Inhalt der jeweils neuesten Blogbeiträge anzeigen. Wir können die Klassen im Windows::Web::Syndication-Namespace verwenden, um die Feeds herunterzuladen. (Wir können diese Klassen auch zum Anzeigen der Daten auf der Benutzeroberfläche verwenden, aber stattdessen erstellen wir eigene Datenklassen, damit wir RSS- und Atom-Feeds auf die gleiche Weise behandeln können.) Wir erstellen drei Klassen:

  • FeedData enthält Informationen zum RSS- oder Atom-Feed.

  • FeedItem enthält Informationen zu einzelnen Blogbeiträgen im Feed.

  • FeedDataSource enthält Methoden zum Herunterladen der Feeds und zum Initialisieren unserer Datenklassen.

Diese Klassen definieren wir als öffentliche Verweisklassen, um die Datenbindung mit den XAML-Elementen zu ermöglichen, die Titel, Autor und Ähnliches anzeigen. Wir weisen mit dem Bindable-Attribut den XAML-Compiler an, dass die Bindung dynamisch an Instanzen dieses Typs erfolgen soll. In einer öffentlichen Verweisklasse werden öffentliche Datenmember als Eigenschaften bereitgestellt. Eigenschaften ohne spezielle Logik benötigen keinen benutzerspezifischen Getter und Setter, da der Compiler Getter und Setter liefert. Beachten Sie, wie wir in der FeedData-Klasse mit IVector einen öffentlichen Auflistungstyp für andere Windows-Runtime-Klassen und -Komponenten bereitstellen. Außerdem verwenden wir die Platform::Collections::Vector-Klasse intern als den konkreten Typ, der IVector implementiert. Weiter unten wird erläutert, wie dieser Typ genutzt wird.

Hh465045.wedge(de-de,WIN.10).gifSo erstellen Sie benutzerdefinierte Datenklassen

  1. Klicken Sie im Projektmappen-Explorer im Kontextmenü für den Projektknoten SimpleBlogReader auf Hinzufügen > Neues Element.

  2. Wählen Sie die Option Headerdatei (.h) aus, und geben Sie den Namen FeedData.h ein.

  3. Öffnen Sie die Datei FeedData.h, und fügen Sie den folgenden Code in die Datei ein. Nehmen Sie sich einen Moment Zeit, sich den Code anzusehen und sich mit den C++-/CX-Konstrukten vertraut zu machen. Beachten Sie die #include-Direktive für „pch.h“. Hier werden <string>, <vector> und andere Systemheader platziert. Standardmäßig ist collection.h in pch.h enthalten. Dieser Header ist für den Platform::Collections::Vector-Typ erforderlich. Weitere Informationen finden Sie unter Klassen und Strukturen (C++/CX).

    
    
    //feeddata.h
    
    #pragma once
    #include "pch.h"
    
    
    namespace SimpleBlogReader
    {
        // To be bindable, a class must be defined within a namespace
        // and a bindable attribute needs to be applied.
        // A FeedItem represents a single blog post.
        [Windows::UI::Xaml::Data::Bindable]
        public ref class FeedItem sealed
        {
        public:
            FeedItem(void){}
    
            property Platform::String^ Title;
            property Platform::String^ Author;
            property Platform::String^ Content;      
            property Windows::Foundation::DateTime PubDate;      
            property Windows::Foundation::Uri^ Link;
    
        private:
            ~FeedItem(void){}
        };
    
        // A FeedData object represents a feed that contains 
        // one or more FeedItems. 
        [Windows::UI::Xaml::Data::Bindable]
        public ref class FeedData sealed
        {
        public:
            FeedData(void)
            {
                m_items = ref new Platform::Collections::Vector<FeedItem^>();
            }
    
            // The public members must be Windows Runtime types so that
            // the XAML controls can bind to them from a separate .winmd.
            property Platform::String^ Title;            
            property Windows::Foundation::Collections::IVector<FeedItem^>^ Items
            {
                Windows::Foundation::Collections::IVector<FeedItem^>^ get() {return m_items; }
            }
    
            property Platform::String^ Description;
            property Windows::Foundation::DateTime PubDate;
            property Platform::String^ Uri;
    
        private:
            ~FeedData(void){}
    
            Platform::Collections::Vector<FeedItem^>^ m_items;
        };   
    
        // A FeedDataSource represents a collection of FeedData objects
        // and provides the methods to download the source data from which
        // FeedData and FeedItem objects are constructed. This class is 
        // instantiated at startup by this declaration in the 
        // ResourceDictionary in app.xaml: <local:FeedDataSource x:Key="feedDataSource" />
        [Windows::UI::Xaml::Data::Bindable]
        public ref class FeedDataSource sealed
        {
        private:
            Platform::Collections::Vector<FeedData^>^ m_feeds;
            std::map<Platform::String^, concurrency::task_completion_event<FeedData^>> m_feedCompletionEvents;
            FeedData^ GetFeedData(Platform::String^ feedUri, Windows::Web::Syndication::SyndicationFeed^ feed);       
    
        public:
            FeedDataSource();
            property Windows::Foundation::Collections::IObservableVector<FeedData^>^ Feeds
            {
                Windows::Foundation::Collections::IObservableVector<FeedData^>^ get()
                {
                    return this->m_feeds;
                }
            }
            void InitDataSource();
            static Windows::Foundation::IAsyncOperation<FeedData^>^ GetFeedAsync(Platform::String^ title);
            static FeedItem^ GetFeedItem(FeedData^ fd, Platform::String^ uniqueiD);
        };
    }
    
    
  4. Erstellen Sie jetzt eine neue CPP-Datei: Wählen Sie im Kontextmenü für den SimpleBlogReader-Projektknoten die Option Hinzufügen > Neues Element. Wählen Sie dann die Option C++-Datei (.cpp), und geben Sie FeedData.cpp als Dateinamen ein. Öffnen Sie die Datei , und fügen Sie den folgenden Code ein:

    
    
    #include "pch.h"
    #include "FeedData.h"
    
    
    using namespace std;
    using namespace concurrency;
    using namespace SimpleBlogReader;
    using namespace Platform;
    using namespace Platform::Collections;
    using namespace Windows::Foundation;
    using namespace Windows::Web::Syndication;
    
    
    
    FeedDataSource::FeedDataSource()
    {
        m_feeds = ref new Vector<FeedData^>();
    }
    
    // Retrieve the data for each atom or rss feed and put it
    // into our custom data structures.
    void FeedDataSource::InitDataSource()
    {
        // Left as an exercise: store the urls separately and let the user configure them.
        // It might be more convenient to use Platform::Strings here, but using wstring 
        // serves to demonstrate how standard C++ types can be used here.
        std::vector<std::wstring> urls; 
        urls.push_back(L"http://windowsteamblog.com/windows/b/developers/atom.aspx");
        urls.push_back(L"http://windowsteamblog.com/windows/b/windowsexperience/atom.aspx");
        urls.push_back(L"http://windowsteamblog.com/windows/b/extremewindows/atom.aspx");
    
        urls.push_back(L"http://windowsteamblog.com/windows/b/business/atom.aspx");
        urls.push_back(L"http://windowsteamblog.com/windows/b/bloggingwindows/atom.aspx");
        urls.push_back(L"http://windowsteamblog.com/windows/b/windowssecurity/atom.aspx");
        urls.push_back(L"http://windowsteamblog.com/windows/b/springboard/atom.aspx");
        urls.push_back(L"http://windowsteamblog.com/windows/b/windowshomeserver/atom.aspx");
        // There is no Atom feed for this blog, so we use the RSS feed.
        urls.push_back(L"http://windowsteamblog.com/windows_live/b/windowslive/rss.aspx");
        urls.push_back(L"http://windowsteamblog.com/windows_live/b/developer/atom.aspx");
        urls.push_back(L"http://windowsteamblog.com/ie/b/ie/atom.aspx");
        urls.push_back(L"http://windowsteamblog.com/windows_phone/b/wpdev/atom.aspx");
    
        // If we are resuming, we need to create a map of completion events so that
        // we don't attempt to restore page's state before it's backing data has been loaded.
        // First we create all the  events in an "unset" state, mapped to the urls as keys.
        // we'll set the event after we asynchronously load the feed.
        for( wstring url : urls)
        {
            auto uri = ref new String(url.c_str());
            task_completion_event<FeedData^> e;
            m_feedCompletionEvents.insert(make_pair(uri, e));
        }
    
        SyndicationClient^ client = ref new SyndicationClient();   
    
        // Range-based for loop. Never write a regular for loop again!
        for(wstring url : urls)
        {
            // Create the async operation. feedOp is an 
            // IAsyncOperationWithProgress<SyndicationFeed^, RetrievalProgress>^
    
            auto uri = ref new String(url.c_str());
            auto feedUri = ref new Uri(uri);
            auto feedOp = client->RetrieveFeedAsync(feedUri);
    
            // Create the task object and pass it the async operation.
            // SyndicationFeed^ is the type of the return value
            // that the feedOp operation will eventually produce.       
    
            // Then, initialize a FeedData object with the feed info. Each
            // operation is independent and does not have to happen on the
            // UI thread. Therefore, we specify use_arbitrary.
            create_task(feedOp)
    
            .then([this, uri]  (SyndicationFeed^ feed) -> FeedData^
            {
                return GetFeedData(uri, feed);
            }, concurrency::task_continuation_context::use_arbitrary())
    
    
            // Append the initialized FeedData object to the list
            // that is the data source for the items collection.
            // This has to happen on the UI thread. By default, a .then
            // continuation runs in the same apartment thread that it was called on.
            // Because the actions will be synchronized for us, we can append 
            // safely to the Vector without taking an explicit lock.
            .then([this] (FeedData^ fd)
            {
                m_feeds->Append(fd);
                m_feedCompletionEvents[fd->Uri].set(fd);
    
                // Write to VS output window in debug mode only. Requires <windows.h>.
                OutputDebugString(fd->Title->Data());
                OutputDebugString(L"\r\n");
            })
    
            // The last continuation serves as an error handler. The
            // call to get() will surface any exceptions that were raised
            // at any point in the task chain.
            .then( [] (task<void> t)
            {
                try
                {
                    t.get();
                }
                // SyndicationClient throws Platform::InvalidArgumentException 
                // if a URL contains illegal characters.
                // We catch this exception for demonstration purposes only.
                // In the current design of this app, an illegal
                // character can only be introduced by a coding error
                // and should not be caught. If we modify the app to allow
                // the user to manually add a new url, then we need to catch
                // the exception.
                catch(Platform::InvalidArgumentException^ e)
                {
                    // For example purposes we just output error to console.
                    // In a real world app that allowed the user to enter
                    // a url manually, you could prompt them to try again.
                    OutputDebugString(e->Message->Data());
                }
            }); //end task chain
        };
    }
    
    
    FeedData^ FeedDataSource::GetFeedData(String^ feedUri, SyndicationFeed^ feed)
    {
    
        FeedData^ feedData = ref new FeedData();
    
        // Knowing this makes it easier to map completion_events 
        // when we resume from termination.
        feedData->Uri = feedUri;
    
        // Get the title of the feed (not the individual posts).
        feedData->Title = feed->Title->Text; 
    
        if (feed->Subtitle->Text != nullptr)
        {
            feedData->Description = feed->Subtitle->Text;
        }	 
        // Use the date of the latest post as the last updated date.
        feedData->PubDate = feed->Items->GetAt(0)->PublishedDate;	
    
        // Construct a FeedItem object for each post in the feed
        // using a range-based for loop. Preferable to a 
        // C-style for loop, or std::for_each.
        for (auto  item : feed->Items)
        {
            auto feedItem = ref new FeedItem();
            feedItem->Title = item->Title->Text; 
            feedItem->PubDate = item->PublishedDate;		
    
            //We only get first author in case of multiple entries.
            feedItem->Author = item->Authors->GetAt(0)->Name; 
    
            if (feed->SourceFormat == SyndicationFormat::Atom10)
            {
                feedItem->Content = item->Content->Text;
                feedItem->Link = ref new Uri(item->Id);
            }
    
            else if (feed->SourceFormat == SyndicationFormat::Rss20)
            {
                feedItem->Content = item->Summary->Text;
                feedItem->Link = item->Links->GetAt(0)->Uri;
            }
    
            feedData->Items->Append(feedItem);
        };
    
        return feedData;
    
    } //end GetFeedData
    
    
    // We use this method to get the proper FeedData object when resuming
    // from shutdown. We need to wait for this data to be populated before
    // we attempt to restore page state. Note the use of task_completion_event
    // which doesn't block the UI thread.
    IAsyncOperation<FeedData^>^ FeedDataSource::GetFeedAsync(String^ uri)
    {
        return create_async([uri]()
        {
            auto feedDataSource = safe_cast<FeedDataSource^>( 
                App::Current->Resources->Lookup("feedDataSource"));
    
            // Does not block the UI thread.
            auto f = feedDataSource->m_feedCompletionEvents[uri];
    
            // In the callers we continue from this task after the event is 
            // set in InitDataSource and we know we have a FeedData^.
            task<FeedData^> t = create_task(f);
            return t;
        });
    }
    
    // We stored the stringID when the app was suspended
    // because storing the FeedItem itself would have required
    // more custom serialization code. Here is where we retrieve
    // the FeedItem based on its string ID.
    FeedItem^ FeedDataSource::GetFeedItem(FeedData^ feed, String^ uniqueId)
    {
        auto itEnd = end(feed->Items);
        auto it = std::find_if(begin(feed->Items), itEnd, 
            [uniqueId] (FeedItem^ fi)
        {
            return fi->Title == uniqueId;
        });
    
        if (it != itEnd)
            return safe_cast<FeedItem^>(*it);
    
        return nullptr;
    }
    
    
    

    Mit der Windows.Web.Syndication.SyndicationClient-Klasse werden RSS- und Atom-Feeds abgerufen und analysiert. Da der Vorgang eine Netzwerk-E/A einschließt, wird die Methode asynchron ausgeführt. Das asynchrone Programmiermodell wird in den gesamten Windows-Runtime-Klassenbibliotheken eingesetzt. Ein asynchroner Methodenaufruf gibt die Steuerung sofort an den UI-Thread zurück, sodass die UI reaktionsfähig bleibt, während der Vorgang für einen Thread im Hintergrund ausgeführt wird.

    Die Windows-Runtime ermöglicht es, asynchrone Vorgänge aufzurufen und nach Abschluss des Vorgangs die jeweiligen Ergebnisse zu erhalten. Sie können direkt für diese API programmieren. Der bevorzugte Ansatz ist jedoch die Verwendung der task class, die in ppltasks.h definiert ist (in pch.h enthalten). Die task-Klasse ruft die gleichen asynchronen Windows-Runtime-APIs auf, mit ihr können Sie jedoch kürzeren Code schreiben. Außerdem sind die Verkettung asynchroner Vorgänge und die Behandlung aller auftretenden Ausnahmen an einer Stelle einfacher. Die task-Klasse wird in der eben erstellten Datei an zwei Stellen verwendet. Bei der task-Klasse sind die grundlegenden Schritte immer gleich.

    1. Sie erstellen einen asynchronen Vorgang, indem Sie eine *Async-Methode der Windows-Runtime aufrufen (beispielsweise Windows::Web::Syndication::ISyndicationClient::RetrieveFeedAsync).

    2. Sie erstellen ein task-Objekt, indem Sie concurrency::create_task aufrufen und den Vorgang dabei als Eingabeparameter verwenden.

    3. Sie definieren eine Aufgabe, die nach Abschluss der ursprünglichen Aufgabe ausgeführt wird, indem Sie task::then aufrufen und einen Lambda-Ausdruck angeben, für den der Rückgabewert der ursprünglichen Aufgabe als Eingabe verwendet wird.

    4. Sie können optional then noch einmal oder mehrmals aufrufen. Diese Klauseln können den Rückgabewert aus der vorherigen Klausel akzeptieren.

    5. Sie geben eine letzte then-Klausel zur Behandlung von Ausnahmen an, die unter Umständen an einer Stelle der Vorgangskette auftreten. Dieser Schritt ist optional, wird aber dringend empfohlen.

Teil 3: Erstellen einer benutzerdefinierten Datumsformatierungsklasse

Jetzt ist ein guter Zeitpunkt, um eine weitere benutzerdefinierte Klasse hinzuzufügen: eine Implementierung von IValueConverter. Diese Schnittstelle verwenden Sie zum freien Konvertieren beliebiger Typen. Unsere Klasse ist ein Date Converter, der den Tag, den Monat und das Jahr als einzelne Zeichenfolgen zurückgibt, wenn ein DateTime-Argument an ihn übergeben wird. Wir werden diese Klasse später in dem visuellen Stil verwenden, den wir für Raster- und Listenansichtselemente definieren.

Hh465045.wedge(de-de,WIN.10).gifSo erstellen Sie eine Klasse für die IValueConverter-Implementierung

  • Fügen Sie im SimpleBlogReader-Projekt einen neuen Header mit dem Namen DateConverter.h hinzu, und fügen Sie dann diese Implementierung ein:

    
    
    //DateConverter.h
    
    #pragma once
    #include <string> //for wcscmp
    
    namespace SimpleBlogReader
    {
    
    public ref class DateConverter sealed : public Windows::UI::Xaml::Data::IValueConverter  
    {
        public:
        virtual Platform::Object^ Convert(Platform::Object^ value,
                                          Windows::UI::Xaml::Interop::TypeName targetType,
                                          Platform::Object^ parameter,
                                          Platform::String^ language)
        {		
            if(value == nullptr)
            {
                throw ref new Platform::InvalidArgumentException();
            }
            auto dt = safe_cast<Windows::Foundation::DateTime>(value);
            auto param = safe_cast<Platform::String^>(parameter);
            Platform::String^ result;
            if(param == nullptr)
            {
                auto dtf =
                Windows::Globalization::DateTimeFormatting::DateTimeFormatter::ShortDate::get();
                result = dtf->Format(dt);
            }
            else if(wcscmp(param->Data(), L"month") == 0)
            {
                auto month = 
                    ref new Windows::Globalization::DateTimeFormatting::DateTimeFormatter("{month.abbreviated(3)}");
                result = month->Format(dt);
            }
            else if(wcscmp(param->Data(), L"day") == 0)
            {
                auto month = 
                   ref new Windows::Globalization::DateTimeFormatting::DateTimeFormatter("{day.integer(2)}");
                result = month->Format(dt);
            }
            else if(wcscmp(param->Data(), L"year") == 0)
            {
                auto month = 
                    ref new Windows::Globalization::DateTimeFormatting::DateTimeFormatter("{year.full}");
    				        result = month->Format(dt);
            }
            else
            {
                // We don't handle other format types currently.
                throw ref new Platform::InvalidArgumentException();
            }
    
            return result; 
         }
    
        virtual Platform::Object^ ConvertBack(Platform::Object^ value,
                                              Windows::UI::Xaml::Interop::TypeName targetType,
                                              Platform::Object^ parameter,
                                              Platform::String^ language)
        {   
            // Not needed in SimpleBlogReader. Left as an exercise.
            throw ref new Platform::NotImplementedException();
        }
    };
    }
    
    

Hh465045.wedge(de-de,WIN.10).gifSo erreichen Sie, dass die App benutzerdefinierte Klassen erkennt

  1. Um zu erreichen, dass die App – vor allem ihre XAML-Steuerelemente – unsere benutzerdefinierten Klassen erkennt, müssen wir zuerst deren Headerdateien einbinden, damit sie kompiliert werden. Dazu fügen wir App.xaml.h den folgenden Code hinzu:
    
    
    #include "FeedData.h"
    #include "DateConverter.h"
    
    
  2. FeedDataSource und DateConverter sollen genauso lange wie die App aktiv sein. Aus diesem Grund speichern wir sie als App-Ressourcen. Sie werden beim Starten der App dann instanziiert. Wir verweisen auf die Instanzen in der App – sowohl im XAML-Code als auch im CodeBehind –, indem wir den im folgenden Code definierten x:Key-Wert verwenden.

    Zum Implementieren dieser Verweise geben Sie in App.xaml direkt vor dem Endtag des Application-Elements "<Application.Resources>" ein (oder verwenden Sie zum Hinzufügen dieses Elements die dynamische Anweisungsvervollständigung). Fügen Sie anschließend die Ressourcen FeedDataSource und DateConverter hinzu, damit der <Application>-Knoten wie folgt aussieht:

    
     <Application
        x:Class="SimpleBlogReader.App"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:SimpleBlogReader">
        <Application.Resources>
            <local:FeedDataSource x:Key="feedDataSource" />
            <local:DateConverter x:Key="dateConverter" />
        </Application.Resources>
    </Application>
    
    
    
  3. Jetzt ist ein guter Zeitpunkt für die Überprüfung, ob die App ohne Fehler kompiliert werden kann, auch wenn in der App momentan noch keine Funktionen genutzt werden können. Drücken Sie F5, um eine Debuginstanz der App zu kompilieren und anzuzeigen. Wenn Sie mit dem Entwickeln fortfahren möchten, wechseln Sie zurück zu Visual Studio und drücken UMSCHALT+F5.

Teil 4: Hinzufügen von Seiten und Navigation

Nun können wir die Benutzeroberfläche erstellen. Wenn wir mehrere Blogs unterstützen möchten, müssen wir der App Seiten hinzufügen und uns mit der Navigation zwischen den Seiten beschäftigen. Praktischerweise verfügt Visual Studio über mehrere Seitenvorlagen, mit denen bereits ein Großteil der benötigten Funktionen für die Navigation und Ausrichtung implementiert wird.

Wir erstellen zuerst eine Seite, auf der die Windows-Teamblogs aufgeführt werden. Dafür können wir die Vorlage Elementseite (Items Page) verwenden. Wenn ein Benutzer einen Blog auf dieser Seite auswählt, wird die Liste mit den Beiträgen für diesen Blog auf einer anderen Seite geladen. Dies ist eine Geteilte Seite (Split Page), die auf der Vorlage basiert. Außerdem verwenden wir die Vorlage Standardseite (Basic Page), um eine Detailseite hinzuzufügen, damit Benutzer einzelne Blogbeiträge lesen können, ohne dass die Listenansicht im Weg ist. Da diese Vorlagen die benötigte Navigation bereits unterstützen, fällt dafür kein Codeaufwand an, und wir können uns auf unsere Hauptaufgaben konzentrieren: das Schreiben von benutzerdefinierter Logik für die Methoden LoadState und SaveState, die in jeder CodeBehind-Klasse auskommentiert sind, sowie das Hinzufügen einer App-Leiste, die über eine Schaltfläche für die Vorwärtsnavigation von der geteilten Seite zur Detailseite verfügt.

Hh465045.wedge(de-de,WIN.10).gifSo fügen Sie der App Seiten hinzu

  1. Öffnen Sie im Projektmappen-Explorer das Kontextmenü für MainPage.xaml, und wählen Sie Entfernen aus. Wir verwenden diese Seite oder die zugehörigen CodeBehind-Dateien nicht in unserem Projekt. Sie können sie entweder endgültig löschen oder nur entfernen.

  2. Ändern Sie in App.xaml.cpp die #include-Direktive für MainPage.xaml.h in #include ItemsPage.xaml.h.

  3. Drücken Sie in App.xaml.cpp die Tastenkombination STRG+H, um nach Instanzen von "MainPage" zu suchen und durch "ItemsPage" zu ersetzen. Beachten Sie, dass die Instanzen von "ItemsPage" mit einer roten Wellenlinie unterstrichen sind. Diese Kennzeichnung wird ausgeblendet, wenn Sie die neue Seite hinzufügen.

  4. Öffnen Sie das Kontextmenü für das SimpleBlogReader-Projekt, und wählen Sie Hinzufügen > Neues Element.

  5. Wählen Sie im Dialogfeld Neues Element hinzufügen im Bereich Installiert die Option Visual C++ > Windows Store.

    Wählen Sie im mittleren Bereich die Option Seite "Elemente", übernehmen Sie den Standardnamen, und wählen Sie die Schaltfläche Hinzufügen.

    Wählen Sie in dem Dialogfeld, in dem Sie gefragt werden, ob die erforderlichen Dateien automatisch hinzugefügt werden sollen, die Option Ja. Diese Dateien enthalten Code zum Unterstützen der Navigation für die Serialisierung und Deserialisierung einfacher Typen, wenn die Nutzung der App beendet und wieder fortgesetzt wird. Sie werden dem Ordner "\Allgemein\" hinzugefügt.

  6. Wiederholen Sie die vorherigen Schritte, aber wählen Sie dabei die Vorlage Geteilte Seite aus.

  7. Wiederholen Sie die Schritte erneut, und wählen Sie nun die Vorlage Standardseite aus, und geben Sie der Seite den Namen "DetailPage".

Auf der Elementseite wird die Liste mit den Windows-Teamblogs angezeigt. Auf der geteilten Seite werden links die Beiträge für die einzelnen Blogs und rechts die Inhalte des ausgewählten Beitrags angezeigt. Auf der Detailseite werden nur die Inhalte des ausgewählten Beitrags, eine Zurück-Schaltfläche und der Seitentitel angezeigt. Auf dieser Seite laden wir den Beitragsinhalt aber nicht wie bei der geteilten Seite aus einer HTML-Zeichenfolge in die WebView. Stattdessen navigieren wir zur URL des Beitrags und zeigen die eigentliche Webseite an. Nach dieser Implementierung sehen die Seiten der App in etwa wie folgt aus:

Navigationsmodell mit drei Seiten

Wenn wir uns den XAML-Code und den CodeBehind für die hinzugefügten Seiten ansehen, wird deutlich, dass uns diese Seitenvorlagen viel Arbeit abnehmen. Gerade weil es zunächst vielleicht sogar ein wenig verwirrend ist, wird dadurch veranschaulicht, dass jede Seitenvorlage drei Hauptabschnitte hat:

Ressourcen

Stile und Datenvorlagen für die Seite werden im Ressourcenabschnitt definiert. Darauf gehen wir im Abschnitt Erzielen eines einheitlichen Aussehens mit Stilen noch genauer ein.

Visual State-Manager

Animationen und Übergänge, mit denen die App an unterschiedliche Layouts und Ausrichtungen angepasst wird, werden im Visual State-Manager (VSM) definiert. Mehr dazu erfahren Sie im Abschnitt Anpassung an verschiedene Layouts.

App-Inhalt

Die Steuerelemente und Inhalte, aus denen sich die UI der App zusammensetzt, werden im Stammlayoutpanel definiert.

 

Navigation zwischen Seiten

Eine der wichtigsten Aktivitäten in einer Windows Store-App besteht im Navigieren zwischen den Seiten. Benutzer können sich mithilfe entsprechender Schaltflächen in der App vor und zurück bewegen oder ein Element auswählen, mit dem eine neue Seite geöffnet wird – beispielsweise eine Seite mit näheren Informationen zum Element. Im Folgenden finden Sie das Navigationsdesign von SimpleBlogReader:

  • Wenn die App gestartet wird, wird auf der Elementseite ein Raster mit Blogfeeds (DataFeed-Objekten) angezeigt. Es werden nur die Blogtitel angezeigt, zusammen mit den Beschreibungen (falls vorhanden) und den Datumsangaben zum letzten Beitrag. Wenn der Benutzer einen bestimmten Feed auswählt, navigiert die App zur jeweiligen geteilten Seite.

  • Wenn sich die App im Querformat befindet, wird links auf der geteilten Seite die Liste der Beiträge (FeedItem-Objekte) angezeigt. Auf der rechten Seite wird eine Vorschau des derzeit ausgewählten Beitrags angezeigt. Im Hochformat sehen Sie auf der geteilten Seite entweder die Liste der Beiträge oder eine Beitragsvorschau. Sobald der Benutzer ein anderes Element in der Liste auswählt, ändert sich die Vorschau.

  • Die geteilte Seite verfügt über eine App-Leiste. Diese kann vom Benutzer durch Wischen vom oberen Rand des Bildschirms oder durch Klicken mit der rechten Maustaste aufgerufen werden. Wenn der Benutzer die Schaltfläche auf der App-Leiste auswählt, navigiert die App zur Detailseite, auf der der Blogbeitrag als vollständige Webseite angezeigt wird.

  • Auf der Detailseite kann der Benutzer die Zurück-Schaltfläche verwenden, um zurück zur geteilten Seite zu wechseln. Die geteilte Seite verfügt ebenfalls über eine Zurück-Schaltfläche, um zurück auf die Elementseite zu gelangen.

Dies ist jedoch noch nicht alles. Bei jedem Ladevorgang einer Seite muss überprüft werden, ob die Navigation durch eine Benutzeraktion (vor oder zurück) oder durch das System (beim Fortsetzen der App nach einer vorangehenden Beendigung) initiiert wurde.

Mit der SuspensionManager-Klasse (in "SuspensionManager.h" und "SuspensionManager.cpp" im Ordner "\Allgemein\") wird ein Großteil des Codes bereitgestellt, der zum Speichern des Seitenstatus erforderlich ist, wenn die App angehalten oder beendet wird, damit der Zustand später wiederhergestellt werden kann. Die spezifische Logik, die zum Laden oder erneuten Laden der Seiten erforderlich ist, muss jedoch von uns implementiert werden.

Das XAML-UI-Framework verfügt über ein integriertes Navigationsmodell mit dem Frames- und dem Pages-Element. Die Navigation funktioniert wie in einem Webbrowser. Das Frame-Steuerelement stellt Pages bereit. Mit seinem Navigationsverlauf können Sie außerdem zwischen den besuchten Seiten vor- und zurückwechseln. Bei der Navigation können auch Daten zwischen den Seiten übergeben werden.

Der Navigationsverlauf beginnt, wenn die App gestartet wird. Die Infrastruktur zur Unterstützung der Navigation ist Teil der App-Klasse. In den Projektvorlagen von Visual Studio ist als Inhalt des App-Fensters ein Frame mit dem Namen rootFrame festgelegt. Sehen wir uns in App.xaml.cpp den Code an, der von der Vorlage bereitgestellt wird. Nach dem Festlegen des rootFrame-Elements wird der aktuelle Zustand von der App überprüft. Es kann sein, dass die App aus dem beendeten Zustand gestartet oder angehalten wurde und fortgesetzt wird und sich ggf. bereits Inhalt im Speicher befindet. Wenn die App beendet und neu gestartet wurde, muss ihr beim Beenden gespeicherter Zustand geladen werden. Nachdem diese verschiedenen Zustände von der App verarbeitet wurden, erfolgt die Navigation zum ersten Fenster.



// Default implementation. Don't paste this into SimpleBlogReader.
 
/// <summary>
/// Invoked when the application is launched normally by the end user.  Other entry points
/// will be used when the application is launched to open a specific file, to display
/// search results, and so forth.
/// </summary>
/// <param name="e">Details about the launch request and process.</param>
void App::OnLaunched(Windows::ApplicationModel::Activation::LaunchActivatedEventArgs^ e)
{

#if _DEBUG
    if (IsDebuggerPresent())
    {
        DebugSettings->EnableFrameRateCounter = true;
    }
#endif

    auto rootFrame = dynamic_cast<Frame^>(Window::Current->Content);

    // Do not repeat app initialization when the Window already has content,
    // just ensure that the window is active
    if (rootFrame == nullptr)
    {
        // Create a Frame to act as the navigation context and associate it with
        // a SuspensionManager key
        rootFrame = ref new Frame();

        if (e->PreviousExecutionState == ApplicationExecutionState::Terminated)
        {
            // TODO: Restore the saved session state only when appropriate, scheduling the
            // final launch steps after the restore is complete

        }

        if (rootFrame->Content == nullptr)
        {
            // When the navigation stack isn't restored navigate to the first page,
            // configuring the new page by passing required information as a navigation
            // parameter
            if (!rootFrame->Navigate(TypeName(ItemsPage::typeid), e->Arguments))
            {
                throw ref new FailureException("Failed to create initial page");
            }
        }
        // Place the frame in the current Window
        Window::Current->Content = rootFrame;
        // Ensure the current window is active
        Window::Current->Activate();
    }
    else
    {
        if (rootFrame->Content == nullptr)
        {
            // When the navigation stack isn't restored navigate to the first page,
            // configuring the new page by passing required information as a navigation
            // parameter
            if (!rootFrame->Navigate(TypeName(ItemsPage::typeid), e->Arguments))
            {
                throw ref new FailureException("Failed to create initial page");
            }
        }
        // Ensure the current window is active
        Window::Current->Activate();
    }
}


Mit den Methoden Navigate, GoForward und GoBack des Frame-Steuerelements können Sie zwischen den Seiten navigieren. Mit der Navigate(TypeName, Object)-Methode können Sie zu einer neuen Seite navigieren und dabei Daten an diese übergeben. Der erste Parameter ist der TypeName der Seite, zu der wir wechseln. Wir verwenden den statischen typeid-Operator, um das TypeName-Element eines Typs zu erhalten. In diesem Fall möchten wir zur Elementseite navigieren. Diese Seite soll dem Benutzer beim Starten oder Fortsetzen der App als Erstes angezeigt werden.

Jede Seite besitzt ein NavigationHelper-Objekt als privaten Member. Dieses Objekt wird automatisch erstellt und befindet sich im Ordner "\Allgemein\" in "NavigationHelper.h" und "NavigationHelper.cpp". Wenn eine Seite aufgerufen wird, übernimmt das NavigationHelper-Element die grundlegende Verwaltung der Navigationsinfrastruktur. Eine Aufgabe des Elements besteht im Aufrufen von LoadState und SaveState zum richtigen Zeitpunkt. Als App-Entwickler ist es Ihre Aufgabe, den Methoden LoadState und SaveState der Seite Logik hinzuzufügen, sodass die Seite stets über die richtigen Daten für die Bindung verfügt und diese Daten abrufen kann, falls die App beendet wird.

Der erste Parameter in LoadState und SaveState ist die Seite, die den Navigationsbefehl gesendet hat. Der zweite Parameter ist ein LoadStateEventArgs-Element, das u. a. eine PageState-Eigenschaft mit den Daten enthält, die wir beim Aufrufen von Navigate übergeben haben. SuspensionManager versucht, alle Objekte zu serialisieren, die über diesen Parameter übergeben werden. Ohne unsere Hilfe ist dies jedoch nur erfolgreich, wenn es sich beim Typ um eine Zeichenfolge, eine GUID oder einen primitiven Datentyp handelt. Daher übergeben wir kein vollständiges FeedData-Objekt. Stattdessen übergeben wir eine Zeichenfolge und verwenden diese als Schlüssel zum Suchen des spezifischen FeedData- oder FeedItem-Objekts. Zu diesem Zweck werden die GetFeedAsync-Methode und die GetFeedItem-Methode in der FeedDataSource-Klasse verwendet. Diese Methoden werden im weiteren Verlauf noch näher betrachtet.

Hh465045.wedge(de-de,WIN.10).gifSo navigieren Sie von der App-Klasse zur Elementseite

  1. In App.xaml.cpp überprüft die App::OnLaunched-Methode, ob das rootFrame-Element bereits vorhanden ist. Ist dies nicht der Fall, wird das Element über den Code erstellt. Wenn wir ein neues rootFrame-Element erstellen, müssen wir außerdem unsere Feedelemente herunterladen und initialisieren. Dieser Code wird beim Starten der App erreicht, wenn sie vom System beendet oder vom Benutzer geschlossen wurde. Tritt keines dieser Ereignisse ein, verbleiben die Feedelemente im Arbeitsspeicher.

    Fügen Sie in App.xaml.cpp in der OnLaunched-Methode nach der rootFrame = ref new Frame();-Anweisung den folgenden Code hinzu:

    
    
    
        FeedDataSource^ feedDataSource = safe_cast<FeedDataSource^>(App::Current->Resources->Lookup("feedDataSource"));
        if (feedDataSource->Feeds->Size == 0)
        {
            feedDataSource->InitDataSource();
        }
    
    
    

    Durch Aufrufen der Navigate-Methode in App::OnLaunched wird schließlich der ItemsPage::LoadState-Ereignishandler aufgerufen. Wie bereits erläutert, können weder das FeedDataSource-Objekt noch die Feeds-Eigenschaft übergeben werden, da wir diese nicht selbst serialisieren möchten. Stattdessen speichern wir einen Verweis auf die FeedDataSource als Ressource in der Datei App.xaml und greifen nur dort darauf zu. Wir müssen trotzdem die Feeds-Eigenschaft verwenden, um DefaultViewModel für ItemsPage zu initialisieren. Hier ist der Feedvektor einem Schlüssel namens "Items" zugeordnet und wird im DefaultViewModel-Member für ItemsPage eingefügt. DefaultViewModel ist eine Windows::Foundation::Collections::IObservableMap. Jede Seite hat ein eigenes DefaultViewModel. Nachdem Sie das DefaultViewModel mit einigen Daten initialisiert haben, zeigt die ItemsViewSource::View-Eigenschaft auf Ihre Daten. Wenn die Elemente in der Auflistung bindbar sind, werden die Elemente auf der Benutzeroberfläche angezeigt.

  2. Ersetzen Sie in ItemsPage.xaml.cpp die LoadState-Methode durch den folgenden Code:

    
    
    void ItemsPage::LoadState(Platform::Object^ sender, Common::LoadStateEventArgs^ e)
    {
        (void)sender;	// Unused parameter
        (void)e;	// Unused parameter
    
        // This is the first page to load on startup. The feedDataSource was constructed when the app loaded
        // in response to this declaration in app.xaml: <local:FeedDataSource x:Key="feedDataSource" />
        // and was initialized aynchronously in the OnLaunched event handler in app.xaml.cpp. 
        // Initialization might still be happening, but that's ok. 
        auto feedDataSource = safe_cast<FeedDataSource^>(App::Current->Resources->Lookup("feedDataSource"));
    
        // In ItemsPage.xaml (and every other page), the DefaultViewModel is set as DataContext:
        // DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
        // Because ItemsPage displays a collection of feeds, we set the Items element
        // to the FeedDataSource::Feeds collection. By comparison, the SplitPage sets this element to 
        // the Items collection of one FeedData object.
        this->DefaultViewModel->Insert("Items", feedDataSource->Feeds);
    
    }
    
    

Drücken Sie jetzt F5, um die App auszuführen. Wie Sie sehen, wird bereits ein Teil der Daten, die wir an die Elementseite übergeben haben, im Raster angezeigt – und das, obwohl wir noch gar keine Änderungen am Code der Vorlage vorgenommen haben. Es sieht ungefähr wie folgt aus, auch wenn die Elemente aufgrund der verwendeten Bildschirmauflösung unter Umständen anders angeordnet sein können:

Elementseite

Jetzt muss die Elementseite nur noch die Information erhalten, welche Aktion ausgeführt werden soll, wenn der Benutzer eines dieser Elemente auswählt.

Hh465045.wedge(de-de,WIN.10).gifSo navigieren Sie von der Elementseite zur geteilten Seite

  1. Wenn der Benutzer einen Blog aus der ItemsPage-Auflistung auswählt, wird von der Elementseite zur geteilten Seite gewechselt. Hierzu dürfen sich die GridView-Elemente nicht wie ausgewählte Elemente verhalten, sondern sie müssen wie eine Schaltfläche funktionieren. Damit die GridView-Elemente wie Schaltflächen funktionieren, legen wir die Eigenschaften SelectionMode und IsItemClickEnabled so fest, wie im folgenden Codeblock zu sehen. Dann fügen wir einen Handler für das ItemClicked-Ereignis des GridView-Elements hinzu. Suchen Sie in ItemsPage.xaml nach dem Starttag für das GridView-Element mit dem Namen itemGridView, das wie folgt aussieht:

    
    
     <GridView
                x:Name="itemGridView"
                AutomationProperties.AutomationId="ItemsGridView"
                AutomationProperties.Name="Items"
                TabIndex="1"
                Grid.RowSpan="2"
                Padding="116,136,116,46"
                ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
                SelectionMode="None"
                IsItemClickEnabled="true"
                IsSwipeEnabled="false">
    
    
    

    Positionieren Sie den Cursor jetzt direkt vor der schließenden Klammer, und drücken Sie die EINGABETASTE, um nach IsSwipeEnabled="false" eine neue Zeile zu erstellen. Geben Sie Margin="0,-10,0,10" ein, und beginnen Sie in einer neuen Zeile mit der Eingabe von ItemClick="itemGridView_ItemClick" . Die automatische Vervollständigung schlägt beim Eingeben von ItemClick= das Element itemGridView_ItemClick vor. Wenn Sie den Vorschlag der automatischen Vervollständigung akzeptieren, wird der CodeBehind für Sie auskommentiert.

  2. Fügen Sie in ItemsPage.xaml.cpp die folgende include-Direktive ein, damit SplitPage erkannt wird:

    
    #include "SplitPage.xaml.h"
    
    
    
  3. Wenn Sie den vorgeschlagenen Standardnamen zum Auskommentieren des CodeBehind verwendet haben, wie dies in Schritt 2 beschrieben ist, werden der itemGridView_ItemClick-Ereignishandlerprototyp in ItemsPage.xaml.h und ein Implementierungs-Stub in ItemsPage.xaml.cpp angezeigt. Fügen Sie einfach den folgenden Code in die Stub-Implementierung ein:

    
    
    
        // We must manually cast from Object^ to FeedData^.
        auto feedData = safe_cast<FeedData^>(e->ClickedItem);
    
        // Store the current URI so that we can look up the
        // correct feedData object on resume.
        App::Current->Resources->Insert("CurrentFeed", feedData);
    
        // Navigate to SplitPage and pass the title of the selected feed.
        // SplitPage will receive this in its LoadState method in the navigationParamter.
        this->Frame->Navigate(TypeName(SplitPage::typeid), feedData->Title);
    
    
    
  4. Wenn von der Elementseite auf die geteilte Seite navigiert wird, wird die SplitPage::LoadState-Methode aufgerufen. Analog zu ItemsPage muss LoadState den Navigationsweg nachvollziehen und den früheren Zustand der App ermitteln. Nähere Informationen finden Sie in den Codekommentaren im folgenden Beispiel. Vieles davon wird Ihnen aus App::OnLaunched und ItemsPage::LoadState bekannt vorkommen.

    Öffnen Sie nun die Datei SplitPage.xaml.cpp, und ersetzen Sie die gesamte LoadState-Methode durch den folgenden Code:

    
    void SplitPage::LoadState(Platform::Object^ sender, Common::LoadStateEventArgs^ e)
    {
        // If we are navigating forward from ItemsPage, there is no existing page state.
        if (e->PageState == nullptr)
        {
            // Current feed was set in the click event in ItemsPage. We don't pass it in
            // through navigationParameter because on suspension, the default serialization 
            // mechanism will try to store that value but fail because it can only handle 
            // primitives, strings, and Guids.
            auto fd = safe_cast<FeedData^>(App::Current->Resources->Lookup("CurrentFeed"));
    
            // Insert into the ViewModel for this page to initialize itemsViewSource->View
            this->DefaultViewModel->Insert("Feed", fd);
            this->DefaultViewModel->Insert("Items", fd->Items);
    
            // When this is a new page, select the first item automatically unless logical page
            // navigation is being used (see the logical page navigation #region below).
            if (!UsingLogicalPageNavigation() && itemsViewSource->View != nullptr)
            {
                this->itemsViewSource->View->MoveCurrentToFirst();
            }
            else
            {
                this->itemsViewSource->View->MoveCurrentToPosition(-1);
            }
        }
    
        // e>PageState != null means either (1) we are returning from DetailPage
        // or (2) we are resuming from termination. If (1), then we still have our
        // state and don't have to do anything. If (2), then we have to restore the page.
        else if (!this->DefaultViewModel->HasKey("Feed"))
        {
            // All we stored is the Uri string for the feed, not the object. 
            auto uri = safe_cast<String^>(e->PageState->Lookup("Uri"));
    
            // FeedDataSource::InitDataSource has already been called. 
            // It's an asynchronous operation, so our FeedData might not
            // be available yet. GetFeedAsync uses a task_completion_event to  
            // wait (on its own thread) until the specified FeedData is available.
            // The next three methods follow the basic async pattern in C++:
            // 1. Call the async method.
            auto feedDataOp = FeedDataSource::GetFeedAsync(uri);
    
            // 2. Create a task from it.
            auto feedDataTask = create_task(feedDataOp);
    
            // 3. Define the work to be performed after the task completes.
            feedDataTask.then([this, e](FeedData^ feedData)
            {
                // Now we have the feedData, so it's safe to get the FeedItem
                // synchronously. Inserting into DefaultViewModel
                // initializes the itemsViewSource-View object.
                this->DefaultViewModel->Insert("Feed", feedData);
                this->DefaultViewModel->Insert("Items", feedData->Items);
    
                // DetailPage has to get the new Uri from this value.
                App::Current->Resources->Insert("CurrentFeed", feedData);
    
                // Now that we have a FeedData^, we can call GetFeedItem safely and
                // pass in the title that we stored before the app was terminated.
                auto itemTitle = safe_cast<String^>(e->PageState->Lookup("SelectedItem"));
                auto selectedItem = FeedDataSource::GetFeedItem(feedData, itemTitle);
    
                if (selectedItem != nullptr)
                {
                    this->itemsViewSource->View->MoveCurrentTo(selectedItem);
                }
            });
        }
    }
    
    
    

    Beim Fortsetzen der App nach der Beendigung ist kein FeedItem-Objekt, sondern nur eine Zeichenfolge vorhanden. Daher müssen wir die Zeichenfolge verwenden, um das FeedItem-Element nachzuschlagen. Dies ist jedoch erst möglich, wenn das FeedItem-Element von FeedDataSource heruntergeladen wurde. Wir möchten aber nicht warten, bis alle Feeds heruntergeladen wurden, sondern nur, bis der Download für den gewünschten Feed abgeschlossen ist. Diese Synchronisierungsvorgänge werden in der LoadState-Methode und in der FeedDataSource::GetFeedAsync-Methode ausgeführt. Nachdem wir nun über das FeedData-Objekt verfügen, rufen wir die GetFeedItem-Methode synchron auf, um das FeedItem für den ausgewählten Beitrag abzurufen und im Vorschaufenster anzeigen zu können.

    Hinweis  Wir fügen die Items-Eigenschaft separat in das DefaultViewModel für die geteilte Seite ein, damit die Elemente für die XAML-Datenbindung zugänglich sind.

    Um zur Elementseite zurückzuwechseln, müssen keine zusätzlichen Schritte ausgeführt werden. Die Seitenvorlage enthält Code zum Behandeln des BackButton.Click-Ereignisses und zum Aufrufen der Frame.GoBack-Methode.

  5. Wenn Sie die App durch Drücken von F5 jetzt ausführen, wird der Blogtext im Vorschaubereich der geteilten Seite beim Klicken auf ein Feeddatenelement als unformatierter HTML-Code angezeigt. Um dies zu beheben, ändern wir das Layout, das für den Titel und den Inhalt des ausgewählten Blogbeitrags verwendet wird. Falls die App ausgeführt wird, schließen Sie sie, indem Sie zurück zu Visual Studio wechseln und UMSCHALT+F5 drücken.

Hh465045.wedge(de-de,WIN.10).gifSo implementieren Sie SplitPage::SaveState

  • In ItemsPage war das Speichern des Seitenzustands nicht erforderlich, da stets alle Elemente angezeigt wurden. In SplitPage muss das derzeit ausgewählte Element jedoch gespeichert werden, damit die App nach dem Beenden und Fortsetzen die Ausführung genau an dieser Stelle wieder aufnehmen kann. Wie bereits erläutert, können nur Zeichenfolgen und Zahlen gespeichert werden, jedoch keine FeedItem-Objekte. Dies ist auf die Verwendung des SuspensionManager für die Serialisierung zurückzuführen. Daher speichern wir für das derzeit ausgewählte Element sowohl den Titel des FeedItem-Objekts als auch den URI des FeedData-Objekts, damit wir das Element nach dem Beenden und Fortsetzen wiederfinden können.

    Suchen Sie in SplitPage.xaml.cpp die SaveState-Methode, und ersetzen Sie sie dann durch den folgenden Code:

    
    void SplitPage::SaveState(Object^ sender, Common::SaveStateEventArgs^ e)
    {
        if (itemsViewSource->View != nullptr)
        {
            auto selectedItem = itemsViewSource->View->CurrentItem;
            if (selectedItem != nullptr)
            {
                auto feedItem = safe_cast<FeedItem^>(selectedItem);
                auto itemTitle = feedItem->Title;
                e->PageState->Insert("SelectedItem", itemTitle);
            }
    
            // Save the feed title also.
            auto feedData = safe_cast<FeedData^>(this->DefaultViewModel->Lookup("Feed"));
            e->PageState->Insert("Uri", feedData->Uri);
        }
    }
    
    

Wir müssen noch ein paar weitere Änderungen vornehmen, um das Hinzufügen von Funktionen zu den neuen Seiten abzuschließen. Nachdem wir diesen Code hinzugefügt haben, können wir uns dem Styling und den Animationen widmen.

Hh465045.wedge(de-de,WIN.10).gifSo ändern Sie die Bindungen und das Layout der SplitPage und der ItemsPage

  1. Ändern Sie in SplitPage.xaml das Grid-Element mit dem Namen titlePanel so, dass es zwei Spalten umfasst:

    
    <!-- Back button and page title -->
    <Grid x:Name="titlePanel" Grid.ColumnSpan="2">
    
    
  2. Da wir beim Hinzufügen unserer Daten zum DefaultViewModel einen Schlüssel namens "Feed" verwendet haben, müssen wir als Nächstes die Bindung im Seitentitel ändern, um eine Bindung an die Feed-Eigenschaft herzustellen. Ändern Sie in SplitPage.xaml die Text-Bindung des TextBlock-Elements mit dem Namen pageTitle, um die Bindung an Feed.Title zu erzielen. Dies sieht wie folgt aus:

    
    <TextBlock x:Name="pageTitle" Text="{Binding Feed.Title}" 
        Style="{StaticResource HeaderTextBlockStyle}" Grid.Column="1" 
        IsHitTestVisible="false" TextWrapping="WrapWholeWords" 
        VerticalAlignment="Bottom" Margin="0,0,30,40"/>
    
    
  3. In ItemsPage.xaml ist der Seitentitel an eine statische Ressource mit dem Schlüssel AppName gebunden. Ändern Sie den Text in dieser Ressource wie folgt in Windows Team Blogs:

    
    <x:String x:Key="AppName">Windows Team Blogs</x:String>
    
    

Hh465045.wedge(de-de,WIN.10).gifSo fügen Sie SplitPage.xaml ein WebView-Steuerelement hinzu

  1. In SplitPage.xaml möchten wir auch das Layout ändern, mit dem Titel und Inhalt des ausgewählten Blogbeitrags angezeigt werden. Verwenden Sie dafür das folgende Layout, um das ScrollViewer-Element mit dem Namen itemDetail zu ersetzen. Ein Großteil dieses XAML-Codes ähnelt den Schritten, die wir in MainPage.xaml bereits ausgeführt haben. Auf den Zweck des Rectangle-Elements gehen wir später in diesem Artikel ein.

    Reduzieren Sie in SplitPage.xaml das vorhandene ScrollViewer-Element, löschen Sie es, und fügen Sie das folgende Markup ein:

    
            <!-- Details for selected item -->
            <ScrollViewer
                x:Name="itemDetail"
                AutomationProperties.AutomationId="ItemDetailScrollViewer"
                Grid.Column="1"
                Grid.RowSpan="2"
                Padding="70,0,120,0"
                DataContext="{Binding SelectedItem, ElementName=itemListView}"
                HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"
                ScrollViewer.HorizontalScrollMode="Disabled" ScrollViewer.VerticalScrollMode="Enabled"
                ScrollViewer.ZoomMode="Disabled">
    
                <Grid x:Name="itemDetailGrid" Margin="0,60,0,50">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
    
                    <Image Grid.Row="1" Margin="0,0,20,0" Width="180" Height="180" Source="{Binding ImagePath}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}"/>
                    <StackPanel x:Name="itemDetailTitlePanel" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2">
                        <TextBlock x:Name="itemTitle" Margin="0,-10,0,0" Text="{Binding Title}" Style="{StaticResource SubheaderTextBlockStyle}"/>
                        <TextBlock x:Name="itemSubtitle" Margin="0,0,0,20" Text="{Binding Subtitle}" Style="{StaticResource SubtitleTextBlockStyle}"/>
                    </StackPanel>
                 <Border x:Name="contentViewBorder" BorderBrush="Gray" BorderThickness="2" Grid.Row="2" Grid.Column="1" Margin="0,15,0,20">
                        <Grid>
                            <WebView x:Name="contentView" />
                            <Rectangle x:Name="contentViewRect" />
                        </Grid>
                    </Border>
                </Grid>
            </ScrollViewer>
    
    
    
  2. Ändern Sie nun in der Datei SplitPage.xaml.cpp den Ereignishandlercode, durch den die WebView aktualisiert wird, wenn sich die Auswahl im ListView-Element ändert. Signatur und Implementierung der Funktion ItemListView_SelectionChanged sind bereits vorhanden. Wir müssen nur noch die folgenden Zeilen am Ende der Methode hinzufügen:

    
    
        // Sometimes there is no selected item, e.g. when navigating back
        // from detail in logical page navigation.
        auto fi = dynamic_cast<FeedItem^>(itemListView->SelectedItem);
        if(fi != nullptr)
        {
            contentView->NavigateToString(fi->Content);
        }    
    
    
    

Hh465045.wedge(de-de,WIN.10).gifSo fügen Sie DetailPage.xaml ein WebView-Steuerelement hinzu

  • In DetailPage.xaml müssen wir den Titeltext an den Titel des Blogbeitrags binden und ein WebView-Steuerelement zum Anzeigen der Blogseite hinzufügen. Ersetzen Sie hierzu das Grid-Element mit der Zurück-Schaltfläche und dem Seitentitel durch die folgenden Grid- und WebView-Elemente:

    
    <!-- Back button and page title -->
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <AppBarButton x:Name="backButton" Height="95" Margin="10,46,0,0"
                Command="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}" 
                AutomationProperties.Name="Back"
                AutomationProperties.AutomationId="BackButton"
                AutomationProperties.ItemType="Navigation Button"
                Icon="Back"/>
        <TextBlock x:Name="pageTitle" Text="{Binding Title}" 
                   Style="{StaticResource HeaderTextBlockStyle}" Grid.Column="1" 
                   IsHitTestVisible="false" TextWrapping="NoWrap" 
                   VerticalAlignment="Bottom" Margin="0,0,30,40"/>
    </Grid>
    <Border x:Name="contentViewBorder" BorderBrush="Gray" BorderThickness="2" 
            Grid.Row="1" Margin="120,15,20,20">
        <WebView x:Name="contentView" />
    </Border>
    
    
    

Hh465045.wedge(de-de,WIN.10).gifSo implementieren Sie LoadState und SaveState in der DetailPage-Klasse

  1. Fügen Sie der DetailPage-Klasse in DetailPage.xaml.h die folgenden privaten Datenmember hinzu:

    
    private:
            Platform::String^ m_itemTitle;
            Platform::String^ m_feedUri;
    
    
    
  2. Fügen Sie DetailPage.xaml.cpp die folgende using-Anweisung hinzu:

    e
    
    using namespace concurrency;
    
    
  3. Fügen Sie der LoadState-Methode in DetailPage.xaml.cpp Code hinzu, um zum Blogbeitrag zu wechseln und den DataContext für die Seite festzulegen. Analog zu SplitPage muss der frühere Zustand der App ermittelt werden. Die aktualisierte Methode sieht wie folgt aus:

    
    
    void DetailPage::LoadState(Object^ sender, Common::LoadStateEventArgs^ e)
    {
    	(void) sender;	// Unused parameter
    	(void) e;	// Unused parameter
    
        // Lookup the URL for the blog title that was either
        // (a) passed to us in this session or
        // (b) saved in the SaveState method when our app was suspended.
        m_itemTitle = safe_cast<String^>(e->NavigationParameter);
    
        // We are navigating forward from SplitPage
        if (e->PageState == nullptr)
        {
            auto feedData = safe_cast<FeedData^>(App::Current->Resources->Lookup("CurrentFeed"));
            m_feedUri = feedData->Uri;
    
            auto feedItem = FeedDataSource::GetFeedItem(feedData, m_itemTitle);
            if (feedItem != nullptr)
            {
                DefaultViewModel->Insert("Title", m_itemTitle);
                // Display the web page.
                contentView->Navigate(feedItem->Link);
            }
        }
    
        // We are resuming from suspension:
        else
        {
            // We are resuming, and might not have our FeedData object yet
            // so must get it asynchronously and wait on the result.
            String^ uri = safe_cast<String^>(e->PageState->Lookup("FeedUri"));
            auto feedDataOp = FeedDataSource::GetFeedAsync(uri); //URL
            auto feedDataTask = create_task(feedDataOp);
    
            feedDataTask.then([this, e](FeedData^ feedData)
            {
                App::Current->Resources->Insert("CurrentFeed", feedData);
    
                m_feedUri = feedData->Uri;
                m_itemTitle = safe_cast<String^>(e->PageState->Lookup("Item"));
                auto feedItem = FeedDataSource::GetFeedItem(feedData, m_itemTitle);
    
                if (feedItem != nullptr)
                {
                    DefaultViewModel->Insert("Title", m_itemTitle);
                    // Display the web page.
                    contentView->Navigate(feedItem->Link);
                }
            });
        }
    }
    
    
  4. Im Gegensatz zu LoadState umfasst SaveState lediglich zwei Codezeilen. Wir speichern den URI des Feeds wie in SplitPage. Der URI wird bei einer Fortsetzung der App nach ihrer Beendigung in LoadState zum Suchen des FeedItem-Elements benötigt. Verwenden Sie in DetailPage.xaml.cpp den folgenden Code, um die vorhandene SaveState-Methode zu ersetzen:

    
    
    void DetailPage::SaveState(Object^ sender, Common::SaveStateEventArgs^ e)
    {
    	   (void) sender;	// Unused parameter
        // Store the itemTitle in case we are suspended or terminated.
        e->PageState->Insert("Item", m_itemTitle);
        e->PageState->Insert("FeedUri", m_feedUri);
    }
    
    
    
  5. Stellen Sie durch Drücken von F5 sicher, dass die App erstellt wird. Wechseln Sie dann zurück zu Visual Studio, und drücken Sie UMSCHALT+F5, um die Ausführung des Buildvorgangs zu beenden.

Teil 5: Hinzufügen von App-Leisten

Ein Großteil der Navigation wird in unserer Blogleser-App voraussichtlich erfolgen, indem Benutzer zwischen der Elementseite und der geteilten Seite wechseln und in den Angeboten auf diesen Seiten stöbern. Auf der geteilten Seite müssen wir es dem Benutzer jedoch ermöglichen, zur Detailansicht des Blogbeitrags zu wechseln. Wir könnten eine Schaltfläche auf der Seite platzieren. Dies würde jedoch nur vom eigentlichen Zweck der App, dem Suchen und Lesen, ablenken. Stattdessen platzieren wir die Schaltfläche auf einer App-Leiste, die erst angezeigt wird, wenn der Benutzer dies wünscht. In diesem Abschnitt fügen wir eine App-Leiste mit einer Schaltfläche hinzu, mit der zur Detailseite gewechselt werden kann.

Eine App-Leiste ist Bestandteil der UI und standardmäßig ausgeblendet. Sie wird ein- oder ausgeblendet, wenn der Benutzer vom Bildschirmrand nach innen wischt oder mit der rechten Maustaste klickt. Auf der App-Leiste findet der Benutzer beispielsweise Navigationsfunktionen, Befehle und Tools. Eine App-Leiste kann am oberen Seitenrand, am unteren Seitenrand oder sowohl oben als auch unten angezeigt werden. Wir empfehlen Ihnen, die Navigation in der oberen App-Leiste zu platzieren und Tools und Befehle in der unteren.

Um eine App-Leiste in XAML hinzuzufügen, weisen wir der TopAppBar- oder BottomAppBar-Eigenschaft einer Page ein AppBar-Steuerelement zu und betten anschließend ein AppBarButton-Element in die AppBar ein.

Hh465045.wedge(de-de,WIN.10).gifSo fügen Sie der App-Leiste der geteilten Seite eine Schaltfläche hinzu

  1. Fügen Sie in SplitPage.xaml nach dem Page.Resources-Endtag das folgende Markup ein, um eine Navigationsleiste mit einem AppBarButton-Steuerelement zum Vorwärtsblättern zu erstellen:

    
    
    <Page.TopAppBar>
        <AppBar Padding="10,0,10,0">
            <Grid>
               <AppBarButton x:Name="fwdButton" Height="95" Margin="150,46,0,0"
                   Command="{Binding NavigationHelper.GoForwardCommand, ElementName=pageRoot}" 
                   AutomationProperties.Name="Forward"
                   AutomationProperties.AutomationId="ForwardButton"
                   AutomationProperties.ItemType="Navigation Button"
                   HorizontalAlignment="Right"
                   Icon="Forward"/>
             </Grid>
         </AppBar>
    </Page.TopAppBar>
    
    
  2. Geben Sie anschließend nach dem Icon-Attribut und vor /> ein Leerzeichen und dann Click="fwdButton_Click" ein. (Sie können den Vorschlag der automatischen Vervollständigung akzeptieren.) Die Ereignishandler werden auf den CodeBehind-Seiten erstellt.

Hh465045.wedge(de-de,WIN.10).gifSo fügen Sie der Detailseite Navigationsfunktionen hinzu

  1. Fügen Sie SplitPage.xaml.cpp die folgende #include-Direktive hinzu:

    
    #include "DetailPage.xaml.h"
    
    
    
  2. Fügen Sie in SplitPage.xaml.cpp dem fwdButton_Click-Methodentext den folgenden Code hinzu:

    
    // Navigate to the appropriate destination page, and configure the new page
    // by passing required information as a navigation parameter.
    
    auto selectedItem = dynamic_cast<FeedItem^>(this->itemListView->SelectedItem);
    
    // selectedItem will be nullptr if the user invokes the app bar
    // and clicks on "view web page" without selecting an item.
    if (this->Frame != nullptr && selectedItem != nullptr)
    {
        auto itemTitle = safe_cast<String^>(selectedItem->Title);
        this->Frame->Navigate(TypeName(DetailPage::typeid), itemTitle);
    }
    
    
    

Ihre App verfügt nun über die Kernfunktionen! Erstellen Sie die App, und führen Sie sie aus, um die Seitennavigation zu testen. Wenn Sie auf der Elementseite ein Element auswählen, sollte die App zur geteilten Seite navigieren. Testen Sie auf der geteilten Seite die Zurück-Schaltfläche zum Wechseln auf die Elementseite. Sie können auch vom oberen Bildschirmrand nach innen wischen (oder mit der rechten Maustaste klicken), um die App-Leiste aufzurufen. Tippen oder klicken Sie dann auf die entsprechende Schaltfläche, um die Detailseite aufzurufen. Verwenden Sie auf der Detailseite die Zurück-Schaltfläche, um zurück zur geteilten Seite zu wechseln. Wenn Sie auf der geteilten Seite verschiedene Elemente auswählen, sollte für das Element eine Vorschau angezeigt werden. Als Nächstes verbessern wir das Aussehen der UI und fügen die Unterstützung für die Ausrichtung im Hochformat hinzu.

Teil 6: Hinzufügen von Animationen und Übergängen

Bei Animationen fallen uns meist zuerst irgendwelche Objekte ein, die auf dem Bildschirm herumhüpfen. Im XAML-Code ist eine Animation dagegen ein Verfahren, mit dem der Wert einer Eigenschaft für ein Objekt geändert wird. Damit eignen sich Animationen für viel mehr als nur für hüpfende Objekte. In unserer Blogleser-App passen wir die UI an unterschiedliche Layouts und Ausrichtungen an, indem wir einige integrierte Animationen und Ausrichtungslogik aus dem Windows.UI.Xaml.Media.Animation-Namespace verwenden.

Hinzufügen von Designanimationen

Eine Designanimation ist eine vorkonfigurierte Animation. Mit PopInThemeAnimation wird beispielsweise erreicht, dass eine Ansicht beim Laden der Seite von rechts nach links eingeblendet wird. Ein höherer Wert der FromHorizontalOffset-Eigenschaft verstärkt diesen Effekt noch. Wir platzieren hier eine PopInThemeAnimation in einem Storyboard und machen sie in DetailPage.xaml zu einer Ressource. Anschließend legen wir das Ziel der Animation auf das Border-Element fest, das unsere Webinhalte umgibt. Dadurch werden das Border-Element und sein gesamter Inhalt animiert.

Hh465045.wedge(de-de,WIN.10).gifSo fügen Sie der Detailseite eine Designanimation hinzu

  1. Fügen Sie den folgenden XAML-(Extensible Application Markup Language-)Ausschnitt in den Knoten Page.Resources in DetailPage.xaml ein:

    
    
    <Storyboard x:Name="PopInStoryboard">
        <PopInThemeAnimation  Storyboard.TargetName="contentViewBorder" FromHorizontalOffset="400"/>
    </Storyboard>
    
    
    
  2. Fügen Sie den folgenden Code am Anfang der DetailPage::LoadState-Methode in DetailPage.xaml.cpp ein. Unsere Außerkraftsetzung von LoadState wird von der OnNavigatedTo-Methode in der LayoutAwarePage-Basisklasse aufgerufen:

    
    // Run the PopInThemeAnimation. 
    Windows::UI::Xaml::Media::Animation::Storyboard^ sb = dynamic_cast<Windows::UI::Xaml::Media::Animation::Storyboard^>(this->FindName("PopInStoryboard"));
    if (sb != nullptr)
    {
        sb->Begin();
    }    
    //... rest of method as before
    
    
    

Hinzufügen von Designübergängen

Ein Designübergang besteht aus einem Satz von Animationen und einem Storyboard. Diese Elemente werden zu einem vorkonfigurierten Verhalten kombiniert, das einem UI-Element angefügt werden kann. Ein ContentThemeTransition wird mit einem ContentControl verwendet und automatisch ausgelöst, wenn sich der Inhalt des Steuerelements ändert.

In unserer App fügen wir dem TextBlock, der die Beitragstitel in der Listenansicht der geteilten Seite enthält, einen Designübergang hinzu. Wenn sich der Inhalt des TextBlocks ändert, wird der ContentThemeTransition ausgelöst und automatisch ausgeführt. Die Animation ist vordefiniert und wird ohne unser Zutun ausgeführt. Wir fügen sie lediglich an den TextBlock an.

Hh465045.wedge(de-de,WIN.10).gifSo fügen Sie SplitPage.xaml einen Designübergang hinzu

  • In SplitPage.xaml ist das TextBlock-Element mit dem Namen pageTitle ein leeres Elementtag. Da wir einen Designübergang in das TextBlock-Element einbetten, müssen wir das TextBlock-Element ändern, damit es über Start- und Endtags verfügt. Ersetzen Sie das vorhandene Tag durch das folgende Markup:

    
    
    <TextBlock x:Name="pageTitle" Grid.Column="1" Text="{Binding Feed.Title}" Style="{StaticResource HeaderTextBlockStyle}"
        IsHitTestVisible="false" TextWrapping="NoWrap" VerticalAlignment="Bottom" Margin="0,0,30,40">
        <TextBlock.Transitions>
            <TransitionCollection>
                <ContentThemeTransition />
             </TransitionCollection>
        </TextBlock.Transitions>
    </TextBlock>
    
    
    

    Wenn sich der Inhalt des TextBlocks ändert, wird der ContentThemeTransition ausgelöst und automatisch ausgeführt. Die Animation ist vordefiniert und wird ohne Weiteres ausgeführt. Wir fügen sie lediglich an den TextBlock an. Weitere Informationen und eine Liste der Designanimationen und -übergänge finden Sie unter Schnellstart: Animieren der Benutzeroberfläche anhand von Bibliotheksanimationen.

Teil 6: Verwenden von Formatvorlagen zum Erzielen eines einheitlichen Aussehens

Für Benutzer soll der Wechsel zwischen der Website mit den Windows-Teamblogs und unserer Blogleser-App reibungslos vonstatten gehen. Das dunkle Standarddesign unserer neuen Windows-UI stimmt jedoch nicht gut mit der Website mit den Windows Teamblogs überein. Dies gilt vor allem für die Detailseite, auf der wir die eigentliche Blogseite in eine WebView laden, wie hier dargestellt:

Detailseite mit dunklem Design

Um unserer App ein einheitliches Erscheinungsbild zu verleihen, das wir bei Bedarf aktualisieren können, verwenden wir Pinsel und Stile. Mit einem Brush können wir an einer Stelle eine Darstellungsweise definieren und diese beliebig weiterverwenden. Mit einem Style können wir Werte für die Eigenschaften eines Steuerelements festlegen und diese Einstellungen in der gesamten App wiederverwenden.

Bevor wir uns aber weiter mit den Details beschäftigen, erklären wir kurz, wie Sie mithilfe eines Brush-Elements die Hintergrundfarbe der Seiten in der App festlegen können. Jede Seite in unserer App hat ein Grid-Stammelement. Dieses Element besitzt eine Background-Eigenschaft, mit der die Hintergrundfarbe der Seite definiert wird. Wir könnten nun die Hintergrundfarbe der Seiten einzeln festlegen, wie hier zu sehen: <Grid Background="Blue">. Besser wäre jedoch, einen Brush als Ressource zu definieren und damit die Hintergrundfarbe aller Seiten zu definieren. Diese Vorgehensweise gilt für die Visual Studio-Vorlagen: <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">.

Das ist in Ordnung, um die Background-Eigenschaft des Grid-Elements festzulegen, aber meistens müssen mehrere Eigenschaften festgelegt werden, um die gewünschte Optik zu erzielen. Sie können Einstellungen für eine beliebige Anzahl von Eigenschaften als Style gruppieren und den Style dann auf ein Steuerelement anwenden.

Der Ort, an dem eine Ressource definiert wird, bestimmt den Bereich, in dem sie verwendet werden kann. Sie können Ressourcen in einer XAML-Datei auf Seitenebene, in der Datei App.xaml auf Anwendungsebene oder in einer separaten XAML-Datei des Ressourcenverzeichnisses definieren. (Eine XAML-Datei mit einem Ressourcenverzeichnis kann App-übergreifend genutzt werden. Außerdem können in einer einzelnen App mehrere Ressourcenverzeichnisse zusammengeführt werden.)

In unserer Blogleser-App definieren wir Ressourcen in App.xaml, um sie für die gesamte App verfügbar zu machen. Außerdem haben wir einige Ressourcen in der XAML-Datei für einzelne Seiten definiert. Sind Ressourcen mit dem gleichen Schlüssel sowohl in App.xaml als auch auf einer Seite definiert, hat die Ressource auf der Seite Vorrang vor der Ressource in App.xaml. Analog dazu hat eine Ressource in App.xaml Vorrang vor einer Ressource, die in einer separaten Ressourcenverzeichnisdatei mit dem gleichen Schlüssel definiert ist. Weitere Informationen finden Sie unter Schnellstart: Formatieren von Steuerelementen.

In einer Style-Definition benötigen wir ein TargetType-Attribut und eine Auflistung eines oder mehrerer Setter-Elemente. Wir legen den TargetType auf eine Zeichenfolge fest, die den Typ angibt, auf den der Style angewendet wird (in diesem Fall Panel). Wenn Sie versuchen, einen Style auf ein Steuerelement anzuwenden, das nicht zum TargetType-Attribut passt, wird eine Ausnahme ausgelöst. Jedes Setter-Element benötigt eine Property und einen Value. Diese Einstellungen geben die Steuerelementeigenschaft an, für die die Einstellung gilt, sowie den Wert, der für diese Eigenschaft festgelegt wird.

Um den Background unserer Seiten zu ändern, ersetzen wir ApplicationPageBackgroundThemeBrush durch ein benutzerdefiniertes Brush-Element. Für unser benutzerdefiniertes Brush-Element verwenden wir den Color-Wert "#FF0A2562" – ein schönes Blau, das wunderbar zu den Farben der Website mit den Windows-Teamblogs passt. Um das Brush-Element für das Systemdesign zu ersetzen, erstellen wir einen Style auf der Grundlage von LayoutRootStyle und ändern darin die Background-Eigenschaft.

Hh465045.wedge(de-de,WIN.10).gifSo legen Sie die Hintergrundfarbe für alle Seiten fest

  1. Um einen neuen Stil für den Layoutstamm zu definieren, fügen Sie diese Brush- und Stildefinitionen in App.xaml in den <Application.Resources>-Knoten ein:

    
    
      <SolidColorBrush x:Key="WindowsBlogBackgroundBrush" Color="#FF0A2562"/>
    
      <Style x:Key="WindowsBlogLayoutRootStyle" TargetType="Panel">
          <Setter Property="Background" Value="{StaticResource WindowsBlogBackgroundBrush}"/>
      </Style>
    
    
  2. Für das Grid-Stammelement auf den einzelnen Seiten der App wird derzeit ein Background-Attribut zum Festlegen der Hintergrundfarbe einer Seite verwendet. In ItemsPage.xaml, SplitPage.xaml und DetailPage.xaml ändern wir dies, um auf das WindowsBlogLayoutRootStyle-Element zu verweisen, das wir App.xaml eben hinzugefügt haben. Dann wird für jede Seite der gleiche Hintergrund verwendet:

    
    
     <Grid Style="{StaticResource WindowsBlogLayoutRootStyle}">
    
    
  3. Drücken Sie F5, um die App zu erstellen und auszuführen, und sehen Sie sich die blauen Seiten an.

Steuerelement- und Datenvorlagen

Mit benutzerdefinierten Datenvorlagen sowie mit Brushes und Styles erhält unsere App ein ähnliches Erscheinungsbild wie die Website mit den Windows-Teamblogs.

Hh465045.wedge(de-de,WIN.10).gifSo fügen Sie eine Steuerelementvorlage für das Datum hinzu

  • Fügen Sie in der Datei App.xaml im <Application.Resources>-Knoten ein ControlTemplate-Element hinzu, mit dem ein Quadrat zum Anzeigen des Datums definiert wird. Wir definieren dieses Element in App.xaml, damit wir sowohl in ItemsPage.xaml als auch in SplitPage.xaml darauf verweisen können.

    
    
     <ControlTemplate x:Key="DateBlockTemplate">
        <Canvas Height="86" Width="86"  Margin="8,8,0,8" 
            HorizontalAlignment="Left" VerticalAlignment="Top">
             <TextBlock TextTrimming="WordEllipsis" TextWrapping="NoWrap" 
                 Width="Auto" Height="Auto" Margin="8,0,4,0" 
                 FontSize="32" FontWeight="Bold">
                 <TextBlock.Text>
                     <Binding Path="PubDate" 
                     Converter="{StaticResource dateConverter}"
                         ConverterParameter="month"/>
                 </TextBlock.Text>
             </TextBlock>
    
             <TextBlock TextTrimming="WordEllipsis" TextWrapping="Wrap" 
                 Width="40" Height="Auto" Margin="8,0,0,0"
                 FontSize="34" FontWeight="Bold" Canvas.Top="36">
                 <TextBlock.Text>
                     <Binding Path="PubDate" 
                         Converter="{StaticResource dateConverter}"
                         ConverterParameter="day"/>
                 </TextBlock.Text>
             </TextBlock>
             <Line Stroke="White" StrokeThickness="2" 
                 X1="54" Y1="46" X2="54" Y2="80"/>
    
             <TextBlock TextWrapping="Wrap" 
                 Width="20" Height="Auto" 
                 FontSize="{StaticResource ControlContentThemeFontSize}"
                 Canvas.Top="42" Canvas.Left="60">
                 <TextBlock.Text>
                     <Binding Path="PubDate"
                     Converter="{StaticResource dateConverter}"
                     ConverterParameter="year"/>
                 </TextBlock.Text>
             </TextBlock>
         </Canvas>
    </ControlTemplate>
    
    
    

    Beachten Sie, dass mit ControlTemplate die Parameter ("day", "month" und "year") definiert werden, die an die bereits im DateConverter erstellte Convert-Funktion übergeben werden. Tag, Monat und Jahr werden als unabhängige Elemente formatiert und verfügen jeweils über eine eigene Schriftgröße. Daher war es erforderlich, neue Funktionen für die Convert-Methode zu schreiben, um die einzelnen Bestandteile des Datums separat zurückgeben zu können.

Hh465045.wedge(de-de,WIN.10).gifSo fügen Sie der Elementseite Datenvorlagen hinzu

  1. In ItemsPage.xaml fügen wir die folgenden Elemente dem <Page.Resources>-Knoten hinzu, um das Erscheinungsbild der Rasterelemente zu definieren:

    
    
    
      <!-- light blue -->
            <SolidColorBrush x:Key="BlockBackgroundBrush" Color="#FF557EB9"/>
    
            <!-- Grid Styles -->
            <Style x:Key="GridTitleTextStyle" TargetType="TextBlock" BasedOn="{StaticResource BaseTextBlockStyle}">
                <Setter Property="FontSize" Value="26.667"/>
                <Setter Property="Margin" Value="12,0,12,2"/>
            </Style>
    
            <Style x:Key="GridDescriptionTextStyle" TargetType="TextBlock" BasedOn="{StaticResource BaseTextBlockStyle}">
                <Setter Property="VerticalAlignment" Value="Bottom"/>
                <Setter Property="Margin" Value="12,0,12,60"/>
            </Style>
    
            <DataTemplate x:Key="DefaultGridItemTemplate">
                <Grid HorizontalAlignment="Left" Width="250" Height="250"
                    Background="{StaticResource BlockBackgroundBrush}" >
                    <StackPanel>
                        <TextBlock Text="{Binding Title}" Style="{StaticResource GridTitleTextStyle}" Margin="5,5,5,5" />
                        <TextBlock Text="{Binding Description}" Style="{StaticResource GridDescriptionTextStyle}" Margin="5,5,5,5" />                    
                    </StackPanel>
                    <StackPanel VerticalAlignment="Bottom" Orientation="Horizontal"
                            Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
                        <TextBlock Text="Last Updated" Margin="12,4,0,8" Height="42"/>
                        <TextBlock Text="{Binding PubDate, Converter={StaticResource dateConverter}}" Margin="12,4,12,8" Height="42"/>
                    </StackPanel>
                </Grid>
            </DataTemplate>
    
        
    
    
    
  2. Aktualisieren Sie in ItemsPage.xaml auch das GridView-Element mit dem Namen itemGridView, damit es auf die gerade hinzugefügte DefaultGridItemTemplate-Ressource verweist und nicht die standardmäßige Inlinevorlage verwendet. Ersetzen Sie das gesamte GridView-Element (vom Starttag bis zum Endtag) durch dieses Element, das nur aus einem einzelnen selbstschließenden Tag besteht:

    
    
     <!-- Horizontal scrolling grid -->
            <GridView
                x:Name="itemGridView"
                AutomationProperties.AutomationId="ItemsGridView"
                AutomationProperties.Name="Items"
                TabIndex="1"
                Grid.RowSpan="2"
                Padding="116,136,116,46"
                ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
                SelectionMode="None"
                ItemTemplate="{StaticResource DefaultGridItemTemplate}"
                IsItemClickEnabled="true"
                IsSwipeEnabled="false"
                ItemClick="itemGridView_ItemClick">
            </GridView>
    
    
    

    Beachten Sie den Verweis auf unsere neue Datenvorlage in dieser Zeile: ItemTemplate="{StaticResource DefaultGridItemTemplate}".

Hh465045.wedge(de-de,WIN.10).gifSo fügen Sie Datenvorlagen der geteilten Seite hinzu

  1. Fügen Sie in SplitPage.xaml dem <Page.Resources>-Knoten die folgenden Elemente hinzu, um das Aussehen der Listeneinträge zu definieren. Beachten Sie, dass zwei Datenvorlagen vorhanden sind, und zwar eine für den Querformatmodus und eine für den Hochformatmodus:

    
    
    <!-- Green    -->
    <SolidColorBrush x:Key="BlockBackgroundBrush" Color="#FF6BBD46"/>
    
    <DataTemplate x:Name="LandscapeItemTemplate">
        <Grid HorizontalAlignment="Stretch" Width="Auto" Height="110" Margin="10,10,10,0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <!-- Green date block -->
            <Border Background="{StaticResource BlockBackgroundBrush}" Width="110" Height="110" />
            <ContentControl Template="{StaticResource DateBlockTemplate}" />
            <StackPanel Grid.Column="1"  HorizontalAlignment="Left"  Margin="12,8,0,0">
                <TextBlock Text="{Binding Title}" FontSize="26.667" TextWrapping="Wrap" MaxHeight="72" Foreground="#FFFE5815" />
                    <TextBlock Text="{Binding Author}" FontSize="18.667" />
            </StackPanel>
        </Grid>
    </DataTemplate>
    
    <DataTemplate x:Name="PortraitItemTemplate">
        <Grid HorizontalAlignment="Stretch" Width="Auto" Height="110" Margin="10,10,10,0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
             </Grid.ColumnDefinitions>
             <!-- Green date block -->
             <Border Background="{StaticResource BlockBackgroundBrush}" Width="110" Height="110" />
             <ContentControl Template="{StaticResource DateBlockTemplate}" />
             <StackPanel Grid.Column="1"  HorizontalAlignment="Left"  Margin="12,8,0,0">
                 <TextBlock Text="{Binding Title}" FontSize="26.667" TextWrapping="Wrap" MaxHeight="72" Foreground="#FFFE5815" />
                 <TextBlock Text="{Binding Author}" FontSize="18.667" />
             </StackPanel>
         </Grid>
    </DataTemplate>
    
    
    
  2. Als Nächstes müssen wir in SplitPage.xaml die ItemTemplate-Eigenschaft im ListView-Element mit dem Namen itemListView aktualisieren, damit es je nach Ausrichtung des Geräts (Querformatmodus oder Hochformatmodus) eine der beiden benutzerdefinierten Datenvorlagen verwendet. Wir geben die Standardvorlage hier im XAML-Code an. Später erfahren Sie dann, wie zur Vorlage für das Hochformat gewechselt wird, indem VisualState-XAML-Elemente und CodeBehind zum Erkennen der Ausrichtungsänderung verwendet werden.

    Ersetzen Sie in SplitPage.xaml das vorhandene ListView-Element durch Folgendes:

    
    
             <!-- Vertical scrolling item list -->
            <ListView
                x:Name="itemListView"
                AutomationProperties.AutomationId="ItemsListView"
                AutomationProperties.Name="Items"
                TabIndex="1"
                Grid.Row="1"
                Margin="-10,-10,0,0"
                Padding="120,0,0,60"
                ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
                IsSwipeEnabled="False"
                SelectionChanged="ItemListView_SelectionChanged"
                ItemTemplate="{StaticResource LandscapeItemTemplate}">
    
                <ListView.ItemContainerStyle>
                    <Style TargetType="FrameworkElement">
                        <Setter Property="Margin" Value="0,0,0,10"/>
                    </Style>
                </ListView.ItemContainerStyle>
            </ListView>
    
    
    

Mit den neuen Stilen passt unsere App wunderbar zum Erscheinungsbild der Windows Teamblogs-Website:

Elementseite mit angewendeten Stilen

Geteilte Seite mit angewendeten Stilen

Detailseite mit angewendeten Stilen

Mit Stilen und Stilvorlagen können wir schnell ein unterschiedliches Aussehen für unsere App definieren und anwenden. Im nächsten Abschnitt kombinieren wir die gewonnenen Erkenntnisse über Animationen und Stile, um dafür zu sorgen, dass sich unsere App während der Ausführung nahtlos verschiedenen Layouts und Ausrichtungen anpasst.

Teil 7: Einrichten der Anpassung an verschiedene Layouts

Die Visual Studio-Vorlagen für Windows Store-Apps enthalten keinen Code, mit dem Änderungen der Bildschirmausrichtung behandelt werden. Wir müssen diesen Code selbst schreiben.

Für den Übergang zwischen verschiedenen Ansichten mithilfe von XAML verwenden wir den VisualStateManger, um VisualStateGroup-Elemente für die einzelnen Seiten zu definieren. Jedes VisualStateGroup-Element enthält mindestens eine VisualState-Definition. Zur Laufzeit rufen Sie im SizeChanged-Ereignishandler VisualStateManager::GoToState auf, damit die Seite das angegebene VisualState-Element zum Rendern der UI verwendet. In dieser App werden für die Elementseite und die geteilte Seite jeweils zwei VisualStateGroups-Elemente verwendet: ein Element für das Querformat (Standard) und ein Element für das Hochformat. Ein VisualState-Element besteht im Wesentlichen aus einer Gruppe von Transformationen für XAML-Elemente. Jedes VisualState-Element verfügt über Animationen, mit denen angegeben wird, welche Seitenelemente geändert werden sollen.

Im Abschnitt Erzielen eines einheitlichen Aussehens mit Stilen dieses Lernprogramms haben wir Stile und Vorlagen erstellt, um das Erscheinungsbild unserer App anzupassen. Diese Stile und Vorlagen werden in der standardmäßigen Querformatansicht verwendet. Damit das benutzerdefinierte Erscheinungsbild im Hochformatmodus erhalten bleibt, müssen wir für diese Ansicht außerdem einige benutzerdefinierte Stile und Vorlagen erstellen.

Hh465045.wedge(de-de,WIN.10).gifSo führen Sie in ItemsPage.xaml die Anpassung an das Hochformat durch

  1. An dieser Stelle müssen wir eine Entscheidung bezüglich des Designs treffen. Wir verwenden ein GridView-Element, um Elemente im Querformatmodus anzuzeigen. Ein GridView-Element wurde für den horizontalen Bildlauf entworfen. Wenn für die Elemente im Hochformat ein vertikaler Bildlauf möglich sein soll, müssen wir ein ListView-Element verwenden. Dazu muss die App zur Laufzeit jedoch zwei separate Steuerelemente laden, was zu einer Verlängerung der Ladezeit und einer Erhöhung des Arbeitsspeicherbedarfs führen kann. (Es lässt sich nur anhand von Tests ermitteln, wie stark der Aufwand, der durch ein zusätzliches Steuerelement entsteht, ins Gewicht fällt.) In dieser App verwenden wir der Einfachheit halber für beide Ausrichtungen dasselbe GridView-Element. Im Hochformat ist dann nur der horizontale Bildlauf möglich, aber wir können die Elemente etwas schmaler gestalten, um die Proportionalität zum Rechteck für das Hochformat zu erhöhen. In diesem Fall müssen wir auch einige Schriftgrößen anpassen, um das übermäßige Umbrechen von Text in Titeln zu verhindern. Zum Durchführen dieser Änderungen definieren wir ein VisualState-Element und füllen es mit Animationen für die einzelnen Elemente.

    Fügen Sie dem <Page.Resources>-Abschnitt in ItemsPage.xaml den folgenden Stil hinzu. Damit werden die Elementtitel mit einem kleineren Schriftgrad versehen, sodass sie besser in das schmalere Rechteck passen. Die Verwendung erfolgt über die Datenvorlage, die wir im nächsten Schritt hinzufügen.

    
    <Style x:Key="GridTitlePortraitTextStyle" TargetType="TextBlock" BasedOn="{StaticResource BaseTextBlockStyle}">
        <Setter Property="FontSize" Value="20"/>
        <Setter Property="Margin" Value="12,0,12,2"/>
    </Style>
    
    
  2. Fügen Sie diese DataTemplate jetzt dem <Page.Resources>-Knoten hinzu, um die Vorlage zu definieren, die vom GridView-Element im Hochformat verwendet wird:

    
    <DataTemplate x:Key="PortraitGridItemTemplate">
        <Grid HorizontalAlignment="Left" Width="160" Height="250"
        Background="{StaticResource BlockBackgroundBrush}">
            <StackPanel>
                <TextBlock Text="{Binding Title}" Style="{StaticResource GridTitlePortraitTextStyle}" Margin="5,5,5,5"/>
                <TextBlock Text="{Binding Description}" Style="{StaticResource GridDescriptionTextStyle}" Margin="5,5,5,5" TextTrimming="CharacterEllipsis"/>
            </StackPanel>
            <StackPanel VerticalAlignment="Bottom" 
                Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
                <TextBlock Text="Last Updated:"/>
                <TextBlock Text="{Binding PubDate, Converter={StaticResource dateConverter}}"/>
            </StackPanel>
        </Grid>
    </DataTemplate> 
    
    
  3. Als Nächstes informieren wir die Seite über die neue Datenvorlage. Hier kommt das VisualState-Element ins Spiel. Fügen Sie in ItemsPage.xaml den folgenden Code direkt oberhalb des Endtags für das Grid-Stammelement hinzu:

    
     <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="ViewStates">
            <VisualState x:Name="DefaultLayout"/>
            <VisualState x:Name="Portrait">
                <Storyboard>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemGridView" Storyboard.TargetProperty="ItemTemplate">
                        <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PortraitGridItemTemplate}"/>
                    </ObjectAnimationUsingKeyFrames>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemGridView" Storyboard.TargetProperty="Padding">
                        <DiscreteObjectKeyFrame KeyTime="0" Value="40,136,116,40"/>
                    </ObjectAnimationUsingKeyFrames>
                </Storyboard>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    
    

    Sehen wir uns diesen Code einmal genauer an. Wir haben ein VisualStateManager.VisualStateGroups-Element hinzugefügt und eine Gruppe darin geschachtelt: ViewStates. Diese Gruppe enthält zwei Zustände, nämlich "DefaultLayout" für den Querformatmodus und "Portrait" für den Hochformatmodus. Beachten Sie, dass "DefaultLayout" leer ist. Dies bedeutet, dass dieses Element mit dem XAML-Code für die Seite identisch ist. Wenn sich das Gerät im Querformat befindet und die Seite ein SizeChanged-Ereignis empfängt, wird der XAML-Code der Seite zum Rendern verwendet. Wenn sich das Gerät im Hochformat befindet, wird von der Seite der XAML-Standardcode geladen, und die von den Animationen angegebenen Änderungen werden ausgeführt. Das GridView-Element verwendet eine andere Elementvorlage. Außerdem passen wir die Abstände etwas an, damit sie für die vertikalen Abmessungen des Hochformats besser geeignet sind.

  4. Fügen Sie jetzt dem Page-Stammelement ein SizeChanged-Ereignis hinzu, indem Sie SizeChanged als letztes Attribut im Element eingeben. Wenn Sie mit der Eingabe beginnen, wird von der UI die Vervollständigung vorgeschlagen, und Sie erhalten Unterstützung bei der Erstellung eines Ereignishandlers. Wenn Sie die Vorschläge der UI akzeptieren, müssen Sie die Methodensignatur und Implementierung nicht manuell vornehmen. Beginnen Sie zum Erstellen des Ereignishandlers mit der Eingabe von pageRoot_SizeChanged, und akzeptieren Sie den Vorschlag der UI, wenn er mit dem Ereignisnamen übereinstimmt.
  5. Als Nächstes müssen wir den CodeBehind hinzufügen, um das richtige VisualState-Element aufzurufen, wenn sich die Ausrichtung des Geräts ändert. Fügen Sie in ItemsPage.xaml.cpp der leeren pageRoot_SizeChanged-Methode den folgenden Code hinzu:
    
    if (e->NewSize.Height / e->NewSize.Width >= 1)
    {
        VisualStateManager::GoToState(this, "Portrait", true);
    }
    else
    {
        VisualStateManager::GoToState(this, "DefaultLayout", true);
    }
    
    

Hh465045.wedge(de-de,WIN.10).gifSo führen Sie in SplitPage.xaml die Anpassung an das Hochformat durch

  1. Im Hochformat werden die beiden Spalten der geteilten Seite wie logische Seiten behandelt. Wenn ein Benutzer die Liste anzeigt, nimmt sie den gesamten Bildschirm ein, und die Spalte mit den Details ist reduziert. Sobald ein Benutzer einen Listeneintrag auswählt, wird die Listenspalte reduziert, und die Spalte mit den Details nimmt den Bildschirm ein. Daher erfordert SplitPage.xaml zwei VisualStates für den Hochformatmodus: SinglePane zum ausschließlichen Anzeigen der Listenseite und SinglePane_Detail zum Anzeigen der Detailseite. Sehen Sie sich für jede Animation das Ziel, die Zieleigenschaft und den Wert an, der der Eigenschaft zugewiesen ist. Diese VisualStates-Elemente sind standardmäßig in SplitPage.xaml enthalten, und sie werden in dieser App nicht geändert. Sehen Sie sich außerdem #pragma region Logical page navigation an. Dort erfahren Sie, wie die Seitenlogik ermittelt, welches VisualState-Element im Hochformatmodus aufgerufen werden soll.

    Wir nehmen eine Änderung an diesem Code vor, damit SplitPage sich auch dann erwartungsgemäß verhält, wenn die Bildschirmbreite 768 Pixel beträgt. Für die UsingLogicalPageNavigation-Memberfunktion wird "false" zurückgegeben, wenn das MinimumWidthForSupportingTwoPanes-Element auf 768 festgelegt ist. Die logische Seitennavigation soll auch für diese genaue Auflösung aufgerufen werden. Daher müssen wir wie im folgenden Codeabschnitt entweder den Operator "<" in der Funktion in "<=" ändern oder die Definition von MinimumWidthForSupportingTwoPanes in der Datei "SplitPage.xaml.h" in 769 ändern.

    
    bool SplitPage::UsingLogicalPageNavigation()
    {
    	return Windows::UI::Xaml::Window::Current->Bounds.Width <= MinimumWidthForSupportingTwoPanes;
    }
    
    
  2. Von SplitPage.xaml wird überraschenderweise standardmäßig kein SizeChanged-Ereignishandler angegeben, sodass wir dies jetzt einrichten müssen. Beginnen Sie im Starttag des Page-Stammelements mit der Eingabe von SizeChanged, und verwenden Sie dann die automatische Vervollständigung zum Abschließen der Anweisung und Auskommentieren des CodeBehind. Fügen Sie in SplitPage.xaml.cpp dem leeren Methodenstub dann den folgenden Code hinzu:
    
    if (e->NewSize.Height / e->NewSize.Width >= 1)
    {
        VisualStateManager::GoToState(this, "SinglePane", true);
    }
    else
    {
        VisualStateManager::GoToState(this, "PrimaryView", true);
    }
    
    

Hh465045.wedge(de-de,WIN.10).gifSo passen Sie den WebView-Rand im Hochformatmodus an

  1. In DetailPage.xaml müssen wir für das Hochformat nur den Rand unserer WebView so anpassen, dass der gesamte verfügbare Platz genutzt wird. Dazu fügen wir zwei VisualStates hinzu. Im Hochformatzustand fügen wir eine Animation hinzu, um den Wert der Margin-Eigenschaft unter contentViewBorder zu ändern. Fügen Sie das folgende Element direkt vor dem letzten Grid-Endtag ein:

    
    
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="ViewStates">
            <VisualState x:Name="Landscape"/>
            <VisualState x:Name="Portrait">
                <Storyboard>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="contentViewBorder" Storyboard.TargetProperty="Margin">
                        <DiscreteObjectKeyFrame KeyTime="0" Value="20,5,20,20"/>
                    </ObjectAnimationUsingKeyFrames>
                </Storyboard>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    
    
  2. Fügen Sie in DetailPage.xaml dem Page-Stammelement ein SizeChanged-Ereignis hinzu. Beginnen Sie wie schon weiter oben beschrieben mit dem Eingeben von SizeChanged="pageRoot_SizeChanged", und verwenden Sie dann die automatische Vervollständigung, um den CodeBehind auszukommentieren.
  3. Fügen Sie in DetailPage.xaml.cpp der leeren neuen pageRoot_SizeChanged-Methode den folgenden Code hinzu:
    
    if (e->NewSize.Height / e->NewSize.Width >= 1)
    {
        VisualStateManager::GoToState(this, "Portrait", true);
    }
    else
    {
        VisualStateManager::GoToState(this, "Landscape", true);
    }
    
    

Weiterer Schritt: Hinzufügen eines Begrüßungsbildschirms

Der erste Eindruck einer App wird Benutzern mit dem Begrüßungsbildschirm vermittelt, der nach dem Starten der App angezeigt wird. Während die App ihre Ressourcen initialisiert, wird Benutzern mit dem Begrüßungsbildschirm gezeigt, dass die App funktioniert. Er wird ausgeblendet, wenn die erste Seite der App zur Anzeige bereit ist.

Ein Begrüßungsbildschirm besteht aus einer Hintergrundfarbe und einem Bild. In unserer App haben wir als von der Projektvorlage bereitgestellte Ressourcen nur den Standardhintergrund und ein Bild (SplashScreen.png) verwendet. Sie können die Farbe und das Bild bei Bedarf jedoch anpassen, indem Sie im Manifest-Editor die Registerkarte Anwendungsbenutzeroberfläche verwenden, um die Einstellungen in Package.appxmanifest zu ändern.

Dies ist nur ein Beispiel für einen Begrüßungsbildschirm für unsere Blogleser-App:

Bild des Begrüßungsbildschirms

Sie können den Begrüßungsbildschirm auch erweitern, indem Sie die Eigenschaften und Methoden der SplashScreen-Klasse verwenden. Beispielsweise können Sie die Koordinaten des Begrüßungsbildschirms abrufen und zum Anordnen der ersten Seite der App nutzen. Sie können auch feststellen, wann der Begrüßungsbildschirm geschlossen wird. Dann wissen Sie, wann Animationen für den Eingang von Inhalten in der App gestartet werden sollten.

Nächste Schritte

In diesem Lernprogramm haben Sie erfahren, wie Sie mit den Seitenvorlagen in Microsoft Visual Studio Express 2012 für Windows 8 eine App mit mehreren Seiten erstellen und wie die Navigation und das Übergeben von Daten zwischen Seiten funktioniert. Außerdem haben Sie gelernt, wie die App mithilfe von Stilen und Vorlagen dem Erscheinungsbild der Website mit den Windows-Teamblogs angeglichen werden kann. Darüber hinaus sind Sie jetzt mit der Verwendung von Designanimationen und einer App-Leiste vertraut und können der App den Charakter einer Windows Store-App verleihen. Außerdem wissen Sie nun, wie Sie die App an verschiedene Layouts und Ausrichtungen anpassen können, um stets eine optimale Darstellung zu gewährleisten.

Unsere App ist fast bereit für den Windows Store. Weitere Informationen zum Übermitteln einer App an den Windows Store finden Sie hier:

Verwandte Themen

Roadmap für Windows-Runtime-Apps mit C++
Entwickeln von Windows Store-Apps mit Visual Studio 11

 

 

Anzeigen:
© 2014 Microsoft