WPF Evaluation: Filter und View

Eine Frage ist natrürlich, was (welcher Ausschnitt der gesamten Programmzeitschrift) dem Anwender wie (etwa die Sortierung) angezeigt werden soll. Für das Beispiel habe mich mich entschieden, dass die Anzeige sortiert nach dem Startzeitpunkt und gruppiert erst nach Datum und dann nach der vollen Stunde erfolgen soll. Für die Evaluation sind diese Entscheidungen auch festgelegt, produktiv muss man da natürlich freier sein. Tatsächlich ist die vorhandene Implementierung bei Anzeige der gesamten Programmzeitschrift (mit einigen Tausend Einträgen) auch zu langsam, aber das macht für das Beispiel erst einmal nichts – zudem wie gleich beschrieben oft eine Filterung aktiv sein wird.

Ich habe mich entschieden, die Art der Anzeige in einer eigene Klasse zu implementieren. Diese wird dann an die Liste via XAML angehängt (ItemsSource=“{Binding Source={StaticResource GuideView}}“) – die Anzeigeklasse ist wieder einmal eine globale Ressource. Interessant ist im Beispiel das Zusammenspiel mit der Filterbedingung.

Zur Auswahl von Teilen der Programmzeitschrift gibt es eine Filterklasse, in der die üblichen Parameter gesetzt werden können. Ich habe diese auf der untersten WPF Ebene implementiert, die sowohl dynamische Eigenschaften (DependencyProperty mit Bindungen) als auch Ereignisse (RoutedEvent) erlaubt: dem FrameworkElement. Eine Instanz der Klasse wird als globale Ressource angeboten. Dazu noch ein paar weitere Details, der Filter selbst ist trivial und nicht wirklich der Rede wert.

Die eindeutige Filterinstanz wird nun auf zweierlei Art verwendet. Zum Einen binden sich verschiedene Oberflächenelemente direkt an die relvanten Filtereigenschaften – etwa das Eingabefeld mit dem Freitextsuchfeld. Interessant ist dabei, dass bei den Bindungen (Text=“{Binding Source={StaticResource GuideFilter}, Path=TextFilter, Mode=OneWayToSource}“) alternierend OneWayToSource und TwoWay (SelectedItem=“{Binding Source={StaticResource GuideFilter}, Path=Time, Mode=TwoWay}“) verwendet wird: im Allgemeinen wäre OneWayToSource die korrekte Wahl, ich möchte aber einige (wenige) Auswahlelement direkt mit den Voreinstellungen belegen, die nur der Filter selbst kennen muss.

Zur Reaktion auf Änderungen der Filterbedingun bindet sich der CollectionSourceView an die Ereignisse, die bei Veränderungen von der Filterinstanz aufgerufen werden – die Verbindung erfolgt aus Basis der globalen Ressourcen (<app:ProgramGuideView x:Key=“GuideView“ GuideFilter=“{StaticResource GuideFilter}“ Source=“{Binding Source={StaticResource TheGuide}, Path=Events, IsAsync=true, Mode=OneTime}“ />). Die Sicht auf die Programmzeitschrift aktualisiert sich dann selbst, gefolgt von der entsprechenden Aktualisierung in der Benutzeroberfläche. Tatsächlich habe ich es nicht so richtig hinbekommen, der Sicht zu sagen, sie solle sich einfach erneuern. Daher im Code der kleine Trick mit dem NullFilterEvent und dem m_Changing Flag, die dafür sorgen, dass die Filter Eigenschaft des Views verändert wird, ohne dass dabei eine mehrfache Auswertung stattfindt – was stark auf die Laufzeit geht.

Soweit dazu

Jochen

full.jpg

WPF Evaluation: Spaß mit der StaticMarkupExtension

Sehr interessant fand ich auch die Möglichkeiten des Einsatzes der x:Static Markup Erweiterung, hier am Beispiel der Konfiguration. Die Konfiguration der Anwendung ist .NET konform in den Settings gespeichert und es wird ein Dialog angeboten, der jeden einzelnen Eintrag zum Ändern anbietet – etwa die URL zum SOAP Web Service des VCR.NET Recording Service. Im Beispiel wird der DataContext des Dialogs direkt an diese Einstellungen gebunden (DataContext=“{x:Static app:Properties.Settings.Default}“). Die einzelnen Formularelemente binden sich dann relativ an die Einzeleintragungen (Text=“{Binding Path=ServerUri, Mode=TwoWay, UpdateSourceTrigger=Explicit}“), wobei ich aber bewußt die explizite Aktualisierung verwende, da bei einem Abbruch keine Veränderungen vorgenommen werden sollen. Das geht in der einfachen Implementierung nur teilweise, dazu gleich mehr.

config.jpg

Ergänzend sieht man im Programmcode des Dialogs, wie diese Aktualisierung auch auf einen Rutsch dynamisch vorgenommen wird, ohne dass jedes Formularelement explizit angesprochen werden muss. Konkret bedeutet das, dass ein neuer Konfigurationseintrag letztlich nur in der XAML mit einem Formularelement eingetragen werden muss um gepflegt zu werden: es ist kein zusätzlicher Programmcode notwendig.

So wie es implementiert ist, funktioniert es allerdings nicht wirklich: an einigen Konfigurationseinträgen hängen Prüfungen (ValidationRule) und nur wenn alle Prüfungen Erfolg melden, kann das Formular gespeichert werden. Bei einer Gesamtprüfung werden aber alle erfolgreichen Eingaben bereits in die Konfiguration übertragen und sind damit zumindest für die weitere Nutzung der Anwendung verändert – auf Platte gespeichert werden kann allerdings nur, wenn alles korrekt eingegeben wurde. Ich habe dem zumindest optisch Rechnung getragen, indem die Abbrechen Schaltfläche deaktiviert wird. Ist aber nur halbherzig, da man den Dialog immer noch über das Kreuzchen rechts oben schliessen kann. Wieder für eine produktive Lösung undenkbar, aber das ist hier erst einmal irrelevant.

Eine zweite Anwendung von x:Static findet man in der Anwendung selbst. Hier wird eine Auflistung (TextFilterTypes[] FilterTypes) aller Alternativen einer .NET Enum angeboten, die in einer Auswahlliste angezeigt werden soll (ItemsSource=“{x:Static app:App.FilterTypes}“). Eine Erweiterung um eine Alternative erfordert erst einmal nur die Veränderung der Enum Definition und die Oberfläche folgt dieser sofort. Dass die Beispielanwendung diese Altenativen über WPF Ressourcen auf Texte abbildet ist für das Prinzp ohne Bedeutung – auch wenn das natürlich heißt, dass man doch zumindest einen weiteren Eintrag in den Ressourcen ergänzen muss.

Viel Spaß

Jochen

Hineintapsen in WPF…

Schon seit lange schiebe ich es vor mir her, mich einmal etwas mit dem .NET Windows Presentation Framework (WPF) auseinander zu setzen. Um zumindestens einmal ein Gefühl dafür zu bekommen, wie dieses tickt, habe ich mir als Beispiel einfach einmal die Programmzeitschrift des VCR.NET Recording Service herausgegriffen und ein wenig gebastelt. Das Ergebis ist weder eine fertige Anwendung noch ein Muster für WPF Programmierung – beides ganz sicher nicht! Ich habe allerdings versucht, die wichtigsten Aspekte des WPF wenigstens einmal anzufassen – wobei alles rund um ein echtes Design nicht betrachtet wurde. Im Zentrum geht es dabei um die dynamische Darstellung von Daten mit dem Versuch, echte (C# et al) Programmlogik so wenig wie möglich zu nutzen und so viel wie möglich auf die Dynamik von WPF und XAML zurückzugreifen.

Wie dem auch sein: ich habe mal eine Version hochgeladen – dito der aktuelle Quelltext. In den nächsten Tagen wird es hier im BLog einige Kommentare dazu geben, wobei ich auch für mich nochmal durch das Gelernte gehen werden (kann also sein, dass sich Quellen und Anwendung dabei noch verändern). Ich werde versuchen, mich jeweils auf einzelne Themen zu beschränken und dabei auch erläutern, was wie und warum geht und was nicht (ja, irgendwie habe ich nicht wirklich alles ans Laufen bekommen). Vielleicht hilft das dem einen oder anderen WPF Einsteiger, auf die richtigen Ideen zu kommen – immer vor dem Hintergrund, dass ich selbst natürlich noch blutiger Laie bin.

Viel Spaß

Jochen

pic.jpg

Vista UAC vs. Full Trust

Da ich nun Vista habe, habe ich auch die UAC aktiviert. Mit einem sehr eigenartigen Effekt betreffs der Nutzung von DVB.NET 3.9: ich wollte mal eben so ein kleines Testprogramm machen und starte Visual Studio 2008 als Normalanwender (nicht als Administrator). C# Projekt angelegt und JMS.DVB.Common aus dem GAC als Referenz hinzugefügt. Keine Fehlermeldung, aber die Klassen der Bibliothek stehen mir nicht zur Verfügung. Nachdem ich das Projekt mit einem Strong Name versehen habe, geht es. Was ist passiert? Ich vermute, dass hier AllowPartiallyTrustedCallers(Attribute) zuschlägt, das DVB.NET bewusst nicht verwendet: mein C# Projekt ist ohne Strong Name nicht berechtigt, die Bibliothek zu verwenden. Ich weiss nicht, ob es gegangen wäre, wenn ich VS als Administrator gestartet habe, aber in jedem Fall ein erst mal (zumindest für mich) überraschender Effekt – ohne irgendwelche Fehlermeldungen im VS.

Happy Coding

Jochen

Spaß mit GetHashCode() oder wie man ein Dictionary verwirrt

Möchte man sehr eigenartige und kaum nachvollziehbare Seiteneffekte vermeiden, so sollte man tunlichst darauf achten, dass die Schlüsselklasse eines Dictionary unveränderlich ist, wie etwa Dictionary<string, …> oder Dictionary<int, …>. Aber eigentlich tut es doch jede Klasse, die vernünftig GetHashCode() und Equals() unterstützt, oder?

Nehmen wir einmal eine solche Klasse:

class ChangableNumber
{
public int Number { get; set; }
public override int GetHashCode()
{
return Number.GetHashCode();
}
public override bool Equals( object obj )
{
ChangableNumber other = obj as ChangableNumber;
if (null == other) return false;
return (Number == other.Number);
}
}

Und bauen uns ein Dictionary mit einem Schlüssel, den wir verändern. Was erzeugt wohl das folgende Programm als Ausgabe:

ChangableNumber c1 = new ChangableNumber { Number = 1 };
Dictionary dict = new Dictionary();
dict[c1] = true;
Console.WriteLine( dict.ContainsKey( c1 ) );
c1.Number = 4;
Console.WriteLine( dict.ContainsKey( c1 ) );
c1.Number = 1;
Console.WriteLine( dict.ContainsKey( c1 ) );

Nun, erst mal wie erwartet: true (1 ist drin), false (4 natürlich nicht) und true (1 ist wieder drin). Was aber, wenn GetHashCode() zufälligerweise für den veränderten Wert den selben Schlüssel liefert (respektive dieser versehen mit dem Modulo des Dictonary den selben Wert hat)? Das ist einfach zu testen: der Inhalt von GetHashCode() wird durch return 1; ersetzt. Nun ist das Ergebnis true, true und wieder true. Wie kommt die 4 denn nun in das Dictionary?

Der Trick ist einfach: ein Dictionary nimmt den Wert von GetHashCode() und ermittelt daraus zum Beispiel durch ein Modulo (mit einer Primzahl, muss aber nicht) einen Indexwert. Für jeden Indexwert wird eine Liste von Schlüssel / Wert [interessiert hier nicht] Paaren verwaltet, i.e. eine Liste enthält alle Schlüssel, die aus Sicht des Dictionary den gleichen Hash haben.

Im ersten Beispiel waren diese (zufällig) unterschiedlich, so dass das Dictionary beim Anlegen eine Liste (sagen wir mal 1) mit einem Schlüssel / Wert Paar anlegte. Das Ändern in 4 führt dazu, dass bei der Prüfung mittels ContainsKey eine nicht vorhandene Liste (4 oder was auch immer) gesucht wird. Diese ist leer, also liefert ContainsKey false.

Im zweiten Beispiel wird nach der Änderung aber die (immer einzige) Liste (1) untersucht. Diese ist nun nicht leer, so dass alle Schlüssel mittels Equals verglichen werden. Natürlich passt unser Schlüssel nun immer, da er mit sich selbst verglichen wird (i.e. in diesem Beispiel kann man jeden beliebigen Wert zuweisen, das zweite ContainsKey ist immer true). Für das ursprüngliche Beispiel wäre übrigens -2147483647 (0x80000001) der zugehörige Killerwert – vermutlich der einzige.

Und das Fazit? Veränderliche Schlüssel für ein Dictionary sollten nur mit vorherigem Nachdenken verwendet werden.

Viel Glück dabei

Jochen