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

 

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!

WPF Eingabe-Validierung

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

 

Validierung basierend auf komplexer Regeln für Geschäftsdaten

WPF weist ein umfassendes Datenbindungssystem auf. Abgesehen davon, dass die Datenbindung wesentlich zur lockeren Kopplung der Benutzeroberfläche mit der Logik (MVVM) beiträgt, bietet es auch eine leistungsstarke und flexible Unterstützung für die Überprüfung der Daten. Dies gilt es zu beherrschen.

  • Wie arbeitet diese Überprüfung?
  • Welche Arten der Überprüfungen gibt es?
  • Wo liegen die Stärken und Schwächen?
  • Wie Können Überprüfungsfehler dem Benutzer angezeigt werden?

Vorbereitung

Kenntnisse in der WPF-Datenbindung, eine Einführung finden Sie z.B. hier Datenbindung in WPF.

Um eine Daten-Pipeline zwischen einer einzelnen Eigenschaft in einem Ziel Steuerelement und einer Datenquellen-Objekteigenschaft zu schaffen verwendet man ein Binding-Objekt.

Das eine Überprüfung eingeleitet werden kann, muss die Mode-Eigenschaft des Binding-Objektes auf TwoWay stehen.

Arten der Überprüft

Es gibt 3 miteinander kombinierbare Überprüfungs-Mechanismen, um zu ermitteln, ob über ein datengebundenes Steuerelement Benutzereingaben gültig sind.

1. Exception

Wenn beim Beschreiben der Quellobjekt-Eigenschaft eine Exception ausgelöst wird, so kann ein Überprüfungsfehler für das Binding-Objekt bestehen.

2. ValidationRule, BindingGroup

Dem Binding-Objekt können eine Sammlung von ValidationRule’s übergeben werden, die Entscheiden ob Überprüfungsfehler durch Benutzereingaben vorliegen. Die BindingGroup unterstützt diesen Xaml-Ansatz um die Möglichkeit Eigenschaftsübergreifend Überprüfungen vornehmen zu können.

3. IDataErrorInfo, INotifyDataErrorInfo

Durch Implementieren von IDataErrorInfo in das gebundene Datenquellen-Objekt Entscheidet dieses ob Überprüfungsfehler vorliegen. Das INotifyDataErrorInfo-Interface stand bis .NET 4.5 nur Silverlight zur Verfügung. Die Implementierungs-Logik unterscheidet sich zu IDataErrorInfo wird jedoch doch von Windows Store App, WPF und Silverlight unterstützt.

Wann wird überprüft

Die Überprüfung findet dann statt, wenn die Bindung die Daten in die Quellobjekt-Eigenschaft schreibt. Dieses Update-Verhalten wird im Binding-Objekt gesetzt:
Text=“{Binding Path=Aufgabe.Beschreibung, UpdateSourceTrigger=PropertyChanged}
Folgende Einstelllungen sind denkbar:

  • PropertyChanged – Bei jeder Änderung im Steuerelement. (Datum in TextBox, Sinnvoll?)
  • FocusChange – Das Update wird mit Wechsel des Steuerelement- Fokusses eingeleitet.
  • Explicit – Der Aufruf erfolgt manuell und wird z.B. in Kombination mit BindingGroup genutzt.

Arbeitsschritte der Überprüft

  1. Benutzereingabe führt zur Änderung einer UIElement-Eigenschaft
  2. Diese Daten werden ggf. in den Quellobjekt-Eigenschaftentyp umgewandelt
  3. Der Wert für die Quellobjekt-Eigenschaft wird gesetzt
  4. Binding.SourceUpdated-Ereignis wird ausgelöst
  5. Exception werden von der Bindung abgefangen, wenn diese vom Setter der Quellobjekt-Eigenschaft ausgelöst wurden
  6. OPTIONAL: IDataErrorInfo- / INotifyDataErrorInfo-Eigenschaften werden vom Quellobjekt abgerufen
  7. Der Benutzer erhält Überprüfungsfehlerangaben
  8. Validation.Error wird ausgelöst

X. OPTIONAL: ValidationRule’s auslösen, je nachdem wie ValidationStep-Eigenschaft gesetzt wurde, z.B. vor- / nach der Typkonvertierung, nach dem aktualisieren Quellobjekt-Eigenschaft oder wenn begonnen wird (s. IEditableObject) die Quellobjekt-Eigenschaft zu ändern.

Prüfung per Exception

Durch Festlegen der ValidatesOnExceptions-Eigenschaft im Binding-Objekt wird ein Überprüfungsfehler festgelegt, wenn im Setter der Quellobjekt-Eigenschaft eine Exception ausgelöst wird.

Text="{Binding Path=MitarbeiterNr, ValidatesOnExceptions=True}"

Auch würde ein Überprüfungsfehler vorliegen, wenn die Exception im Typkonvertierungs-Prozess ausgelöst wurde.

Prüfung per ValidationRule

Dem Binding-Objekt können mehrere ValidationRule-Objekte über die Eigenschaft ValidationRules hinzugefügt weden.

Um eigene ValidationRule-Objekte zu nutzen, leitet man diese Klassen von ValidationRule ab und überschreiben die Validate-Methode. Das Bindung-Objekt ruft diese Methode auf sobald sich die Daten im gebundenen Steuerelement ändern.

Wenn die Validate-Methode ein ungültiges ValidationResult-Objekt zurückgibt, wird für diese Bindung ein Überprüfungsfehler festgelegt.

public class RegexValidationRule : ValidationRule
{
    public string RegexMuster { get; set; }

    public override ValidationResult Validate(object value,
       CultureInfo cultureInfo)
    {
        if (RegexMuster == null)
            return ValidationResult.ValidResult;

        if (!(value is string))
            return new ValidationResult(
                isValid: false,
                errorContent: "Die Eingabe muss aus Text bestehen.");

        if (!Regex.IsMatch((string)value, RegexMuster))
            return new ValidationResult(
                isValid: false, errorContent: "Falsches Eingabeformat.");

        return ValidationResult.ValidResult;
    }
}

Eigenschaften, die in einer ValidationRule öffentlich verfügbar gemacht werden (s. z.B. RegexMuster), können von der XAML am Punkt der Verwendung festgelegt werden.
Das zurückgebende ValidationResult kann die Fehlermeldung die in der Benutzeroberfläche angezeigt werden soll.


  
    
      
        
      
    
  

ValidationRule-Objkete können jweils unterschiedliche Werte für die ValidationStep-Eigenschaft aufweisen.

Wenn eine ValidationRule einen Fehler zurückgibt werden die darauffolgenden Regeln nicht ausgewertet.

PRO

  • Implementierung kompliziertere Überprüfungs-Logik möglich

CONTRA

  •  Das ValidationRule-Objekt hat keinen Zugriff auf andere Eigenschaften im Quellobjekt und kann daher seinen Wert nicht mit diesen, für die Validierung in Beziehung setzen.
  • Die ValidationRule-Objkete werden im XAML, in einem erweiterten Binding-Element angeben (= Aufwand).

Prüfung per IDataErrorInfo

public interface IDataErrorInfo
{
    string Error { get; }
    string this[string columnName] { get; }
}

Durch Implementieren von  IDataErrorInfo in das Datenquell-Objekt und durch Festlegen der ValidatesOnDataErrors-Eigenschaft im Binding-Objekt, führt die Bindung Aufrufe der IDataErrorInfo-API aus.

Text="{Binding Path=Aufgabe.Beschreibung, ValidatesOnDataErrors=True}

Wenn von diesen IDataErrorInfo-Eigenschaften Zeichenfolgen zurückgegeben werden, die nicht Null oder nicht leer sind, wird ein Überprüfungsfehler festgelegt.

public class Kunde : IDataErrorInfo
{
    public string this[string propertyName]
    {
        get { return IsValid(propertyName); }
    }

    public string Email { get; set; }
    public bool IstInternetKunde { get; set; }

    private string IsValid(string propertyName)
    {
        switch (propertyName)
        {
            case "Email":
                if (!string.IsNullOrWhiteSpace(Email)
                    && IstInternetKunde)
                    return "Als Internet-Kunde wird eine"
                           + " Email-Adresse benötigt.";
                break;
        }
        return String.Empty;
    }
}

Der Indexer wird verwendet, um Fehler auf den einzelnen Eigenschaftenebenen anzugeben.

Die Error-Eigenschaft wird verwendet, um einen Fehler für das Objekt als Ganzes anzugeben, z. B. auf Datensatz-Ebene wie im DataGrid oder in einer BindingGroup.

PRO

  • Der Vorteil von IDataErrorInfo besteht darin, dass untereinander verbundene Eigenschaften einfacher verarbeitet werden können.

CONTRA

  •  Die Implementierung des Indexers führt in der Regel zu einer umfangreichen switch-case-Block.
  • Die Implementierung von IDataErrorInfo erst aufgerufen, nachdem der Eigenschaftswert bereits in dem Objekt festgelegt wurde. D.h. die anderen Objekte wurden bereits über Änderungen informiert (siehe INotifyPropertyChanged) obwohl die Daten im Anschluss als ungültig erklärt werden.
  • Nur ein Fehler pro Eigenschaft möglich.
  • Benutzen des Indexer der evtl. für eigen Logik benötigt wird.

Prüfung per INotifyDataErrorInfo

Das Konzept wurde in Silverlight eingeführt und mit der .NET 4.5 Version auch auf die anderen Bereiche wie WPF, Windows Store Apps ausgeweitet. Vom Grundgedanken her ist es dem IDataErrorInfo-Konzept ähnlich, das Interface schreibt jedoch folgende Member vor:

// Definiert Member, die von Datenentitätsklassen
// implementiert werden können, um benutzerdefinierten,
// synchronen und asynchronen Validierungssupport bereitzustellen.
public interface INotifyDataErrorInfo
{
    // Ruft einen Wert ab, der angibt, ob die Entität
    // über Validierungsfehler verfügt.
    bool HasErrors { get; }

    // Tritt ein, wenn sich die Validierungsfehler
    // für eine Eigenschaft oder für die ganze Entität geändert haben.
    event EventHandler ErrorsChanged;

    // Ruft die Validierungsfehler für eine
    // angegebene Eigenschaft oder für die ganze Entität ab.
    IEnumerable GetErrors(string propertyName);
}

Dieses Konzept arbeitet idealerweise mit der ValidationContext-Klasse sowie den Mebmern des Namensraumes System.ComponentModel.DataAnnotations zusammen. Hierbei werden die Validierungsregeln als Attribute der Eigenschaften im Quell-Objekt beschrieben.

Eine Implementierung würde Konzeptionell wie folge Dargestellt:

public class Kunde : INotifyDataErrorInfo
{
    #region Bindables

    public string Email { get; set; }

    public bool IstInternetKunde { get; set; }

    #endregion

    #region INotifyPropertyChanged Members

    public event EventHandler ErrorsChanged;

    public bool HasErrors { get {
      return _FehlerListe == null ? false : _FehlerListe.Count > 0; } }

    public IEnumerable GetErrors(string propertyName)
    {
        IList list;
        return _FehlerListe.TryGetValue(propertyName, out list)
          ? list : Enumerable.Empty();
    }

    #endregion

    #region Helpers for INotifyDataErrorInfo

    // Im Setter der jeweiligen Eigenschaft aufrufen
    // Ideale Kombiniert mit Techniken aus dem
    // Namespace System.ComponentModel.DataAnnotations
    private void Validate([CallerMemberName] string propertyName = null)
    {
        switch (propertyName)
        {
            case "Email":
                if (!string.IsNullOrWhiteSpace(Email)
                     && IstInternetKunde)
                    SetErrors(propertyName, new[]{"Als Internet-Kunde"
                     + " wird eine Email-Adresse benötigt."});
                else
                    ClearErrors(propertyName);
                break;
        }
    }

    private void SetErrors(string propertyName,
                  IEnumerable validationResults)
    {
        _FehlerListe[propertyName] = validationResults.ToList();
        OnErrorsChanged(propertyName);
    }

    private void ClearErrors(string propertyName)
    {
        _FehlerListe.Remove(propertyName);
        OnErrorsChanged(propertyName);
    }

    private void OnErrorsChanged(string propertyName)
    {
        if (ErrorsChanged != null)
            ErrorsChanged(this, new DataErrorsChangedEventArgs(
                         propertyName));
    }

    private readonly IDictionary<string, IList> _FehlerListe
             = new Dictionary<string, IList>();

    #endregion
}

Prüfen per BindingGroup

Eine BindingGroup ist speziell so konzipiert, dass die Überprüfung in einer Gruppe von Bindungen gleichzeitig ausgewertet werden können. Dies ist z.B. nützlich, wenn ein Benutzer-View erst auf OK-Klick validiert werden soll.
Um eine BindingGroup zu verwenden, benötigen Sie eine Gruppe von Steuerelementen mit normalen Bindungen, die ein Vorgängerelement gemeinsam verwenden.


    
        
            
                
            
        
    

Eine ValidationRule, die mit einer BindingGroup verknüpft ist, funktioniert etwas anders als eine ValidationRule, die mit einer einzigen Bindung verknüpft ist.

public class KundeValidationRule : ValidationRule
{
    public override ValidationResult Validate(
         object value, CultureInfo cultureInfo)
    {
        BindingGroup bindingGroup = value as BindingGroup;
        if (bindingGroup == null)
            return new ValidationResult(false, "KundeValidationRule"
                             + " arbeitet nur mit einer BindingGroup");

        object item = bindingGroup.Items[0];
        if ('Prüfung für item' == false)
            return new ValidationResult(false, "Fehlermeldung ....");

        return ValidationResult.ValidResult;
    }
}

Anzeige von Überprüfungsfehlern

Die standardmäßige Kennzeichnung für einen Überprüfungsfehler ist ein roter Rahmen um das Steuerelement ohne einer Anzeige der verknüpfte Fehlermeldung.
Das Hinzufügen einer ToolTip-Anzeige, die den Fehlertext anzeigt, ist ganz einfach:


    
        
            
                
                    
                
            
        
    

Um die standardmäßige Überprüfungsfehleranzeige 100% den eigenen Vorstellung anzupassen, müssen Sie die angefügte Validation.ErrorTemplate-Eigenschaft festlegen:


    
        
            
                
                    
                    
                
            
        
        
    

Die Verknüpfung dieser Vorlage mit einem Steuerelement würde so aussehen:


    

Gut zu wissen

Wenn Sie in der View eine spezielle Aufgabe bei ungültige Daten ausführen müssen, z.B. Controls deaktivieren nutzen Sie das Validation.Error-Event.