0
votes

I have this (simplified) class which has dynamic members exposed via a IDynamicMetaObjectProvider:

public abstract class MyDynViewModel: ViewModelBase, IDynamicMetaObjectProvider
{
    public DynamicMetaObject GetMetaObject(Expression parameter)
    {
        return new MyDynViewModelDynamicMetaObject(parameter, this);
    }

    public object GetDynamicObject(string name)
    {
        return GetChild(name) ?? GetCommand(name);
    }
}

Here is my (simplified) BindGetMember method:

public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
{
    var self = Expression.Convert(Expression, LimitType);
    Expression expression;
    var propertyName = binder.Name;
    var args = new Expression[1];

    args[0] = Expression.Constant(propertyName);
    expression = Expression.Call(self, typeof(MyDynViewModel).GetMethod("GetDynamicObject", BindingFlags.Public | BindingFlags.Instance), args);

    var getMember = new DynamicMetaObject(expression, BindingRestrictions.GetTypeRestriction(Expression, LimitType));
    return getMember;
}

Basically, GetChild(string) and GetCommand(string) return null if there is no child/command that matches the name.

What happens here is that if I bind a non-existent child/command name in a XAML, the binding will successfully resolve to a null value.

However, the behavior I would like to have is that the binding does not successfully resolve. The reason is that it would allow me to use PriorityBinding.

So far, I got two solutions:

  • throw a RuntimeBinderException if there is no matching child/command name. This is what the dynamic object does when you try to access an invalid member. But I think it's a bit too heavy here.
  • return DependencyProperty.UnsetValue, which is enough to have a PriorityBinding work (as explained in the documentation).

However, none of these solution lead to the usual binding error message:

System.Windows.Data Warning: 40 : BindingExpression path error: '****' property not found on 'object' ''Object' (HashCode=13794971)'. BindingExpression:Path=****; DataItem='Object' (HashCode=13794971); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')

So, any idea/advice on the best way to achieve this behavior?

1

1 Answers

2
votes

As far I can tell, the closest you're going to get is this:

System.Windows.Data Error: 17 : Cannot get 'EX' value (type 'Object') from '' (type 'Test'). BindingExpression:Path=EX; DataItem='Test' (HashCode=11280399); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String') InvalidOperationException:'System.InvalidOperationException: Property path is not valid. 'System.Dynamic.DynamicObject+MetaDynamic' does not have a public property named 'Items'.

Now, how is tricky question. I see you have quite complicated code, that's because you're creating everything from zero. Have you considered this:

public class TestViewModel : DynamicObject
{
   public override bool TryGetMember(GetMemberBinder binder, out object result)
   {
       result = null;
       return false; // if we didn't find member.

   }
}

instead?

If that does not suit your needs, first, you'll want to modify your GetDynamicObject signature, with an extra parameter "result".

you need to wrap the functionality in your Expression, the pseudo-code:

public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
{
    var self = Expression.Convert(Expression, LimitType);
    Expression expression;
    var propertyName = binder.Name;
    var args = new Expression[1];

    args[0] = Expression.Constant(propertyName);
    expression = Expression.Call(self, typeof(MyDynViewModel).GetMethod("GetDynamicObject", BindingFlags.Public | BindingFlags.Instance), args);

    expression = "if GetDynamicObject returned false, means we found nothing.
          then we should return binder.FallbackGetMember(this, e)";

    var getMember = new DynamicMetaObject(expression, BindingRestrictions.GetTypeRestriction(Expression, LimitType));
    return getMember;
}

You can check PropertyPath.cs DLR implementation, and track it:

// Define other methods and classes here
// 6. IDynamicMetaObjectProvider
// This supports the DLR's dynamic objects
if (accessor == null && 
    SystemCoreHelper.IsIDynamicMetaObjectProvider(item))
{
    accessor = SystemCoreHelper.NewDynamicPropertyAccessor(
                    item.GetType(), propertyName);
}