重新评估由标记扩展计算的xaml页面中的所有值

时间:2016-11-11 19:04:41

标签: c# xaml xamarin.forms

在xaml页面上的xamarin应用程序中,我使用xaml扩展加载本地化字符串(详细信息描述为here)。例如:

<Label Text={i18n:Translate Label_Text}/>

现在,我希望用户能够在运行时更改应用程序的语言(使用选择器)。如果发生这种情况,我想立即更改语言。

我可以以某种方式重新加载所有翻译的文本吗?

我可以删除所有页面并重新创建它们,但我试图避免这种情况。

我还可以将所有本地化文本绑定到页面模型中的字符串。但这对于真正的静态字符串来说是很多不必要的代码。

2 个答案:

答案 0 :(得分:7)

不幸的是,您无法强制使用 XAML 中的标记扩展设置控件来使用这些扩展重新评估其属性 - 评估仅在解析 XAML 文件时执行一次。幕后发生的事情是:

  1. 您的扩展程序已实例化
  2. 在创建的实例上调用
  3. ProvideValue方法,并在目标控件上使用返回的值
  4. 未创建对已创建实例的引用(或者是弱引用,我不确定),因此您的扩展已准备好用于GC
  5. 您可以通过定义终结器(析构函数)并在其中设置断点来确认您的扩展只使用一次。加载页面后很快就会出现(至少在我的情况下 - 你可能需要明确地调用GC.Collect())。所以我认为问题很明显 - 您无法在任意时间再次呼叫ProvideValue您的分机,因为它可能已不复存在。

    但是,您的问题有一个解决方案,甚至不需要对 XAML 文件进行任何更改 - 您只需要修改TranslateExtension类。我们的想法是,它将设置正确的绑定,而不是简单地返回一个值。

    首先我们需要一个类作为所有绑定的源(我们将使用单例设计模式):

    public class Translator : INotifyPropertyChanged
    {
        public string this[string text]
        {
            get
            {
                //return translation of "text" for current language settings
            }
        }
    
        public static Translator Instance { get; } = new Translator();
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        public void Invalidate()
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(Binding.IndexerName));
        }
    }
    

    此处的目标是Translator.Instance["Label_Text"]应返回当前扩展程序为"Label_Text"返回的翻译。然后,扩展应在ProvideValue方法中设置绑定:

    public class TranslateExtension : MarkupExtension
    {
        public TranslateExtension(string text)
        {
            Text = text;
        }
    
        public string Text { get; }
    
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            var binding = new Binding
            {
                Mode = BindingMode.OneWay,
                Path = new PropertyPath($"[{Text}]"),
                Source = Translator.Instance,
            };
            return binding.ProvideValue(serviceProvider);
        }
    }
    

    现在,您需要做的就是每次更改语言时都要调用Translator.Instance.Invalidate()

    请注意,使用{i18n:Translate Label_Text}相当于使用{Binding [Label_Text], Source={x:Static i18n:Translator.Instance}},但更简洁,可以省去修改 XAML 文件的工作。

答案 1 :(得分:3)

我试图实现@ Grx70的伟大提议解决方案,但是该示例使用的一些类和属性是Xamarin内部的,因此不能以这种方式使用。 尽管不是最初的建议,但是我们可以做到这一点:

获取它们的最后一条评论是获得它的工作的线索。
public class TranslateExtension : IMarkupExtension<BindingBase>
{       
    public TranslateExtension(string text)
    {
        Text = text;            
    }

    public string Text { get; set; }

    object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
    {
    return ProvideValue(serviceProvider);
    }

    public BindingBase ProvideValue(IServiceProvider serviceProvider)
    {
        var binding = new Binding
        {
            Mode = BindingMode.OneWay,
            Path = $"[{Text}]",
        Source = Translator.Instance,
        };
    return binding;
    }        
}

这是最初提出的译者类,但为了清楚起见,这里转载了GetString调用:

public class Translator : INotifyPropertyChanged
{
    public string this[string text]
    {
    get
    {
        return Strings.ResourceManager.GetString(text, Strings.Culture);
    }
    }        

    public static Translator Instance { get; } = new Translator();

    public event PropertyChangedEventHandler PropertyChanged;

    public void Invalidate()
    {
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
    }
}

然后如原始帖子所示,而不是用以下内容绑定文本:

{i18n:Translate Label_Text}

绑定

{Binding [Label_Text], Source={x:Static i18n:Translator.Instance}}

我在项目结束时(添加多种语言)点击了这一点,但是使用Visual Studio社区和使用RegEx进行搜索/替换,可以在项目中替换绑定,替换:

\{resources:Translate (.*?)\}

with:

{Binding [$1], Source={x:Static core:Translator.Instance}}

注意:Regex假设原始Translate宏的'resources'命名空间和Translator类的'core'命名空间,您可能必须适当更新。 我很欣赏这是对@ Grx70的另一个很好的解决方案的一个小调整(我站在这个巨人的肩膀上),但是我在这里发布这个,因为同样的问题让这个工作。