WPF binding failure within CompositeCollection - wpf

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.

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>

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>

Related

Unable to get SelectedItems through Xceed CheckListBox using MVVM

I have been using wpf xceed third party library for some of the UI components. I really like the way CheckListBox is being displayed at the screen. But I am not able to get the selectedItems bound to any property in the viewmodel (the setter never triggers). Here is the code -
I am using a dataprovider to get values from enum -
<UserControl.Resources>
<ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type sys:Enum}" x:Key="DeviceClassDataProvider">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="Model:HANDeviceClass" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
And then the control has been declared something like this -
<ext:CheckListBox Focusable="False" SelectedMemberPath="{Binding IsChecked, UpdateSourceTrigger=PropertyChanged}" SelectedItemsOverride="{Binding SelectedDeviceGroups, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Grid.Row="1" Grid.RowSpan="7" Grid.Column="4" Padding="5" BorderThickness="0.8" BorderBrush="Gray" ItemsSource="{Binding Source={StaticResource DeviceClassDataProvider}}"/>
How will i get the selected items in it's viewmodel?
Any quick help would be highly appreciated!
Thanks in advance
Should work provided that SelectedDeviceGroups is a public property that returns an ICollection<HANDeviceClass>:
public ICollection<HANDeviceClass> SelectedDeviceGroups { get; } = new ObservableCollection<HANDeviceClass>();
XAML:
<ext:CheckListBox ItemsSource="{Binding Source={StaticResource DeviceClassDataProvider}}"
SelectedItemsOverride="{Binding SelectedDeviceGroups}" />
<TextBlock Text="{Binding SelectedDeviceGroups.Count}" />
Items will be added to and removed from the source collection as you check and uncheck items respectively.

Source for CompositeCollection: why can't I bind against the data context of another control but have to use a CollectionViewSource?

In another question I recently asked, I was told to use a CompositeCollection in order to access various sources for a ListBox.
The example used a XmlDataProvider to provide some dummy data. I, however, have a view model, which contains the data.
It took me some time to bind my ListBox against the view model's data. Eventually I figured it out, but now I'd like to understand why my previous approaches didn't work.
The key to success was a CollectionViewSource. My initial attempts were:
<CollectionContainer Collection="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Movies}"/>
<CollectionContainer Collection="{Binding ElementName=Window, Path=DataContext.Movies}"/>
My idea was to find the Window, which has the appropriate DataContext, and bind against the data. You can do that via FindAncestor or via ElementName, so I tried both. That seemed very logically to me, but apparently I was wrong. I didn't see nothing when I ran the application.
I also tried binding against another control which has the data context; e.g. the StackPanel.
So, why don't I get the data with FindAncestor and ElementName1, but have to provide a CollectionViewSource explicitly?
Here's the code that is working.
<StackPanel DockPanel.Dock="Top">
<ListBox ItemTemplateSelector="{StaticResource CustomDataTemplateSelector}">
<ListBox.Resources>
<CollectionViewSource x:Key="ViewSource" Source="{Binding Movies}"/>
</ListBox.Resources>
<ListBox.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource ViewSource}}"/>
<CollectionContainer Collection="{Binding Source={StaticResource MyButtonsData}}"/>
</CompositeCollection>
</ListBox.ItemsSource>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True"
Width="{Binding (FrameworkElement.ActualWidth),
RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</StackPanel>
1 No, I didn't forget to name the window and there wasn't a typo either.
I've found a thread on microsoft.com discussing that issue.
Seems that this 'bug' is known for years, but has never been fixed.
The workaround I'm using (CollectionViewSource) is suggested there, too.
Furthermore, you can indeed not use ElementName. I don't know for what reason, but the workaround for ElementName is using x:Reference as suggested in another question's thread.
<CollectionContainer Collection="{Binding Source={x:Reference dummy}, Path=DataContext.Movies}"/>
Interestingly, the XAML compiler will show an object reference not set to an instance of an object error while editing.
It's possible to compile and run though, provided you're not using an ancestor type, because than you'll get an XmlParseException because of a circular dependency.
To avoid the circular dependency error, you can put the CompositeCollection in the resources and link to there via StaticResource. Then you can use an ancestor type, too.
<ListBox.Resources>
<CompositeCollection x:Key="CompositeCollection">
<CollectionContainer Collection="{Binding Source={x:Reference stackPanel}, Path=DataContext.Movies}"/>
</CompositeCollection>
</ListBox.Resources>
<ListBox.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource CompositeCollection}}"/>
</CompositeCollection>
</ListBox.ItemsSource>

How to set ItemsSource in XAML?

I have set ItemsSource of a ListBox as follows :
<ListBox ItemsSource="{Binding abc}" />
What I want
<ListBox>
<listBox.ItemsSource>
?????????????
<listBox.ItemsSource>
</ListBox>
<Window xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<ListBox>
<ListBox.ItemsSource>
<x:Array Type="sys:String">
<sys:String>1st item</sys:String>
<sys:String>2nd item</sys:String>
</x:Array>
<ListBox.ItemsSource>
</ListBox>
</Window>
<ListBox>
<listBox.ItemsSource>
<Binding Path = "abs" />
<listBox.ItemsSource>
</ListBox>
Xamarin Example
If you wandered into this page looking for a Xamarin example (the question seems generic to XAML), then you can try -
<Picker x:Name="picker"
Title="Select a monkey"
TitleColor="Red">
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Baboon</x:String>
<x:String>Capuchin Monkey</x:String>
<x:String>Blue Monkey</x:String>
<x:String>Squirrel Monkey</x:String>
<x:String>Golden Lion Tamarin</x:String>
<x:String>Howler Monkey</x:String>
<x:String>Japanese Macaque</x:String>
</x:Array>
</Picker.ItemsSource>
</Picker>
From -
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/picker/populating-itemssource#populating-a-picker-with-data
This uses Picker as an example, but the ItemsSource syntax is interchangeable based on the outer control, like so -
<ListView>
<ListView.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>mono</x:String>
<x:String>monodroid</x:String>
<x:String>monotouch</x:String>
<x:String>monorail</x:String>
<x:String>monodevelop</x:String>
<x:String>monotone</x:String>
<x:String>monopoly</x:String>
<x:String>monomodal</x:String>
<x:String>mononucleosis</x:String>
</x:Array>
</ListView.ItemsSource>
</ListView>
#HighCore, #DanPazey, and #Vishal:
In fact, the markup binding syntax may prove to be useful and even necessary.
Not to mention multibinding, consider the following.
Suppose you need to bind your ListBox to CollectionViewSource (for sorting or else). Like this:
<Window.Resources>
<CollectionViewSource x:Key="abc_CVS_Key" Source="{Binding abc}" />
</Window.Resources>
<ListBox ItemsSource="{Binding Source={StaticResource abc_CVS_Key}}">
</ListBox>
You may want then, for technical reasons, to limit a scope of the CVS resource to only the ListBox in question.
If you write down ItemsSource binding in an attribute
<ListBox ItemsSource="{Binding Source={StaticResource abc_CVS_Key}}">
<ListBox.Resources>
<CollectionViewSource x:Key="abc_CVS_Key" Source="{Binding abc}" />
</List.Resources>
</ListBox>
your code will compile, but runtime your program will not find your abc_CVS_Key resource key, because the resource has been defined later in code. You need to define the resource before you refer to it in ListBox' ItemsSource binding. Like this:
<ListBox>
<ListBox.Resources>
<CollectionViewSource x:Key="abc_CVS_Key" Source="{Binding abc}" />
</List.Resources>
<ListBox.ItemsSource>
<Binding Source="{StaticResource abc_CVS_Key}" />
</ListBox.ItemsSource>
</ListBox>
This code compiles and executes OK.

How do I convert a ComboBox to use a bound CompositeCollection?

I have a ComboBox that has a bound items source... I've stripped my example down to the key pieces:
<UserControl x.Class="My.Application.ClientControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:conv="clr-namespace:My.Utilities.Converters"
Name="ClientControl">
<UserControl.Resources>
<ResourceDictionary>
<CollectionViewSource Key="x:ClientsCollection" />
</ResourceDictionary>
<conv:ClientOptions x:Key="ClientOptions" />
</UserControl.Resources>
...
<ComboBox Name="Options"
DataContext="ClientsCollection"
ItemsSource="{Binding [ClientNumber], Converter={StaticResource ClientOptions}" />
</UserControl>
This works, but I now want to add a single manual item to my combobox that will trigger alternate functionality called "Other..." so I'm having to move to using the CompositeCollection... like so:
<ComboBox Name="Options"
DataContext="ClientsCollection">
<ComboBox.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding [ClientNumber], Converter={StaticResource ClientOptions} />
<ComboBoxItem>Other...</ComboBoxItem>
</CompositeCollection>
</ComboBox>
Try as I might, the bound items just won't populate when using the CompositeCollection. It only shows the manual ComboBoxItem "Other...". If I remove that item, the list is empty. If I attach a breakpoint to the converter it doesn't catch anything, which seems to indicate that the binding isn't even attempted.
I am obviously not understanding something about how the binding function in the CompositeCollection is happening. Can someone see an error in my XAML or explain what I'm missing?
Declare the CompositeCollection in ComboBox.Resources and use it with ItemsSource="{Binding Source={StaticResource myCompositeCollection}}" .
<UserControl x.Class="My.Application.ClientControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:conv="clr-namespace:My.Utilities.Converters"
Name="ClientControl">
<UserControl.Resources>
<ResourceDictionary>
<CollectionViewSource Key="x:ClientsCollection" />
</ResourceDictionary>
<conv:ClientOptions x:Key="ClientOptions" />
<CompositeCollection x:Key="myCompositeCollection">
<CollectionContainer Collection="{Binding Source={StaticResource ClientsCollection}, Path=[ClientNumber], Converter={StaticResource ClientOptions} />
<ComboBoxItem>Other...</ComboBoxItem>
</CompositeCollection>
</UserControl.Resources>
...
<ComboBox Name="Options"
DataContext="ClientsCollection"
ItemsSource="{Binding Source={StaticResource myCompositeCollection}}" />
If you declare the CompositeCollection inside the ItemsSource property in element syntax, the Binding for the CollectionContainer.Collection doesn't find its DataContext.
Inside the Resources section, Freezables like CompositeCollection inherit the DataContext of their declaring element, as if they were logical children of the element. However, this is a speciality of the Resources property and properties like ContentControl.Content or similar properties which contain the logical children of a control (and maybe a few others). If you use element syntax to set the value of a property, in general you would have to expect that property value inheritance for properties like DataContext doesn't work, and so Bindings without an explicit Source won't work, either.

Edit DataGridTemplateColumn in Code Behind?

I would think that this would be incredibly simple, but I've searched all over and can't seem to find an answer. I have a DataGridTemplateColumn that I want to use to display a value that isn't in the DataContext of the DataGrid. I.e. I have an entity that has different names based on the culture. When the grid loads, I want to go get the appropriate name based on the current culture. Every time I see anything about DataGridTemplateColumns, they're always using the Binding syntax. I can't do that here. What C# code do I need to access the "nameValue" TextBlock in the following XAML, and in what event handler should I access it:
<Datagrid:DataGridTemplateColumn Header="Name" x:Name="nameField">
<Datagrid:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<TextBlock x:Name="nameValue" />
</StackPanel>
</DataTemplate>
</Datagrid:DataGridTemplateColumn.CellTemplate>
</Datagrid:DataGridTemplateColumn>
Thanks to all in advance and I'm sorry for the super n00b question.
You can still use the binding syntax, it sounds like you just need to bind to a static method instead of the data context of the grid. There is a good reference here http://blog.mrlacey.co.uk/2011/03/binding-to-static-classes-in-windows.html Taking that as an example and modifying for your case.
First: Setup your grid as you would normally, item source and columns, standard data binding. This will take care of any columns you need from the database or other source.
Second: In your project add your static class
namespace StaticBinding
{
public class MyStaticClass
{
private static string myStaticProperty;
public static string MyStaticProperty
{
get
{ return
(CultureInfo.CurrentCulture.Name == "en-US" ? "US" : "Other");
}
set { myStaticProperty = value; } }
}
}
Third: Add your new resource to app resources
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:SampleData="clr-namespace:Expression.Blend.SampleData.SampleDataSource" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
x:Class="SilverlightApplication28.App"
xmlns:myns="clr-namespace:StaticBinding"
>
<Application.Resources>
<myns:MyStaticClass x:Name="MyStaticClass"></myns:MyStaticClass>
</Application.Resources>
Finally: Set the binding in your TextBlock, if you've built your project, you should be able to see the property in the binding editor window.
<sdk:DataGrid AutoGenerateColumns="False" Height="171" HorizontalAlignment="Left" Margin="61,53,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="263" ItemsSource="{Binding Collection}" LoadingRow="dataGrid1_LoadingRow" Loaded="dataGrid1_Loaded" >
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Binding="{Binding Property1}" Header="Property1"/>
<sdk:DataGridCheckBoxColumn Binding="{Binding Property2}" Header="Property2"/>
<sdk:DataGridTextColumn Binding="{Binding Property3}" Header="Property3"/>
<sdk:DataGridTemplateColumn>
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<TextBlock x:Name="nameValue" Text="{Binding Source={StaticResource MyStaticClass}, Path=MyStaticProperty}" />
</StackPanel>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
</sdk:DataGrid.Columns>
</sdk:DataGrid>

Resources