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

Nat*_*han 4 c# xaml xamarin.forms

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

<Label Text={i18n:Translate Label_Text}/>
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

Grx*_*x70 13

遗憾的是,您无法强制在XAML中使用标记扩展设置的控件使用这些扩展重新评估其属性 - 评估仅在解析XAML文件时执行一次.幕后基本上发生的是:

  1. 您的扩展程序已实例化
  2. ProvideValue 在创建的实例上调用method,并在目标控件上使用返回的值
  3. 未存储对已创建实例的引用(或者是弱引用,我不确定),因此您的扩展已准备好用于GC

您可以通过定义终结器(析构函数)并在其中设置断点来确认您的扩展只使用一次.加载页面后很快就会出现(至少在我的情况下 - 你可能需要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));
    }
}
Run Code Online (Sandbox Code Playgroud)

这里的目标是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);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在你需要做的就是Translator.Instance.Invalidate()每次语言改变时都要打电话.

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

  • 好答案!但如果你想将同样的事情应用到 .NET MAUI,你需要实现 `IMarkupExtension&lt;BindingBase&gt;` 而不是扩展 `MarkupExtension` (3认同)

Ste*_*veC 6

我试图实现@ 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;
    }        
}
Run Code Online (Sandbox Code Playgroud)

这是最初提出的Translator类,但为了清晰起见,在此处通过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));
    }
}
Run Code Online (Sandbox Code Playgroud)

然后按照原始帖子的建议,而不是使用以下方法绑定文本:

{i18n:Translate Label_Text}
Run Code Online (Sandbox Code Playgroud)

绑定

{Binding [Label_Text], Source={x:Static i18n:Translator.Instance}}
Run Code Online (Sandbox Code Playgroud)

我在项目结束时就添加了多种语言,但是在Visual Studio Community和Search / Replace中使用RegEx,可以在整个项目中替换绑定,替换为:

\{resources:Translate (.*?)\}
Run Code Online (Sandbox Code Playgroud)

与:

{Binding [$1], Source={x:Static core:Translator.Instance}}
Run Code Online (Sandbox Code Playgroud)

注意:Regex假定原始Translate宏的“资源”命名空间和Translator类的“核心”命名空间,您可能必须适当地进行更新。我很欣赏这对@ Grx70否则很好的解决方案的细微调整(我站在这个巨人的肩膀上),但是我将在此发布此消息,以解决遇到此问题的所有问题。

  • @TarekSalah可以使用TranslateExtension来简化XAML。您可以只编写{{i18n:Translate Label_Text}},这将使用TranslateExtension,后者又将通过Translator类设置绑定。 (2认同)