Читать книгу: «Dojos für Entwickler 2», страница 2

Шрифт:

Platine

Der nächste Schritt bestand darin, die drei Funktionseinheiten zu einem Fluss zusammenzustecken. Diesmal habe ich dazu nicht das Tooling aus dem ebclang-Projekt verwendet [3], sondern die Platine „zu Fuß“ in C# implementiert. Ich wollte auf diese Weise einmal überprüfen, ob mir etwas fehlt, wenn ich nicht per Tooling eine Visualisierung des Flows erhalten kann. Listing 5 zeigt die Platine.

Listing 5
Teile auf der Platine integrieren.

public class Zerlege_MarkDown_Text { private Action<string> process; public Zerlege_MarkDown_Text() { var verpacke_in_Text = new Verpacke_in_TextElement(); var extrahiere_fett = new Extrahiere_Fett(); var extrahiere_kursiv = new Extrahiere_Kursiv(); process += verpacke_in_Text.Process; verpacke_in_Text.Result += extrahiere_fett.Process; extrahiere_fett.Result += extrahiere_kursiv.Process; extrahiere_kursiv.Result += textElements => Result(textElements); } public void Process(string input) { process(input); } public event Action<IEnumerable< TextElement>> Result; }

Auch zur Platine habe ich Tests geschrieben. Ich wollte auf diese Weise sicherstellen, dass auch Kombinationen aus fett und kursiv gesetzten Texten möglich sind. Das war nämlich bei meiner Implementation anfangs nicht der Fall. Herausgefunden habe ich das durch exploratives Testen: Ich habe mir einen kleinen Testrahmen erstellt, in dem das UserControl verwendet wird. Darin habe ich dann mit verschiedenen Texten experimentiert und herausgefunden, dass nicht alle Kombinationen von fett und kursiv korrekt funktionierten. Als ich das erkannt hatte, habe ich die Integrationstests für die Platine ergänzt, um damit das Problem reproduzieren zu können. Erst als ich einen entsprechenden fehlschlagenden Test hatte, habe ich die Implementation korrigiert.

UserControl

Im letzten Schritt müssen die TextElement-Objekte noch in einem Control visualisiert werden. Ich habe dazu ein WPF-UserControl-Projekt erstellt. Das UserControl nimmt TextBlock-Controls in einem WrapPanel auf, die entsprechend den Angaben der jeweiligen TextElement-Objekte formatiert werden. Wenn die Eigenschaft Fett gesetzt ist, muss im TextBlock-Control die Font-Weight-Eigenschaft auf FontWeights.Bold gesetzt werden. Kursive Schrift wird erreicht, indem man die FontStyle-Eigenschaft auf den Wert FontStyles.Italic setzt.

Um nun das WrapPanel-Control mit entsprechenden TextBlock-Controls zu befüllen, will ich natürlich wieder eine Funktionseinheit im Flow ergänzen. Dabei sollen die Abhängigkeiten zu den WPF-Assemblies jedoch nicht auf die bestehenden Projekte durchschlagen. Daher habe ich die Funktionseinheit, die aus TextElement-Objekten WPF-TextBlock-Objekte macht, im UserControl-Projekt untergebracht. Dort ist die Abhängigkeit zu den WPF-Assemblies ohnehin unvermeidbar. Listing 6 zeigt die Tests zur Funktionseinheit TextBlöcke_erzeugen.

Listing 6
Textblöcke testen.

[TestFixture, RequiresSTA] public class TextBlöcke_erzeugen_Tests { 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 nicht vorgesehen. Hierarchische Tags sind zum Beispiel nützlich, wenn man die Fotos mit den Namen der abgebildeten Personen versehen möchte. Da können einige Personen zusammenkommen, sodass es sinnvoll ist, beispielsweise Personen/Familie/Stefan von Personen/Freunde/Stefan zu unterscheiden.

Adobe hat hierarchische Schlagworte erstmalig in den XMP-Daten abgelegt. An diese „Vorlage“ sollten sich nun alle Softwarehersteller halten.

Die Aufgabe für diesen Monat lautet: Durchsuchen Sie ein Verzeichnis, und listen Sie alle gefundenen Schlagworte in einer Hierarchie auf. Die Auflistung kann auf der Konsole erfolgen. Genauso gut können Sie aber auch eine grafische Benutzerschnittstelle verwenden und ein TreeView-Control entsprechend füllen.

Um an Testdaten zu kommen, können Sie die Fotoverwaltung Ihrer Wahl verwenden. Am Anfang steht „Forschungsarbeit“, um herauszufinden, wie genau nun die Schlagworte in den Exif-, IPTC-, XMP- oder sonstigen Metadaten abgelegt sind. Und natürlich müssen Sie den Zugriff auf die JPEG-Dateien nicht selbst programmieren, für diesen Zweck gibt es fertige .NET-Bibliotheken. Selbst im .NET Framework ist ab 3.0 dazu einiges vorhanden.

Wer mag, kann die Übung dann erweitern: Listen Sie die Dateinamen aller Fotos, die mit einem gewählten Schlagwort versehen sind. Viel Spaß beim Durchforsten Ihrer Fotosammlung.

[ml]

Lösung 2
JPEG-Metadaten auslesen
Ausgezeichnete Fotos

Mit den Formaten Exif und IPTC können Sie JPG-Dateien Stichworte zuordnen. Viele Hersteller von Grafikpro­gram­men nutzen dieses Format aber nicht. Dieses und weitere Probleme löst Stefan auf seinem Weg zur digitalen Fotosammlung.

Dieses Mal begann die Übung für mich mit einem Spike. Ich hatte zwar eine grobe Vorstellung davon, wie die Metadaten mithilfe von .NET-Bordmitteln aus den JPEG-Dateien ausgelesen werden können. Aber schon die Frage, welche Assemblies dafür referenziert werden müssen, konnte ich nicht beantworten. Folglich habe ich als Erstes ein Spike-Projekt in meiner Visual-Studio-Solution angelegt. Darin habe ich so lange herumprobiert, bis ich einige Metadaten aus einer JPEG-Datei auf der Konsole ausgeben konnte. Dabei habe ich gelernt, welche Assemblies referenziert werden müssen, es sind:

 PresentationCore,

 WindowsBase,

 System.Xaml.

Nach dem Hinzufügen dieser drei Referenzen, die alle aus dem Bereich WPF stammen, konnte ich mit meiner kleinen Minianwendung starten. Bei meinen Spikes verwende ich üblicherweise NUnit [1], um mithilfe des ReSharper-Unit-Test-Runners [2] eine Methode innerhalb von Visual Studio ausführen zu können. Typischerweise werden für solche Zwecke wohl eher Konsolenanwendungen erstellt. Ich bevorzuge die Variante über Tests, weil ich so in meinem Spike-Projekt viele Methoden unterbringen kann, die ich mit dem Test-Runner bequem einzeln starten kann. Bei einer Konsolenanwendung müsste man sich in solchen Fällen Gedanken machen, wie man unterschiedliche Teile eines Spikes ausführt. Ein kleines Menü wäre denkbar, oder auch das Auskommentieren einzelner Teile. Für mich ist das Starten über den Testrunner die einfachste Variante. Ausgaben auf der Konsole sind natürlich trotzdem möglich.

Metadaten lesen

Mein erster Spike schreibt alle Metadaten, die über Properties der Klasse BitmapMetadata zur Verfügung stehen, auf die Konsole, siehe Listing 1. Gestartet wird diese Methode durch eine kleine Testmethode, die Listing 2 zeigt.

Listing 1
Metadaten ausgeben.

public class Exif { public static void ShowExif(string filename) { BitmapSource img = BitmapFrame.Create( new Uri(filename, UriKind.Relative)); var metadata = ( BitmapMetadata)img.Metadata; Console.WriteLine( metadata.CameraManufacturer); Console.WriteLine(metadata.CameraModel); Console.WriteLine(metadata.Author); Console.WriteLine(metadata.DateTaken); Console.WriteLine(metadata.Format); foreach (var keyword in metadata.Keywords) { Console.WriteLine(keyword); } Console.WriteLine(metadata.Location); Console.WriteLine(metadata.Rating); Console.WriteLine(metadata.Subject); Console.WriteLine(metadata.Title); } }

Listing 2
Den Spike starten.

[TestFixture] public class RunDemos { [Test] public void Spike_1() { Exif.ShowExif(@"..\DSCF1243.JPG"); } }

Die Verwendung der Klasse BitmapMetadata hat den Vorteil, dass sie sehr leicht zu bedienen ist. Das liegt vor allem daran, dass die wichtigsten Metadaten aus der JPEG-Datei als Properties unmittelbar zur Verfügung stehen.

Das gilt allerdings nicht für alle Metadaten. Denn das Modell der Metadaten muss natürlich so flexibel sein, dass es erweitert werden kann, ohne dass in diversen Frameworks zusätzliche typisierte Properties ergänzt werden müssten. Folglich musste man sich für den Zugriff auf die Metadaten einen allgemeineren Ansatz überlegen. Die Metadaten einer Bilddatei werden jeweils über ­einen Pfad identifiziert. Mithilfe des Metadaten-Pfades und der Methode GetQuery können sämtliche Metadaten ausgelesen werden:

var value = metadata.GetQuery( "/ifd/iptc/keywords");

Auf diese Weise lassen sich nun zwar alle Metadaten auslesen, allerdings hat dieser generische Ansatz den Nachteil, dass die gelieferten Werte dann vom Typ object sind. Man muss die Werte daher gegebenenfalls auf den korrekten Typ casten.

Erstaunen und Ärger

Nach diesen Spikes dachte ich, es könne mit der Lösung der Aufgabenstellung losgehen. Doch es zeigte sich, dass nicht alle Programme dasselbe Verfahren zur Ablage von Stichwörtern verwenden. Für meine Spikes hatte ich mit dem Windows Explorer Stichwörter an JPEG-Dateien gesetzt. Abbildung 1 zeigt die Dateieigenschaften.


[Abb. 1]

Stichwörter mit dem Windows Explorer vergeben.

Diese Stichwörter lassen sich mit dem im Spike gezeigten Verfahren über meta­data.Key­words leicht auslesen. Doch schon mein erster Versuch, die Stichwörter eines Fotos auszulesen, die ich mit dem Adobe Photoshop Elements Organizer an die Datei gesetzt hatte, schlug fehl. Eine kurze Kontrolle mit dem Windows Explorer zeigte: Auch hier keine Spur von Stichwörtern zu sehen. Das liegt daran, dass der Elements Organizer die Stichwörter in einer eigenen Datenbank ablegt. Dagegen ist ja an sich nichts einzuwenden. Ich vermute mal, dass die Suche in einem großen Fotobestand ohne eigene Datenbank sehr langsam wäre. Doch was mir nicht einleuchten will, ist die Tatsache, dass der Elements Organizer die Stichwörter nicht parallel auch in der JPEG-Datei ablegt. Zwar kann man das durch einen Export erreichen, doch den muss man explizit starten. Und wenn man anschließend Stichwörter oder deren Zuordnung ändert, muss man diese erneut exportieren.

Eine kurze Recherche erbrachte, dass Adobe nicht der einzige Hersteller ist, der diesen Weg gewählt hat. Ich halte das für keine gute Lösung, denn dadurch wird der Zugriff auf einen verschlagworteten Fotobestand von mehreren Arbeitsplätzen aus oder auch mit unterschiedlichen Programmen unnötig verkompliziert. Besonders ärgerlich finde ich, dass die eigens für Stichwörter vorgesehenen Metadaten in den Bilddateien nicht genutzt werden. Dann hätte man sich die ganze Standardisierung auch gleich sparen können. Ein weiteres Ärgernis: Beim Export der Stichwörter ignoriert Adobe leider die Hierarchie und exportiert die Stichwörter ohne die Struktur. „Motive/Kirchen“ wird zu „Kirchen“, „Personen/Familie/Stefan“ wird zu „Stefan“. Das ist sehr schade.

Erste Iteration

Als mein Ärger über diese unschöne Vor­gehensweise der Softwarehersteller etwas verflogen war, habe ich begonnen, die erste Iteration der Lösung zu entwickeln. In der ersten Iteration möchte ich ein Verzeichnis rekursiv durchsuchen und die Stichwörter der gefundenen Dateien auf der Konsole ausgeben. Natürlich sollen dabei mehrfach vorkommende Stichwörter nur einmal ausgegeben werden.

Den Entwurf für die erste Iteration zeigt Abbildung 2. Ich gehe in drei Schritten vor:

 Die Dateinamen werden ermittelt.

 Zu den gefundenen Dateinamen werden die Stichwörter ermittelt.

 Die gefundenen Stichwörter werden so gefiltert, dass nur eindeutige Stichwörter übrig bleiben.


[Abb. 2]

Stichwörter mit dem Windows Explorer vergeben.

Mal nicht Test-first

Durch die drei im Entwurf gezeigten Bauteile habe ich mich von oben nach unten durchgearbeitet. Dabei habe ich diesmal nicht Tests zuerst geschrieben, sondern zuerst implementiert und dann Tests geschrieben. Das hat hier gut funktioniert, da durch den Entwurf klar war, was die Bauteile machen sollen. Ferner gibt es keine Abhängigkeiten, sodass die Tests flüssig von der Hand gingen. Zu beachten ist allerdings, dass zwei von den drei Bauteilen von der externen Ressource Dateisystem beziehungsweise von JPEG-Dateien abhängig sind. Dies musste ich natürlich bei den automatisierten Tests berücksichtigen.

Listing 3 zeigt die Implementation für das Bauteil Dateinamen_suchen.

Die eigentliche Arbeit übernimmt die statische Methode Directory.Enumerate­Files aus dem .NET Framework. Drumher­um ist lediglich eine Event Based Component (EBC) gelegt. Die Komponente besteht aus der Input-Methode Process und dem Output-Event Result. Da das Bauteil für seine Arbeit zwei Angaben benötigt, den Pfad und das Suchmuster, werden diese zu einem Tuple<string, string> zusammengefasst. Dadurch hat die Process-Methode lediglich einen Parameter. Das ist wichtig, um Standardbausteine und Tooling verwenden zu können. In diesem Fall ist das zwar nicht relevant, aber es könnte sein, dass eine spätere Iteration den Einsatz von Standardbausteinen wie etwa Join sinnvoll erscheinen lässt. Daher ist es eine Tugend, mit nur einem Parameter zu arbeiten.

Um innerhalb der Methode zu erkennen, welche Bedeutung Item1 und Item2 des Tupels haben, weise ich beide jeweils an eine lokale Variable zu. So kann ein sprechender Bezeichner vergeben werden, aus dem sich die Bedeutung erschließt.

1 435,42 ₽
Возрастное ограничение:
0+
Объем:
356 стр. 95 иллюстраций
ISBN:
9783844259261
Издатель:
Правообладатель:
Bookwire
Формат скачивания:
epub, fb2, fb3, ios.epub, mobi, pdf, txt, zip

С этой книгой читают