XAML使用多个参数绑定到索引器

时间:2017-05-02 15:34:21

标签: c# wpf xaml

我试图将一些属性绑定到具有多个参数的索引器。

public decimal this[CalculationType calcType, AmountSource source]
{
    get
    {
        var foundRemittance = this.RemittanceAmounts.FirstOrDefault(a => a.CalculationType == calcType);
        if (foundRemittance != null)
        {
            return foundRemittance[source];
        }
        return 0.0m;
    }
}

我的约束力:

Value="{Binding Path=WorkingEntity.AmountDetails[{x:Static edm:CalculationType.RRQRPC}\,{x:Static edm:AmountSource.Applicable}]}"

无论我做什么,价值都不会出现。

The whole code Here

索引器从Watch:

返回值

enter image description here

1 个答案:

答案 0 :(得分:1)

我对此进行了测试,发现绑定使用带有两个int参数的索引属性(例如this[int x, int y]),但它不能与枚举一起使用。我将PresentationTraceSources.TraceLevel=High放在绑定上,对于枚举索引器,它告诉我At level 1 - for AmountDetails[] found accessor <null> - 而附近的绑定在同一个类的同一个实例上找到[int,int]索引器重载没有问题。

如果我添加一个索引器public String this[object a, object b],它将使用字符串"{x:Static local:CalculationType.RRQRPC}“和"{x:Static local:AmountSource.Applicable}"调用两个索引器参数。因此XAML解析器不会解析那些{{1}事情。

我是这样做的。它不像你想要的那么干净,但它有效。

如果需要绑定两个索引器参数的不同值,可以编写一个类似的多值转换器并使用多重绑定。

x:Static

XAML:

public class AmountDetailsIndexer : MarkupExtension, IValueConverter
{
    public AmountDetailsIndexer()
    {
    }

    public AmountDetailsIndexer(CalculationType ctype, AmountSource asource)
    {
        CalculationType = ctype;
        AmountSource = asource;
    }

    public CalculationType CalculationType { get; set; }
    public AmountSource AmountSource { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }

    public object Convert(Object value, Type type, Object converterParameter, System.Globalization.CultureInfo cultureInfo)
    {
        var details = value as AmountDetails;

        return details[CalculationType, AmountSource];
    }

    public object ConvertBack(Object value, Type type, Object converterParameter, System.Globalization.CultureInfo cultureInfo)
    {
        throw new NotImplementedException();
    }
}

使用这种类型的东西,或者使用多值转换器,我认为编写使用反射的广义多索引器应该是相对微不足道的。

更新

这是使用反射的通用版本。

<!-- Constructor with parameters -->
<Label 
    Content="{Binding AmountDetails, 
              Converter={local:AmountDetailsIndexer RRQRPC, Applicable}}"
    />

<!-- 
Parameterless constructor. More typing, but here you get intellisense 
when you set the properties 
-->
<Label 
    Content="{Binding AmountDetails, 
              Converter={local:AmountDetailsIndexer CalculationType=RRQRPC, AmountSource=Applicable}}"
    />

XAML:

public class Indexer : MarkupExtension, IValueConverter
{
    public Indexer(object a0)
    {
        _arguments.Add(a0);
    }
    public Indexer(object a0, object a1)
    {
        _arguments.Add(a0);
        _arguments.Add(a1);
    }
    public Indexer(object a0, object a1, object a2)
    {
        _arguments.Add(a0);
        _arguments.Add(a1);
        _arguments.Add(a2);
    }
    public Indexer(object a0, object a1, object a2, object a3)
    {
        _arguments.Add(a0);
        _arguments.Add(a1);
        _arguments.Add(a2);
        _arguments.Add(a3);
    }

    private List<object> _arguments = new List<object>();

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }

    public object Convert(Object value, Type type, Object converterParameter, System.Globalization.CultureInfo cultureInfo)
    {
        var argTypes = _arguments.Select(p => p.GetType()).ToList();

        //  Indexers with the correct number of parameters
        var sameAryIndexers = value.GetType().GetProperties()
            .Where(prop => 
                prop.Name == "Item"
                //  Must have same number of parameters
                && prop.GetIndexParameters().Length == argTypes.Count)
            .ToList();

        var indexerProperties =
            sameAryIndexers
            .Where(prop =>
                prop.GetIndexParameters()
                    .Select(pi => pi.ParameterType)
                    .Zip(argTypes, (paramType, argType) => paramType.Equals(argType))
                    .All(b => b)
            ).ToList();

        //  If no exact match, try overloads. This is sketchy, if you ask me. 
        if (indexerProperties.Count != 1)
        {
            indexerProperties =
                sameAryIndexers
                .Where(prop =>
                    prop.GetIndexParameters()
                        .Select(pi => pi.ParameterType)
                        .Zip(argTypes, (paramType, argType) => paramType.IsAssignableFrom(argType))
                        .All(b => b)
                ).ToList();
        }

        if (indexerProperties.Count != 1)
        {
            var argTypeNames = String.Join(", ", argTypes.Select(t => t.Name));

            throw new Exception($"Unable to resolve overload: Input arguments {argTypeNames}, {indexerProperties.Count} matching indexers.");
        }

        try
        {
            var x = indexerProperties.First().GetValue(value, _arguments.ToArray());

            return x;
        }
        catch (Exception ex)
        {
            return null;
        }
    }

    public object ConvertBack(Object value, Type type, Object converterParameter, System.Globalization.CultureInfo cultureInfo)
    {
        throw new NotImplementedException();
    }

    protected bool IsTypesAssignableFrom(IEnumerable<Type> to, IEnumerable<Type> from)
    {
        return to.Zip(from, (tt, tf) => tt.IsAssignableFrom(tf)).All(b => b);

    }
}