Dynamic resx translation in Xamarin Forms using MVVM - xamarin

We have an app written with Pages and no pattern and I want to re-write it using MVVM. Currently we use a Picker for language selection and when the culture changes we set all label.Text controls again in order to redraw them in the new language.
I re-wrote the same page using MVVM and now SelectedItem in the Picker is bound to a Language object. In the setter for SelectedItem I also change the culture of my resx (AppResources.Culture) but the UI bound to it (e.g. Text="{x:Static resources:AppResources.Title) doesn't change language.
Full code in my SelectedItem setter:
SetProperty(ref selectedLanguage, value);
AppResources.Culture = value.Culture;
cultureManager.SetLocale(value.Culture);
How should I update all the Text of my UI? Is there any clean way to do something like this, it seems like a basic translation need... or it wasn't meant to be done, especially not without closing the view/app?
The approaches I found for localization using IMarkupExtension and this thread on Xamarin forums which in the end effectively re-creates the page...
My goal is to ideally reload text without having to re-create the view/close the app, using MVVM and clean code. I have about 10 views so it has to be something reusable.

Create you RESX Resources first. I use en, nl, fr for example.
Create the view model to binding the LocalizedResources.
public class ViewModelBase : INotifyPropertyChanged
{
public LocalizedResources Resources
{
get;
private set;
}
public ViewModelBase()
{
Resources = new LocalizedResources(typeof(LocalizationDemoResources), App.CurrentLanguage);
}
public void OnPropertyChanged([CallerMemberName]string property = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
public event PropertyChangedEventHandler PropertyChanged;
}
In SettingsPage, use a picker to choose the language.
<StackLayout>
<Label Text="{Binding Resources[PickLng]}" />
<Picker ItemsSource="{Binding Languages}" SelectedItem="{Binding SelectedLanguage, Mode=TwoWay}" />
</StackLayout>
View model of SettingsPage.
public class SettingsViewModel : ViewModelBase
{
public List<string> Languages { get; set; } = new List<string>()
{
"EN",
"NL",
"FR"
};
private string _SelectedLanguage;
public string SelectedLanguage
{
get { return _SelectedLanguage; }
set
{
_SelectedLanguage = value;
SetLanguage();
}
}
public SettingsViewModel()
{
_SelectedLanguage = App.CurrentLanguage;
}
private void SetLanguage()
{
App.CurrentLanguage = SelectedLanguage;
MessagingCenter.Send<object, CultureChangedMessage>(this,
string.Empty, new CultureChangedMessage(SelectedLanguage));
}
}
Do not forget to binding the context.
I have upload on GitHub, you could download from DynamicallyBindingRESXResources folder on my GitHub for reference.
https://github.com/WendyZang/Test.git

Related

Xamarin access to entry in other view

I'm developing Multiplatform app through Xamarin.
I'm using custom entry in a separate view and I'm using in some pages of my app
This is my simple code for entry
<ContentView
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="prova.MyView">
<ContentView.Content>
<Entry x:Name="MyEntry"
TextChanged="MyEntry_TextChanged"
Margin="100"/>
</ContentView.Content>
and cs file
public partial class MyView : ContentView
{
public MyView()
{
InitializeComponent();
}
void MyEntry_TextChanged(System.Object sender, Xamarin.Forms.TextChangedEventArgs e)
{
}
}
in my pages I insert entry with this simple code
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="prova.MainPage"
xmlns:pages="clr-namespace:prova">
<StackLayout>
<pages:MyView/>
</StackLayout>
I wonder how can I get when MyEntry_TextChanged is fired in my ContentPage?
one solution is to use MessaggingCenter but I wonder if there's a better and more elegant solution
There are two ways I can think of to do this.
1. Inherit from the Entry class as Jason commented.
public class MyView : Entry
{
public MyView()
{
InitializeComponent();
}
}
This will expose the bindable TextChanged property which you can reference in XAML like you want to do.
2. Create the binding yourself
You can create the binding to a custom "TextChanged" property yourself, but this is more complicated and may achieve the same result with extra effort. You will also need to create a bindable "Text" property. The code below is untested, but uses the bindings I found in the Xamarin.Forms InputView class (which is what Entry derives from). This will be along the lines of what you need to do if you do not do it the way of #1. Exposing bindable properties to XAML will look like this:
public class MyView : ContentView
{
public MyView()
{
InitializeComponent();
}
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(MyView), defaultValue: "", propertyChanged:
(bindable, oldValue, newValue) => ((MyView)bindable).OnTextChanged((string)oldValue, (string)newValue));
public event EventHandler<TextChangedEventArgs> TextChanged;
protected virtual void OnTextChanged(string oldValue, string newValue)
{
TextChanged?.Invoke(this, new TextChangedEventArgs(oldValue, newValue));
}
}
I hope my more comprehensive answer helps you choose the direction you want to go for this. If you want to learn more about Bindable Properties, check out Edward's link.

Xamarin binding EventHandler

How can I bind event handler from xaml to ViewModel
in my xamal:
<Entry Text="{Binding SecondName , Mode=TwoWay}" Focused="{Binding FocusedEventhandler}" Completed="{Binding Compleated}">
How can i get that event handler actions in my viewModel?
Try this way
public class TestViewModel
{
public ICommand FocusedEventhandler { get; private set; }
public TestViewModel()
{
FocusedEventhandler = new Command(async () => await ShowContactList());
}
async Task ShowContactList()
{
//your code here
}
}
Call your ViewModel from your page
public partial class TestPage: ContentPage {
public AddContact() {
InitializeComponent();
BindingContext = new TestViewModel();
}
This is just overview, to give you an idea how we can do it.
I believe the proper answer to this question is now to use Behaviors to map from Event Handler to Behavior to Command, in the case that the UI Control's XAML property, e.g. SearchBar.TextChanged, expects an event handler. Please see the documentation here: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/behaviors/creating
and here: https://devblogs.microsoft.com/xamarin/turn-events-into-commands-behaviors/

Xamarin Forms - force a control to refresh value from binding

Given the following ViewModel...
public class NameEntryViewModel
{
public NameEntryViewModel()
{
Branding = new Dictionary<string, string>();
Branding.Add("HeaderLabelText", "Welcome to the app");
}
public Dictionary<string, string> Branding { get; set; }
}
Bound to the page...
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Monaco.Forms.Views.NameEntryPage">
<Label Text="{Binding Branding[HeaderLabelText]}" />
</ContentPage>
When the page comes up the Label will get the text 'Welcome to the app'. This works great and fits into our plan for being able to customize and globalize our app. Then Branding dictionary is statically set in this example, but in the real world it is initialized by data from a service call.
However, should the user want to switch the language to Spanish, we would need each bound label to update to the new value. It's easy enough to reset the Branding dictionary and fill it with Spanish translations, but how can we force the controls to refresh from their bound sources?
I am trying to avoid two-way data binding here b/c we don't want the code overhead of creating a backing property for each Text property of the controls. Hence we are binding to a dictionary of values.
ANSWER
I accepted the answer below, but I didn't use a traditional property setter. Instead when a user wants to toggle a different language, we now have a centralized handler that repopulates our Dictionary and then notifies of the change to the Dictionary. We are using MVVMCross, but you can translate to standard forms...
public MvxCommand CultureCommand
{
get
{
return new MvxCommand(async () =>
{
_brandingService.ToggleCurrentCulture();
await ApplyBranding(); // <-- this call repopulates the Branding property
RaisePropertyChanged(() => Branding);
});
}
}
As #BillReiss mentioned you need to use the OnPropertyChanged event inherit the NameEntryViewModel from this class:
public class BaseViewModel : INotifyPropertyChanged
{
protected BaseViewModel ()
{
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged ([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler (this, new PropertyChangedEventArgs (propertyName));
}
}
And create a private property that you can assign to you public property something like:
Dictionary<string, string> _branding = new Dictionary<string, string>();
public Dictionary<string, string> Branding
{
get
{
return _branding;
}
set
{
_branding = value;
OnPropertyChanged(nameof(Branding));
}
}
And with this every time you set the Branding property it will let the View know that something changed! Sometimes if you are doing this on a back thread you have to use Device.BeginInvokeOnMainThread()
In your BaseViewModel class is a method:
protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName] string propertyName = "",
Action onChanged = null) {}
like:
string title = string.Empty;
public string Title
{
get { return title; }
set { SetProperty(ref title, value); }
}
Use SetProperty in the setter-properties of your inherited view-model class.
Then it will work.
INotifyPropertyChanged is already implemeted in BaseViewModel. So nothing to change here.

How to change a property of ViewModel property and make the change reflects on the bound control?

I'm writing a windows Phone app; and as a good citizen I'm using MVVM pattern :) Being not so expert in MVVM, I faced the following issue that I hope I find solution for here.
I have the following code:
XAML
<Grid x:Name="LayoutRoot" Background="Transparent" DataContext="{Binding MyPOCO}">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding IsActive}"/>
</StackPanel>
</Grid>
Code Behind
this.DataContext = new ViewModel();
ViewModel
public class ViewModel : ViewModelBase
{
private POCO myPOCO;
public ViewModel()
{
this.myPOCO = new POCO();
this.ToggleActiveStatusCommand = new RelayCommand(this.ToggleActiveStatus);
}
public POCO MyPOCO
{
get
{
return this.myPOCO;
}
}
public RelayCommand ToggleActiveStatusCommand { get; private set; }
private void ToggleActiveStatus()
{
this.MyPOCO.IsActive = !this.MyPOCO.IsActive;
System.Diagnostics.Debug.WriteLine(this.MyPOCO.IsActive);
this.RaisePropertyChanged("MyPOCO");
}
}
POCO
public class POCO
{
public string Name { get; set; }
public bool IsActive { get; set; }
}
What I'm trying to achieve is to change the TextBlock text as the value of IsActive changes... how to do that? I mean other than exposing the required properties from POCO as properties to ViewModel.
Thanks
I think you will have to implement properties in your ViewModel that call into the POCO properties unless you implement INotifyPropertyChanged in your POCO which then goes against MVVM where your Model should just hold your data and your ViewModel is the glue that links the Model with the View.
Creating properties in your ViewModel may seem like alot of additional work but it does add the benefit of being able to define many ViewModels based on a single Model. So in the specific ViewModel you only expose properties that you need rather than everything the Model can offer you.
Once you have defined your ViewModel properties, you may need to set the Mode of your Binding to TwoWay so that the UI can be updated when the underlying ViewModel properties change.

ItemsControl Doesn't Resort or Refilter After Page First Loaded

I'm creating an application with a weather map that shows the heat as a Pushpin at different places. To do this, supplied by own PushpinModel that supports the INotifyPropertyChanged interface:
public class PushpinModel: INotifyPropertyChanged
{
#region // events
public event PropertyChangedEventHandler PropertyChanged;
#endregion events
#region // fields
Heat heat = Heat.normal;
#endregion fields
#region // properties
public string Placename { get; set; }
public GeoCoordinate Location { get; set; }
public Heat Heat
{
get { return heat; }
set
{
if (heat != value)
{
heat = value;
OnPropertyChanged("Heat");
}
}
}
public string IDno { get; set; }
#endregion properties
#region // handlers
protected virtual void OnPropertyChanged(string propChanged)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propChanged));
}
#endregion handlers
}
The PushpinModel objects are contained in an ObservableCollection called Pushpins which are periodically updated to ShowWeather:
public class Pushpins: ObservableCollection<PushpinModel>
{
#region // METHODS
public void ShowWeather( WeatherReport fromWeatherReport)
{
foreach (WeatherRecord w in fromWeatherReport.WeatherRecords)
{
this.First<PushpinModel>(p => p.IDno == w.PlaceID).Heat = w.Heat;
}
}
#endregion methods
}
I display the Pushpins on a Bing Map, but also as items in an ItemsControl:
<ItemsControl x:Name="ItemList" ItemsSource="{Binding Source={StaticResource placesSortedAndFiltered}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border>
<TextBlock Text="{Binding Placename}" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The ItemsSource is defined as a CollectionViewSource:
<CollectionViewSource x:Key="placesSortedAndFiltered" Source="{Binding ElementName=MyMainPage, Path=Pushpins}" Filter="PlaceHeat_Filter">
<CollectionViewSource.SortDescriptions>
<componentmodel:SortDescription PropertyName="Placename" Direction="Ascending" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
with a Filter in the codebehind defined as:
private void PlaceHeat_Filter(object sender, FilterEventArgs e)
{
e.Accepted = (((PushpinModel)e.Item).Heat != Heat.na);
}
where a public enum Heat {na,cool,normal,warm,hot}
The problem is that ItemsControl list display appropriately sorted and filtered on page load, but does NOT update when the PushpinModel objects properties are changed. Please note that when the Pushpins object is bound to a Bing Map Control, the PushpinModel objects do update as expected. So somehow, my ItemsControl list isn't updating even though it is bound via a CollectionView to an ObservableCollection
I believe that the CollectionViewSource implementation only supports automatic filtering when the underlying collection is changed (or reset). The filter does not get invoked if a property of an underlying data item changes.
You could either call Refresh() on the CollectionViewSource when a property for an item in the collection changes, or you could implement your own CollectionViewSource which listens to property changed events on underlying data objects, or you could bind directly to a filtered (and sorted) collection rather than using the CollectionViewSource.

Resources