0
votes

I am factoring some code into UserControls which parameters are bound when consumed. I am meeting difficulties with the use of ObservableCollection as a DependencyProperty.

The example showing the difficulty is a project consisting in a MainWindow with two DependencyProperty:

  1. one representing a String (named "Data") and
  2. another one representing an ObservableCollection (named "Origin");

and a UserControl (named UserControl1) exposing two similar DependencyProperty (named resp. "Liste" and "Noun").

The MainWindow contains a TextBlock which Text is bound to "Data" and a ComboBox which ItemsSource is bound to "Origin". Both are working fine. Both controls are factored into UserControl1, with the DependencyProperty "Liste" and "Noun" acting as intermediate, and UserControl1 is consumed in MainWindow.

Each DataContext (of MainWindow and of UserControl1) is set to "this".

The trouble is while the factored TextBlock (within UserControl1) is working and showing the content of "Data", the factored ComboBox is not working and its DropDown is empty.

The code of MainWindow.xaml is:

<Window x:Class="ChainedBindingUserControl.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow"
    Height="350" Width="525"
    xmlns:Local="clr-namespace:ChainedBindingUserControl"
    >
    <StackPanel>
        <TextBlock Text="{Binding Data}"
                   Width="150"
                   />
        <ComboBox ItemsSource="{Binding Origin}"
                   Width="150"
                  />
        <Label Content="--------------------------------------------------"
               Width="200"
              />
        <Local:UserControl1 Liste="{Binding Origin}"
                            Noun="{Binding Data}"
                            Height="50" Width="150"
                            />
    </StackPanel>
</Window>

Its code behind is :

namespace ChainedBindingUserControl
{
    public partial class MainWindow : Window
    {
        public ObservableCollection<String> Origin
        {
            get { return (ObservableCollection<String>)GetValue(OriginProperty); }
            set { SetValue(OriginProperty, value); }
        }
        public static readonly DependencyProperty OriginProperty =
            DependencyProperty.Register("Origin", typeof(ObservableCollection<String>), typeof(MainWindow),
                    new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));

        public String Data
        {
            get { return (String)GetValue(DataProperty); }
            set { SetValue(DataProperty, value); }
        }
        public static readonly DependencyProperty DataProperty =
            DependencyProperty.Register("Data", typeof(String), typeof(UserControl1),
                    new FrameworkPropertyMetadata("Blablabla", FrameworkPropertyMetadataOptions.AffectsRender));



        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;

            ObservableCollection<String> zog = new ObservableCollection<String>();
            zog.Add("A");
            zog.Add("B");
            zog.Add("C");

            Origin = zog;
        }
    }
}

The file UserControl1.xaml is :

<UserControl x:Class="ChainedBindingUserControl.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d"
             Name="root"
             d:DesignHeight="300" d:DesignWidth="300">
    <StackPanel>
        <TextBlock Text="{Binding Noun}"
                   />
        <ComboBox ItemsSource="{Binding Liste}"
                  />
    </StackPanel>
</UserControl>

Its code behind is :

namespace ChainedBindingUserControl
{
    public partial class UserControl1 : UserControl
    {
        public ObservableCollection<String> Liste
        {
            get { return (ObservableCollection<String>)GetValue(ListeProperty); }
            set { SetValue(ListeProperty, value); }
        }
        public static readonly DependencyProperty ListeProperty =
            DependencyProperty.Register("Liste", typeof(ObservableCollection<String>), typeof(UserControl1),
                    new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));

        public String Noun
        {
            get { return (String)GetValue(NounProperty); }
            set { SetValue(NounProperty, value); }
        }
        public static readonly DependencyProperty NounProperty =
            DependencyProperty.Register("Noun", typeof(String), typeof(UserControl1),
                    new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender));


        public UserControl1()
        {
            InitializeComponent();
            this.DataContext = this;
        }
    }
}

`

EDIT

According to the pieces of information and snippets provided on http://sshumakov.com/2012/11/13/how-to-create-dependency-properties-for-collections/ , I changed the code behind of UserControl1 into

    public partial class UserControl1 : UserControl
    {
        public IList Liste
        {
            get { return (List<String>)GetValue(ListeProperty); }
            set { SetValue(ListeProperty, value); }
        }
        public static readonly DependencyProperty ListeProperty =
            DependencyProperty.Register("Liste", typeof(IList), typeof(UserControl1),
                    new FrameworkPropertyMetadata(new List<String>(), FrameworkPropertyMetadataOptions.AffectsRender));

        public String Noun
        {
            get { return (String)GetValue(NounProperty); }
            set { SetValue(NounProperty, value); }
        }
        public static readonly DependencyProperty NounProperty =
            DependencyProperty.Register("Noun", typeof(String), typeof(UserControl1),
                    new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender));


        public UserControl1()
        {
            InitializeComponent();
            this.DataContext = this;
            SetValue(ListeProperty, new List<String>());
        }
    }

but it is still not working.

The trouble doesn't come from the DataContext since the TextBlock works as expected.

The trouble here is specific: why a DependecyProperty acting as an intermediate for Binding is working when the property is of type String while it doesn't work when it is of type ObservableCollection (or List, etc).

Thanks in advance for any explanation.

2
ItemsSource="{Binding Liste}" is not correct "Liste" is a dependency property. you cant use it for setting another dependency property "ItemSouce" sshumakov.com/2012/11/13/… - Abin
OK, so, how could I implement a UserControl which has a DependencyProperty which represents a collection (ObservableCollection or List or etc) in order for this DependencyProperty to be bound when the UserControl is consumed and bound within the UserControl? Thanks in advance for any clue on the hows. - Julien Ferté
I believe you have gone thru the link i have given above. its an example of "How to create Dependency Properties for Collections" hope this helps - Abin
OK, I tried to re-run my project but it is not working anymore. I give up. - Julien Ferté

2 Answers

0
votes

Your problem is in the UserControl's xaml, here:

<TextBlock Text="{Binding Noun}"
           />
<ComboBox ItemsSource="{Binding Liste}"
          />

These binding expressions are attempting to locate Noun and Liste properties on the DataContext of your UserControl, not on the UserControl itself. You need to specify a different target. Since you've already named your UserControl element, you can replace the bindings with this:

<TextBlock Text="{Binding ElementName=root, Path=Noun}"
           />
<ComboBox ItemsSource="{Binding ElementName=root, Path=Liste}"
          />
0
votes

Imagine that you are creating control that has property that accepts collection:

 public class CustomControl : Control
 { 
 public IEnumerable<string> Items { get; set; } 
 }

If you want property Items to act as binding target you must change it to be dependency property:

public class CustomControl : Control
{
 public static readonly DependencyProperty ItemsProperty = 
             DependencyProperty.Register("Items", typeof(IEnumerable<string>), typeof (CustomControl), new PropertyMetadata(new List<string>())); 

     public IEnumerable<string> Items 
     { 
         get { return (IEnumerable<string>) GetValue(ItemsProperty); } 
         set { SetValue(ItemsProperty, value); } 
     } 
}

As you can see, we changed this property to dependency property and supplied new instance of List class as default parameter. As it turned out, this default value will be used on class level (i.e. it will be created only once and each instance of CustomControl will have reference to the same collection). Therefore, we need one modification:

public class CustomControl : Control
 {
     public CustomControl() 
     {
        Items = new List<string>(); 
     }
 }

Now you can use this control and supply value for Items property via binding:

<Grid> 
 <DependencyPropertiesCollection:CustomControl Items="{Binding   ItemsSource}"/> 
</Grid>

Currently this control has one limitation – Items property can’t be filled directly in XAML like this code does:

<Grid>
   <DependencyPropertiesCollection:CustomControl> 
       <DependencyPropertiesCollection:CustomControl.Items> 
          <System:String>Item 1</System:String> 
          <System:String>Item 2</System:String> 
          <System:String>Item 3</System:String> 
          <System:String>Item 4</System:String> 
          <System:String>Item 5</System:String>
       </DependencyPropertiesCollection:CustomControl.Items>
  </DependencyPropertiesCollection:CustomControl> 
 </Grid>

To fix this, you need to change property type from IEnumerable to IList:

public class CustomControl : Control  
{ 
      public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof (IList), typeof (CustomControl), new PropertyMetadata(new List<string>())); 

  public IList Items 
  { 
       get { return (IList)GetValue(ItemsProperty); } 
       set { SetValue(ItemsProperty, value); }
  } 

  public CustomControl() 
  {
        Items = new List<string>(); 
  } 
 }

Credits:- http://sshumakov.com/2012/11/13/how-to-create-dependency-properties-for-collections/