1
votes

Ok, this missing binding is driving me nuts, can you help? I've isolated the problem to a simple situation. I'm sure I'm missing something obvious, but it's been a few hours now...

In the user control.xaml below I get no binding failure on the textblock binding, but do on the first collection container binding, with complaint that "Cannot find source: RelativeSource FindAncestor AncestorType=`System.Windows.Controls.UserControl,AncestorLevel='1'. Definitely there is an observablecollection property TheWeeksBlocks instantiated in the viewmodel TimeTableViewModel

In the usercontrol TimeTableView.xaml I have

...
<UserControl.DataContext>
        <local:TimeTableViewModel/>
</UserControl.DataContext>
...
<Grid>
 <Grid.RowDefinitions>
  <RowDefinition Height="Auto"/>
  <RowDefinition Height="Auto"/> 
 </Grid.RowDefinitions>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}},Path=DataContext.MyString}" Grid.Row="0"/>

<ItemsControl  Name="TheVisualizationPane" Visibility="Visible"  VerticalAlignment="Top" Grid.Row= "1">
<ItemsControl.ItemsSource>
  <CompositeCollection>
      <CollectionContainer Collection="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}},Path=DataContext.TheWeeksBlocks}"/>
      <CollectionContainer Collection="{Binding Source={x:Static local:SchedulingParameters.ReferenceBlocks}}"/>
  </CompositeCollection>
</ItemsControl.ItemsSource>
... subsequent itemspanel, etc etc...
</ItemsControl>
</Grid>

Both the textblock binding and the compositecollection binding refer to the same parent usercontrol. What's going on? I've tried messing with ancestorlevel etc, not that that should make a difference here.

Edit: changed the title of the question after solutions provided, may be more helpful to someone in the future.

2
Why do you set RelativeSource at all, when the DataContext is already set? Seems entirely redundant.Clemens
@Clemens I agree. Using <CollectionContainer Collection="{Binding TheWeeksBlocks}"/> was my first choice, but then I get the binding failure "Cannot find FrameworkElement or FrameworkContentElement for target element" So the RelativeSource was an attempt around that. Still don't understand the difference between the two bindings...user3486991

2 Answers

1
votes

This is a long-standing problem.
For some unknown reason, the CompositeCollection is implemented directly from Object, and the CollectionContainer class is implemented from DependencecyObject.
And for a resource (and not a UI element) to be included in the visual tree, the object must derive from Freezable.

In this case, since the CompositeCollection is not included in the visual tree, searching up the visual tree (this is what the Binding AncestorType is doing) will yield nothing.

This can be solved by using an additional intermediate static bridge, a proxy. CollectionViewSource is very often used for this purpose, but a more general approach with a custom implementation proxy can be used.

Specifically for your task, you don't even need this, since you are instantiating the ViewModel in XAML. You can create it not in DataContex, but in resources.

Example:

<UserControl -----
    --------------
    DataContext="{DynamicResource viewModel}">
    <UserControl.Resources>
        <local:TimeTableViewModel x:Key="viewModel"/>
    </UserControl.Resources>
    <CompositeCollection>
        <CollectionContainer Collection="{Binding Source={StaticResource viewModel},
                                                  Path=TheWeeksBlocks}"/>
        <CollectionContainer Collection="{Binding Source={x:Static local:SchedulingParameters.ReferenceBlocks}}"/>
    </CompositeCollection>

Perhaps in the future you will find it useful to have a more general solution when you cannot refer directly to the ViewModel in XAML through static resources. An example of a solution using the Proxy class:

<UserControl.Resources>
        <proxy:Proxy x:Key="dataContext" Value="{Binding}"/>
</UserControl.Resources>
    <CompositeCollection>
        <CollectionContainer Collection="{Binding Source={StaticResource dataContext},
                                                  Path=Value.TheWeeksBlocks}"/>
        <CollectionContainer Collection="{Binding Source={x:Static local:SchedulingParameters.ReferenceBlocks}}"/>
    </CompositeCollection>
0
votes

It's not necessary to use a custom proxy class as suggested in another answer. This custom proxy class is redundant in 99.99% of problems where it is suggested as solution. It overcomplicates the simple solution using knowledgs of the WPF framework and existing library classes.

The problem is that the CompositeCollection is not part of the visual tree. Therefore it requires a static reference to the source data in order to be correctly constructed during the XAML parsing.
You can use a CollectionViewSource as this static data provider or make the source collection static:

Using CollectionViewSource

<UserControl>
  <UserControl.DataContext>
    <local:TimeTableViewModel/>
  </UserControl.DataContext>

  <UserControl.Resources>
    <CollectionViewSource x:Key="TheWeeksBlocksSource" Source="{Binding TheWeeksBlocks}" />
    
    <CompositeCollection x:Key="CompositeCollectionSource">
      <CollectionContainer Collection="{Binding Source={StaticResource TheWeeksBlocksSource}}" />
    </CompositeCollection>
  </Window.Resources>

  <ListBox ItemsSource="{Binding Source={StaticResource CompositeCollectionSource}}" />
</UserControl>

Using staic ObservableCollection
This example assumes that TimeTableViewModel.TheWeeksBlocks is a static ObservableCollection.

<UserControl>
  <UserControl.DataContext>
    <local:TimeTableViewModel/>
  </UserControl.DataContext>
    
    <CompositeCollection x:Key="CompositeCollectionSource">
      <CollectionContainer Collection="{x:Static local:TimeTableViewModel.TheWeeksBlocks}" />
    </CompositeCollection>
  </Window.Resources>

  <ListBox ItemsSource="{Binding Source={StaticResource CompositeCollectionSource}}" />
</UserControl>