Asynchrone Programmierung in WinRT

Meine neue WebSite finden Du jetzt unter https://AttilaKrick.com.

Beispiele zu Asynchrone API

MSDN Asynchrone Programmierung

Ein Benutzer erwartet das auf seine Aktion unmittelbare Reaktion von der App erfolgt. Um so verwirrter reagiert der Benutzer wenn diese Reaktion auf sich warten lässt weil die App noch synchron mit dem laden einer Datei aus dem Internet blockiert ist. Eine flüssige App würde hier den Download asynchron durchführen, so das der Benutzer weiter mit seiner App interagieren kann.

Wer mit dem asynchronen Mustern nicht vertraut ist kann sich die Asynchronität wie eine Bestellung beim Pizza-Dienst vorstellen. Sie rufen an und geben Ihre Bestellung durch und können anschließend Ihre bisherige Tätigkeit fortsetzen. Sobald der Pizza-Man oder -Frau mit der fertig Pizza klingelt können Sie sich nun um das leckere Esswerk kümmern. Niemand würde auf die Idee kommen nach der Bestellung apathisch in Inaktivität zu verfallen bis es klingelt.

Async-Namenskonvention – Üblicherweise enden asynchrone Methoden auf ...Async. Diese Methoden finden Sie dort wo Dauer eine Rolle spielt, z.B.

ContactPicker.PickSingleContactAsync
XmlDocument.SaveToFileAsync
DataReader.LoadAsync
RandomAccessStream.CopyAsync
SyndicationClient.RetrieveFeedAsync

await & async – Wenn Sie den await-Operator in einer Methode verwenden muss diese Methode das async-Schlüsselwort enthalten. Im Konstruktor oder in einer Eigenschaft können Sie await nicht verwenden. TIPP Erstellen Sie eine Methode, lagern den await-Code dorthin aus und ruf anschließend die erstellte Methode im Konstruktor bzw. in der Eigenschaft auf.

Threads, Kontextwechsel und Verteiler spielen für Sie keine Rolle mehr. Der Aufruf einer asynchronen API mittels await erfolgt im selben Kontext wie der ursprüngliche Aufruf. Das bedeutet dass Sie die Benutzeroberfläche mit den Ergebnissen aktualisieren können ohne sich um die Rückkehr zum Benutzeroberflächen-Thread zu kümmern.

Task – Sie können aus dem .NET Framework Task und Task<TResult>
verwenden um eigene asynchrone Methode zu implementieren. Hierzu gibt es die Erweiterungsmethoden (WindowsRuntimeSystemExtensions) AsAsyncAction oder AsAsyncOperation<TResult>. Diese liefern Async-WinRT-Konforme Ergebnisse die sich in WinRT entsprechend weiter verwerten lassen. Hierzu später mehr.

Asynchrone Methoden verwenden

Angenommen wir wollen Nachrichten-Beiträge direkt aus dem Internet herunterladen. Hierbei ist es wichtig dass die App reaktionsfähig bleibt. Um diese Reaktionsfähigkeit sicherzustellen, stellt WinRT eine asynchrone Methode SyndicationClient.RetrieveFeedAsync zum Herunterladen von Newsfeeds bereit deren asynchrone Verwendung wie folgt aussieht:

async void Button_Click(object sender, RoutedEventArgs e)
{
    var feedUri = new Uri("http://rss.golem.de/rss.php?feed=RSS2.0");
    var client = new SyndicationClient();
    try
    {
        var feed = await client.RetrieveFeedAsync(feedUri);
        StatusTextBox.Text = feed.Title.Text;
    }
    catch (Exception ex)
    {
        StatusTextBox.Text = ex.Message;
    }
}

Anmerkungen

  1. Der await-Operator teilt dem Compiler mit dass Sie eine asynchrone Methode aufrufen und er zusätzliche Arbeiten für Sie erliegen muss.
  2. Das Schlüsselwort asyncmuss in der Methodendeklaration aller Methoden angegeben werden, in denen Sie den await-Operator verwenden.
  3. Durch den Aufruf von ... await client.RetrieveFeedAsync initiiert die Methode den Abruf asynchron und beendet den Click-Ereignishandler vorübergehend. Ihre App kann dadurch andere Ereignisse verarbeiten und bleibt für den Benutzer reaktionsfähig.
  4. Wenn RetrieveFeedAsync abgeschlossen und das Ergebnis SyndicationFeed verfügbar ist macht Ihre App im Click-Ereignishandler an der Stelle weiter wo sie aufgehört hat var feeds ... um den Rest des Handlers abzuarbeiten.
  5. Während des asynchronen Vorgangs könnte z.B. die Netzwerkverbindung zum RSS-Feed abbrechen. Daher können Sie wie gewohnt die Fehlertoleranz erhöhen, indem Sie den asynchronen Code in einen try-catch-Block ausführen.

Ergebnisse asynchroner Methoden

Der Rückgabetyp von RetrieveFeedAsync ist kein SyndicationFeed sondern IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress>. Es ist wichtig zu Verstehen das der await-Operator tatsächlich auf Basis des Rückgabewerts und nicht auf der Methode agiert. Wenn Sie await anwenden erhalten Sie das Ergebnis der asynchronen Methoden SyndicationFeed, wenn Sie await nicht anwenden erhalten Sie IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress>.

Wenn Sie eine asynchrone Methode verwenden reflektieren Sie üben den Rückgabetyp um Infos zum Ergebnis zu erhalten. Alle asynchronen Methoden in WinRT geben einen der folgenden Typen zurück:

IAsyncAction                                    = Einfache Aktion
IAsyncActionWithProgress<TProgress>             = inkl Fortschritt 
IAsyncOperation<TResult>                        = inkl Ergebnis
IAsyncOperationWithProgress<TResult, TProgress> = inkl Ergebnis + Forts.

Diese Rückgabetypen eröffnen Ihnen weitere Möglichkeiten beim Umgang mit asynchronen Vorgängen die im weiteren Verlauf besprochen werden.

Ablaufsteuerung mithilfe asynchroner Rückgabetypen

Status

Ein asynchroner Vorgang beginnt im .Status==Started und wird mit Canceled, Completed oder Error fortgesetzt.

var feedUri = new Uri("http://rss.golem.de/rss.php?feed=RSS2.0");
var client = new SyndicationClient();

var asyncInfo = client.RetrieveFeedAsync(feedUri);
var id = asyncInfo.Id;
var status = asyncInfo.Status;

Abbrechen

In unterschiedlichen Situation kann es nötig sein einen asynchronen Vorgang abzubrechen. Rufe Sie dazu die Cancel-Methode auf:

IAsyncOperationWithProgress<SyndicationFeed, 
    RetrievalProgress> _FeedAsyncInfo;

void Starten_Click(object sender, RoutedEventArgs e)
{
    var feedUri = new Uri("http://rss.golem.de/rss.php?feed=RSS2.0");
    var client = new SyndicationClient();

    _FeedAsyncInfo = client.RetrieveFeedAsync(feedUri);
    var status = _FeedAsyncInfo.Status; // = Started
}

void Abbrechen_Click(object sender, RoutedEventArgs e)
{
    _FeedAsyncInfo**.Cancel()**;
    var status = _FeedAsyncInfo.Status; // = Canceled
}

Abschluss

Bei einem asynchronen Vorgang wird der Completed-Handler immer benötigt da dieser u. a. das tatsächliche Ergebnis liefert aber auch ausgeführt wird wenn abgebrochen wurde oder einen Fehler auftrat. Überprüfen Sie den Status und rufen nur im Erfolgsfall über GetResults() das Ergebnis ab:

var feedUri = new Uri("http://rss.golem.de/rss.php?feed=RSS2.0");
var client = new SyndicationClient();

var asyncInfo = client.RetrieveFeedAsync(feedUri);
Meldung.Text = String.Format("Start {0:ss.fff}: ID {1} STATUS {2}\n",
    DateTime.Now,
    asyncInfo.Id,
    asyncInfo.Status);

asyncInfo.Completed = async (ai, status) =>
{
    await Dispatcher.RunIdleAsync((f) =>
    {
        if (status == AsyncStatus.Completed)
            Meldung.Text = String.Format("{0:ss.fff}: ID {1} STATUS {2} ERGEBNIS {3}\n",
                DateTime.Now,
                ai.Id,
                ai.Status,
                ai.GetResults().Title.Text);

        else if (status == AsyncStatus.Canceled)
            Meldung.Text = String.Format("Abbruch {0:ss.fff}: ID {1}\n",
                DateTime.Now,
                ai.Id);

        else if (status == AsyncStatus.Error)
            Meldung.Text = String.Format("Fehler {0:ss.fff}: ID {1} "
                + "ERROR {2}\n",
                DateTime.Now,
                ai.Id, 
                ai.ErrorCode);
    });
};

Der Completed-Handler wird nicht im UI-Thread ausgeführt und kann so auch nicht direkt auf UI-Elemente zugreifen. Über await Dispatcher.RunIdleAsync(...) wäre ein Zugriff auf UI-Elemente wieder möglich da diese im UI-Thread ausgeführt werden.

Fortschritt

Einige asynchrone Methoden unterstützen Fortschrittsbenachrichtigungen während sie ausgeführt werden. Sie können diese Benachrichtigungen für aktuelle Fortschrittsberichte zum asynchronen Vorgang in der UI verwenden:

var feedUri = new Uri(FeedUri.Text);
var client = new SyndicationClient();
var asyncInfo = client.RetrieveFeedAsync(feedUri);

progressBar.Value = 0;
asyncInfo.Progress = async (ai, args) =>
{
    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
        progressBar.Value = args.BytesRetrieved 
            / args.TotalBytesToRetrieve * 100;
    });
};

Eigene Aufgaben asynchron ausführen

Neben den asynchronen WinRT-Methoden kann es hilfreich sein eigene Vorgänge mit großer Dauer asynchron in einem anderen Thread als den der Oberfläche auszuführen. Fügen Sie diese Aufgabe der Warteschlange des Threadpool hinzu. Implementieren wie oben einen Handler für Completed und werten darin im Erfolgsfall das Ergebnis aus.

Wenn nun der asynchrone Vorgang in der Warteschlange des ThreadPools abgeschlossen ist, wird der Completed-Handler aufgerufen. In diesem können Sie jedoch keine Änderung in der UI vornehmen da wir uns in einem anderen Thread befinden und würde sogar eine Wrong-Thread-Excaption kassieren. Um dies zu umgehen, kann der UI zugeordnete CoreDispatcher verwendet werden um die UI im richtigen Thread zu ändern.

Und so könnte z.B. das Click-Ereignis aussehen:

void Button_Click(object sender, RoutedEventArgs e)
{
    var asyncInfo = ThreadPool.RunAsync((a) =>
    {
        // Fake rechenintensiver Code
        new ManualResetEvent(false).WaitOne(2000);
    });
    asyncInfo.Completed = async (ai, status) =>
    {
        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
            switch (status)
            {
                case AsyncStatus.Completed:
                    LogThreadPoolRunAsync.Text += String.Format("Fertig {0:ss.fff} ID {1} STATUS {2}",
                        DateTime.Now,
                        asyncInfo.Id,
                        asyncInfo.Status);
                    break;

                case AsyncStatus.Error:
                    LogThreadPoolRunAsync.Text += String.Format("Fehler {0:ss.fff} ID {1} STATUS {2}",
                        DateTime.Now,
                        asyncInfo.Id,
                        asyncInfo.Status);
                    break;

                case AsyncStatus.Canceled:
                    LogThreadPoolRunAsync.Text += String.Format("Abbruch {0:ss.fff} ID {1} STATUS {2} EX {3}",
                        DateTime.Now,
                        asyncInfo.Id,
                        asyncInfo.Status,
                        ai.ErrorCode);
                    break;
            }
        });
    };
    LogThreadPoolRunAsync.Text = String.Format("Start {0:ss.fff} "
        + "ID {1} STATUS {2} >>> ",
        DateTime.Now,
        asyncInfo.Id,
        asyncInfo.Status);
}

Das gleiche Ergebnis mit dem await-Operator würde so aussehen:

async void Button_Click(object sender, RoutedEventArgs e)
{
    LogThreadPoolRunAsync.Text = String.Format("Start {0:ss.fff} >>> ", DateTime.Now);
    try
    {
        await ThreadPool.RunAsync((a) =>
        {
            // Fake rechenintensiver Code
            new ManualResetEvent(false).WaitOne(2000); 
        });
        LogThreadPoolRunAsync.Text += String.Format("Fertig {0:ss.fff}", DateTime.Now);
    }
    catch (Exception ex)
    {
        LogThreadPoolRunAsync.Text +=String.Format("Fehler {0:ss.fff} {1}", DateTime.Now, ex);
    }
}

Task nach WinRT-Async umwandeln

Um Ihre vorhandenen Task-Objekte im WinRT-Async-Model nutzen zu können oder über den Umweg der Task-Klasse an weitere Threading-Techniken wie z.B. den Cancel-Token zu gelangen, können Sie Task weiter verwenden und diese in die oben beschriebenen Async-Rückgabetypen umwandeln.

Hierfür enthält das System.Runtime.WindowsRuntime-Assembly Erweiterungsmethoden AsAsyncAction und AsAsyncOperation die Sie bei Task und Task<TResult>-Objekten nutzen können:

async void Button_Click(object sender, RoutedEventArgs e)
{
    LogTaskToAsyncOperationTest.Text = "Läuft ...";
    LogTaskToAsyncOperationTest.Text = await TaskToAsyncOperationAsync();
}

IAsyncOperation<string> TaskToAsyncOperationAsync()
{
    var t = Task<string>.Run(() =>
        {
            // Fake rechenintensiver Code
            new ManualResetEvent(false).WaitOne(3000);
            return "Nach 3s 43 asynchron heruntergeladen.";
        });
    return t.AsAsyncOperation();
}

Eigene erweiterte asynchrone Aufgaben ausführen

Das Umwandeln von Task mittels AsAsyncAction und AsAsyncOperation bietet grundlegende Unterstützung für Task’s in WinRT-Async. Darüber hinaus erhalten Sie erweiterter Unterstützung, wenn Sie AsyncInfo.Run und dessen Überladungen verwenden. Dazu gehören:

Abbruch

Mit AsyncInfo.Run werden Abbrüche mit asynchronen WinRT-Methoden unterstützt:

void Start_Click(object sender, RoutedEventArgs e)
{
    _JetztArbeitenAsyncInfo = JetztArbeitenAsync();
    _JetztArbeitenAsyncInfo.Completed = (asyncInfo, asyncStatus) => 
    {
        if (asyncStatus== AsyncStatus.Completed)
            LogAsyncInfoRunCancelTest.Text += String.Format("STATUS {0} "
                + "RESULT {1}",
                asyncStatus, asyncInfo.GetResults());

        else if (asyncStatus == AsyncStatus.Canceled)
            LogAsyncInfoRunCancelTest.Text += String.Format("STATUS {0}",
                asyncStatus);
    };
    LogAsyncInfoRunCancelTest.Text = String.Format("START {0} "
        + "STATUS {1} >>> ",
        _JetztArbeitenAsyncInfo.Id,
        _JetztArbeitenAsyncInfo.Status);
}

void Abbrechen_Click(object sender, RoutedEventArgs e)
{
    if (_JetztArbeitenAsyncInfo!=null)
    {
        _JetztArbeitenAsyncInfo.Cancel();
    }
}

private IAsyncOperation<string> _JetztArbeitenAsyncInfo;

public static IAsyncOperation<string> JetztArbeitenAsync()
{
    return AsyncInfo.Run(cancellationToken =>
    {
        return Task<string>.Run(() =>
        {
            for (int i = 0; i < 100; i++)
            {
                // Fake rechenintensiver Code
                new ManualResetEvent(false).WaitOne(25);

                if (cancellationToken.IsCancellationRequested)
                {
                    // Operation wurde abgebrochen
                    // Code der Aufräumen
                    cancellationToken.ThrowIfCancellationRequested();
                }
            }
            return "Fertig mit Async-Operation";
        });
    });
}

Fortschritt

AsyncInfo.Run stellt Unterstützung für Fortschrittsberichte durch asynchrone WinRT-Methoden bereit:

void Button_Click(object sender, RoutedEventArgs e)
{
    var ai = JetztArbeitenMitFortschrittAsync();
    ai.Progress = (asyncInfo, progressInfo) =>
    {
        LogAsyncInfoRunFortschrittTest.Text += String.Format("{0}% ",
            progressInfo);
    };
    LogAsyncInfoRunFortschrittTest.Text = String.Format("START {0} "
        + "STATUS {1} >>> Fortschritt ",
        ai.Id,
        ai.Status);
}

public static IAsyncOperationWithProgress<string, int> JetztArbeitenMitFortschrittAsync()
{
  return AsyncInfo.Run((CancellationToken cancellationToken, IProgress<int> progress) =>
    {
        return Task<string>.Run(() =>
        {
            for (int i = 0; i <= 10; i++)
            {
                progress.Report(i*10);

                // Fake rechenintensiver Code
                new ManualResetEvent(false).WaitOne(200);
            }
            return "Fertig mit Async-Operation";
        });
    });
}

App Studio

AppStudio Start

Kapitel Tile-Benachrichtigung und geöffnetes Beispiel-FlyoutKapitel SplashScreenSuchen nach SchlagwörternTool Viewer und Picker für FarbenTool Viewer und Picker für Glyphen aus der Schrift Segoe UI SymbolTool Viewer und Picker für AppBar-ButtonsTool Viewer aller AppIn-BeispieleViewer Illustrationen, Diagramme und Grafiken

Prolog

Was früher ein Fachbuch war ist heute App Studio die App. Pures Wissen auf den Punkt gebracht. Viele bunte Illustrationen. Interaktive Beispiele zum anfassen und experimentieren. Code einfach kopieren. Nach Schlagwörtern suchen. Überall lesen und stöbern und keinen dicken Schinken schleppen. Beispiele, Projekte, Vorlagen und Muster direkt aus der App öffnen und weiter nutzen. Ständige Updates direkt aus dem Store.

Meine neue WebSite finden Du jetzt unter https://AttilaKrick.com.

Inhalt

App Studio gibt Ihnen das Werkzeug an die Hand um Windows Store Apps zu entwerfen und im Windows Store zu veröffentlichen. Sie erfahren was Sie brauchen, wo Sie anfangen sollten und was Sie über das Erstellen wissen sollten. Die App dient als Einführung für den C# Programmierer aber auch als Nachschlagewerk für die tägliche Arbeit und das zu folgenden Themen:

Animationen               Ansichten-Status   App-Datenspeicher
Dateien und Ordner        Datenbindung       Drucken
Einstellungs-Flyout       Ressourcen         Hintergrundaufgaben
Lebenszyklus einer App    MVVM               Navigation
Gestaltung einer App      Sensoren           SpalshScreen  
Text und Typografie       Tastatur           Teilen
UI Elemente im Showroom   Touch              Video und Audio
Visual Studio 2012        Windows 8          Windows Store
WinRT und .NET            XAML               Zeichnen
Tile-, Badge-, Glyph-,    Tile- und          Toast-Benachrichtigung 
LockScreen                Suche-Funktion     u.v.m.

In App Studio direkt über die AppBar finden Sie Tools für die tägliche Arbeit wie:

Picker & Viewer für AppBar-Button Style
                    Color
                    Gylphen aus Segoe UI Symbol
                    StandardStyle
                    InApp-Beispiele

Vorlagen und Muster für Seiten-Layout
                        Schriftbild
                        Windows Store Checkliste
                        Visual Studio Handout

WCF Web Service für Test- und Debugging

DiaShow durch alle Illustrationen und Diagramme

Über den Autor

Attila Krick ist ein gestandener IT-Spezialist, Softwareentwickler, Autor und Trainer spezialisiert auf Betriebs-, Mail-, Datenbank-Systeme, .NET und Office aus dem Hause Microsoft (MCP, MCSA, MCT).

Seit 2005 ist er mit seinem Angebot für Sie tätig. Den Schwerpunkt seiner Tätigkeit gliedert sich in die Bereiche IT-Service und Beratung, Software-Entwicklung und Coaching und Schulung.

App Studio im Windows Store

 

Model-View-ViewModel (MVVM)

Meine neue WebSite finden Du jetzt unter https://AttilaKrick.com.

MVVM

Siehe auch: MSDN – Implementing the MVVM Pattern

MVVM (Model View ViewModel) ist ein Programm-Entwurfsmuster zur Entkoppelung von Markup und Logik der UI. Es sieht die Rollentrennung von UI-Designern (VIEW) und Entwicklern (MODEL und VIEWMODEL) vor.

Vorteile: Durch diese Trennung können die Anwendungsschichten von verschiedenen Arbeitsgruppen entwickelt werden. Designer können einen Fokus auf das Benutzererlebnis und die UI legen (VIEW) und Entwickler unabhängig davon die UI- und Geschäftslogik schreibe (MODEL und VIEWMODEL). In der Regel sind keine aufwendigen UI-Tests nötig. Stattdessen genügen codebasierte Modul-Tests des ViewModel. Auch ist eine leichtere Austauschbarkeit der View z.B. nach WPF oder Windows Phone möglich. Unterstützung in WPF, Silverlight, Windows Phone und Windows Store Apps.

Nachteil: MVVM stellt einen Mehraufwand dar und ist für Anwendungen mit einfacher UI ein Overkill. Für größere Anwendungen kann das Design eines ausreichend allgemeinen ViewModel’s im Voraus schwierig sein. Außerdem führe eine schlecht verwaltete Datenbindung zu einem erheblichen Speicherbedarf der Anwendung.

Das MVVM-Entwurfsmuster besteht aus den folgenden Schichten und Kern-Technologien:

View : XAML, UX-Logik, Bindung zum ViewModel/Model und der Datenkonvertierung über IValueConverter

ViewModel : Stellt Bindbare Propertys und Commands bereit und enthält UI-Logik. Hierfür stehen folgende Komponenten zur Verfügung:

ICommand, INotifyPropertyChanged, NotifyCollectionChanged, INotifyCollectionChanged und INotifyDataErrorInfo.

Model : Entität, Geschäftsregeln und Validierungs-Logik. Hierfür stehen folgende Komponenten zur Verfügung:

ObservableCollection<T>, INotifyPropertyChanged, INotifyCollectionChanged, und INotifyDataErrorInfo.

[Data|View|Etc.]Services : Diverse Dienste die MVVM zuarbeiten und auf verschiedenen Plattformen unterschiedlich implementiert werden. Zum Beispiel der Zugriff auf unterschiedliche Datenbank-Hersteller oder die Implementierung der MessageBox in Apps, WPF oder auf einem Windows Phone.

DataService

Die DataService-Schicht übernimmt die Aufgabe der Datenbeschaffung (GetAllKunden()) bzw. der Datenaktualisierung (UpdateKunde(kunde)).

Die DataAccess-Schicht wird Ausgeführt im Model oder im ViewModel.

In einfachen Anwendungen schließt das Model diesen Code zur Unterstützung von Datenzugriff und Caching mit ein.

Stehen unterschiedliche Daten-Anbieter (DB, XML, WCF, etc.) zur Verfügung kann die Aufgaben der Datenbereitstellung aus dieser Schicht entkoppelt und in eine eigene ProviderService-Klasse implementiert werden. Diese ProviderService’s sollten ein zuvor erstelltes Interface (z.B. IProviderService) implementieren um Provider-Unabhängig im DataService Daten beschaffen zu können:

/* ZUM BEISPIEL
DataService ds;
ds = new DataService(new WCFProviderService()); // If Online
ds = new DataService(new DBProviderService()); // else Offline
*/

public interface IProviderService {
    object GetKunden();
}

public class DBProviderService : IProviderService {
    public object GetKunden() {
        return "Geladene Kunden von der DB";
    }
}

public class WCFProviderService : IProviderService {
    public object GetKunden() {
        return "Geladene Kunden vom HTTPS-WCF-Dienst";
    }
}

public class DataService {
    public DataService(IProviderService providerService) {
        ProviderService = providerService;
    }

    IProviderService ProviderService;

    public object GetKunden() {
        var kunden = ProviderService.GetKunden();
        // Aufbereiten der Kunden
        return kunden;
    }
}

Model

Das Model ist für die Verwaltung, Konsistenz und Gültigkeit der enthaltenen Entitäten verantwortlich.

Das Model besteht aus:

Gekapselter Entität und deren Daten

Geschäftsregeln

Validierungs-Logik

Eine Anwendungs-Domäne besteht aus mehreren Model’s der Geschäftsentität, aus Auflistungen von Geschäftsentitäten oder aus Kombinationen beider.

Bei einfacher DataService-Schicht kann diese Aufgabe direkt im Model implementiert werden.

Das Model ist eine nicht-visuelle Klasse die weder von einer visuellen Klasse abgeleitet wird noch visuelle Objekte implementiert.

Das Model besitzt keine Referenz oder Abhängigkeit zum ViewModel oder zur View.

Wenn Ihre Anwendungs-Domäne aus mehreren Model’s besteht empfiehlt es sich die benötigten Interfaces in einer abstrakten Klasse ModelBase zu implementieren und von dieser zu erben.

Das Model könnte auch in einem WCF-Dienst implementiert sein was das ViewModel anschließend konsumiert.

Für Benachrichtigung über Eigenschaftsänderungen implementiert das Model INotifyPropertyChanged bzw. INotifyCollectionChanged. Diese Benachrichtigungen benötigt ViewModel/View um ihre Daten/Anzeige zu aktualisieren. Diese Benachrichtigungsdienst kann auch vom ViewModel übernommen werden.

Auflistungen von Objekten die später an die View gebunden werden sollen, werden i.d.R. von ObservableCollection<T> erstellt da diese Klasse u.a. INotifyCollectionChanged implementiert.

Validierung : Für die Datenüberprüfung und Fehlerberichterstattung wird INotifyDataErrorInfo im Model implementiert oder diese Aufgabe übernimmt auch das ViewModel.

ViewModel

Durch die Implementierung der Präsentations-Logik im ViewModel koordiniert diese die Interaktion zwischen den Model‘s und der View.

Das ViewModel besteht aus:

Durch-gereichte / Gekapselte Model‘s

Bindbare Eigenschaften für die View

Bindbare Kommandos für die View

Benachrichtigungen für die View

Gekapselte Präsentations-Logik

Ein ViewModel implementiert Model’s und wird für die Aufgabenstellung einer View entwickelt.

Während das Model System-unabhängig ist, tendiert das ViewModel zu einem konkreten System wie WPF, Silverlight, Windows Store App, etc.

Das ViewModel ist eine nicht-visuelle Klasse das weder von einer visuellen Klasse abgeleitet wird noch visuelle Objekte implementiert.

Das ViewModel besitzt keine Kenntnisse oder Referenz über die View, d.h. eine Abhängigkeit von der View darf nicht entstehen. Soweit nicht gekapselt könnte das ViewModel jedoch über das Model Kenntnisse über den DataService haben.

Das ViewModel arbeitet mit bereits initiierten Model’s oder stößt das Befüllen der Model’s mittels der DataAccess-Schicht an.

Wenn Ihre Anwendungs-Domäne aus mehreren ViewModel’s besteht empfiehlt es sich die benötigten Interfaces in einer abstrakten Klasse ViewModelBase zu implementieren und von diesen abzuleiten.

Test : Das ViewModel ist unabhängig von der View und dem Model testbar.

Die Daten die in der View präsentiert werden, können im ViewModel manipulieren und aufbereitet werden. Separate Konverter-Klassen (IValueConverter) werden unabhängig dem Binding-Objekt in der View zugewiesen und übernehmen die Aufgabe der Konvertierung in Richtung View-ViewModel und umgekehrt.

Das ViewModel implementiert Befehle die als Eigenschaft vom Typ ICommand bereitgestellt werden. Diese wiederum können später in der View gebunden werden:

<Button Content="{Binding Email}"
        Command="{Binding SendenCommand}" />

Bindung : Das ViewModel kann die Eigenschaften des Model‘s direkt der View zugänglich machen. Alternativ kapselt das ViewModel die Eigenschaften des Models, bereitet die Daten auf und stellte diese der View dann zur Verfügung. Entscheidend ist die nicht redundante Änderungsbenachrichtigung der View die entweder vom Model oder vom ViewModel kommen. Hierzu implementieren Sie im ViewModel bzw. Model INotifyPropertyChanged, INotifyCollectionChanged oder ObservableCollection<T>.

Das ViewModel definiert logische Zustände und informiert über INotifyPropertyChanged und INotifyCollectionChanged die View über Änderungen die dem Benutzer visuell dargestellt werden können.

Validierung : Für die Datenüberprüfung und Fehlerberichterstattung wird INotifyDataErrorInfo im ViewModel implementiert oder diese Aufgabe übernimmt auch das Model.

View

Die Datenbindungs-Infrastruktur ermöglicht eine lose Kopplung, bei der die View und die verknüpften Daten synchronisiert bleiben bzw. alle Benutzereingaben zu den entsprechenden Befehlen geleitet werden.

Datenbindung - Schema

Die View besteht aus:

UI-Elemente (XAML, DataTemplate’s, UserControl’s, Style‘s, Resource’s, etc.

Property-Bindungen auf Eigenschaften der ViewModel

Command-Bindungen auf Eigenschaften der ViewModel

UX (User Experience)-Logik

Die View geht eine 1:1 Beziehung mit dem entsprechenden ViewModel ein. Eine View kann sich aus mehreren Views (z.B. UserControl’s) zusammensetzen, die wiederum ihr eigenes ViewModel besitzen.

Die UI-Elemente binden ihre Eigenschaften mit einem Binding-Objekt an das dazugehörige ViewModel:

<... Text="{Binding Email}"
     Command="{Binding SpeichernCommand}" ... />

UX : In der dazugehörigen Code-Behind-Klasse liegt kein bis wenig Code der sich gekapselt nur auf die UX (User Experience)-Logik auswirkt.

Separate Konverter-Klassen die IValueConverter implementieren werden unabhängig dem Binding-Objekt in der View zugewiesen und übernehmen die Aufgabe der Konvertierung in Richtung View-ViewModel und umgekehrt.

Bindung : Für umgehende Aktualisierung der UI-Elemente ist es wichtig dass die gebunden Quelleigenschaft des Model’s bzw. des ViewModel‘s die View korrekt benachrichtigt. Dies tun sie, wenn Sie die folgenden Interfaces implementiert haben: INotifyPropertyChanged, INotifyCollectionChanged, ObservableCollection<T>, oder INotifyDataErrorInfo.

Die konkrete Umsetzung der Bindung können Sie im Kapitel Datenbindung nachvollziehen.

Benachrichtigung über Toast (Windows Store App)

Meine neue WebSite finden Du jetzt unter https://AttilaKrick.com.

Interessant: „Popupbenachrichtigungen“ auf http://goo.gl/enhq4

Ein Toast oder auch Popup ist eine Benachrichtigungs-Streifen in der rechten oberen Ecke. Toast bietet die Möglichkeit zeitkritische Informationen anzuzeigen und mit einem Tap bzw. Klick in die Anwendung zu kommen. Ein Toast ist die einzige Art mit Benutzer zu interagieren, wenn die App nicht Vordergrund ist. Diese Form eignet sich besonders gut für lokal-, zeit- und push-gesteuerte Benachrichtigung.

Beispiel-Szenarios für Tost’s könnten sein:

Eine Musik App zeigt beim Liedwechsel den neuen Titel an.

Ein Kalender zeigt eine Erinnerung für einen bevorstehenden Termin.

Die Shopping App informiert über Preisänderung eines beobachteten Artikels.

Die Messaging App zeigt eine neue eingehende Nachricht für IM, Email, SMS, VoIP, News oder der gleichen an.

Wichtig: Ein Update sollte nicht nur dann erfolgen wenn der User gerade Ihrer App nutzt sonder auch außerhalb dieser Zeit. Wenn Ihre App jedoch beendet oder suspended ist, können Sie Update nur über Hintergrundaufgaben ausführen. Werfen Sei dazu einen Blick in das Kapitel Hintergrundaufgaben

Toast sind als Einladung zusehen, um zu einem bestimmten Ziel / Zeitpunkt in der App geführt zu werden.

Vorlagen: Toast können Text, Bild und Audio-Botschaften enthalten die jedoch reglementiert sind. Eine Vorlage kann über die Methode ToastNotificationManager.GetTemplateContent bezogen werden. Diese erwartet eine Enumeration von ToastTemplateType.
Eine Vorschau aller möglichen Vorlagen finden Sie unter http://goo.gl/d5nvc sowie die Vorgaben von Maßen im XML-Schema unter http://goo.gl/6ePnJ

App-Manifest Um Toast auslösen zu können müssen Sie diese im package.appxmanifest deklariert:

<VisualElements ... ToastCapable="true">

Die Anzeigedauer eines Toast wird vom System vorgegeben und besteht aus short für eine Standard-Dauer oder long mit 25 Sekunden:

var vorlage = ToastTemplateType.ToastImageAndText02;
var toast = ToastNotificationManager.GetTemplateContent(vorlage);
var root = toast.DocumentElement;
root.SetAttribute("duration", "long");

Denken Sie daran optionale Startparameter festzulegen. Diese werden beim Klick auf die Toast-Benachrichtigung, an Ihre App übergeben.

var vorlage = ToastTemplateType.ToastImageAndText02;
var toast = ToastNotificationManager.GetTemplateContent(vorlage);
var root = toast.DocumentElement;
string launch = "{\"type\":\"sofortToast\",\"param1\":\"12345\",\"param2\":\"67890\"}";
root.SetAttribute("launch", launch);

Das optionale Bild, das Sie in einem Toast verwenden, muss 150x150px abmessen und darf nicht größer als 200 KB sein:

var vorlage = ToastTemplateType.ToastImageAndText02;
var toast = ToastNotificationManager.GetTemplateContent(vorlage);
var root = toast.DocumentElement;
var image = root.GetElementsByTagName("image");
((XmlElement)image[0]).SetAttribute("src", "ms-appx:///Buch/Beispiele/_Tile.jpg");
((XmlElement)image[0]).SetAttribute("alt", "Wasser fließt durch einen Bambus auf einen Stein");

Wann: Wenn Sie Toast’s an das System senden, so können Sie dies sofort, geplant oder geplant mit Intervall tun.

? Fangen Sie Push-Toast’s mit dem PushNotificationReceived-Ereignis ab, wenn die App ausgeführt wird.

? Eine Toast-Benachrichtigung kann auch von einer klassischen Desktop-Anwendung gesendet. Mehr dazu unter http://goo.gl/qhaHy.

? Berücksichtigen Sie das andere aktive Apps Toast-Benachrichtigungen ausblenden können.

? Dieses Thema ist stark verflochten mit Hintergrundaufgaben (s. Seite 200 ff.) da in der Regel Aktualisierungen im Hintergrund ausgeführt werden.

? Um über WNS Push-Benachrichtigungen zu senden lesen Sie bitte auch folgende Richtlinien unter http://goo.gl/6fV8Z.

Tips

Fassen Sie mehrere zusammenhängende Updates, die innerhalb kurzer Zeit anfallen, zu einem einzigen Toast zusammen.

Präsentieren Sie Informationen so einfach wie möglich.

Fügen Sie Bilder ein, wenn diese den Inhalt der Nachricht verdeutlichen, sonst besser nicht.

No Go

Verwenden Sie Toast’s nicht für wichtige Infos die nicht übersehen werden dürfen. Besser hierfür wären Flyout’s, Dialogfelder, die AppBar oder ein anderes Inline-Element.

Verzichten Sie auf Formulierungen wie „Klicken Sie hier, …“.

Verwenden Sie Toast’s nicht für sehr häufig (z.B. Aktien) anfallende Benachrichtigungen.

Verwenden Sie keine Toast’s, wenn die App im Vordergrund ist. Besser hierfür wären Flyout’s, Dialogfelder, die AppBar oder ein anderes Inline-Element.

Fügen Sie im Bildfeld einer Toast keine generischen Bilder wie Symbole oder das App-Logo ein.

Troubleshooting

Sollte Ihre Toast-Benachrichtigung nicht angezeigt werden überprüfen Sie folgende Punkte:

Überprüfen der Benutzereinstellungen

Überprüfen der Einträge im App-Manifest

Überprüfen der Bildgrößen

Untersuchen der Bildformate

Überprüfen der URLs

Überprüfen der XML-Syntax

Überprüfen der Ablaufzeit der Benachrichtigung

Lösungsansätze finden Sie auch in dem Artikel „Richtlinien und Prüfliste für Popup-Benachrichtigungen“, im Bereich „Problembehandlung“ unter http://goo.gl/NDdht

Eine einfache Toast-Nachricht anzeigen

var vorlage = ToastTemplateType.ToastText01;
var toast = ToastNotificationManager.GetTemplateContent(vorlage);

var text = toast.GetElementsByTagName("text");
text[0].AppendChild(toast.CreateTextNode("Geröstetes Brot (Toast) "
    + "wurde früher in gefüllte Trinkgläser getaucht."));

var notification = new ToastNotification(toast);
var notifier = ToastNotificationManager.CreateToastNotifier();
notifier.Show(notification);

Eine komplexe Toast-Nachricht anzeigen

var vorlage = ToastTemplateType.ToastImageAndText02;
var toast = ToastNotificationManager.GetTemplateContent(vorlage);

var root = toast.DocumentElement;
root.SetAttribute("duration", "long"); // 25 Se. Dauer

// + Parameter für den Wiedereinstieg in die App nützlich
string launch = "{\"type\":\"sofortToast\",\"param1\":\"12345\",\"param2\":\"67890\"}";
root.SetAttribute("launch", launch);

// + Text
var text = root.GetElementsByTagName("text");
text[0].AppendChild(toast.CreateTextNode("Guten Morgen"));
text[1].AppendChild(toast.CreateTextNode("Ich bin ein Toast mit einer Nachricht."));

// + Bild
var image = root.GetElementsByTagName("image");
((XmlElement)image[0]).SetAttribute("src", "ms-appx:///Buch/Beispiele/_Tile.jpg");
((XmlElement)image[0]).SetAttribute("alt", "Wasser fließt durch einen Bambus auf einen Stein");

// + Audio
var audio = toast.CreateElement("audio");
root.AppendChild(audio);
audio.SetAttribute("loop", "true");
audio.SetAttribute("src", "ms-winsoundevent:Notification.Looping.Alarm2");

var notifier = ToastNotificationManager.CreateToastNotifier();

// Sofortige Anzeige
var sofortToast = new ToastNotification(toast);
notifier.Show(sofortToast);

// ODER als geplant Anzeige
var startzeitA = DateTime.Now.AddSeconds(5);
var geplanterToast = new ScheduledToastNotification(toast, startzeitA);
notifier.AddToSchedule(geplanterToast);

// ODER als intervall Anzeigen
var startzeitB = DateTime.Now.AddSeconds(10);
var intervallToastB = new ScheduledToastNotification(
    content: toast,
    deliveryTime: startzeitB,
    snoozeInterval: new TimeSpan(0, 0, 60),
    maximumSnoozeCount: 3);
notifier.AddToSchedule(intervallToastB);

App-Datenspeicher (ApplicationData)

Meine neue WebSite finden Du jetzt unter https://AttilaKrick.com.

Der App-Lebenszyklus sieht vor das eine App nicht permanent im Arbeitsspeicher gehalten wird. Sollen jedoch Benutzer- und Anwendungsdaten erhalten bleiben so müssen diese in einem nicht flüchtigen Speicher abgelegt werden. Apps können diese Daten lokal, servergespeichert und temporär in den Anwendungsdaten-Speicher schreiben.

Sollte die App deinstalliert werden, werden die lokalen Anwendungsdaten auch gelöscht soweit sie nicht explizit außerhalb z.B. in „Eigen Dateien“ gespeichert wurden.

Greift die Anwendung nicht innerhalb von 30 Tage auf die servergespeicherten Daten zu werden diese in der Cloud gelöscht.

Der Benutzer kann die temporären Anwendungsdateien durch das Programm Cleanup aufräumen.

Jede App besitzt einen Speicher pro Benutzer für die Anwendungsdaten. Die Dateien werden ins Dateisystem geschrieben. Sie haben jedoch keinen direkten Zugriff auf diesen Daten, daher ist der klassische Pfad uninteressant.

Lokal Der lokale Datenspeicher liegt auf dem jeweiligen Gerät unter:

ApplicationData.Current.LocalSettings

ApplicationData.Current.LocalFolder

Roaming Der servergespeicherte Datenspeicher steht auf allen Geräten des Benutzers zur Verfügung unter:

ApplicationData.Current.RoamingSettings

ApplicationData.Current.RoamingFolder

Temporory Der temporäre Dateispeicher liegt lokal und kann jederzeit vom System gelöscht werden:

ApplicationData.Current.TemporaryFolder

Dem Speicher können einfache WinRT basierende Datentypen (http://goo.gl/wVDph) zugewiesen werden oder von diesen Typen sogenannte Verbundeinstellung oder verschachtelte Einstellung.

Order oder Schlüsseltiefe dürfen 32 nicht überschreiten.

Es gibt keine Größenbeschränkung für das lokale Datenvolumen.

Das Cloud-Datenvolumen ist pro App individuell limitiert und liegt bei 100KB.

Verwenden Roaming-Speicherung wenn der Benutzer eine Aufgabe auf mehreren Geräten fortsetzen soll.

Für eine Echtzeit-Synchronisierung ist der Roaming-Speicher nicht konzipiert.

TIP Bei Verwendung vom Roaming-Speicher prüfen Sie vorher ob ausreichen Datenspeicher vorhanden ist.

TIP Berücksichtigen und planen Sie Versionsänderungen ihrer App im Zusammenhang mit Ihren Datenspeichern ein.

Schreiben in LocalSettings:

var settings = ApplicationData.Current.LocalSettings;
// Einfache Einstellungen
settings.Values["Einfach"] = "Hallo Windows";
// atomarer Einstellungsverbund
var composite = new ApplicationDataCompositeValue();
composite["CompositeA1"] = 1;
composite["CompositeA2"] = "Zabzarab";
settings.Values["Composite"] = composite;
// Einstellungskontainer
var container = settings.CreateContainer("Container", ApplicationDataCreateDisposition.Always);
if (settings.Containers.ContainsKey("Container"))
    settings.Containers["Container"].Values["EinfacheImContainer"] = "Alowa Windows";

Lesen aus LocalSettings:

var settings = ApplicationData.Current.LocalSettings;
// Einfach
var einfach = settings.Values["Einfach"];
StatusAnzeiger.Items.Add(String.Format("    - Einfache = {0}", einfach));
// Composite
var composite = (ApplicationDataCompositeValue)settings.Values["Composite"];
StatusAnzeiger.Items.Add(String.Format("    - Composite = {0}, {1}", composite["CompositeA1"], composite["CompositeA2"]));
// Container
object containerEinfach = settings.Containers["Container"].Values["EinfacheImContainer"];
StatusAnzeiger.Items.Add(String.Format("    - Container => Einfach = {0}", containerEinfach));

Schreiben in RoamingSettings:

var settings = ApplicationData.Current.RoamingSettings;
// Einfache Einstellungen
settings.Values["Einfach"] = "Hallo Welt";
// atomarer Einstellungsverbund
var composite = new ApplicationDataCompositeValue();
composite["CompositeA1"] = 23;
composite["CompositeA2"] = "Abrakadabra";
settings.Values["Composite"] = composite;
// Einstellungskontainer
var container = settings.CreateContainer("Container", ApplicationDataCreateDisposition.Always);
if (settings.Containers.ContainsKey("Container"))
    settings.Containers["Container"].Values["EinfachImContainer"] = "Alowa Welt";

Lesen aus RoamingSettings:

var settings = ApplicationData.Current.RoamingSettings;
// Einfach
var einfach = settings.Values["Einfach"];
StatusAnzeiger.Items.Add(String.Format("    - Einfache = {0}", einfach));
// Composite
var composite = (ApplicationDataCompositeValue)settings.Values["Composite"];
StatusAnzeiger.Items.Add(String.Format("    - Composite = {0}, {1}", composite["CompositeA1"], composite["CompositeA2"]));
// Container
object containerEinfach = settings.Containers["Container"].Values["EinfachImContainer"];
StatusAnzeiger.Items.Add(String.Format("    - Container => Einfach = {0}", containerEinfach));

Schreiben in LocalFolder:

var folder = ApplicationData.Current.LocalFolder;
var file = await folder.CreateFileAsync("LocalDataFile.txt", CreationCollisionOption.ReplaceExisting);
await FileIO.WriteTextAsync(file, "Guten Tag Windows !");

Lesen aus LocalFolder:

StatusAnzeiger.Items.Add("Lese Datei von LocalFolder:");
var folder = ApplicationData.Current.LocalFolder;
var file = await folder.GetFileAsync("LocalDataFile.txt");
var inhalt = await FileIO.ReadTextAsync(file);

StatusAnzeiger.Items.Add("    - " + inhalt);

Schreiben in RoamingFolder:

var folder = ApplicationData.Current.RoamingFolder;
var file = await folder.CreateFileAsync("RoamingDataFile.txt", CreationCollisionOption.ReplaceExisting);
await FileIO.WriteTextAsync(file, "Guten Tag Welt !");

Lesen aus RoamingFolder:

var folder = ApplicationData.Current.RoamingFolder;
var file = await folder.GetFileAsync("RoamingDataFile.txt");
var inhalt = await FileIO.ReadTextAsync(file);
StatusAnzeiger.Items.Add("    - " + inhalt);

Schreiben in TempFolder:

var folder = ApplicationData.Current.TemporaryFolder;
var file = await folder.CreateFileAsync("TempDataFile.txt", CreationCollisionOption.ReplaceExisting);
await FileIO.WriteTextAsync(file, "Guten Tag !");

Lesen aus TemporaryFolder:

StatusAnzeiger.Items.Add("Lese Datei von TemporaryFolder:");
var folder = ApplicationData.Current.TemporaryFolder;
var file = await folder.GetFileAsync("TempDataFile.txt");
var inhalt = await FileIO.ReadTextAsync(file);
StatusAnzeiger.Items.Add("    - " + inhalt);

}

Löschen ApplicationData:

StatusAnzeiger.Items.Add("Lösche Einträge in LocalSettings");
var localSettings = ApplicationData.Current.LocalSettings;
// Einfach
localSettings.Values.Remove("einfacheEinstellung");
// Cmposite
localSettings.Values.Remove("CompositeEinstellung");
// Container
localSettings.DeleteContainer("ContainerEinstellung");

StatusAnzeiger.Items.Add("Lösche Einträge in RoamingSettings");
var roamingSettings = ApplicationData.Current.RoamingSettings;
// Einfach
roamingSettings.Values.Remove("einfacheEinstellung");
// Cmposite
roamingSettings.Values.Remove("CompositeEinstellung");
// Container
roamingSettings.DeleteContainer("ContainerEinstellung");

StatusAnzeiger.Items.Add("Lösche Datei in LocalFolder");
var localFolder = ApplicationData.Current.LocalFolder;
var localFile = await localFolder.GetFileAsync("LocalDataFile.txt");
await localFile.DeleteAsync(StorageDeleteOption.PermanentDelete);

StatusAnzeiger.Items.Add("Lösche Datei in RoamingFolder");
var roamingFolder = ApplicationData.Current.RoamingFolder;
var roamingFile = await roamingFolder.GetFileAsync("RoamingDataFile.txt");
await roamingFile.DeleteAsync(StorageDeleteOption.PermanentDelete);

Windows Store App erweiterter Begrüßungsbildschirm

Meine neue WebSite finden Du jetzt unter https://AttilaKrick.com.

Standard SplashScreen

Windows Store App – Standard SplashScreen

 

Windows Store App - erweiterter SplashScreen

Windows Store App – erweiterter SplashScreen

 

Beispiel: Erweiterter SplashScreen

 

Während App-Ressourcen noch initialisieren zeigt Windows den SplashScreen an. Sobald Ihre App für die Interaktion bereit wird dieser ausgeblendet. Sollte die Initialisierung länger als 2 Sekunden dauern, können Sie die Zeit mit dem erweiterten Begrüßungsbildschirm überbrücken.

Subjektiv wird der Ladevorgang kürzer wahrgenommen, wenn eine Animation zu sehen ist. Es muss ja nicht gleich der aktuelle Blockbuster sein aber Informatives aus Ihrer App oder zumindest einen ProgressRing bzw. eine ProgressBar.

Um eine Anpassung an die Vielzahl möglicher Bildschirmgrößen bzw. Auflösungen zu ermöglichen wird die Größe des Hintergrunds geändert und Ihr Bild skaliert. Um Artefakte und franzende Ränder zu vermeiden sollten Sie folgende Bilder im App-Manifest bereitstellen:

  • Assets\SplashScreen.scale-100.png entspricht 620 x 300px
  • Assets\SplashScreen.scale-140.png entspricht 868 x 420px Optional
  • Assets\SplashScreen.scale-180.png entspricht 1116 x 540px Optional

Weitere Information finden Sie im Kapitel Bildressourcen.

Der SplashScreen sowie die optionale Hintergrundfarbe werden in der
Package.appxmanifest-Datei festgelegt.

SplashScreen.Dismissed Über dieses Ereignis werden Sie Benachrichtigt nach dem das System sein Setup beim Starten Ihrer App vollzogen und der reguläre Begrüßungsbildschirm ausgeblendet wird. Z. B. um einen Feed aus dem Internet parallel zum weiten App-Logik-Verlauf zu laden.

Wenn Sie keine Farbe für Ihren SplashScreen-Hintergrund angeben, wird als Hintergrundfarbe die Hintergrundfarbe der Kachel verwendet.

Der erweiterte SplashScreen sollte genauso aussehen wie der von Windows angezeigte SplashScreen.

TIP Um optimale optische Ergebnisse zu erzielen verwenden Sie ein PNG-Bild dessen Hintergrundfarbe identisch ist wie mit der der SplashScreen-Hintergrundfarbe. Da i.d.R. Ränder besser im Grafikprogramm zu Hintergrund gezeichnet werden können.

TIP Behandeln Sie das Window.Current.SizeChanged-Ereignis um auf Geräte-Rotation oder App-Snapping zu reagieren um den erweiterten Splashscreen zu repositionieren.

NOGO Verwenden Sie den Begrüßungsbildschirm nicht zur Anzeige von Werbung, nicht zur Anzeige mehrerer unterschiedlicher Begrüßungsbildschirm-Bilder und nicht zur Anzeige einer Infoseite.

Erweiterter SplashScreen festlegen

1. Erstellen Sie eine Seite als Ansicht für Ihre App, die den unter Windows angezeigten Begrüßungsbildschirm imitiert. Zum Beispiel mit folgenden Elementen:

<Page x:Class="Ak.AppStudio.View.ErweiterterSplashScreen"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid Background="#FF008AAA">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Canvas>
            <Image Name="ErweiterterSplashScreenImage" 
                   Source="/Assets/SplashScreen.png" />
        </Canvas>
        <ProgressBar Name="StatusBar"
                     Grid.Row="1"
                     Width="600"
                     Height="5"
                     Foreground="White"
                     Maximum="1000" />
    </Grid>
</Page>

2. Passen Sie die dazugehörige Codebehind-Datei wie folgt an:

public sealed partial class ErweiterterSplashScreen : Page {
    SplashScreen _SplashScreen;

    public ErweiterterSplashScreen(SplashScreen splashScreen) {
        _SplashScreen = splashScreen;

        InitializeComponent();

        Window.Current.SizeChanged += (obj, args) => {
            PositioniereErweiterterSplashScreen();
        };

        _SplashScreen.Dismissed += (obj, args) => {
            // Wird ausgelöst, wenn der Begrüßungsbildschirm 
            // der App geschlossen wird.
        };

        PositioniereErweiterterSplashScreen();

        // Simuliere Last
        var timer = new DispatcherTimer();
        timer.Interval = TimeSpan.FromMilliseconds(100);
        timer.Tick += (obj, args) => {
            StatusBar.Value += timer.Interval.Milliseconds;
            if (StatusBar.Value >= StatusBar.Maximum) {
                timer.Stop();
                NavigateToMainPage();
            }
        };
        timer.Start();
    }

    void PositioniereErweiterterSplashScreen() {
        if (_SplashScreen != null) {
            ErweiterterSplashScreenImage.SetValue(
                Canvas.LeftProperty, _SplashScreen.ImageLocation.X);
            ErweiterterSplashScreenImage.SetValue(
                Canvas.TopProperty, _SplashScreen.ImageLocation.Y);
        }
    }

    static void NavigateToMainPage() {
        var rootFrame = new Frame();
        rootFrame.Navigate(typeof(MainPage));
        Window.Current.Content = rootFrame;
    }
}

3. Fügen Sie der App.OnLaunched Code hinzu, um den erweiterte Begrüßungsbildschirm anzuzeigen.

protected override void OnLaunched(LaunchActivatedEventArgs args) {
    base.OnLaunched(args);

    if (args.PreviousExecutionState == ApplicationExecutionState.Running) {
        Window.Current.Activate();
        return;
    }

    var extendedSplash = new ErweiterterSplashScreen(args.SplashScreen);
    Window.Current.Content = extendedSplash;
    Window.Current.Activate();

}

4. Fertig!

Windows Store App – Seiten-Gestaltung

Meine neue WebSite finden Du jetzt unter https://AttilaKrick.com.

Windows Store App Page Template

Windows Store App Page Template

Die Benutzeroberfläche einer Windows 8-App soll in allen Anwendungen eine
einheitliche Silhouette haben. Merkmale dieser Silhouette sind breite Ränder oben, unten und links. Durch die breiten Ränder am richtigen Rand wird dem Benutzer die horizontale bzw. vertikale Verschiebungsrichtung für den Inhalt intuitiv vorgelegt.

Durch feste Muster wird die Einheitlichkeit in allen Anwendungen gewahrt und den Benutzern das Verständnis der systemweiten Interaktionen erleichtert.

Windows 8 und sein Apps zelebrieren den Slogan clean and fluid. Zu mindestens bedeutet dass für clean, das von einem klassischen Windows-Fenster alles verband wurde was kein Inhalt darstellt und der Inhalt gleichzeitige die Interaktionsfläche für den Benutzer wird. Unterschätzen Sie jedoch nicht den Aufwand für eine funktionelle minimalistische Benutzeroberfläche.

Seitenlayout

Unter einem Seitenlayout bezeichnet man das Anpassen und Positionieren von Objekten auf der Benutzeroberfläche. Sie müssen Ihre visuellen Objekte in Container-Objekt platzieren, um diese auf der Seite zu positionieren. Zu den Container gehören u.a. Canvas, StackPanel, Grid, GridView und ListView.

Das Seitenlayout wird für Ihre Apps in verschiedenen Ansichten benötigt. Eine Ansicht ist die Art, in der sich der Inhalt im UI einpasst und wie der Benutzer auf die App zugreift z. B. im Portrait-Format oder Ihre App ist an eine Seite angedockt. Eine App die mehrere Ansichten unterstützt, kann auf Geräten verschiedener Größen und Ausrichtungen verwendet werden, und der Benutzer kann den Inhalt entsprechend seinen Anforderungen und Vorlieben bearbeiten.

Windows 8-App’s müssen als Vollbild-Anwendungen konzeptioniert werden.

Ihr Inhalt ist das primäre Element auf der Oberfläche, sekundäre Inhalt sollte nur dann auftreten wenn sie benötigt werden, daher verlagern Sie diesen z.B. in Flyouts oder in die AppBar.

Eingabefehler zeigen Sie Inline an, d.h. dort wo sie entstanden sind.

Geben Sie Ihrem Benutzer Feedback für jede Handlung die er tut, gerne auch als Animation, z.B. beim Berühren von Elemente.

Machen Sie sich Gedanken über die Fläche die der Benutzer oft berührt bzw. nicht oft berührt und legen dort berührungswürdige bzw. berührungsunwürdige Elemente ab.

Visual Studio bietet für diese Designsprache zwei Vorlagen. _Rasteranwendung zeigt Gruppen von Daten in einem Rasterformat an,
z.B. Shopping-, Nachrichten-, Foto-, Video-Apps oder sowie RSS-Reader und _Geteilte Anwendung zeigt eine Master-Detail-Liste an.

Blend for Visual Studio 2012 ist ein ideales Werkzeug zum Bearbeiten von Layout, Bild, Grafik und Animation. Natürlich können auch alle Aufgaben mit Visual Studio realisiert werden, nur müssen Sie hierfür vieles manuell in XAML erstellen.

Für Adobe Photoshop können Sie PDS-Dateien unter http://goo.gl/jVTHl herunterladen.

Rastersystem

Das hier gezeigte Rastersystem ist ein Design-Mittel, das dazu beiträgt, visuelle Einheitlichkeit für verschiedene Anwendungen und Features zu erzielen.

Visual Studio Projekt-Vorlagen aber auch die Hilfe in der MSDN beziehen sich auf dieses Rastersystem.

Anzeigen eines Ausrichtungsgitters und weitere Rastersystem-Funktionen können in der Status-Leiste des Visual Studio-Designer aktiviert werden.

Sie finden im Download-Bereich dieser App eine Handout mit den wichtigsten Maßen.

Für Adobe Photoshop können unter http://goo.gl/jVTHl können Sie Vorlagen als PSD-Dateien downloaden.

Bei einer Auflösung von 1366x768px ergibt sich folgende Rasteraufteilung:

Einheiten 20 × 20 Pixel
Untereinheiten 5 × 5 Pixel
Quadrateinheit 16 Untereinheiten

Abstände des Seitenkopfes

Vom oberen Rand 5 Einheiten
Vom linken Rand 6 Einheiten

Abstände zum Inhaltsbereich

Vom oberen Rand 7 Einheiten
Vom linken Rand 6 Einheiten
Vom unteren Rand 2,5 – 6,5 Einheiten je nachdem ob der Inhalte Horizontal
oder Vertikal verschoben wird

Vertikale Aufteilung von Elementen

Stark konturierte Elemente 2 Untereinheiten z.B. zw. Kachel
und dem zugehörigen Text
Listen Spalten-Abstand 2 Einheiten
Themen-Gruppen-Abstand 4 Einheiten

Horizontale Aufteilung von Elementen

Kachel-Textlisteneinträgen zum Nächsten 1 Einheit
Stark konturierte Objekte 2 Untereinheiten

Windows Store App-Gestaltung

Meine neue WebSite finden Du jetzt unter https://AttilaKrick.com.

Windows Store Apps müssen als Vollbild-Anwendungen konzipiert werden da eine Fenster-Darstellung nicht mehr möglich ist. Im Grund ist das neue Windows 8 UI-Layout simpel. Nehmen Sie eine klassische Windows-Fenster-Anwendung und entfernen Sie davon jedes Pixel was kein Inhalt ist, Fertig!

Geben Sie Ihrem Inhalt den primären Fokus, nutzen Sie Ihren Inhalt zur Interaktion mit dem Benutzer und platzieren alles anderen in sekundäre Bereiche.

Ihre App sollte sich schnell und flüssig, sanft und nicht hart anfühlen.

Apps können mit allen Windows 8-Bildschirmgrößen ausgeführt werden. In der Regel haben größere Bildschirme auch höhere Auflösungen. Daher bieten diese einen größeren Anzeigebereich denn es gilt zu nutzen und zu kleineren Auflösungen abzugrenzen.

Sie sollten das Layout früh in die Planung Ihrer App einbeziehen. Unterschätzen Sie auch nicht den Aufwand für Anpassungen diverser Bildschirmauflösungen, Ansichten-Gestaltung und Animation die einen erheblichen Einfluss auf das Layout haben.

Eine Seite muss für verschiedene Ansichten mehrfach gestallt werden. Eine Ansicht ist die Art, in der sich der Inhalt an der UI anpasst in Abhängigkeit wie der Benutzer auf Ihre App zugreift. Zum Beispiel im Portrait-Format oder Ihre App ist an einer Seite angedockt. Eine App die mehrere Ansichten unterstützt, kann auf Geräten verschiedener Größen und Ausrichtungen verwendet werden, und der Benutzer kann den Inhalt entsprechend seinen Anforderungen und Vorlieben bearbeiten.

Viele dieser Faktoren werden beim Zertifizierungsverfahren für den Microsoft Store berücksichtigt.

Bildschirmskalierung

Für die Unterstützung aller Features, wie z. B. das Andocken ist eine Mindestauflösung von 1366x768px erforderlich.

Der Benutzer erwartet bei größeren Zoll-Auflösungen mehr Inhalt und Funktionalität.

Entwerfen Sie Ihre App so, dass alle UI-Elemente wie Navigation, Steuerelemente und Inhalte auf den Bildschirm passen, d. h. also für die Mindestauflösung von 1024x768px.

Entwerfen Sie Ihre App so, dass alle UI-Elemente ohne leere Flecke auf den Bildschirm passen, d. h. also für die Idealauflösung von 1366x768px.

Berücksichtigen Sie auf einem größeren Bildschirme Layout, Ästhetik, Proportionen und die Anordnung Ihrer Steuerelemente.

Statisches Layout Eine Möglichkeit ist das statische Layout. Dieses treffen Sie oft bei Spielen an die hauptsächlich aus Bitmap-Bildern bestehen. Hier mehr Inhalt anzuzeigen ist nicht möglich oder bringt vermutlich keinen Mehrwert. Windows 8 skaliert feste Layouts mit einem integrierten Verfahren automatisch bis zu maximal 200%.

Über die Eigenschaften .Canvas.Left und .Canvas.Top können Sie ihre UI Elemente in einem statischen Layout über X- und Y-Koordinaten genau positioniert.

Dynamisches Layout Diese zweite Möglichkeit sehen Sie häufig in Inhalt-Apps. Diese Layouts bestehen i.d.R. aus definierten proportionalen Elementen wie Kopfzeile, Fußzeile und in der Mitte einen Inhalts-Bereich. Dieses Layout ändert sich dynamisch und passt sich an verschiedenen Bildschirmgrößen an. Die Größe der Benutzeroberfläche wird automatisch an unterschiedliche Bildschirmauflösungen angepasst.

Zu den wichtigsten dynamischen Positions-Eigenschaften gehören .Width, .Height,.MinWidth,.MinHeight,.Margin,.Padding,.VerticalAlignment,.HorizontalAlignment` u.v.m.

Statisches Layout erstellen

1. Beginnen Sie mit den Basisauflösungen 1024×768 und 1366×768.

2. Platzieren Sie den festen Inhalt in eine ViewBox, um ein festes Layout auf den Bildschirm zu skaliert.

3. Legen Sie die Größen der ViewBox z.B. auf 1366x768px fest.

4. Platzieren Sie keine dynamischen Steuerelemente in die ViewBox, da sich diese Elemente automatisch an verschiedene Bildschirmgrößen anpassen.

5. Definieren Sie Stil & Farbe für das Portrait-Format.

6. Stellen Sie Vektor-Ressourcen oder Raster-Ressourcen mit hoher Auflösung bereit.

<Canvas Height="125" Margin="20">
    <Rectangle Canvas.Left="40" Canvas.Top="20" Fill="Yellow" Width="75" Height="75" />
</Canvas>

Dynamisches Layout erstellen

1. Bestimmen Sie für jede Zelle, die Sie als in horizontaler oder vertikaler Richtung dynamisch identifiziert haben, wie das App-Layout diese Fläche auf einem größeren Bildschirm nutzen soll.

2. Bestimmen Sie das Layout-Drahtmodell, hier sollten die Positionen der Bereiche für Kopfzeile, Navigation und Inhalt hervorgehen.

3. Bestimmen Sie, welche Teile des Layout statisch bzw. dynamisch sind.

4. Ein ListView oder GridView füllt automatisch die verfügbare Fläche mit weiteren Elementen.

5. Verwenden Sie für Text gegebenenfalls ein mehrspaltiges Layout.

6. Verwenden Sie gegebenenfalls für Bilder ein Canvas, da dieses automatisch erweitert wird, um die verfügbare Fläche auszufüllen.

7. Zeigen Sie mehr Leerraum oder zeigen Sie mehr von der App.

Für ein dynamisches Layout nutzen Sie u.a. die Steuerelemente StackPanel, Grid, GridView und ListView, zum Beispiel:

<StackPanel Orientation="Horizontal">
    <Rectangle Fill="Fuchsia" Width="50" Height="50" Margin="10" />
    <Rectangle Fill="Honeydew" Width="50" Height="50" Margin="10" />
    <Rectangle Fill="Orange" Width="50" Height="50" Margin="10" />
</StackPanel>

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition />
        <RowDefinition Height="40" />
        <RowDefinition />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="65" />
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <TextBox Grid.Column="0" Grid.Row="0" Text="Z1 | S1"/>
    <TextBox Grid.Column="1" Grid.Row="0" Text="Z1 | S2"/>
    <TextBox Grid.Column="0" Grid.Row="1" Text="Z2 | S1"/>
    <TextBox Grid.Column="1" Grid.Row="1" Text="Z2 | S2"/>
    <TextBox Grid.Row="2" Grid.ColumnSpan="2" Text="Z3 | S1-2"/>
    <TextBox Grid.Column="3" Grid.RowSpan="3" Text="Z1-3 | S3"/>
</Grid>

Ansichten

Angedockte Ansichten (Snapped) und gefüllte Ansichten (Filled) sind nur für Displays mit einer horizontalen Mindestauflösung von 1366px verfügbar.

Der Benutzer kann mit Ihrer App im Querformat(Lanscape), Hochformat (Portrait) oder mit zwei Apps gleichzeitig arbeiten (Filled und Snapped).
Um diese vier Zustände (ViewStates) bedienen zu können, kann Ihre App verschieden Layouts bevorraten oder eins entsprechend modifizieren.

Weitere Informationen finden Sie auch im Kapitel ApplicationViewState.

Lanscape Querformat und i. d. R. die meistbenutzte Ansicht.

Portrait Hochformat

Filled Die App füllt den verbleibenden Bildschirmbereich aus, der nicht von der angedockten App belegt wird. Kann nur im Querformat genutzt werden. Diesem Layout stehen min. 1024px Breite zur Verfügung.

Snapped Die App wird am linken oder rechten Rand angedockt. Kann nur im Querformat genutzt werden. Diesem Layout stehen min. 320px Breite zur Verfügung.

TIP Bei der angedockten App handelt es sich um keine Gadget-Version Ihrer App, daher sollten Sie für den Benutzer Status, Kontext und die Interaktivität aufrechterhalten.

TIP Sorgen Sie für Feature-Parallelität in den verschiedenen Zuständen.

TIP Geben Sie den Benutzern die Kontrolle, d.h. docken Sie die App nicht programmgesteuert ab.

TIP Nutzen Sie das hohe Seitenverhältnis des angedockten Layouts für vertikale Verschiebungen Ihres Inhaltes.

TIP Angesichts der geringen Breite von 320 Pixel im angedockten Zustand sollten Sie vom mehrspaltigen Layout zum einspaltigen Layout übergehen.

Vektor- und Raster-Ressourcen

Vektor-Ressourcen (z.B. SVG, XAML) werden ohne Skalierungsartefakte oder Verschwimmen skaliert.

Raster-Ressourcen (z.B. BMP) werden mit Skalierungsartefakte oder Verschwimmen skaliert. Stellen Sie Bilder bereit, die doppelt so groß sind wie die Designgröße um beim automatischen vergrößern nicht verpixelt zu wirken oder geben mehrere Bilder mit unterschiedlichen Auflösungen an. Mehr darüber erfahren Sie im Kapitel Bild-Ressource.

Testen des App-Layouts

In Visual Studio über den Windows Simulator und im XAML Designer in der Geräte-Ansicht können Sie diverse Auflösungen und Ansichten testen.

In Blend über den XAML Designer und in der Geräte-Ansicht.

Code on WordPress Tipps

Meine neue WebSite finden Du jetzt unter https://AttilaKrick.com.


Posting Source Code

Das BBCode-Element heißt ‚code‘ und muss mit seinene Attributen in ‚[]‘ stehen, sowei am Ende mit ‚/‘ abgeschlossen werden.

code lang=“csharp“ title=“Mein Favorit“

[TestMethod]
public void PropertyChange_ModelBase()
{
    // siehe http://wp.me/p3i5NY-1R
    var myModel = new MyModelTest();
    ModelBase modelBase = myModel;
    string propertyName_SOLL = "Kurzname";
    myModel.PropertyChanged += (fired, args) =>
    {
        Assert.AreEqual(propertyName_SOLL, args.PropertyName);
    };
}

code light=“true“

[TestMethod]
public void PropertyChange_ModelBase()
{
    // siehe http://wp.me/p3i5NY-1R
    var myModel = new MyModelTest();
    ModelBase modelBase = myModel;
    string propertyName_SOLL = "Kurzname";
    myModel.PropertyChanged += (fired, args) =>
    {
        Assert.AreEqual(propertyName_SOLL, args.PropertyName);
    };
}

code Highlight=“5,10″

[TestMethod]
public void PropertyChange_ModelBase()
{
    // siehe http://wp.me/p3i5NY-1R
    var myModel = new MyModelTest();
    ModelBase modelBase = myModel;
    string propertyName_SOLL = "Kurzname";
    myModel.PropertyChanged += (fired, args) =>
    {
        Assert.AreEqual(propertyName_SOLL, args.PropertyName);
    };
}

collapse=“true“ title=“BBCode Beispiel zugeklappt“

[TestMethod]
public void PropertyChange_ModelBase()
{
    // siehe http://wp.me/p3i5NY-1R
    var myModel = new MyModelTest();
    ModelBase modelBase = myModel;
    string propertyName_SOLL = "Kurzname";
    myModel.PropertyChanged += (fired, args) =>
    {
        Assert.AreEqual(propertyName_SOLL, args.PropertyName);
    };
}

Weitere Details hier

Komponententest mit Visual Studio

Meine neue WebSite finden Du jetzt unter https://AttilaKrick.com.

 

Wichtige Attribute der Test-Methoden

[TestClass]
internal class MeineTestKlasse
{
    [TestMethod]
    [TestCategory("Core Features")]
    [Owner("Uwe")]
    [Priority(2)]
    [Ignore]
    [Timeout(1000)]
    internal void Test()
    {
        // Test was !
    }
}

Dem Test weitere Funktionalität hinzufügen

[ExcludeFromCodeCoverage]
[GeneratedCode("I'm", "1.0")]
[TestClass]
internal class MyTest
{
    /// Testkontext der Informationen und Funktionalität für den
    ///  Testlauf bietet.
    public TestContext TestContext { get; set; }

    #region Sie können in Tests zusätzlichen Attribute verwenden:

    // Bevor der ersten Test in der Klasse ausgeführt wird.
    [ClassInitialize()]
    public static void MyClassInitialize(TestContext testContext)
    { } // Code

    // Nachdem alle Tests in einer Klasse ausgeführt wurden.
    [ClassCleanup()]
    public static void MyClassCleanup()
    { } // Code

    // Vor jedem einzelnen Test Code.
    [TestInitialize()]
    public void MyTestInitialize()
    { } // Code

    // Nach jedem einzelnen Test Code.
    [TestCleanup()]
    public void MyTestCleanup()
    { } // Code

    #endregion
}

Tipps

  • Der Text Explorer in VS bietet ausgefeilte Filter- und Such-Möglichkeiten:
    Link

Weiterführende Informationen

  • Exemplarische Vorgehensweise zum Erstellen und Ausführen von Komponententests:
    Link
  • Unit-Testing
    Link

Was zeichnet einen guten Test aus?

  • Ein guter Test ist isoliert – Sie sind voneinander unabhängig,
    so dass die Reihenfolge ihrer Ausführung das Testergebnis nicht beeinflusst.
  • Ein guter Test überprüft jeweils nur eine Eigenschaft.
  • Ein guter Test ist vollständig automatisiert.
  • Ein guter Test ist leicht verständlich und kurz.
  • Ein guter Test weißt die gleiche Codequalität auf, wie der Produktivcode,
    Stichwort Redundanzen, Style, Metrik, Code Conventions, etc.
  • Ein guter Test ist nach Testfixture gruppiert, nicht um eine Klasse.
  • Ein guter Test wird vor dem zu testenden Code geschrieben,
    Stichwort „Testgetriebene Entwicklung“.

Ziel

  • Qualitätssicherung
  • Voraussetzung für Refactorings
  • Im Zeitpunkt der Erstellung das Design der Software steuern
  • Aufdecken von versehentliche Änderungen des Verhaltens
  • Aufdecken der unbeabsichtigten Fernwirkung von neuen Funktionen
    auf bereits bestehende