重新计算xaml页面中由一个markup-extension计算的所有值

8oomwypt  于 2023-08-01  发布在  其他
关注(0)|答案(2)|浏览(78)

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

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

字符串
现在,我希望用户能够在运行时更改应用程序的语言(使用选择器)。如果发生这种情况,我想立即改变语言。
我可以以某种方式重新加载所有翻译文本吗?
我可以删除所有页面并重新创建它们,但我正在努力避免这种情况。
我还可以将所有本地化文本绑定到页面模型中的字符串。但是对于真正的静态字符串来说,这是很多不必要的代码。

d4so4syb

d4so4syb1#

不幸的是,您不能强制使用 XAML 中的标记扩展设置的控件使用这些扩展重新评估其属性-评估仅在解析 XAML 文件时完成一次。基本上在幕后发生的事情是这样的:
1.您的扩展已示例化
1.在创建的示例上调用ProvideValue方法,并在目标控件上使用返回值
1.对创建的示例的引用没有被存储(或者是一个弱引用,我不确定),所以您的扩展已经为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));
    }
}

字符串
这里的目标是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 文件的工作。

q3aa0525

q3aa05252#

我尝试过实现@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;
    }        
}

字符串
这是最初提出的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));
    }
}


然后,正如最初的帖子所建议的那样,而不是用以下内容绑定文本:

{i18n:Translate Label_Text}


绑定

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


我会在项目结束时(添加多种语言)点击这个按钮,但是使用Visual Studio Community和Search/Replace with RegEx,可以在整个项目中替换绑定,替换:

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


与:

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


注意:Regex假设原始Translate宏的命名空间为'resources',而Translator类的命名空间为'core',您可能需要进行适当的更新。我很感激这是对@Grx70的其他伟大解决方案的一个小调整(我站在巨人的肩膀上),但我在这里发布这个,因为任何人都有同样的问题。

相关问题