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文件时执行一次.幕后基本上发生的是:
ProvideValue 在创建的实例上调用method,并在目标控件上使用返回的值您可以通过定义终结器(析构函数)并在其中设置断点来确认您的扩展只使用一次.加载页面后很快就会出现(至少在我的情况下 - 你可能需要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文件的工作量.
我试图实现@ 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否则很好的解决方案的细微调整(我站在这个巨人的肩膀上),但是我将在此发布此消息,以解决遇到此问题的所有问题。