Dojos für Entwickler 2. Stefan Lieser
{ private TextBlöcke_erzeugen sut; private IEnumerable<TextBlock> result; [SetUp] public void Setup() { sut = new TextBlöcke_erzeugen(); sut.Result += x => result = x; } [Test] public void Texte _werden_übernommen() { sut.Process(new[] { new TextElement {Text = "x"}, new TextElement {Text = "y"}, new TextElement {Text = "z"}, }); Assert.That(result.Select(x => x.Text), Is.EqualTo(new[] {"x", "y", "z"})); } [Test] public void Fett_Eigenschaft _wird_übernommen() { sut.Process(new[] { new TextElement {Fett = true}, new TextElement {Fett = false}, new TextElement {Fett = true}, }); Assert.That(result.Select( x => x.FontWeight), Is.EqualTo(new[] { FontWeights.Bold, FontWeights.Normal, FontWeights.Bold })); } [...] }
Die Funktionseinheit muss für jedes TextElement-Objekt ein TextBlock-Objekt erzeugen. Da die WPF-Controls nur im sogenannten Single Thread Apartment (STA) laufen, ist die Testklasse mit dem Attribut RequiresSTA versehen. Die Tests überprüfen, ob der Text korrekt übernommen wird. Ferner wird überprüft, ob Fett- und Kursivschrift korrekt in FontWeight und FontStyle übernommen werden. Dabei lasse ich es für diese Funktionseinheit bewenden. Visuelle Controls erfordern nämlich ohnehin immer eine visuelle Prüfung. Am Ende interessiert den Anwender natürlich nicht, ob irgendwelche Texteigenschaften korrekt gesetzt sind, sondern der Text muss fett bzw. kursiv zu sehen sein, wie auch immer das intern bewerkstelligt wird. Folglich habe ich im WPF-Testprojekt eine Instanz des UserControls in der MainForm erzeugt und mit einem Beispieltext gefüllt. So kann ich visuell prüfen, ob die Formatierung korrekt ist. Abbildung 2 zeigt das Ergebnis. Listing 7 zeigt die Implementation der Funktionseinheit.
[Abb. 2]
Visuelle Erscheinung des Controls.
Listing 7
Textblöcke erzeugen.
public class TextBlöcke_erzeugen { public void Process( IEnumerable<TextElement> textElements) { Result(Alle_TextBlöcke _erzeugen(textElements)); } private IEnumerable<TextBlock> Alle_TextBlöcke_erzeugen( IEnumerable<TextElement> textElements) { foreach (var textElement in textElements) { yield return new TextBlock { Text = textElement.Text, FontStyle = textElement.Kursiv ? FontStyles.Italic : FontStyles.Normal, FontWeight = textElement.Fett ? FontWeights.Bold : FontWeights.Normal }; } } public event Action<IEnumerable< TextBlock>> Result; }
Einzige Besonderheit in der Implementation ist die Methode Alle_TextBlöcke_ erzeugen. Die habe ich lediglich angelegt, um das yield return-Konstrukt verwenden zu können. Das Konstrukt steht nur in Methoden zur Verfügung, die einen Rückgabewert vom Typ IEnumerable<T> haben. Das ist bei der Methode Process nicht der Fall. Folglich habe ich die Schleife für die Erzeugung der TextBlock-Objekte in eine private Methode verschoben.
Im UserControl muss nun noch der Flow zusammengebaut werden: eine Instanz der Platine Zerlege_MarkDown_Text muss mit dem Baustein TextBlöcke_erzeugen verbunden werden, siehe Listing 8.
Listing 8
Das fertige Steuerelement.
public partial class MarkDownLabel : UserControl { private string markDownText; private readonly Action<string> parse_Markdown; public MarkDownLabel() { InitializeComponent(); var zerlege_MarkDown_Text = new Zerlege_MarkDown_Text(); var textBlöcke_erzeugen = new TextBlöcke_erzeugen(); zerlege_MarkDown_Text.Result += textBlöcke_erzeugen.Process; textBlöcke_erzeugen.Result += textBlocks => { wrapPanel.Children.Clear(); foreach (var textBlock in textBlocks) { wrapPanel.Children.Add(textBlock); } }; parse_Markdown = new Action<string>( zerlege_MarkDown_Text.Process); } public string MarkDownText { get { return markDownText; } set { markDownText = value; parse_Markdown(markDownText); } } }
Immer wenn der Markdown-Text des UserControls geändert wird, muss der neue Text in den Flow gegeben werden. Am Ende kommen dann TextBlock-Controls heraus, die als Child-Controls in das WrapPanel eingefügt werden. Natürlich muss der bestehende Inhalt des WrapPanel-Controls zuvor entfernt werden. Die beiden Instanzen des Flows werden erzeugt und miteinander verbunden. Der Output von zerlege_ Mark-Down_Text wird zum Input von textBlöcke_ erzeugen, indem die Process-Methode mit dem Result-Event verbunden wird. Der Output von textBlöcke_erzeugen wird mittels Lambda-Expression ins WrapPanel-Control übernommen, nachdem dessen Inhalt mit Children.Clear entfernt wurde. Zuletzt wird eine Action parse_ Markdown als Feld der Klasse angelegt. Diese Action wird im Setter der MarkdownText-Eigenschaft aufgerufen, um den Text zu parsen und in TextBlock-Objekte zu übersetzen.
Fazit
Die vorgestellte Implementation ging leicht von der Hand. Da der Flow nicht groß ist, habe ich die Visualisierungsmöglichkeiten des ebclang-Toolings [3] nicht vermisst. Allerdings war ich bislang der Einzige, der in den Code geschaut hat. Vielleicht mögen Sie mir ja einen Leserbrief schreiben zur Frage, ob die Implementation durch eine ebc.xml-Datei zur Definition des Flows besser geworden wäre? [ml]
[1] http://de.wikipedia.org/wiki/Markdown [2] http://daringfireball.net/projects/markdown/ [3] http://ebclang.codeplex.com
Aufgabe 2
Schlagworte für Digitalfotos
Ordnung im Fotokarton
Früher habe ich meine Fotos in Tüten gesteckt und in Schuhkartons gesammelt. Heute im Digitalzeitalter stehen mit Tags bessere Ordnungssysteme zur Verfügung. Doch was passiert da hinter den Kulissen?
Die Digitalfotografie hat die analoge Fotografie im Prinzip verdrängt. Ich selbst möchte die Möglichkeiten der Digitalfotografie auch nicht mehr missen, unter anderem deshalb, weil ich in digitalen Fotos viel besser Ordnung halten kann. Zwar muss ich auch bei jeder Fotodatei entscheiden, in welchen „Schuhkarton“ ich sie ablege, sprich: wo ich sie speichere. Doch mithilfe sogenannter Tags oder Keywords, zu Deutsch: Schlagworte, kann ich weitere Ordnungskriterien anlegen, die orthogonal zur Verzeichnisstruktur sind.
Orthogonal bedeutet hier, dass die Schlagworte unabhängig vom Speicherort der Datei sind. Ich kann die Schlagworte ändern, ohne die Datei verschieben zu müssen. Und ich kann die Datei verschieben, ohne dass sich dadurch die Schlagworte verändern würden. Beide sind unabhängig, eben orthogonal. Beim Schuhkarton war das nicht so! Ein Bild im Schuhkarton „Dampfloks“ und auch unter „Familienfotos“ abzulegen geht nur mit entsprechenden Kopien der Bilder.
Doch wie funktioniert das Verschlagworten von Fotodateien? Eine JPEG-Datei enthält neben den Bilddaten auch Metadaten, die sogenannten Exif-Daten. In diesen Metadaten ist beispielsweise hinterlegt, wann die Aufnahme getätigt wurde und welche Kamera, Brennweite, Belichtungszeit etc. verwendet wurden. Hier ist auch ein Platz für Schlagworte vorgesehen, siehe Abbildung 1.
[Abb. 1]
In den Tags lassen sich Schlagworte speichern.
Bei Verwendung von Schlagworten stellen sich nun prinzipiell zwei Fragen:
Wie werden mehrere Schlagworte abgelegt?
Wie werden hierarchische Schlagworte abgelegt?
In der Datei ist nur eine Eigenschaft für Schlagworte definiert. Will man dort mehrere Schlagworte reinschreiben, müssen diese durch ein Komma getrennt werden. Will ich ein Foto also mit den Schlagworten „Blumen“ und „Wald“ versehen, müssen die Schlagworte kommagetrennt in den Metadaten abgelegt werden: „Blumen, Wald“.
Hierarchische Schlagworte waren ursprünglich im IPTC-Standard