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

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.

Related

WPF binding failure within CompositeCollection

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>

Binding one dependency property to another

I have a custom Tab Control that I have created, but I am having an issue. I have an Editable TextBox as part of the custom TabControl View.
<Controls:EditableTextControl x:Name="PageTypeName"
Style="{StaticResource ResourceKey={x:Type Controls:EditableTextControl}}" Grid.Row="0" TabIndex="0"
Uid="0"
AutomationProperties.AutomationId="PageTypeNameTextBox"
AutomationProperties.Name="PageTypeName"
Visibility="{Binding ElementName=PageTabControl,Path=ShowPageType}">
<Controls:EditableTextControl.ContextMenu>
<ContextMenu x:Name="TabContextMenu">
<MenuItem Header="Rename Page Type" Command="{Binding Path=PlacementTarget.EnterEditMode, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
AutomationProperties.AutomationId="RenamePageTypeMenuItem"
AutomationProperties.Name="RenamePageType"/>
<MenuItem Header="Delete Page Type" Command="{Binding Path=PageTypeDeletedCommand}"
AutomationProperties.AutomationId="DeletePageTypeMenuItem"
AutomationProperties.Name="DeletePageType"/>
</ContextMenu>
</Controls:EditableTextControl.ContextMenu>
<Controls:EditableTextControl.Content>
<!--<Binding Path="CurrentPageTypeViewModel.Name" Mode="TwoWay"/>-->
<Binding ElementName="PageTabControl" Path="CurrentPageTypeName" Mode ="TwoWay"/>
</Controls:EditableTextControl.Content>
</Controls:EditableTextControl>
In the Content section I am binding to a Dependency Prop called CurrentPageTypeName. This Depedency prop is part of this custom Tab Control.
public static DependencyProperty CurrentPageTypeNameProperty = DependencyProperty.Register("CurrentPageTypeName", typeof(object), typeof(TabControlView));
public object CurrentPageTypeName
{
get { return GetValue(CurrentPageTypeNameProperty) as object; }
set { SetValue(CurrentPageTypeNameProperty, value); }
}
In another view, where I am using the custom TabControl I then bind my property, with the actual name value, to CurrentPageTypeName property as seen below:
<Views:TabControlView Grid.Row="0" Name="RunPageTabControl"
TabItemsSource="{Binding RunPageTypeViewModels}"
SelectedTab="{Binding Converter={StaticResource debugConverter}}"
CurrentPageTypeName="{Binding Path=RunPageName, Mode=TwoWay}"
TabContentTemplateSelector="{StaticResource tabItemTemplateSelector}"
SelectedIndex="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.SelectedTabIndex}"
ShowPageType="Hidden" >
<!--<Views:TabControlView.TabContentTemplate>
<DataTemplate DataType="{x:Type ViewModels:RunPageTypeViewModel}">
<RunViews:RunPageTypeView/>
</DataTemplate>
</Views:TabControlView.TabContentTemplate>-->
</Views:TabControlView>
My problem is that nothing seems to be happening. It is grabbing its Content from the Itemsource, and not from my chained Dependency props. Is what I am trying even possible? If so, what have I done wrong.
Thanks for looking.
Unless I'm missing something this is definitely possible. Here is a simplified working example.
User control with a dependency property named TestValue, containing a TextBox bound to this property:
<UserControl x:Class="TestApp.TestControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"
x:Name="TestControlName">
<Grid>
<TextBox Text="{Binding ElementName=TestControlName, Path=TestValue, Mode=TwoWay}"/>
</Grid>
</UserControl>
A different view using this user control, binding the above mentioned dependency property to something:
<Window x:Class="TestApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:TestApp="clr-namespace:TestApp" Title="MainWindow"
Height="350" Width="525">
<StackPanel>
<TestApp:TestControl TestValue="{Binding ElementName=SourceTextBox, Path=Text, Mode=TwoWay}" />
<TextBox Name="SourceTextBox" />
</StackPanel>
</Window>
It sounds that the issue is somewhere in the part of the code you have not posted (e.g. wrong name used in Content binding).
I think you already solved this yourself for the "SelectedIndex" property. Just do the same thing for the "CurrentPageType" property i.e. use RelativeSource

Converter problem with XmlDataProvider

Sorry for this, I've just started programming with wpf. I can't seem to figure out why the following xaml displays "System.Xml.XmlElement" instead of the actual xml node content. This is displayed 5 times in the listbox whenever I run it. Not sure where I'm going wrong...
<Window x:Class="TestBinding.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<XmlDataProvider x:Key="myXmlSource" XPath="/root">
<x:XData>
<root xmlns="">
<name>Steve</name>
<name>Arthur</name>
<name>Sidney</name>
<name>Billy</name>
<name>Steven</name>
</root>
</x:XData>
</XmlDataProvider>
<DataTemplate x:Key="shmooga">
<TextBlock Text="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemTemplate="{StaticResource shmooga}"
ItemsSource="{Binding Source={StaticResource myXmlSource}, XPath=name}">
</ListBox>
</Grid>
</Window>
Any help would be very much appreciated. Thanks!
Your ItemsSource Binding is returning the collection of 'name' elements. These are of type XmlElement. This is done because it allows bindings to get at other properties of the XmlElement, but means that if you textually display the result of the binding then you get System.Xml.XmlElement rather than the textual content.
To get the textual content, add an additional XPath to your ItemTemplate binding to specify that the TextBlock.Text property should bind specifically to the text of the element, not the element object itself:
<DataTemplate x:Key="shmooga">
<TextBlock Text="{Binding XPath=text()}"/> <!-- Note XPath on Binding -->
</DataTemplate>

WPF ListBox ItemsSource StaticResource/Binding question

Given the following code:
<Window x:Class="WpfApplication76.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:col="clr-namespace:System.Collections;assembly=mscorlib"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<CollectionViewSource x:Key="myCol">
<CollectionViewSource.Source>
<col:ArrayList>
<ListBoxItem>Uno</ListBoxItem>
<ListBoxItem>Dos</ListBoxItem>
<ListBoxItem>Tres</ListBoxItem>
</col:ArrayList>
</CollectionViewSource.Source>
</CollectionViewSource>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{StaticResource myCol}" />
<ListBox ItemsSource="{Binding Source={StaticResource myCol}}" />
</Grid>
</Window>
In this example, the
<ListBox ItemsSource="{StaticResource myCol}" />
Gives me an error complaining that it cannot bind to a "CollectionViewSource" object.
But the other listbox:
<ListBox ItemsSource="{Binding Source={StaticResource myCol}}" />
binds perfectly fine.
So my question is why does one work and the other one does not? AT the end, aren't both ItenSources being set to the same "CollectionViewSource" object?
Thank you.
The ItemsSource property is of type IEnumerable. A CollectionViewSource is not an IEnumerable. CollectionViewSource's View property will give you an IEnumerable.
When you Bind to a CollectionViewSource the Binding is smart enough to grab the View property and actually bind to that. Maybe CollectionViewSource has a [DefaultBindingProperty] on it.
It boils down to the fact that when you go through the Binding you don't actually bind to the CollectionViewSource, but its View property.

How to databind to a property on a collection behind a collectionviewsource?

I currently have a collection with a HasChanges property on it (each object in the collection also has its own HasChanges property) and the collection is the source of my CollectionViewSource.
When I try to databind the HasChanges property of the collection behind the CollectionViewSource to one of my custom controls, it binds to the HasChanges property of the currently selected object, instead of HasChanges property of the CollectionViewSource's source collection. Is there a way that I can explicitly tell the binding to look on the collection object rather than the objects in the collection?
My code looks something like this:
<Window x:Class="CollectionEditWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Local="clr-namespace:My.Local.Namespace;assembly=My.Local.Namespace">
<Window.Resources>
<CollectionViewSource x:Name="CVS" x:Key="MyCollectionViewSource" />
</Window.Resources>
<Local:MyCustomControl HasChanges="{Binding HasChanges, Source={StaticResource
MyCollectionViewSource}}">
<!-- Code to set up the databinding of the custom control to the CollectionViewSource-->
</Local:MyCustomControl>
</Window>
Thanks.
When you bind to CollectionViewSource you get a CollectionView, which has a SourceCollection property that you can use to get the collection behind the CollectionViewSource, like so:
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Grid.Resources>
<x:Array x:Key="data" Type="{x:Type sys:String}">
<sys:String>a</sys:String>
<sys:String>bb</sys:String>
<sys:String>ccc</sys:String>
<sys:String>dddd</sys:String>
</x:Array>
<CollectionViewSource x:Key="cvsData" Source="{StaticResource data}"/>
</Grid.Resources>
<StackPanel>
<ListBox ItemsSource="{Binding Source={StaticResource cvsData}}"/>
<TextBlock Text="{Binding Source={StaticResource cvsData}, Path=Length, StringFormat='{}Length bound to current String = {0}'}"/>
<TextBlock Text="{Binding Source={StaticResource cvsData}, Path=SourceCollection.Length, StringFormat='{}Length bound to source array = {0}'}"/>
</StackPanel>
</Grid>

Resources