Bindings on child dependency object of usercontrol

2020-03-25 04:36发布

问题:

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?

回答1:

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).



回答2:

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.