0
votes

I have created custom control with generic collection dependency property. But whenever this property is changed from xaml, value in view model in setter is null.

CustomControl:

   public IList A       
   {
            get { return (IList)GetValue(AProperty); }
            set { SetValue(AProperty, value); }
   }

   public static readonly DependencyProperty AProperty =
            DependencyProperty.Register(nameof(A), typeof(IList), typeof(CustomControl), new PropertyMetadata(new List<object>()));

ViewModel:

  List<B> collectionB;
  public List<B> CollectionB
        {
            get { return collectionB; }
            set
            {
                if (collectionB == value) return;
                collectionB = value;
            }
        }
        

If I change type of CollectionB to List<object> it works fine.

2
You haven't shown us what exactly "changed from xaml" means. Is there a binding like A="{Binding CollectionB, Mode=TwoWay}"? And how does the control change the collection?Clemens
The binding was A="{Binding CollectionB, Mode=OneWayToSource}"Adam Stawarek
The default value (see my remarks in the answer) is of type List<object>. That can't be assigned to a source property of type List<B>. OneWayToSource is not a well-suited approach here. Do not set a default value at all and use a TwoWay Binding instead.Clemens
@AdamStawarek: Of course you can set an IList property to a List<A>. If the property is still null you are doing something wrong when you set it, or maybe you don't set it all. It's hard to say without some sample code.mm8

2 Answers

1
votes

The most generic type of a collection property is IEnumerable - see for example the ItemsControl.ItemsSource property.

Also be aware that setting a non-null default value for a collection-type dependency property is problematic, because all instances of the owning class would by default operate on the same collection instance. Adding an element to the A property of one control would change the collection of all other control instances.

You should declare the property like this:

public IEnumerable A       
{
    get { return (IEnumerable)GetValue(AProperty); }
    set { SetValue(AProperty, value); }
}

public static readonly DependencyProperty AProperty =
    DependencyProperty.Register(
        nameof(A), typeof(IEnumerable), typeof(CustomControl));

In case you really need a non-null default value, add this to the control's constructor:

SetCurrentValue(AProperty, new List<object>());

Update: Using a OneWayToSource Binding of a collection type property like

<local:CustomControl A="{Binding CollectionB, Mode=OneWayToSource}" />

can only work if the default value of A is assignment-compatible with the source property of the Binding, which does not hold true for List<object> and List<B>.

You should instead not set a default value at all, and instead use a TwoWay Binding

<local:CustomControl A="{Binding CollectionB, Mode=TwoWay}" />

with

private List<B> collectionB = new List<B>();

public List<B> CollectionB
{
    get { return collectionB; }
    set { collectionB = value; }
}

or just

public List<B> CollectionB { get; set; } = new List<B>();
0
votes

That's because IList<T> does not implement IList so casting from one type to the other will fail.

If you want a binding with A then you must bind it to something that's also implementing IList