4

I'm trying to get a binding to work on a child object of a user control. The Xaml looks like this:

<MyGrid>
    <MyColumn ExtendedColumnData="{Binding ColumnToolTipDescriptions}"/>
</MyGrid>

Here is how the classes are defined:

[ContentProperty("Columns")]
public class MyGrid : UserControl
{
    private MyColumnCollection _columns;

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content), Category("Data")]
    public MyColumnCollection Columns
    {
        get
        {
            if (_columns == null)
                _columns = new MyColumnCollection();

            return _columns;
        }
    }
}

public class MyColumnCollection : ObservableCollection<MyGridColumn>
{
}

public class MyGridColumn : DependencyObject
{
    public object ExtendedColumnData
    {
        get { return (object)GetValue(ExtendedColumnDataProperty); }
        set { SetValue(ExtendedColumnDataProperty, value); }
    }

    public static readonly DependencyProperty ExtendedColumnDataProperty =
        DependencyProperty.Register("ExtendedColumnData", typeof(object), typeof(MyGridColumn), new UIPropertyMetadata(null));
}

From what I can tell, the binding is not even attempting to get the data as I've tried putting a converter against the binding, and the breakpoint on the Convert method never gets hit.

I'm using the MVVM pattern so the window's DataContext property is set to a view model.

I've read some other questions on here and tried various permutations of the binding such as:

<MyColumn ExtendedColumnData="{Binding DataContext.ColumnToolTipDescriptions, ElementName=MyViewName}" />
<MyColumn ExtendedColumnData="{Binding DataContext.ColumnToolTipDescriptions, RelativeSource={RelativeSource AncestorType={x:Type local:MyView}}" />

But still no luck, the binding doesn't fire! The annoying thing is, this seems to work fine (if I add the property to the grid):

<MyGrid ExtendedColumnData="{Binding ColumnToolTipDescriptions}">
    <MyColumn />
</MyGrid>

I'm not that experienced with WPF so I'm sure I'm missing something?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
David Masters
  • 8,069
  • 2
  • 44
  • 75

2 Answers2

2

The problem is that MyColumnCollection is not inheriting data context (usual properties of a control are not part of inheritance context). If you don't have a data context bindings will not work.

To fix that, try inheriting MyColumnCollection not from ObservableCollection, but from FreezableCollection (freezable properties are part of inheritance context).

Pavlo Glazkov
  • 20,498
  • 3
  • 58
  • 71
  • I have changed it to a FreezableCollection but still doesn't seem to work. I don't need to manually set the DataContext do I? – David Masters Feb 10 '11 at 12:17
  • Oh, forgot about MyGridColumn... You also have to change the base class for it to Freezable. And DataContext you only have to set on your window or UserControl that contains MyGrid. It has to be the object with ColumnToolTipDescriptions property. – Pavlo Glazkov Feb 10 '11 at 12:21
0

The problem is logical tree. The direct solution should be (adapted to your example):

public MyColumnCollection Columns
{
    get
    {
        if (_columns == null)
        {
            _columns = new MyColumnCollection();
            _columns.CollectionChanged += Columns_CollectionChanged;
        }
        return _columns;
    }
}

private void Columns_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if (e.NewItems != null)
    {
        foreach (var item in e.NewItems)
        {
            AddLogicalChild(item);
        }
    }
    if (e.OldItems != null)
    {
        foreach (var item in e.OldItems)
        {
            RemoveLogicalChild(item);
        }
    }
    // not sure about Action == reset
}

protected override IEnumerator LogicalChildren
{
    get
    {
        return _columns == null ? null : _columns.GetEnumerator();
    }
}

to maintain the logical tree of controls in order for dataContext to be propagated to children. When you call AddLogicalChild, it marks MyGrid as containing logical children, then LogicalChildren will be read and the dataContext of those children will be set (you can listen to DataContextChanged event in them). Overriding LogicalChildren is essential because FrameworkElement doesn't keep the list of children through AddLogicalChild and RemoveLogicalChild calls, oddly.

jiping-s
  • 483
  • 1
  • 5
  • 13