Xamarin IMarkupExtension,获取 ViewModel 属性 值
Xamarin IMarkupExtension, Get ViewModel Property Values
下面是 MvxLang 的修改实现。
我的目标是能够使用存储在 .json 文件中的现有字符串值简洁地实现屏幕 reader 文本,以及从中检索到的动态生成的文本.cs 文件。
我希望使用以下语法:
xct:SemanticEffect.Description="{mvx:MvxLang ViewModel.SomeStringFromCsFile | SomeStringFromJsonFile | ViewModel.SomeOtherStringFromCsFile}"
这样我们的 ViewModels/Xaml 就不会被屏幕 reader 文本 logic/markup.
臃肿
当仅从 .json 文件中检索字符串值时,我的实现工作正常,但我也希望使用 .cs 文件中的各种值...
调用 GetValue() 时,我的问题出现在这段代码中,我可以 return 值,但似乎在 ViewModel:
之前调用了 IMarkupExtensions
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;
}
有没有办法 return 这里的运行时值?
这是代码的其余部分:
[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;
}
}
在意识到 IMarkupExtension class 在 ViewModel 设置其属性之前触发后,最终采用了此解决方案。
[ContentProperty(nameof(Values))]
public class Provider : IMarkupExtension<MultiBinding>
{
readonly static IMvxTextProvider _textProvider = Mvx.IoCProvider.Resolve<IMvxTextProvider>();
public string Values { set; get; }
IList<BindingBase> Bindings { get; set; } = new List<BindingBase>();
string StringFormat { get; set; } = "{0}";
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider) => ProvideValue(serviceProvider);
public MultiBinding ProvideValue(IServiceProvider serviceProvider)
{
var valueProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
var _property = valueProvider?.TargetProperty as BindableProperty;
object rootObject = 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();
var name = string.Empty;
var page = (VisualElement)rootObject;
name = page.GetType().BaseType.GetGenericArguments()[0].Name;
if (!string.IsNullOrEmpty(name))
{
var propertyName = _property.PropertyName;
if (SemanticPropertyCheck(_property, propertyName))
{
BuildBindings(propertyName, name, Values);
}
else
{
Bindings.Add(new Binding()
{
Path = $"[{name}|{Values}]",
Source = InternalValue.Instance
});
}
}
return new MultiBinding()
{
Bindings = Bindings,
StringFormat = StringFormat
};
}
bool SemanticPropertyCheck(BindableProperty property, string name) =>
property.DeclaringType.Name.Equals("SemanticEffect") && (name.Equals("Description") || name.Equals("Hint"));
void BuildBindings(string propertyName, string name, string valueString)
{
if (!string.IsNullOrEmpty(valueString) && !string.IsNullOrEmpty(propertyName))
{
switch (propertyName)
{
case "Description":
if (valueString.Contains('|'))
{
var values = (valueString.Split(new char[] { '|' }) as IEnumerable<string>).ToList();
values.ForEach(s =>
{
var index = values.IndexOf(s);
Bindings.Add(CreateBinding(name, s.Trim(), false));
if (index > 0)
StringFormat += $", {{{index}}}";
});
}
else
{
Bindings.Add(CreateBinding(name, valueString, false));
}
break;
case "Hint":
Bindings.Add(CreateBinding(name, valueString, true));
break;
default:
break;
}
}
}
BindingBase CreateBinding(string name, string key, bool isHint)
{
var prefix = "ViewModel.";
return (key.Contains(prefix))
? new Binding() { Path = key.TrimStart(prefix.ToCharArray()) }
: new Binding() { Path = $"[{name}|{key}]", Source = InternalValue.Instance };
}
sealed class InternalValue
{
readonly static IMvxTextProvider _textProvider = Provider._textProvider;
public static InternalValue Instance { get; } = new InternalValue();
public static InternalValue HintInstance { get; } = new InternalValue() { _isHint = true };
bool _isHint { get; set; } = false;
public string this[string _nameKey] => GetText(_nameKey.Split('|'));
private string GetText(string[] nameKey)
{
var name = nameKey[0];
var key = nameKey[1];
var prefix = _isHint
? "Double tap to "
: string.Empty;
var appliedText = _textProvider.GetText(name, key);
return $"{prefix}{appliedText}";
}
}
}
下面是 MvxLang 的修改实现。
我的目标是能够使用存储在
我希望使用以下语法:
xct:SemanticEffect.Description="{mvx:MvxLang ViewModel.SomeStringFromCsFile | SomeStringFromJsonFile | ViewModel.SomeOtherStringFromCsFile}"
这样我们的 ViewModels/Xaml 就不会被屏幕 reader 文本 logic/markup.
臃肿当仅从
调用 GetValue() 时,我的问题出现在这段代码中,我可以 return 值,但似乎在 ViewModel:
之前调用了 IMarkupExtensionsvar 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;
}
有没有办法 return 这里的运行时值?
这是代码的其余部分:
[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;
}
}
在意识到 IMarkupExtension class 在 ViewModel 设置其属性之前触发后,最终采用了此解决方案。
[ContentProperty(nameof(Values))]
public class Provider : IMarkupExtension<MultiBinding>
{
readonly static IMvxTextProvider _textProvider = Mvx.IoCProvider.Resolve<IMvxTextProvider>();
public string Values { set; get; }
IList<BindingBase> Bindings { get; set; } = new List<BindingBase>();
string StringFormat { get; set; } = "{0}";
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider) => ProvideValue(serviceProvider);
public MultiBinding ProvideValue(IServiceProvider serviceProvider)
{
var valueProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
var _property = valueProvider?.TargetProperty as BindableProperty;
object rootObject = 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();
var name = string.Empty;
var page = (VisualElement)rootObject;
name = page.GetType().BaseType.GetGenericArguments()[0].Name;
if (!string.IsNullOrEmpty(name))
{
var propertyName = _property.PropertyName;
if (SemanticPropertyCheck(_property, propertyName))
{
BuildBindings(propertyName, name, Values);
}
else
{
Bindings.Add(new Binding()
{
Path = $"[{name}|{Values}]",
Source = InternalValue.Instance
});
}
}
return new MultiBinding()
{
Bindings = Bindings,
StringFormat = StringFormat
};
}
bool SemanticPropertyCheck(BindableProperty property, string name) =>
property.DeclaringType.Name.Equals("SemanticEffect") && (name.Equals("Description") || name.Equals("Hint"));
void BuildBindings(string propertyName, string name, string valueString)
{
if (!string.IsNullOrEmpty(valueString) && !string.IsNullOrEmpty(propertyName))
{
switch (propertyName)
{
case "Description":
if (valueString.Contains('|'))
{
var values = (valueString.Split(new char[] { '|' }) as IEnumerable<string>).ToList();
values.ForEach(s =>
{
var index = values.IndexOf(s);
Bindings.Add(CreateBinding(name, s.Trim(), false));
if (index > 0)
StringFormat += $", {{{index}}}";
});
}
else
{
Bindings.Add(CreateBinding(name, valueString, false));
}
break;
case "Hint":
Bindings.Add(CreateBinding(name, valueString, true));
break;
default:
break;
}
}
}
BindingBase CreateBinding(string name, string key, bool isHint)
{
var prefix = "ViewModel.";
return (key.Contains(prefix))
? new Binding() { Path = key.TrimStart(prefix.ToCharArray()) }
: new Binding() { Path = $"[{name}|{key}]", Source = InternalValue.Instance };
}
sealed class InternalValue
{
readonly static IMvxTextProvider _textProvider = Provider._textProvider;
public static InternalValue Instance { get; } = new InternalValue();
public static InternalValue HintInstance { get; } = new InternalValue() { _isHint = true };
bool _isHint { get; set; } = false;
public string this[string _nameKey] => GetText(_nameKey.Split('|'));
private string GetText(string[] nameKey)
{
var name = nameKey[0];
var key = nameKey[1];
var prefix = _isHint
? "Double tap to "
: string.Empty;
var appliedText = _textProvider.GetText(name, key);
return $"{prefix}{appliedText}";
}
}
}