1

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.

user3486991
  • 451
  • 6
  • 14
  • Why do you set RelativeSource at all, when the DataContext is already set? Seems entirely redundant. – Clemens Aug 20 '21 at 19:34
  • @Clemens I agree. Using 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 Aug 20 '21 at 19:40

2 Answers2

1

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>
EldHasp
  • 6,079
  • 2
  • 9
  • 24
  • Now that is helpful! Exactly the issue. Thanks! – user3486991 Aug 20 '21 at 20:06
  • I supplemented my answer with the second option. Read it - it may be useful to you too. – EldHasp Aug 20 '21 at 20:22
  • You almost never need this Proxy class. It somehow shows some lack of understanding. All you need is a static reference, because (as you stated correctly) the CompositeCollection is not part of the visual tree. This has nothing gdo do with implementing Freezable. ItemsSource is a simple property and not an element of the visual tree. Therefore the value assigned to ItemsSource can never be part of the visual tree. The ItemsControl internally creates UIElements for each data item and then adds it to its visual sub tree. It's enough to make use of a CollectionViewSource as static data provider. – BionicCode Aug 21 '21 at 09:13
  • I wrote about CollectionViewSource in my answer. To give an example with him, in my opinion, does not make sense, since there are a lot of them on the Internet. The implementation with Proxy allows to get a static link to the entire ViewModel or to any desired property, not just the collection. For this task, this is somewhat redundant. Which I also wrote about. And the example is given only for the purpose of showing possible other options. Their knowledge can be useful for TC in other tasks. – EldHasp Aug 21 '21 at 09:21
  • I see, you are right about mentioning CollectionViewSource. But the Proxy is redundant here as you could have used the CollectionViewSource that you have suggested. It even simplifies the binding. It doesn't make much sense to add a static resource link to the view model if you only need the collection. I like to keep it simple. – BionicCode Aug 21 '21 at 09:32
  • Your answer is not wrong. The point is it's good to know how to do it with the means you got on board. CollectionViewSource is exactly the sort of proxy you need here. The point is that you should learn to understand how the framework works instead of copy & pasting some classes from some doubtful source. If you can use a library class then why use an external class? This can introduce more problems. The solution is already there. A custom proxy class to capture the data context is almost never necessary. – BionicCode Aug 21 '21 at 09:39
0

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>
BionicCode
  • 1
  • 4
  • 28
  • 44