Below is a modified implementation of MvxLang.
My goal is to be able to implement screen reader text concisely with existing string values stored in <ViewModelName>.json files in our projects Resources, as well as dynamically generated text retrieved from <ViewModelName>.cs files.
I wish to use the following syntax:
xct:SemanticEffect.Description="{mvx:MvxLang ViewModel.SomeStringFromCsFile | SomeStringFromJsonFile | ViewModel.SomeOtherStringFromCsFile}"
This way our ViewModels/Xaml will not be bloated with screen reader text logic/markup.
My implementation works fine when only retrieving string value from <ViewModelName>.json files, but I wish to use a variety of values from <ViewModelName>.cs files as well...
My troubles occur in this block of code when calling GetValue(), I can return the value, but it appears IMarkupExtensions are called before the the ViewModel:
var prefix = "ViewModel.";
if (str.Contains(prefix))
{
var vm = (rootObject is MvxContentPage)
? ((MvxContentPage)rootObject).GetViewModel()
: ((MvxContentView)rootObject).GetViewModel();
PropertyInfo prop = vm.GetType().GetProperty(str.Replace(prefix, string.Empty));
var propValue = prop.GetValue(vm);
return propValue as string ?? string.Empty;
}
Is there a way to return the runtime values here?
Here is the rest of the code:
[ContentProperty("Source")]
public class MvxLang : IMarkupExtension
{
readonly static IMvxTextProvider _textProvider = Mvx.IoCProvider.Resolve<IMvxTextProvider>();
public static string TransitioningViewModel { private get; set; }
public string Source { set; get; }
public object ProvideValue(IServiceProvider serviceProvider)
{
var valueProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
var rootProvider = serviceProvider.GetService(typeof(IRootObjectProvider)) as IRootObjectProvider;
object rootObject = null;
if (rootProvider == null)
{
var propertyInfo = valueProvider.GetType()
.GetTypeInfo()
.DeclaredProperties
.FirstOrDefault(dp => dp.Name.Contains("ParentObjects"));
var parentObjects = (propertyInfo.GetValue(valueProvider) as IEnumerable<object>).ToList();
rootObject = parentObjects.Last();
}
else
rootObject = rootProvider.RootObject;
var name = string.Empty;
if (!(rootObject is MvxContentPage || rootObject is MvxContentView))
{
// Transitioning
name = TransitioningViewModel;
}
else
{
var page = (VisualElement)rootObject;
name = page.GetType().BaseType.GetGenericArguments()[0].Name;
}
if (!string.IsNullOrEmpty(name))
{
var value = string.Empty;
(bool, string) targetPropertyCheck = this.TargetPropertyCheck_ADA(valueProvider.TargetProperty);
if (targetPropertyCheck.Item1)
{
value = ProvideValue_ADA(targetPropertyCheck.Item2, _textProvider, rootObject, name, Source);
return value;
}
else
{
value = _textProvider.GetText(name, Source);
return value;
}
}
return string.Empty;
}
public (bool, string) TargetPropertyCheck_ADA(object targetProperty)
{
var propertyName = string.Empty;
var isADA = false;
if (targetProperty is BindableProperty _targetProperty)
{
if (_targetProperty.DeclaringType.Name.Equals("SemanticEffect"))
{
propertyName = _targetProperty.PropertyName;
isADA = propertyName.Equals("Description") || propertyName.Equals("Hint");
}
}
return (isADA, propertyName);
}
public string ProvideValue_ADA( string propertyName, IMvxTextProvider textProvider, object rootObject, string name, string keyString)
{
if (!string.IsNullOrEmpty(keyString) && !string.IsNullOrEmpty(propertyName))
{
switch (propertyName)
{
case "Description":
if (keyString.Contains('|'))
{
var parameters = keyString.Split(new char[] { '|' });
IEnumerable<string> appliedStrings = parameters.Select(s =>
{
var str = s.Trim();
var prefix = "ViewModel.";
if (str.Contains(prefix))
{
var vm = (rootObject is MvxContentPage)
? ((MvxContentPage)rootObject).GetViewModel()
: ((MvxContentView)rootObject).GetViewModel();
PropertyInfo prop = vm.GetType().GetProperty(str.Replace(prefix, string.Empty));
var propValue = prop.GetValue(vm);
return propValue as string ?? string.Empty;
}
else
{
return textProvider.GetText(name, str);
}
});
return string.Join(", ", appliedStrings);
}
else
{
return textProvider.GetText(name, keyString);
}
case "Hint":
var appliedText = textProvider.GetText(name, keyString);
return $"Double tap to {appliedText}";
default:
break;
}
}
return string.Empty;
}
}