MarkupExtensions in MVVM
Ein gutes und bereits prominentes Beispiel für den Einsatz von MarkupExtensions im MVVM-Umfeld ist die Umsetzung einer Lokalisierung von Oberflächenelementen (ein Beispiel gibt es hier). Hier soll skizziert werden, welchen Weg die Lösung einschlägt, da daran einige interessante Eigenschaften der MarkupExtensions sichtbar werden. Vorweg soll bereits gezeigt werden, wie die Übersetzungserweiterung eingesetzt werden soll: In einem beliebigen Steuerelement sollen beliebige Eigenschaften übersetzbar sein, indem folgende Schreibweise angewendet wird:
Listing 5: Die Basisklasse LanugageDictionary und eine einfache Implementierung
public abstract class LanguageDictionary { public static LanguageDictionary Current { get; set; } public Object this[String Key] { get { return GetTranslatedValue(Key); } } protected abstract Object GetTranslatedValue(string Key); } public class TestLanguageDictonary : LanguageDictionary { private Dictionarym_TranslatedValues = new Dictionary (); public TestLanguageDictonary(CultureInfo Culture) { m_TranslatedValues.Add("Lab.MVVM.Client.Content1View;Label1;Text", "Test " + Culture.Name); } protected override Object GetTranslatedValue(string Key) { return m_TranslatedValues[Key]; } }
Die Klasse LanguageDictionary selbst ist abstrakt und verfügt lediglich über eine statische Property, die das aktuell zu verwendende Wörterbuch beinhaltet. Über einen Indexer kann unter Angabe eines Ressourcenschlüssels die entsprechend lokalisierte Ressource abgerufen werden. Dieser Indexer bedient sich der abstrakten Methode GetTranslatedValue, die den eigentlichen übersetzten Wert beschaffen muss. Dadurch trifft die Klasse LanguageDictionary keine Annahmen über die Beschaffung und Ablage der Daten. Die Klasse TestLanguageDictionary stellt eine einfache Testimplementierung des Wörterbuchs dar: Die übersetzten Werte werden in einem Dictionary gespeichert, das fest mit einem einzigen Wert gefüllt wird. Bei der Instanziierung wird die gewünschte Kultur übergeben, die einfach dem übersetzen Wert angehängt wird. Das hier gewählte Format des Schlüssels ist
Listing 6: Die Klasse TranslateExtension
public class TranslateExtension : MarkupExtension { public override object ProvideValue(IServiceProvider serviceProvider) { IProvideValueTarget targetProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget; IRootObjectProvider rootObject = serviceProvider.GetService(typeof(IRootObjectProvider)) as IRootObjectProvider; FrameworkElement root = rootObject.RootObject as FrameworkElement; FrameworkElement instance = targetProvider.TargetObject as FrameworkElement; PropertyInfo prop = targetProvider.TargetProperty as PropertyInfo; DependencyProperty dependecyProp = DependencyPropertyResolver.Current.Resolve(instance, prop.Name); String Key = String.Format("{0};{1};{2}", root.GetType().FullName, instance.Name, prop.Name); Binding translateBinding = new Binding(String.Format("[{0}]", Key)) { Source = LanguageDictionary.Current, Mode=BindingMode.TwoWay }; return translateBinding.ProvideValue(serviceProvider); } }
Die ProvideValue–Methode bekommt ein Argument vom Typ IServiceProvider übergeben. Dieses Interface definiert die Methode GetService, über die man abfragen kann, ob der Dienstanbieter ein bestimmtes Interface implementiert; wenn ja, erhält man eine entsprechende Instanz zurück. Aktuell implementiert der übergebene serviceProvider die beiden Interfaces IProvidedValueTarget und IRootObjectProvider. Die Eigenschaften der Interfaces sind in Tabelle 1 dargestellt. Diese erhält man durch die folgenden Befehle:
IProvideValueTarget targetProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget; IRootObjectProvider rootObject = serviceProvider.GetService(typeof(IRootObjectProvider)) as IRootObjectProvider;
Interface |
Eigenschaft |
Beschreibung |
IProvideValueTarget |
TargetObject |
Ein Object, das die Instanz des Steuerelements beinhaltet, auf das die Erweiterung angewendet wird |
IProvideValueTarget |
TargetProperty |
Ein PropertyInfo für die Eigenschaft, auf die die Erweiterung angewendet wird (in WPF wird hier ein Objekt vom Typ DependencyProperty geliefert) |
IRootObjectProvider |
RootObject |
Ein Object, das das Wurzelelement des TargetObject beinhaltet, in der Regel das UserControl, in dem sich das Steuerelement befindet |
Tabelle 1: Methoden der Interfaces IProvideValueTarget und IRootObjectProvider