ローカライズの例での拡張バインディングとxamlマークアップ

xaml指向アプリケーションの開発における重要なポイントの1つは、バインディングの使用です。 バインディング仲介者 (仲介者)であり、関連するオブジェクト間でどのプロパティ値が同期されるかを助けます。



明らかですが、重要なニュアンスに注意する価値があります: バインディングは何らかの方法でオブジェクトの相互作用を指しますが、ガベージコレクションを阻止しません!



Bindingクラスからの継承は許可されていますが、セキュリティ上の理由から、作業のメインロジックに関連付けられているProvideValueメソッドのオーバーライドは許可されていません。 これは、どういうわけか、開発者がコンバーターのパターンを使用するように促します。これは、バインディングのテーマと密接に関連しています。



バインディングは非常に強力なツールですが、場合によっては、その宣言が冗長であり、たとえばローカライズなどの通常の使用では不便であることがわかります。 この記事では、コードをよりクリーンで美しいものにする、シンプルでエレガントな方法を分析します。





xamlでバインディングを宣言すること 、2つの方法で有効です。



<TextBlock> <TextBlock.Text> <Binding ...> </TextBlock.Text> </TextBlock>
      
      





 <TextBlock Text="{Binding ...}"/>
      
      





明らかに、最初の方法はあまり簡潔ではありませんが、2番目の方法はマークアップ拡張機能の使用に基づいて最も頻繁に使用されます。 WPFプラットフォームでは、 カスタムマークアップ拡張機能を作成できます。 たとえば、ローカライズに便利です。



 <TextBlock Text="{Localizing AppTitle}"/>
      
      





最も単純なケースでは、 MarkupExtensionクラスを継承し、キーによって目的の値を取得するProvideValueメソッドを実装する必要があります。



しかし、そのような実装は、プログラム実行中の言語のホットスワップをサポートしていません。 この改善を行うには、まず、ローカライズされたインターフェイス要素へのリンクを保存する必要があります。次に、あまり明らかではないが、何らかの方法でアプリケーションのLocalizingクラスのインスタンスへのリンクを作成し、ガベージコレクションから保護する必要があります。言語変更イベントからサブスクリプションとサブスクリプションを正しく実装する必要があります。



これらのことを誤って行うことで、ビューが作成され、アプリケーション中に動的に消えた場合にメモリリークが発生することが保証されます。多くの場合、これは事実です。 つまり、一見複雑ではないように思われる機能を追加すると、 イベント 弱いリンク弱いサブスクリプションの重要なトピックに対処する必要があります 。 また、コードはそれほど単純ではありません。



さらに、 Windows PhoneWindows Store 、およびXamarin.Formsのxaml プラットフォームでは、カスタムマークアップ拡張機能を作成する方法がありません。 これにより、バインディングをマークアップ拡張機能として使用するという考え方が浮かび上がります ...



茂みに打ち勝つのはやめましょう、必要なものは次のとおりです。



  public abstract class BindingExtension : Binding, IValueConverter { protected BindingExtension() { Source = Converter = this; } protected BindingExtension(object source) // set Source to null for using DataContext { Source = source; Converter = this; } protected BindingExtension(RelativeSource relativeSource) { RelativeSource = relativeSource; Converter = this; } public abstract object Convert(object value, Type targetType, object parameter, CultureInfo culture); public virtual object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
      
      





バインディングがそれ自体のコンバーターであることは注目に値します。 その結果、 MarkupExtensionクラスから継承する場合と非常によく似た動作が得られますが、さらに、 ガベージコレクションを制御するための標準メカニズムを使用することも可能です。



現在、ローカライズのロジックはどこにも簡単ではありません。



  public partial class Localizing : Base.BindingExtension { public static readonly Manager ActiveManager = new Manager(); public Localizing() { Source = ActiveManager; Path = new PropertyPath("Source"); } public Localizing(string key) { Key = key; Source = ActiveManager; Path = new PropertyPath("Source"); } public string Key { get; set; } public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var key = Key; var resourceManager = value as ResourceManager; var localizedValue = resourceManager == null || string.IsNullOrEmpty(key) ? ":" + key + ":" : (resourceManager.GetString(key) ?? ":" + key + ":"); return localizedValue; } }
      
      





  public partial class Localizing { public class Manager : INotifyPropertyChanged { private ResourceManager _source; public ResourceManager Source { get { return _source; } set { _source = value; PropertyChanged(this, new PropertyChangedEventArgs("Source")); } } public string Get(string key, string stringFormat = null) { if (_source == null || string.IsNullOrWhiteSpace(key)) return key; var localizedValue = _source.GetString(key) ?? ":" + key + ":"; return string.IsNullOrEmpty(stringFormat) ? localizedValue : string.Format(stringFormat, localizedValue); } public event PropertyChangedEventHandler PropertyChanged = (sender, args) => { }; } }
      
      





文字の大文字と小文字を変更する機能を簡単に追加できます。



  public partial class Localizing : Base.BindingExtension { public enum Cases { Default, Lower, Upper } public static readonly Manager ActiveManager = new Manager(); public Localizing() { Source = ActiveManager; Path = new PropertyPath("Source"); } public Localizing(string key) { Key = key; Source = ActiveManager; Path = new PropertyPath("Source"); } public string Key { get; set; } public Cases Case { get; set; } public override string ToString() { return Convert(ActiveManager.Source, null, Key, Thread.CurrentThread.CurrentCulture) as string ?? string.Empty; } public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var key = Key; var resourceManager = value as ResourceManager; var localizedValue = resourceManager == null || string.IsNullOrEmpty(key) ? ":" + key + ":" : (resourceManager.GetString(key) ?? ":" + key + ":"); switch (Case) { case Cases.Lower: return localizedValue.ToLower(); case Cases.Upper: return localizedValue.ToUpper(); default: return localizedValue; } } }
      
      





xamlでは、レコードは便利で美しいように見えますが、異なるプラットフォーム上のマークアップパーサーにはいくつかの制限があります。



 <!--WPF--> <TextBlock Text="{Localizing AppTitle, Case=Upper}"/> <TextBlock Text="{Localizing Key=AppDescription}"/> <!--WPF, Windows Phone--> <TextBlock Text="{m:Localizing Key=AppTitle, Case=Upper}"/> <TextBlock Text="{m:Localizing Key=AppDescription}"/> <!--WPF, Windows Phone, Windows Store--> <TextBlock> <TextBlock.Text> <m:Localizing Key=AppDescription> </TextBlock.Text> </TextBlock>
      
      





WPFで必須のm:プレフィックスを取り除くには、マークアップ拡張機能を別のアセンブリに配置し、 Properties / AssemblyInfo.csで次のディレクティブを指定する必要があります。



 [assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "Aero.Markup")] [assembly: XmlnsPrefix("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "m")]
      
      





Windows Phoneまたはストアでプレフィックス名を調整するには:



 [assembly: XmlnsDefinition("clr-namespace:Aero.Markup;assembly=Aero.Phone", "Aero.Markup")] [assembly: XmlnsPrefix("clr-namespace:Aero.Markup;assembly=Aero.Phone", "m")]
      
      





WPF バインディング拡張機能を使用しても、通常のマークアップ拡張機能が除外されるわけではありませんが、場合によってはより安全で簡単なオプションです。 また、これはすべてローカライズだけに限定されず、他の多くの目的に適しています...



実証済みのアプローチは、 少し前に説明したAeroフレームワークライブラリで集中的に使用されます。 また、これらのメカニズムがすべて動作しているのを確認できるサンプルプロジェクトも付属しています。 ご清聴ありがとうございました!



All Articles