Use a text string as the binding parameter in Xamarin Value Convertor

469 views Asked by At

I use IValueConverters a lot in my Xamarin projects. I would like to use any arbitrary string of text as the binding parameter, rather than have to bind to a property on my View or ViewModel. Often I find myself having to create what I see as arbitrary / superfluous properties on my view model just to support the functionality I want, which clutters my code. The use case for this is using a converter to get a translated string from a custom XML file. (In my project I cannot use the standard resx file i18n approach - I need to traverse an XML file to find the string I need).

I would like to do this in Xaml:

<Label Text="{Binding 'my-awesome-string-of-text',
 Converter={StaticResource ConvertMyStringToSomethingElseConvertor}}"/>

and to have the string 'my-awesome-string-of-text' flow through to the object parameter of the IValueConvertor (ConvertMyStringToSomethingElseConvertor in this example).

But I find myself having to do this instead

<Label Text="{Binding MyUneccessaryStringProperty,
 Converter={StaticResource ConvertMyStringToSomethingElseConvertor}}"/>

and on my view model (mvvm light syntax) having to maintain the property

private string _myUneccessaryStringProperty 
public string MyUneccessaryStringProperty 
        {
            get => _myUneccessaryStringProperty ;
            set => SetProperty(ref _myUneccessaryStringProperty , value);
        }

Can what I want be done? For a page with a lot of different translatable strings the ViewModel quickly becomes cluttered.

2

There are 2 answers

4
Adam Diament On BEST ANSWER

EDIT! This can be done with an IMarkupExtension!

It has been implemented in the Xamarin Commmunity toolkit for getting translated strings from RESX files in the TranslateExtension. I have adapted their approach to pull from my custom translations XML file and bind to that, something like this:

    [ContentProperty(nameof(Text))]
    public class TranslateExtension : IMarkupExtension<BindingBase>
    {
        public string Text { get; set; }
        public string StringFormat { get; set; }
        object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider) => ProvideValue(serviceProvider);

        public BindingBase ProvideValue(IServiceProvider serviceProvider)
        {
#if !NETSTANDARD1_0

                var t = MyCustomTranslationsHolder.Get(Text);
                var binding = new Binding
                {
                    Mode = BindingMode.OneTime,
                    Path = $"text",
                    Source = new {text = !string.IsNullOrEmpty(t) ? t : Text},
                    StringFormat = StringFormat
                };
                return binding;
            
#else
            throw new NotSupportedException("Translate XAML MarkupExtension is not supported on .NET Standard 1.0");
#endif
        }
    }
    ```

Then in the xaml for example the Text property of a label

    Text="{converters:Translate 'The.Particular.Resource.Key'}"

The string displayed will be either the translation if it was successful, or the resource string if not, for debugging.

Original answer, for posterity

I found an acceptable answer, relating to Andrew's suggestion in the comments.

Basically I create a static class called translations and set static properties to the value of the translations in the way my app gets them.

namespace MyApp.Utilities
{

    private static string _myAwesomeString;

    public static string MyAwesomeString

    {
        get
        {
            if (string.IsNullOrEmpty(_myAwesomeString))
            {
                MyAwesomeString= GetTranslation("MyAwesomeResource");
            }
            return _myAwesomeString;
        }
        private set => _myAwesomeString = value;
    }
}

And then reference in xaml like so:

<Label Text="{x:Static utils:Translations.MyAwesomeString}"></Label>

Using the get/set accessors ensure that the GetTranslation call will only run once, effectively giving me efficient in memory dictionary-like access to resources that are used across multiple screens.

I still need to actually have the properties in a class, which is not ideal - but at least this way they are all in one place and not cluttering the viewmodels. If anyone knows a solution where I don't need to keep actual properties please let me know!

2
Ivan Ičin On

It can be done, but not exactly like that. You need to have your namespace prefix that would eventually do the similar thing that the converter does. Look at the Plugin.Multilingual on github. Of course there is no binding. Binding is meant for binding, not running the arbitrary code in the arbitrary way.