Home > database >  Binding generic type ItemsControl ItemsSource in custom control to DependencyProperty
Binding generic type ItemsControl ItemsSource in custom control to DependencyProperty

Time:07-28

As for question, I've created a custom UserControl. It has an ItemsControl which ItemsSource is binded to a property bindable as DependecyProperty.

MyControl XAML:

<Grid>
    <ItemsControl ItemsSource="{Binding Path=InternalControl}" >
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
</Grid>

MyControl Code:

    public static readonly DependencyProperty SetInternalControlProperty =
        DependencyProperty.Register(
            nameof(ObservableCollection<dynamic>),
            typeof(ObservableCollection<dynamic>),
            typeof(UtilityExpander));

    public ObservableCollection<dynamic> InternalControl
    {
        get { return (ObservableCollection<dynamic>)GetValue(SetInternalControlProperty); }
        set { SetValue(SetInternalControlProperty, value); }
    }

Main XAML:

    <Controls:UtilityExpander InternalControl="{Binding GasControl}"/>

Main Code:

    private ObservableCollection<UtilityGas> _gasControl;
    public ObservableCollection<UtilityGas> GasControl { get => _gasControl; set { _gasControl = value; NotifyPropertyChanged(); } }

When running on the InitializeComponent() I get

System.Windows.Markup.XamlParseException: 'A 'Binding' cannot be set on the 'InternalControl' property of type 'UtilityExpander'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.'

The reason why I'm using dynamic is that I want to use the control with different type of IEnumerable<objects>

CodePudding user response:

There are two errors in the declaration of the dependency property.

The second argument of the Register method must be the name of the property, not that of the property's type. The backing field must be named like the property, with the suffix Property.

public static readonly DependencyProperty InternalControlProperty =
    DependencyProperty.Register(
        nameof(InternalControl),
        typeof(ObservableCollection<dynamic>),
        typeof(UtilityExpander));

public ObservableCollection<dynamic> InternalControl
{
    get { return (ObservableCollection<dynamic>)GetValue(InternalControlProperty); }
    set { SetValue(InternalControlProperty, value); }
}

Besides that, the property would be more usable if it would be declared as IEnumerable<dynamic>. Whether its value implements INotifyCollectionChanged or not could be checked at runtime.

The dynamic collection type argument seems also redundant. When you declare the property like shown below, you can assign any type of collection, even with different element types in a single collection instance. This is by the way how the ItemsSource property is defined.

public static readonly DependencyProperty InternalControlProperty =
    DependencyProperty.Register(
        nameof(InternalControl),
        typeof(IEnumerable),
        typeof(UtilityExpander));

public IEnumerable InternalControl
{
    get { return (IEnumerable)GetValue(InternalControlProperty); }
    set { SetValue(InternalControlProperty, value); }
}

Finally, you should also avoid to set the DataContext of your control to itself, because that breaks the standard data binding behaviour of the control's properties, e.g. InternalControl="{Binding GasControl}".

Bind to the own property in the control's XAML with an ElementName or RelativeSource Binding:

<ItemsControl ItemsSource="{Binding Path=InternalControl,
    RelativeSource={RelativeSource AncestorType=UserControl}}" >
  • Related