Search code examples
c#data-bindinguwplocalizationxamarin.uwp

Textblock binding property changed not working in UWP


I'm trying to achieve a simple "press a button to change the App language and all dependent strings" example. (You can see the whole code at this GitHub repository)

In this example I have three TextBlock and one button bound to my language Resources:

Resources hierarchy

And:

<StackPanel
    HorizontalAlignment="Center"
    VerticalAlignment="Center"
    Orientation="Vertical">

    <TextBlock
        HorizontalAlignment="Center"
        FontSize="20"
        Text="{Binding Source={StaticResource Localized}, Path=[A]}" />
    <TextBlock
        HorizontalAlignment="Center"
        FontSize="20"
        Text="{Binding Source={StaticResource Localized}, Path=[B]}" />
    <TextBlock
        HorizontalAlignment="Center"
        FontSize="20"
        Text="{Binding Source={StaticResource Localized}, Path=[C]}" />

    <Button
        x:Name="btnChangeLanguage"
        Margin="10"
        Padding="10"
        Click="btnChangeLanguage_Click"
        Content="{Binding Source={StaticResource Localized}, Path=[Change]}" />

</StackPanel>

As you can see I expressed the binding of the TextBlock(s) with: Text="{Binding Source={StaticResource Localized}, Path=[A]}"

Localized of type LocalizedStrings, and it is a static property of App class:

public class LocalizedStrings : INotifyPropertyChanged
{
    private readonly ResourceLoader _resources;
    private readonly LocalizationHelper _localizationHelper;

    public event PropertyChangedEventHandler PropertyChanged;

    public LocalizedStrings()
    {
        _resources = ResourceLoader.GetForViewIndependentUse();
        _localizationHelper = App.LocalizationHelper;
        _localizationHelper.OnLocalizationChange += _localizationHelper_OnLocalizationChange;
    }

    public string this[string key]
    {
        get
        {
            return _resources.GetString(key);
        }
    }

    private void _localizationHelper_OnLocalizationChange(object sender, System.EventArgs e)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(string.Empty));
    }
}

And the most interesting part of LocalizationHelper is just:

public void ChangeLocalizationTo(string cultureCode)
{
    Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = cultureCode;
    OnLocalizationChange?.Invoke(null, new PropertyChangedEventArgs(string.Empty));
}

Lastly, the code of the MainPage button:

private void btnChangeLanguage_Click(object sender, RoutedEventArgs e)
{
    if (LocalizationHelper.IsCultureItalian(App.LocalizationHelper.GetCurrentLocalizationCode()))
        App.LocalizationHelper.ChangeLocalizationTo("en-US");
    else App.LocalizationHelper.ChangeLocalizationTo("it-IT");
}

The bindings are working correctly, but they bind just the first time and never get updated. So, I start the application I see the strings (also during design time), but when I press the button nothing happens. It seems that the OnLocalizationChange?.Invoke(null, new PropertyChangedEventArgs(string.Empty)); (which is called) doesn't let the the application call the property to get the translation. (Note that I used string.Empty as property name to notify that all properties in this class are changed)

The "funny" fact is that the same identical example made with WPF works perfectly!

WPF Example

This makes me think that there are some different behaviors with the two platforms binding logic.

I already tried to put a Mode=OneWay binding but it doesn't work.

(You can see the whole code at this GitHub repository)


Solution

  • After modify the PrimaryLanguage, you need refresh your page manually. Please add the following code into btnChangeLanguage_Click method.

    private void btnChangeLanguage_Click(object sender, RoutedEventArgs e)
    {
        if (LocalizationHelper.IsCultureItalian(App.LocalizationHelper.GetCurrentLocalizationCode()))
            App.LocalizationHelper.ChangeLocalizationTo("en-US");
        else App.LocalizationHelper.ChangeLocalizationTo("it-IT");
    
        //refresh current page
        Frame.Navigate(this.GetType());
    }
    

    Update

    I'd thought I could avoid reloading the page by relying on binding

    If you want to refresh your binding, you could change the page datacontext to the new object, and we need re-write the xaml binding like below.

    Xaml

    <StackPanel
        HorizontalAlignment="Center"
        VerticalAlignment="Center"
        Orientation="Vertical">
    
        <TextBlock
            HorizontalAlignment="Center"
            FontSize="20"
            Text="{Binding [A]}" />
        <TextBlock
            HorizontalAlignment="Center"
            FontSize="20"
            Text="{Binding [B]}" />
        <TextBlock
            HorizontalAlignment="Center"
            FontSize="20"
            Text="{Binding [C]}" />
    
        <Button
            x:Name="btnChangeLanguage"
            Margin="10"
            Padding="10"
            Click="btnChangeLanguage_Click"
            Content="{Binding [Change]}" />
    
    </StackPanel>
    

    Code Behind

     public MainPage()
     {
         InitializeComponent();
         this.DataContext = new LocalizedStrings();
     }
    
     private async void btnChangeLanguage_Click(object sender, RoutedEventArgs e)
     {
         if (LocalizationHelper.IsCultureItalian(App.LocalizationHelper.GetCurrentLocalizationCode()))
             App.LocalizationHelper.ChangeLocalizationTo("en-US");
         else App.LocalizationHelper.ChangeLocalizationTo("it-IT");
         await Task.Delay(100); // used to prepare the resource.
         this.DataContext = new LocalizedStrings();
     }