We are developing several mobile apps, with Common .NET Standard library between them, which holds the common functionallity. (MVVM)
The Common project has a TranslationManager Class, and a Resource file, which holds the common translations.
TranslationManager uses constructor injection, to inject the app specific translation resources.
public TranslationManager(ResourceManager appSpecificLanguageResources)
{
_commonResources = CommonTranslationResources.ResourceManager;
_appSpecificLanguageResources = appSpecificLanguageResources;
}
With this code, we earn the possibilty to use common translations, and application specific translations with using only one Translation provider.
if (string.IsNullOrWhiteSpace(translationKey))
return null;
string commonTranslation = _commonResources.GetString(translationKey, new CultureInfo(_preferenceCache.CultureName));
string appSpecificTranslation = _appSpecificLanguageResources.GetString(translationKey, new CultureInfo(_preferenceCache.CultureName));
if (commonTranslation == null && appSpecificTranslation == null)
{
MobileLogger.Instance.LogWarning($"Translate could not found by translationKey: {translationKey}");
return $"TRANSLATION_{translationKey}";
}
if (!string.IsNullOrWhiteSpace(commonTranslation) && !string.IsNullOrWhiteSpace(appSpecificTranslation))
{
MobileLogger.Instance.LogDebug(TAG, $"Warning! Duplicate translate found for '{translationKey}' translationkey. Common translate is : '{commonTranslation}' , AppSpecific Translation is: {appSpecificTranslation}. Returning with appspecific translation.");
return appSpecificTranslation;
}
if (commonTranslation == null)
return appSpecificTranslation;
else
return commonTranslation;
In XAML, we have one MarkupExtension which provides the translation for the current language.
public class TranslateMarkupExtension : IMarkupExtension
{
public TranslateMarkupExtension()
{
}
public string TranslationKey { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{
if (string.IsNullOrWhiteSpace(TranslationKey)) return "nullref";
return Resolver.Resolve<TranslationManager>().GetTranslationByKeyForCurrentCulture(TranslationKey);
}
}
XAML Usage seem to be like:
Entry Placeholder="{extensions:TranslateMarkup TranslationKey=PlaceholderPhoneNumber}"
The problem is, when i set the language at runtime, the translation extension markup does not evaluate the new translation.
Raising propertychanged with null parameter refreshes the bindings on the view, but does not affect MarkupExtensions.
I do not want to push the same page to the navigation stack, it seems patchwork for me.
The problem is, when i set the language at runtime, the translation extension markup does not evaluate the new translation.
you may need to use INotifyPropertychanged interface for TranslationManager,when you change UI culture, all string that are bound to the index would update.
More detailed info, please refer to:
Xamarin.Forms change UI language at runtime (XAML)
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;
}
}
and this the Translator class as initially proposed, but reproduced here for clarity with the GetString call:
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));
}
}
Binding text with:
{i18n:Translate Label_Text}
To trigger the update of languages you just then need to call:
Translator.Instance.Invalidate()
Solution from:
https://forums.xamarin.com/discussion/82458/binding-indexername-and-binding-providevalue-in-xamarin-forms
Related
I am currently migrating a Xamarin.Forms app to .NET MAUI, and I am noticing that to make custom controls it has changed to creating a custom handler. I have followed the documentation values and an article in the .NET MAUI Wiki on GitHub but I have come across when making the MapText that when assigning the value that I declare in my interface it throws me the error CS0131 "The left-hand side of an assignment must be a variable, property or indexer" and this prevents me from continuing.
For the moment I have continued using the classic way to create the custom controls, but since I have already indicated deprecated I have decided to adopt this new way, but the error mentioned above prevents me, in this case not only with a custom Entry, but also with a custom Label.
I would be very grateful for your support
This is my CustomEntryHandler:
public partial class CustomEntryHandler : ViewHandler<ICustomEntry, EditText>
{
private static PropertyMapper<ICustomEntry, CustomEntryHandler> CustomEntryMapper = new(ViewMapper)
{
[nameof(ICustomEntry.Text)] = MapText,
};
public CustomEntryHandler(CommandMapper commandMapper = null) : base(CustomEntryMapper, commandMapper)
{
}
protected override EditText CreatePlatformView()
{
return new EditText(Context);
}
private static void MapText(EntryHandler handler, ICustomEntry entry)
{
handler.PlatformView?.Text = entry.Text;// This line is the error CS0131
}
}
And this is my interface and Custom Control Class:
public interface ICustomEntry : IView
{
public string Text { get; }
public Color TextColor { get; }
}
public class CustomEntry : View, ICustomEntry
{
public string Text { get; set; }
public Color TextColor { get; set; }
}
You can change your code:
if(handler.PlatformView != null)
handler.PlatformView.Text = entry.Text;
The null propogation operator returns a value. Since, its on the left hand side and not a value, it cant be use this way.
Someone also opened an issue:
https://github.com/dotnet/csharplang/issues/2883
I want to write a control derived from CheckedListBoxControl (DevExpress), and I need to add a property to the (DataBindings)
These are the standard Properties shown in the PropertyGrid:
So I can only choose between Tag and Text.
What I want is to add a third option called gttMasterField (which will be of type int, don't know if this matters)
I have been experimenting with the documentation but with no results.
These don't seem to cover exact what I am looking for, I don't know the correct search terms to find this, which makes it difficult to google for it. It will probably be somewhere in the documentation but also there I don't know on what terms to look for.
Create a Windows Forms user control that supports simple data binding
Create a Windows Forms user control that supports lookup data binding
Create a Windows Forms user control that supports complex data binding
Here is some code with comments that will also help to explain what I am searching for
public partial class gttDXManyToManyCheckedListBox : CheckedListBoxControl
{
private int _gttMasterField;
// This I want populated by setting the binding property MasterField
public int gttMasterField
{
get { return _gttMasterField; }
set { _gttMasterField = value; }
}
}
The project is a dotnet framework 4.7.2
To make a custom Property appear in the PropertyGrid's (DataBindings), decorate the Property with the BindableAttribute set to true:
[Bindable(true)]
public int gttMasterField
{
get { return _gttMasterField; }
set { _gttMasterField = value; }
}
Optionally also decorate with the desired DesignerSerializationVisibilityAttribute attribute
[Bindable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public int gttMasterField
{
get { return _gttMasterField; }
set { _gttMasterField = value; }
}
The class can also specify the default bindable Property, settings a DefaultBindingPropertyAttribute:
[DefaultBindingProperty("gttMasterField")]
public partial class gttDXManyToManyCheckedListBox : CheckedListBoxControl
{
private int _gttMasterField;
[Bindable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public int gttMasterField
{
get { return _gttMasterField; }
set { _gttMasterField = value; }
}
}
Important note:
the class should implement INotifyPropertyChanged -> a bindable Property is supposed to raise notification events. When the binding is set in the Designer, a BindingSource is generated to mediate the binding, but it requires that the objects involved send change notifications (mostrly to determine when the Property value is updated, usually as DataSourceUpdateMode.OnPropertyChanged).
For example:
using System.Runtime.CompilerServices;
[DefaultBindingProperty("gttMasterField")]
public partial class gttDXManyToManyCheckedListBox : CheckedListBoxControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _gttMasterField;
[Bindable(true)]
public int gttMasterField
{
get { return _gttMasterField; }
set { _gttMasterField = value; NotifyPropertyChanged(); }
}
private void NotifyPropertyChanged([CallerMemberName] string PropertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
}
Setting DesignerSerializationVisibility.Content implies initialization. If a BindingSource is used, this object supports initialization on itself, the Attribute is not strictly required.
It could be set to DesignerSerializationVisibility.Hidden, though, depending on the use case.
I've been struggling for a while now with how to best "interact" with settings on a binding level in a WinRT/UWP app. I've been looking for best practices regarding this, but I haven't found clear answers. What I've done so far in my apps is the following:
Define a BindableBase which implements INotifyPropertyChanged.
Create an AppSettings class which inherits from BindableBase and looks a bit like this:
public class AppSettings : BindableBase
{
ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
public string MySetting
{
get
{
if (!localSettings.Values.ContainsKey("MySetting"))
localSettings.Values["MySetting"] = "Some default value";
return localSettings.Values["MySetting"].ToString();
}
set
{
localSettings.Values["MySetting"] = value;
RaisePropertyChanged();
}
}
}
Define a ViewModel which has a an AppSettings property:
public class SettingsViewModel
{
public AppSettings Settings { get; set; } = new AppSettings();
}
Bind to the Settings property in the View:
<TextBlock Text="{Binding Settings.MySetting, Mode=TwoWay}">
I've seen and used implementations in the past that had a settings service, but these applications didn't need the settings changes to go in effect immediately. So what I'm basically asking is: If changes to settings should go immediately into effect, is the above implementation a good way to bind to settings? If not, what do you recommend?
I was very inspired by this article so I came up with something a tad bit more elegant to avoid having to copy and paste the property name (key) so many times. I use the following:
public class SettingsViewModel : BindableBase
{
public string MyProperty
{
get
{
return Get("MyProperty", "SomeDefaultValue");
}
set
{
Set("MyProperty", value);
}
}
public T Get<T>(string PropertyName, T DefaultValue)
{
//If setting doesn't exist, create it.
if (!App.Settings.ContainsKey(PropertyName))
App.Settings[PropertyName] = DefaultValue;
return (T)App.Settings[PropertyName];
}
public void Set<T>(string PropertyName, T Value)
{
//If setting doesn't exist or the value is different, create the setting or set new value, respectively.
if (!App.Settings.ContainsKey(PropertyName) || !((T)App.Settings[PropertyName]).Equals(Value))
{
App.Settings[PropertyName] = Value;
OnPropertyChanged(PropertyName);
}
}
}
Assuming your App class defines the following property:
public static IPropertySet Settings
{
get
{
return Windows.Storage.ApplicationData.Current.LocalSettings.Values;
}
}
The latter is more convenient than writing,
ApplicationData.Current.LocalSettings.Values
And can still be accessed globally.
This seems fine in a general case. The only thing I'd change would be to only raise the notification if the data actually changes:
set
{
if (MySetting != value)
{
localSettings.Values["MySetting"] = value;
RaisePropertyChanged();
}
}
This will avoid raising notifications unnecessarily.
Depending on the frequency the values are read, you may want to keep your own in memory copy of the setting values (in a private field) instead of reading them from the container each time.
Consider a WPF dialogue with lots of input fields, which are bound to properties in a view-model. E.g.
...
<TextBox Text="{Binding FirstName}">
...
public string FirstName {
get { return mFirstName; }
set {
if (mFirstName == value) return;
mFirstName = value;
OnPropertyChanged("FirstName");
}
}
As there are tens of fields like this, I would like to minimize the boilerplate C# code to be written. What options do I have?
If you have the option of using a base class, consider inheriting view model objects from something like this:
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Equals(storage, value))
{
return false;
}
storage = value;
// ReSharper disable once ExplicitCallerInfoArgument
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void OnPropertiesChanged(params string[] propertyNames)
{
foreach (string propertyName in propertyNames)
{
// ReSharper disable once ExplicitCallerInfoArgument
OnPropertyChanged(propertyName);
}
}
}
Example usage, showing that the boilerplate is greatly reduced:
public sealed class ViewModel : BindableBase
{
private string name;
public string Name
{
get { return name; }
private set { SetProperty(ref name, value); }
}
}
(If you can't use a base class (e.g., you already have one or are using properties on framework elements), you still have the option of adding similar support directly in the class in question.)
I can make your code a little easier to transform into a snippet.
if (mFirstName != value) {
mFirstName = value;
OnPropertyChanged("FirstName");
}
If just the time taken to write it is a pain, and you're using WPF a lot, snippets may also be of use. I know in Sublime Text, VS Code, and Visual Studio, Snippets can be invaluable. Otherwise, I think it's as bare bones as you can get, unless there's something I am not seeing
I use Fody to inject property changed code at compile time. Your class gets an [ImplementPropertyChanged] attribute, then your { get; set; } properties become notifying properties in the compiled code.
https://github.com/Fody/PropertyChanged
First, as I guess you already use Microsoft.Prism, you can drop the string and profit from CallerMemberNameAttribute behind the scenes for you, so that your code would look like this:
public string FirstName {
get { return mFirstName; }
set {
if (mFirstName == value) return;
mFirstName = value;
OnPropertyChanged();
}
}
This is also equivalent to c# 6.0 nameof(FirstName) operator.
Second, you can dig into AOP and abstract the boilerplate to an attribute. One of the AOP frameworks that deals with this is PostSharp and using it your code could look like this:
[NotifyPropertyChanged]
public class Customer
{
public string FirstName { get; set; }
Though it's not free, and AOP has it's drawbacks (thanks Evk).
Similar questions have been asked 1,2, and there does not seem to be optimal answer right now sadly, as it's everyones pain.
In a xamarin app on a xaml page I am loading localized strings using a xaml extension (the details are described here). For example:
<Label Text={i18n:Translate Label_Text}/>
Now, I want the user to be able to change the language of the app at runtime (using a picker). If that happens, I want to change the language immediately.
Can I somehow reload all translated texts?
I could delete all pages and recreate them, but I am trying to avoid that.
I could also bind all localised texts to strings in the pages model. But that is a lot of unnecessary code for truly static strings.
Unfortunately you cannot force controls set up with markup extensions in XAML to reevaluate their properties using those extensions - the evaluation is only done once upon parsing XAML file. What basically happens behind the scenes is this:
Your extension is instantiated
ProvideValue method is called on the created instance and the returned value is used on the target control
The reference to the created instance is not stored (or is a weak reference, I'm not sure), so your extension is ready for GC
You can confirm that your extension is only used once by defining a finalizer (desctructor) and setting a breakpoint in it. It will be hit soon after your page is loaded (at least it was in my case - you may need to call GC.Collect() explicitly). So I think the problem is clear - you cannot call ProvideValue on your extension again at an arbitrary time, because it possibly no longer exists.
However, there is a solution to your problem, which doesn't even need making any changes to your XAML files - you only need to modify the TranslateExtension class. The idea is that under the hood it will setup proper binding rather than simply return a value.
First off we need a class that will serve as a source for all the bindings (we'll use singleton design pattern):
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));
}
}
The goal here is that Translator.Instance["Label_Text"] should return the translation that your current extension returns for "Label_Text". Then the extension should setup the binding in the ProvideValue method:
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);
}
}
Now all you need to do is to call Translator.Instance.Invalidate() every time the language is changed.
Note that using {i18n:Translate Label_Text} will be equivalent to using {Binding [Label_Text], Source={x:Static i18n:Translator.Instance}}, but is more concise and saves you the effort of revising your XAML files.
I'd tried to implement #Grx70's great proposed solution, but some of the classes and properties the example used are internal to Xamarin so couldn't be used in that way.
Picking up on their last comment though, was the clue to get it working, though not quite as elegantly as initially proposed, we can do this:
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;
}
}
and this the Translator class as initially proposed, but reproduced here for clarity with the GetString call:
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));
}
}
Then as the original post suggested, instead of binding text with:
{i18n:Translate Label_Text}
Bind
{Binding [Label_Text], Source={x:Static i18n:Translator.Instance}}
I'd hit this right at the end of a project (adding the multiple languages), but using Visual Studio Community and Search/Replace with RegEx, the binding can be replaced across the project, replacing:
\{resources:Translate (.*?)\}
with:
{Binding [$1], Source={x:Static core:Translator.Instance}}
NOTE: The Regex assumes the 'resources' namespace for the original Translate macro, and 'core' namespace for the Translator class, you may have to update as appropriate.
I appreciate this is a small tweak to #Grx70's otherwise great solution (I'm standing on the shoulders of giants with this one), but I'm posting this here for any that follow with the same problem of getting this working.