【问题标题】:Xamarin IMarkupExtension, Get ViewModel Property ValuesXamarin IMarkupExtension,获取 ViewModel 属性值
【发布时间】:2022-01-19 14:16:36
【问题描述】:

下面是 MvxLang 的修改实现。

我的目标是能够使用存储在我们项目资源中的 .json 文件中的现有字符串值以及从 .cs 文件中检索到的动态生成的文本来简洁地实现屏幕阅读器文本。

我希望使用以下语法:

xct:SemanticEffect.Description="{mvx:MvxLang ViewModel.SomeStringFromCsFile | SomeStringFromJsonFile | ViewModel.SomeOtherStringFromCsFile}"

这样,我们的 ViewModels/Xaml 就不会因屏幕阅读器文本逻辑/标记而臃肿。

当仅从 .json 文件中检索字符串值时,我的实现工作正常,但我也希望使用 .cs 文件中的各种值...

在调用GetValue()时,我的麻烦出现在这段代码中,我可以返回值,但似乎在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;
}

有没有办法在这里返回运行时值?

下面是剩下的代码:

[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;
    }
}

【问题讨论】:

  • 您介意与我们分享一个基本的、最小的测试项目吗?您可以将其上传到github并在此处附上链接。
  • 原来创建样本比较麻烦,但我在下面有一个解决方案。

标签: c# xamarin viewmodel semantic-markup getvalue


【解决方案1】:

在意识到 IMarkupExtension 类在 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}";
        }
    }
}

【讨论】:

    猜你喜欢
    • 2017-07-27
    • 2012-10-01
    • 2021-01-03
    • 2011-11-30
    • 2017-01-18
    • 2019-04-02
    • 2017-11-25
    • 2019-05-31
    • 1970-01-01
    相关资源
    最近更新 更多