Silverlight Tipp der Woche: DataBinding mit der Code-Behind Klasse

by St. Lange 23. October 2010 17:05

In diesem Tipp geht es um das Binden an Properties aus der Code-Behind Klasse eines UserControls.

Zusammenfassung

Wenn der DataContext eines UserControls auf das ViewModel zeigt, kann man mit Hilfe von ElementBinding dennoch einfach auf Properties aus der Code-Behind Klasse zugreifen, indem man das UserControl mit einem Namen versieht.

Beschreibung

Üblicherweise wird der DataContext eines UserConrols auf dessen ViewModel gesetzt. Das ViewModel enthält alle für die Anzeige benötigten Daten und das DataBinding der Unter-Controls bindet ausschließlich gegen das ViewModel.

Es kann aber auch vorkommen, dass ein UserControl selbst Properties implementiert, an die einzelne seiner Unter-Controls binden sollen. Wenn die Property DataContext jedoch auf ein ViewModel gesetzt ist, ist es nicht so ganz offensichtlich, wie denn ein Unter-Control innerhalb von XAML an eine Property aus der Code-Behind Klasse binden könnte. Ein einfacher Trick besteht darin, dem UserControl einen Namen zu geben und dann mit Element-Binding den Bezug zur Code-Bedhind Property herzustellen.

In folgendem Beispiel soll die Property SomeItems aus der Code-Behind Klasse als Datenquelle für eine Listbox dienen. Das UserControl erhält hier den Namen „codeBehind“ und bietet darüber der Listbox Property ItemsSource den Zugriff auf SomeItems.

<UserControl x:Name="codeBehind" x:Class="MyProject.MainPage" … />
  <Grid Background="White">
    <ListBox ItemsSource="{Binding SomeItems, ElementName=codeBehind}" … />
  </Grid>
</UserControl>

Obwohl dieser Weg auch bei WPF und Windows Phone funktioniert, klappt die Tool Unterstützung mit „Apply Data Binding…“ im Property Explorer von Visual Studio 2010 nicht. Es werden nur die Properties der Basisklassen aufgelistet. Bei Blend funktioniert der Property Explorer dagegen korrekt.

Anwendungsbeispiel

Es stellt sich natürlich die Frage, wann man überhaupt an Properties aus einem UserControl binden sollte. Dies ist dann sinnvoll und richtig, wenn das UserControl ein wiederverwendbares Control repräsentiert und unabhängig von der Datenmodellierung einer konkreten Anwendung ist. Ein Beispiel wäre ein UserControl „RichTextEditor“ zum Formatieren von Text. Neben einer RichTextBox enthielt so ein UserControl auch eine Toolbar mit Buttons und ComboBoxes zum Einstellen von Schriftart, Größe etc. Ein solcher RichTextEditor benötigt beispielsweise eine Property EditorFontFamily zum Setzen und Abfragen des Default-Fonts. Damit der RichTextEditor wie ein Control als Teil von anderen UserControls verwendet werden kann, sollten seine Properties DataBinding unterstützen. Dazu müssen sie als Dependency Properties implementiert werden, denn nur Dependency Properties können Target Objekte einer Datenbindung sein. Die Implementierung von EditorFontFamily als Dependency Property sieht wie folgt aus.

public partial class RichTextBox : UserControl
{
  public FontFamily EditorFontFamily
  {
    get { return (FontFamily)GetValue(EditorFontFamilyProperty); }
    set { SetValue(EditorFontFamilyProperty, value); }
  }
  public readonly DependencyProperty EditorFontFamilyProperty =
    DependencyProperty.Register("EditorFontFamily", typeof(FontFamily), typeof(RichTextEditor),
    new PropertyMetadata("Verdana"));
 }

Nun kann RichTextEditor in einem anderen UserControl wie jedes andere Control auch verwendet werden. In
diesem Kontext kann dann EditorFontFamily an eine Property MyFontFamily aus einem ViewModel
gebunden werden.

<UserControl x:Class="MyProject.MyPage" … />

  <Grid Background="White">
    <my:RichTextEditor EditorFontFamily="{Binding MyFontFamily}" … />
  </Grid>
</UserControl>

Damit dies funktioniert muss der DataContext auf das ViewModel des umschließenden Controls MyPage zeigen. Daher kann man nicht einfach im Constructor von RichTextEditor den DataContext auf this setzen.

Innerhalb von RichTextEditor holt sich dessen RichTextBox seine FontFamily aus der Code-Behind Klasse.

<UserControl x:Name="codeBehind" x:Class="MyProject.RichTextEditor"
  <Grid>

    <!-- … -->
    <RichTextBox FontFamily="{Binding EditorFontFamily, ElementName=codeBehind}"… />
    <!-- … -->
  </Grid>

</UserControl>

Dieses Binden über zwei Stufen ist der einfachste Weg, UserContols als eigenständige Controls wiederzuverwenden, und funktioniert in der beschriebenen Art und Weise ohne zusätzlichen Code in der Code-Behind Datei.

Hier geht's zum nächsten Tipp.

Tags:

Silverlight | WPF

Silverlight Tipp der Woche: Design-Time Data verwenden

by St. Lange 30. September 2010 22:04

In diesem Tipp geht es um die Verwendung von Design-Time Attributen wie d:DataContext, d:DesignInstance oder d:DesignData, die einem das Arbeiten im Visual Studio Designer ganz schön erleichtern können.

Zusammenfassung

Die Verwendung von Design-Time Attributen erlaubt die Nutzung des Property Explorers beim Erstellen von Datenbindungen im XAML-Code, sowie das Anzeigen von Beispieldaten im Designer zur Design-Zeit. Auf das Verhalten der Silverlight- Anwendung zur Laufzeit haben sie keinen Einfluss.

Beschreibung

Vor Visual Studio 2010 war das Erstellen von Datenbindungen in XAML mehr oder weniger ein Blindflug. Ob man alles richtig eingetippt hat, konnte man letztlich erst nach einem Start der Anwendung überprüfen. Und wenn man keine Daten angezeigt bekam, musste man recht mühsam nach den Ursachen suchen. Auch das Erstellen von Datatemplates war ohne Expression Blend praktisch nicht effizient möglich. Mit dem neuen Visual Studio ist das sehr viel einfacher geworden, wenn man ein paar Formalitäten beachtet.

Das Erstellen für den korrekten XAML-Code eines Databindings kann über Property Explorer durchgeführt werden. Dazu muss man ihm allerdings die Information über den zu bindenden Datentyp geben. Das folgende Beispiel zeigt, was zu tun ist:

<UserControl x:Class="DesignTimeData.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
    xmlns:data="clr-namespace:DesignTimeData"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
  <Grid x:Name="LayoutRoot" d:DataContext="{d:DesignInstance Type=data:Person}">
    <TextBox Text="{Binding Path=Name, Mode=TwoWay}"/>  
  </Grid>
</UserControl>

Der XML-Namespace d wird standardmäßig angelegt und verweist auf die Design-Time Attribute. Außerdem sorgt die Zeile mc:Ignorable="d" dafür, dass alle Elemente aus diesem Namespace zur Laufzeit ignoriert werden. Damit ist es nun möglich, einem äußeren Element wie hier dem Grid einen DataContext zuzuweisen, der nur zur Designzeit verwendet wird. Im Beispiel sorgt das Attribut DesignInstance dafür, dass dem DataContext eine Instanz des Typs Person zugewiesen wird. Mit diesen Informationen ausgestattet kann der Property Explorer den Datentyp analysieren und beispielsweise zum Binden der Property Text dem Entwickler einen hilfreichen Dialog anbieten:

Der Dialog zeigt die Properties der Klasse Person als mögliche Werte für den Binding Path. Den Dialog erhält man über die rechte Maustaste auf der Property Text im Property Explorer und dann über den Menüpunkt "Apply Data Binding…". Damit kann ein Databinding viel leichter erstellt werden als es bisher möglich war. Es empfiehlt sich also, in allen User-Controls generell im LayoutRoot-Element den Design-Time DataContext auf den zur Laufzeit verwendeten Datentyp zu setzen.

Design-Time Daten

Bei komplexeren Controls wie einem DataGrid kann es zusätzlich hilfreich sein, zur Designzeit ein paar Beispieldaten angezeigt zu bekommen. Dadurch kann beispielsweise das individuelle Layout der Spalten besser angepasst werden.

Im Beispiel gibt es zwei vereinfachte Datenklassen Person und Address, sowie eine Collection von Person Objekten:

public class Person
{
  public string Name { get; set; }
  public DateTime Birthday { get; set; }
  public Address Address { get; set; }
}
 
public class PersonCollection : ObservableCollection<Person>
{ }

public class Address
{
  public string City { get; set; }
}

Mit etwas XAML-Code werden damit die Beispieldaten angelegt:

<data:PersonCollection xmlns:data="clr-namespace:DesignTimeData">
  
  <data:Person Name="Miller" Birthday="1.2.1999">
    <data:Person.Address>
      <data:Address City="Cologne"/>

    </data:Person.Address>
  </data:Person>
 
  <data:Person Name="Baker" Birthday="2.3.1988">
    <data:Person.Address>
      <data:Address City="Berlin"/>
    </data:Person.Address>

  </data:Person>
 
  <data:Person Name="Smith" Birthday="3.4.1977">
    <data:Person.Address>
      <data:Address City="Munich"/>
    </data:Person.Address>
  </data:Person>

 
</data:PersonCollection>

Die Beispieldaten kommen in eine XAML-Datei, die Teil des Projektes ist. Die Build Action dieser Datei muss auf DesignData (oder DesignDataWithDesignTimeCreatableTypes) stehen.

Mit diesen Daten wird nun ein DataGrid wie im folgenden Beispiel versorgt:

<sdk:DataGrid Width="376" Height="138" HorizontalAlignment="Left" Margin="12,96,0,0" VerticalAlignment="Top"
          d:DataContext="{d:DesignData Source=./DesignTimeSampleData.xaml}"
          ItemsSource="{Binding}" AutoGenerateColumns="False">
  <sdk:DataGrid.Columns>
    <sdk:DataGridTextColumn Binding="{Binding Path=Name}" Header="Name" />
    <sdk:DataGridTextColumn Binding="{Binding Path=Birthday, StringFormat='dd.MM.yyy'}" Header="Birthday" />
    <sdk:DataGridTextColumn Binding="{Binding Path=Address.City, Mode=TwoWay}" Header="City" />
  </sdk:DataGrid.Columns>

</sdk:DataGrid>

Der DataContext des DataGrids wird über das Design-Time Attribut DesignData direkt mit den Beispieldaten aus der XAMLDatei verbunden. Als Effekt davon werden die Beispieldaten im Designer angezeigt:

Das ist äußerst praktisch, wenn man beispielsweise die Spalten über Data-Temples layouten möchte, denn ohne eine WYSIWYG-Datenvorschau geht das nur mit sehr sehr viel Übung.

Expression Blend verfügt schon etwas länger über die Möglichkeit, das Layout mit generierten Beispieldaten auszustatten. Das dabei verwendete Verfahren ist aber komplizierter und nutzt dafür u.a. Static Resources. Außerdem werden die Daten dem "echten" DataContext zugwiesen und sind daher auch zur Laufzeit vorhanden. Der Entwickler muss sie also entweder irgendwann wieder aus dem XAML entfernen oder zumindest per Code den DataContext überschreiben.

Das beschriebene Verfahren mit den Design-Time Attributen hat dagegen keine Auswirkungen zur Laufzeit und eignet sich auch sehr gut für Entwickler, die keinen Zugriff auf Blend haben.

Weitere Details finden sich in der MSDN Hilfe.

Das Projekt, mit dem die Screenshots gemacht wurden, ist hier:

DesignTimeData.zip (8 kB)

Hier geht's zum nächsten Tipp.

Tags:

Silverlight | WPF

Silverlight Tipp der Woche: .restext statt .resx Dateien für Text–Ressourcen

by St. Lange 4. September 2010 10:48

In diesem Tipp geht es um die Verwaltung von String Ressourcen in .restext Dateien, welche beispielsweise bei der Lokalisierung Vorteile gegenüber üblichen .resx Dateien haben.

Zusammenfassung

Beim Verwalten von Texten als Ressourcen beispielsweise für die Lokalisierung werden üblicherweise .resx Dateien verwendet. Für Texte sind Dateien im .restext Format jedoch oft besser geeignet.

Beschreibung

Der Standardweg zum Verwalten von Ressourcen in .NET sind die XML-basierten .resx Dateien. Mit ihnen können Daten aller Art als sog. Embedded Resources in Assemblies eingebettet werden. In .NET gibt es aber schon von Anfang an ein Format, mit dem man Text-Ressourcen sehr viel leichter erfassen und verwalten kann. Dabei handelt es sich um einfache Text-Dateien mit folgendem Aufbau:

; Kommentare beginnen mit ; oder #
; Texte werden als Name/Wertepaare aufgeschrieben

Bezeichner1 = Das ist ein Text.
Bezeichner2 = Das ist ein anderer Text.

Das ist schon alles, was man zum Aufbau dieser Dateien wissen muss. Bis einschließlich Visual Studio 2005 hatten diese Dateien einfach die Endung .txt und mussten mit dem SDK Tool resgen mehr oder weniger manuell in .resources Dateien konvertiert werden. Im Gegensatz dazu wurden .resx Dateien immer schon automatisch in .resources Dateien umgewandelt. Die .resources Dateien enthalten die Ressourcen in dem internen Format, wie sie in Assemblies eingebettet werden.

Seit Visual Studio 2008 kann man die Endung .restext anstatt .txt verwenden. Wenn man dann noch die Build Action auf Embedded Resource stellt, wird automatisch mit resgen eine entsprechende .resources Datei erzeugt und eingebettet.

Der Zugriff auf die Texte erfolgt wie bei .resx Dateien über die Klasse System.Resources.ResourceManager. Verschiedene Sprachen werden über den Dateinamen unterschieden. Beispielsweise enthält die Datei MyTextStrings .restext die neutrale bzw. default Sprache. Die Dateien MyTextStrings.de.restext und MyTextStrings.fr.restext enthalten die deutschen bzw. französischen Übersetzungen, die Datei MyTextStrings.de-CH.restext die schweizerischen Texte, die von denen aus MyTextStrings.de.restext abweichen usw. Der ResourceManager sucht abhängig von den UI-Einstellungen im laufenden Programm automatisch den richtigen Text heraus.

Der große Vorteil der .restext Dateien liegt in ihrem einfachen Format. Im Gegensatz zu dem für einfache Texte viel zu unübersichtlichen XML-Format der .resx Dateien kann man .restext Dateien ohne Weiteres einem Übersetzer geben. Dieser sieht sofort, was für ihn zu tun ist, wenn er die Datei in einem schlichten Texteditor öffnet. Als Einziges muss beachtet werden, dass die Datei im UTF-8 Format gespeichert werden muss, da resgen Unicode erwartet.

Hier geht's zum nächsten Tipp.

Tags:

.net | Silverlight | WPF

bonn-to-code.net – Blend für Nichtdesigner

by St. Lange 24. June 2010 15:12

Zu meinem Vortrag „Blend für Nichtdesigner“ am 22.06.2010 bei der .NET User Group für Bonn und Umgebung finden sich hier die Materialien.

In diesem Vortrag ging es um den Nutzen von Blend 4 speziell für den Software-Entwickler. Anhand vieler Beispiele wurde gezeigt, dass Blend keineswegs nur ein Werkzeug für Grafiker ist, sondern auch für Entwickler praktisch gleichberechtigt neben Visual Studio 2010 stehen sollte.

Da der überwiegende Teil des Vortrags mit Blend durchgeführt wurde, sind die Slides eigentlich wenig hilfreich, aber auf mehrfachen Wunsch stehen sie hier dennoch zum Download bereit.

Die am Ende gezeigte Anwendung von der Mix 2010 kann mir hier downloaden: Dynamic Layout and Transitions in Blend 4

Materialien zu diesem Vortrag:

Tags:

Silverlight | WPF

BASTA! 2010 Spring – Anwendungsarchitektur für WPF und Silverlight

by St. Lange 25. February 2010 13:30

Zu meinem Vortrag „WPF und Silverlight Architektur“ auf der BASTA! Spring finden sich hier die Materialien.

In diesem Vortrag ging es um die Architektur von WPF und Silverlight Anwendungen. Es wurden Möglichkeiten zur Strukturierung von Anwendungen vorgestellt und auch gezeigt, wie man Code zwischen WPF und Silverlight gemeinsam nutzen kann.

Der Beispielcode liegt in C# und VB.NET vor. An dieser Stelle ein großes Dankeschön an Thomas, der mir bei der Erstellung des VB Codes kräftig geholfen hat.

Materialien zu diesem Vortrag:

Tags:

Silverlight | WPF | Architecture

BASTA! 2010 Spring – Von WinForms nach WPF

by St. Lange 25. February 2010 08:30

Zu meinem Vortrag „Von WinForms zu WPF“ auf der BASTA! Spring finden sich hier die Materialien.

In diesem Vortrag wurde die Schrittweise Migration von WinForms nach WPF diskutiert. Die Slides und der Beispielcode stehen zum Download bereit.

Materialien zu diesem Vortrag:

Tags:

WPF | Architecture

BASTA! 2010 Spring – Blend für Nichtdesigner

by St. Lange 24. February 2010 20:00

Zu meinem Vortrag „Blend für Nichtdesigner“ auf der BASTA! Spring finden sich hier die Materialien.

In diesem Vortrag ging es um den Nutzen von Blend für den Software Entwickler beispielsweise beim Erstellen von Data Templates oder beim Designen von Custom Controls. Da der überwiegende Teil des Vortrags mit Blend durchgeführt wurde sind die Slides eigentlich wenig hilfreich, der Vollständigkeit halber aber stehen sie hier zum Download bereit.

Materialien zu diesem Vortrag:

Tags:

Silverlight | WPF

SmartBackgroundWorker

by St. Lange 9. January 2010 13:01

Eine alternative Implementierung der Klasse BackgroundWorker erlaubt es, in ein und derselben Funktion beliebig zwischen UI- und Worker-Thread hin und her zu wechseln. Die verblüffende Implementierung führt zu sehr übersichtlichem Code ganz ohne Callbacks und Delegates.

Schon seit .NET 1.0 leistet die Klasse System.ComponentModel.BackgroundWorker gute Dienste, wenn es darum geht, zeitintensive Funktionen von einem Hintergrund-Thread ausführen zu lassen, damit das Userinterface nicht vorübergehend eingefroren wird. Über die drei Events DoWork, ProgressChanged und RunWorkerCompleted kann man sich über den Fortschritt der Ausführung informieren lassen. So weit, so gut. Wird die Aufgabenstellung komplizierter, beispielsweise weil man während der Hintergrundausführung die Oberfläche mit den bisher berechnenden Zwischenergebnissen aktualisieren oder mehrere Aufgaben hintereinander ausführen möchte und dabei ggf. Fehlermeldungen anzeigen möchte, wird der benötigte Code schnell recht unübersichtlich. Das liegt daran, dass man bei den meisten nicht trivialen Problemen immer eine einfache State Machine (einen Endlichen Automaten) bauen muss, welche(r) die Aktionen des UI-Threads und des Worker-Threads synchronisiert. Die benötigten Delegates, Lambda-Expressions und Synchronization-Contexte führen schnell zu schwer lesbarem und somit auch schwer wartbarem Code.

Wäre es nicht schön, wenn man ganz einfach sequenziellen Code und einfache Schleifen in einer übersichtlichen Funktion „einfach so“ hinschreiben könnte und dabei für einzelne Codeabschnitte jeweils angeben könnte, ob sie im UI-Thread oder im Background-Thread ausgeführt werden sollen?  Wäre nett, geht aber nicht, hätte ich gesagt, bevor ich letztes Jahr den Kurzvortrag von Ralf Hoffmann bei einem Treffen der Bonner .NET Usergroup gesehen habe. Ralf verwendet den yield-Befehl für eine erstaunliche Lösung des beschriebenen Problems. Da Ralf keinen Blog betreibt, der Ansatz aber sehr nützlich ist, möchte ich seine Idee hier vorstellen.

Beispielanwendung sdddd

Zur Demonstration des Verfahrens nehmen wi xxxr an, wir wollen irgendwelche Items berechnen und eine Listbox damit füllen. Das Berechnen eines jeden Items dauert so lange, dass es in einem Background-Thread ausgeführt werden muss. Jedes Item soll jedoch sofort angezeigt werden, wenn es berechnet wurde und nicht erst am Ende des Vorgangs, wenn alle Ergebnisse vorliegen. Außerdem soll die Berechnung über das UI abgebrochen werden können, wenn es dem Anwender zu lange dauert.

Hier das Beispiel als Silverlight-Anwendung. Die Berechnung jedes Items dauert eine knappe Sekunde und lastet dabei den Worker-Thread voll aus, wie ein Blick in den Task-Manager zeigen kann. Der Browser und die Silverlight-Anwendung werden davon aber nicht blockiert. Der Vorgang kann jederzeit über einen Button abgebrochen und wieder neu gestartet werden.

Die zugehörige Worker-Funktion aus der Codebehind-Datei sieht wie folgt aus:

/// <summary>
/// A simple function that gets alternating executed by
/// the UI-thread and a background-thread.
/// </summary>
IEnumerator<SwitchTo> MyWorkerFunktion(string someParameter, int someOtherParameter)
{
  // At start we run in the background thread, so switch to UI-thread
  // to update the StartStopButton and clear the ListBox
  yield return SwitchTo.UIThread;
  StartStopButton.Content = "Stop";
  Listbox.Items.Clear();

  // Now switch to background thread and start the loop
  yield return SwitchTo.BackgroundThread;
  for (int idx = 1; idx <= 25; idx++)
  {
    // Get the next item
    string item = DoSomeHardWorkToGetAnItem(idx);

    // Switch to UI-thread to update the list-box
    yield return SwitchTo.UIThread;
    Listbox.Items.Add(item);

    // Check if user pressed the Stop button
    if (_stop)
    {
      Listbox.Items.Add("<Operation was canceled by user>");
      _stop = false;
      break;
    }

    // Switch back to background thread for next loop iteration
    yield return SwitchTo.BackgroundThread;
  }

  // We have done and reset Start/Stop button in UI-thread
  yield return SwitchTo.UIThread;
  StartStopButton.Content = "Start";
}

Unglaublich, oder? Über den Enum-Typ SwitchTo wird im yield return angegeben, ob der nächste Codeabschnitt im UI- oder im Background-Thread ausgeführt werden soll.  Der Code ist praktisch selbstdokumentierend und sehr gut lesbar.

Aufgerufen wird diese Funktion beim Drücken des Start-Buttons wie folgt:

_smartBackgroundWorker.RunWorkerAsync(MyWorkerFunktion("Hello", 42));

Die Parameter der Woker-Funktion werden im Beispiel nicht gebraucht und sollen nur andeuten, dass man ohne weiteres beliebige Parameter übergeben kann.

Warum funktioniert das?

Um den SmartBackgroundWorker zu verwenden, muss man nicht in allen Einzelheiten verstehen, warum bzw. wie er funktioniert. Für Interessierte hier eine knappe Zusammenfassung der internen Struktur.

Der in C# 2 eingeführte yield-Befehl kann in Funktionen verwendet werden, die IEnumerator als Rückgabewert haben. Zu einer solchen Funktion generiert der Compiler eine passende private Klasse, die u.a. von IEnumerator  abgeleitet ist und daher auch die Funktion MoveNext implementieren muss. Der ursprüngliche Code mit den yield return-Anweisungen wird nun vom Compiler in eine State Machine konvertiert, die beim Aufruf von MoveNext jeweils in den nächsten Zustand übergeht. Als Effekt davon führt ein Durchlaufen des Iterators mit MoveNext dazu, dass jeweils genau die Statements bis zum nächsten (ursprünglichen) yield return ausgeführt werden (bzw. der Durchlauf beendet wird). Der generierte Code von MoveNext kann dabei sehr umfangreich und verworren werden, wie ein Blick darauf mit dem .NET Reflector zeigt. Diese Komplexität stört aber nicht weiter, da der Code ja vom Compiler unsichtbar und zuverlässig generiert wird.

Durch den Einsatz von yield return haben wir also erreicht, dass der Code unserer Worker-Funktion abschnittsweise ausgeführt wird. Wir müssen nun nur noch dafür sorgen, dass zwischen den einzelnen Aufrufen ggf. der ausführende Thread gewechselt wird. Dies wird innerhalb von SmartBackgroundWorker in der Funktion OnRun durchgeführt.

/// <summary>
/// Steps through the worker function.
/// </summary>
void OnRun(object argument)
{
  try
  {
    SwitchTo context = SwitchTo.BackgroundThread;
    var enumerator = (IEnumerator<SwitchTo>)argument;
    bool moveNext = true;
    SendOrPostCallback nextStep = obj =>
                                    {
                                      moveNext = enumerator.MoveNext();
                                      if (moveNext)
                                        context = enumerator.Current;
                                    };
    while (moveNext)
    {
      if (context == SwitchTo.UIThread)
      {
        // Run next step synchronously on UI thread
        _uiSynchronizationContext.Send(nextStep, null);
      }
      else
      {
        // Run next step on background thread
        nextStep(null);
      }
    }
  }
  finally
  {
    _isRunning = false;
  }
}

Ein SmartBackgroundWorker merkt sich im Constructor den SynchronizationContext des UI-Threads und muss daher immer im UI-Thread angelegt werden. Beim Start der Worker-Funktion über RunWorkerAsync wird diese über ein Delegate an einen Thread aus dem ThreadPool gebunden. Bis hierher ist die Implementierung identisch mit dem .NET Typ BackgroundWorker. Der Thread aus dem Pool startet dann in obigem OnRun, die den Unterschied zu BackgroundWorker ausmacht.

In der Variablen nextStep wird der Code zum Durchlaufen des nächsten Iterator-Schritts gespeichert. Die Variable context speichert den Rückgabewert von yield return, also entweder UIThread oder BackgroundThread.

In der while-Schleife wird über den Wert von context unterschieden, in welchem Thread der nächste Codeabschnitt aufgerufen werden soll. Ist es der Background-Thread, wird nextStep direkt aufgerufen, denn OnRun läuft ja bereits im Worker-Thread. Ist es der UI-Thread, wird nextStep mit der Funktion Send von SynchronizationContext an den UI-Thread „gesendet“. Technisch passiert dabei folgendes: Durch Send wird eine Art „spezielles Event“ in die Event-Queue des UI-Threads eingereiht. Ist dieses Event an der Reihe, wird der Code in nextStep durch den UI-Thread ausgeführt und somit auch der nächste Abschnitt in unserer Worker-Funktion. Bis diese Ausführung abgeschlossen ist, blockiert der Background-Thread, d.h. der Funktionsaufruf von Send kommt erst dann zurück, wenn nextStep vom UI-Thread vollständig ausgeführt wurde.

Mit etwas Abstand betrachtet liegt also die Innovation von SmartBackgroundWorker in Folgendem: Anstatt einen normalen BackgroundWorker zu verwenden und selber eine State Machine zu bauen, die zwischen dem UI-Thread und einem Background-Thread jongliert, schreibt man relativ linearen Code zusammen mit yield return und lässt den Compiler die passende State Machine generieren.

Coole Sache. Wie schon gesagt, ich habe es nicht erfunden, sondern meine Implementierung nur aus Code von Ralf Hoffmann abgeleitet, der wiederum von einem Screencast von Jeffrey Richter inspiriert wurde (vermutlich diesem hier).

Bewertung

Der Einsatz von SmartBackgroundWorker ist nicht auf Silverlight beschränkt, sondern funktioniert mit WPF oder WinForms genauso. Ich habe nur deshalb Silverlight verwendet, damit ich die Demo direkt in diesen Artikel im Blog einbauen kann.

Im letzten Jahr haben wir den SmartBackgroundWorker in verschiedenen Silverlight-Anwendungen verwendet und er hat sich als sehr nützlich erwiesen. Hier meine persönliche pro/contra-Liste.

Vorteile

  • Leicht zu verwenden
  • Keine zusätzlichen Eventhandler, Delegates oder Lambdas notwendig
  • Führt zu übersichtlichem und intuitiv nachvollziehbarem Code
  • Worker-Funktionen lassen sich sehr leicht verketten bzw. ineinander verschachteln

Nachteile

  • Exception-Handling in der Worker-Funktion kann unübersichtlich werden, da yield nicht innerhalb von try/catch verwendet werden kann
  • In VB.NET nicht verwendbar, da diese Sprache nicht über ein yield-Statement verfügt (was aber eher eine Einschränkung der Sprache ist)

Insgesamt kann ich den SmartBackgroundWorker sehr empfehlen.

Fazit

Man kann sich natürlich fragen, ob das Ganze wirklich eine gute Idee oder nur ein Hack ist, da ja der yield-Befehl für etwas missbraucht wird, für das er nicht erfunden wurde. Meiner Meinung nach ist es eine sehr gute Idee, denn es ist eine saubere Lösung für ein konkretes Problem. Und dies ist das Einzige, worauf es letztlich ankommt, wenn man den Nutzen einer Idee bewerten will. Und im Vergleich zu anderen Innovationen finde ich die Schreibweise der Worker-Funktion sogar sehr elegant. Die Parallel-Extensions von .NET 4.0 sind beispielsweise auch sehr nützlich und innovativ, aber der Code, den man teilweise schreiben muss, ist doch arg gewöhnungsbedürftig. Aber dies ist ein anderes Thema.

Hier der Quellcode zum Downloaden:

SmartBackgroundWorker.zip (7,77 kB)

Tags:

.net | Silverlight | WPF

DataTemplate Selector für Silverlight

by St. Lange 9. October 2009 21:09

Data Templates sind eine feine Sache. Sie erlauben es, die Präsentation von Daten sehr einfach und präzise zu definieren. Hierzu vorab ein einfaches Beispiel in Form einer Silverlight Listbox, die die 16 deutschen Bundesländer auflistet. Standardmäßig ist das Data Template für die Items einer Listbox einfach ein ContentPresenter. Dieser ruft an den Items die Funktion ToString auf und stellt den resultierenden Text dar. Durch das Zuweisen eines Data Templates wird aus dem simplen String eine viel informativere Darstellung.

Hier kann man das Beispiel ausprobieren

In diesem Beispiel wird beim Drücken von „Apply Template“ ein Data Template geladen und an die Property ItemTemplate der Listbox zugewiesen. Dies hat zur Folge, dass die Listbox zur Darstellung ihrer Items nicht mehr einfach nur den Default Content Presenter verwendet, sondern ein individuell auf die Daten zugeschnittenes Template. Diese Funktionalität ist in WPF und Silverlight nahezu identisch.

WPF bietet nun zusätzlich noch die Möglichkeit, das Template abhängig von Daten Items individuell auszuwählen. Dazu muss man eine eigene Klasse MyTemplateSelector von DataTemplateSelector ableiten und darin die Funktion SelectTemplate überschreiben. Eine Listbox hat wie jedes Items Control in WPF die Property ItemTemplateSelector. Dieser Property muss man MyTemplateSelector zuweisen. Die Listbox ruft nun für jedes Daten Item was sie in Ihrer Items Source findet die Funktion SelectTemplate in MyTemplateSelector auf. So hat man die Möglichkeit, im Code abhängig von den Daten jedes einzelnen Items unterschiedliche Templates auszuwählen.

Lösung in Silverlight

Silverlight 3 kennt diese WPF Funktionalität nicht, aber man kann sich eine vergleichbare Funktionalität einfach selber bauen. Da Silverlight für alle Daten Items immer das gleiche Template verwendet, muss man einen Trick anwenden: Man baut sich ein spezielles Data Template, welches genau nur ein Content Control beinhaltet. In diesem Content Control redefiniert man die Funktion OnContentChanged und wechselt hier einfach das Data Template des Content Controls abhängig vom aktuellen Content. Diese zusätzliche Indirektion liefert im Ergebnis das gleiche wie in WPF.

Ein Beispiel soll zeigen, wie es im Einzelnen funktioniert. Um es einfach zu halten, bauen wir das obige Beispiel mit den Bundesländern so um, dass alle Bundesländer, die Stadtstaaten sind, mit einem anderen Template dargestellt werden sollen.

Zunächst die Klasse DataTemplateSelector für Silverlight:

/// <summary>
/// Base class for Silverlight DataTemplateSelector.
/// </summary>
public class DataTemplateSelector : ContentControl
{
  /// <summary>
  /// Called when the value of the <see cref="System.Windows.Controls.ContentControl.Content"/> property changes.
  /// </summary>
  protected override void OnContentChanged(object oldContent, object newContent)
  {
    base.OnContentChanged(oldContent, newContent);

    var template = SelectTemplate(newContent, null);
    if (template != null)
      ContentTemplate = template;
  }

  /// <summary>
  /// When overridden in a derived class, returns a DataTemplate based on custom logic.
  /// </summary>
  /// <param name="item">The data object for which to select the template. </param>
  /// <param name="notUsedInSilverlight">The not used in silverlight. Exists for WPF compatibility only.</param>
  /// <returns>Returns a DataTemplate or a null reference. The default value is a null reference.</returns>
  public virtual DataTemplate SelectTemplate(object item, DependencyObject notUsedInSilverlight)
  {
    return null;
  }
}

Wie man sieht, ist diese Klasse von ContentControl abgeleitet. Ändert sich der Content, wird über die Funktion OnContentChanged die Funktion SelectTemplate aufgerufen. Diese Funktion hat die gleiche Signatur wie ihr WPF Gegenstück.

Um unser Beispiel umzusetzen wird eine neue Klasse MyTemplateSelector von DataTemplateSelector abgeleitet:

public class MyTemplateSelector : DataTemplateSelector
{
  public override DataTemplate SelectTemplate(object item, DependencyObject container)
  {
    var stateInfo = item as States.StateInfo;
    if (stateInfo != null)
    {
      if (stateInfo.Capital != "–")  // the state is not one of the three German city-states
      {
        // This variation demonstrates how to read the template from a resource file
        var info = Application.GetResourceStream(new Uri("/DataTemplateSelectorSample;component/Assets/RegularStateTemplate.xaml", UriKind.Relative));
        using (var reader = new StreamReader(info.Stream))
        {
          return XamlReader.Load(reader.ReadToEnd()) as DataTemplate;
        }
      }

      // This variation demonstrates how to read the template from the Application resources
      return Application.Current.Resources["CityStateTemplate"] as DataTemplate;
    }
    return null;
  }
}

Anhand der Property Capital wird unterschieden, ob es sich bei einem Daten Item um ein Stadtstaat oder ein normales Bundesland handelt. Je nachdem was es ist, werden unterschiedliche Templates geladen.

Zuletzt muss noch die Listvox mit der Klasse MyTemplateSelector verknüpft werden:

<ListBox x:Name="lbxStatesOfGermany" Margin="10 10 10 10">
  <ListBox.ItemTemplate>
    <DataTemplate>
      <tmpl:MyTemplateSelector Content="{Binding}"/>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>

Wie bereits gesagt, wird MyTemplateSelector zum Item Template der Listbox. Die Listbox erzeugt nun für jedes ihrer Daten Items eine Instanz von MyTemplateSelector und bindet das jeweilige Item daran. Abhängig vom Item setzt nun jede Instanz von MyTemplateSelector wiederum ihr eigenes Content Template.

Hier das Ergebnis zum ausprobieren

Fazit

Der Silverlight Data Template Selector lässt sich genau so einfach verwenden wie in WPF. Er funktioniert natürlich nicht nur mit Listboxen, sondern mit alle Items Controls. Dieses Beispiel zeigt wieder einmal, wie einfach und elegant man Silverlight erweitern kann.

Hier der gesamte Quellcode der beiden Beispiele zum Downloaden:

SilverlightDataTemplateSelector.zip (202,00 kb)

Tags: ,

Silverlight | WPF

BASTA! 2009 – Unterlagen zu „WPF-Anwendungsarchitektur“

by St. Lange 24. September 2009 23:30

Heute durfte ich meinen Vortrag „WPF-Anwendungsarchitektur“ auf der BASTA! halten. Dabei ging es um die Schichtentrennung in WPF (View, ViewModel, ApplicationModel, DataModel), sowie um die Composite Application Guidance (Prism 2.0) von der Microsoft Patterns & Practices Group.

Das Interesse an Anwendungsarchitektur war viel höher, als ich erwartet hatte. Nach Ende des Vortrags habe ich noch 1½ Stunden mit einer Gruppe von Zuhören zusammengesessen und angeregt diskutiert.

Tags: ,

WPF | Architecture

Powered by BlogEngine.NET 1.6.1.0 - Impressum