Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom control with cascading DataContext to child elements in collection

I'm trying to create custom user control, with similar functionality as DataGrid (but DataGrid is not the right option here).

What I'd like to achieve is something like this:

<my:CustomList ItemsSource="{Binding Items}">
    <my:CustomList.Columns>
        <my:Column Width="60" Binding="{Binding MyCustomProperty}" />
    </my:CustomList.Columns>
</my:CustomList>

where Items would come from ViewModel (for example) like this:

public ObservableCollection<Item> Items { get; set; }

public class Item
{
    public string MyCustomProperty { get; set; }
    public string MyAnotherCustomProperty { get; set; }
}

The problem I have is with binding to MyCustomProperty.

If I inherit my custom control from DataGrid and use its Columns, DataContext flows from ItemsSource to Bindings in columns just fine. I would like to do the same with my custom control, which doesn't inherit from DataGrid. What is the magic behind DataGrid.Columns getting the context from ItemsSource?

Edit: Let me ask another way around about this:

If I implement custom DataGridColumn

public class MyDataGridColumn : DataGridBoundColumn
{
    private Binding _bindingSubText;

    public Binding BindingSubText
    {
        get
        {
            return _bindingSubText;
        }
        set
        {
            if (_bindingSubText == value) return;
            var oldBinding = _bindingSubText;
            _bindingSubText = value;
            OnBindingChanged(oldBinding, _bindingSubText);
        }
    }

    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
        var textTextBlock = new TextBlock();
        var bindingText = Binding ?? new Binding();
        textTextBlock.SetBinding(TextBlock.TextProperty, bindingText);

        var textSubTextBlock = new TextBlock();
        var bindingSubText = BindingSubText ?? new Binding();
        textSubTextBlock.SetBinding(TextBlock.TextProperty, bindingSubText);

        var stackPanel = new StackPanel() { Orientation = Orientation.Vertical };
        stackPanel.Children.Add(textTextBlock);
        stackPanel.Children.Add(textSubTextBlock);

        return stackPanel;
    }

    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
    {
        // I don't want to edit elements
        return null;
    }
}

and try to use it in XAML like this:

<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <my:MyDataGridColumn Binding="{Binding MyCustomProperty}" BindingSubText="{Binding MyAnotherCustomProperty}" />
    </DataGrid.Columns>
</DataGrid>

Binding for BindingSubText propertywould still come with DataContext of DataGrid parent's, offering me Items. MyAnotherCustomProperty would have wigglies in designer, but it would work OK runtime (because of dynamic binding). My problem is when someone else will use this custom DataGridColumn, he/she would need to know that, and would have "wrong" IntelliSense for the binding.

How is context for Binding property of DataGridColumn set, so that IntelliSense works as expected?

like image 820
Janez Lukan Avatar asked Dec 15 '14 14:12

Janez Lukan


1 Answers

Your question is really broad but first of all if you wish to extend DataGrid consider overriding its style and add triggers or something like that.

Its the matter in your design.. and its hard to tell which right or wrong here.

The columns property in DataGrid and in GridView is more or less just a dummy object holding values which cells later will need eg cell templates, binding, width, height... etc. Lets say it like this columns do not have true DataContext...

The DataContext is passed down the funcional visual/logical tree but columns property are not there. Thats why you have your problems. All binding which you may set in such a column object are actually place holders. However cells take part in visual tree, since they are an own control, and so once a cell is generated they do kinda stuff like this before they get drawn: cellTextBlock.SetBinding(TextBlock.TextProperty, columnBindingPlaceHolder), cellTextBlock.SetBinding(TextBlock.HeightProperty, columnHeightPlaceHolder).

Cells use those placeholders from columns

If you wish however with your own custom columns property to have the same DataContext as DataGrid consider changing ObseravableCollection to FreezableCollection. Also make the Column object a Freezable object. Just work with Freezables. Because in WPF they have the ability to inherit DataContext. Brushes in Wpf are freezables for example.

Hope this helps you any futher.

like image 66
dev hedgehog Avatar answered Oct 24 '22 00:10

dev hedgehog