Reputation: 10244
I have a MainWindow.xaml file:
<Window.Resources>
<CollectionViewSource x:Key="cvs"
Source="{Binding Source={StaticResource ResourceKey=DetailsCollection}}" />
<CollectionViewSource x:Key="DetailScopes">
<CollectionViewSource.Source>
<ObjectDataProvider
MethodName="GetValues"
ObjectType="{x:Type system:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="entities:DetailScope" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</CollectionViewSource.Source>
</CollectionViewSource>
<DataTemplate x:Key="AccountDetail"
DataType="{x:Type entities:AccountDetail}">
<DockPanel>
<ComboBox
DockPanel.Dock="Left"
ItemsSource="{Binding Source={StaticResource ResourceKey=DetailScopes}}"
SelectedItem="{Binding Path=Scope}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Converter={StaticResource DetailScopeConverter}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBox Text="{Binding Path=Value}" />
</DockPanel>
</DataTemplate>
</Window.Resources>
...
<ListBox
ItemTemplate="{StaticResource ResourceKey=AccountDetail}"
ItemsSource="{Binding Source={StaticResource ResourceKey=cvs}}" />
and its code-behind class, where I defined the filter for detail scopes:
public class MainWindow
{
public MainWindow()
{
CollectionViewSource detailScopes;
InitializeComponent();
// Attach filter to the collection view source
detailScopes = this.Resources["DetailScopes"] as CollectionViewSource;
detailScopes.Filter += new FilterEventHandler(DetailScopesFilter);
private void DetailScopesFilter(object sender, FilterEventArgs e)
{
DetailScope scope;
scope = (DetailScope)e.Item;
if (scope == DetailScope.Private ||
scope == DetailScope.Business)
{
e.Accepted = true;
}
else
{
e.Accepted = false;
}
}
}
}
Next, there's the AccountDetail
class:
public class AccountDetail
{
public string Value
{
get { return this.value; }
set { this.value = value; }
}
public DetailScope Scope
{
get { return scope; }
set { scope = value; }
}
private string value;
private DetailScope scope;
}
Finally, an enum:
public enum DetailScope
{
Private,
Business,
Other
}
When I run my code, I get a list box populated with a bunch of account details, each having its own combo box with a selected scope and a text box with the appropriate value. The problem is that all the selected values in the combo boxes match the scope set for the last entered detail and changing any of the combo box values updates all of them, as if they are all bound to the same account detail.
When I take out the ObjectDataProvider
from the CollectionViewSource
DetailScopes and bind it directly to the combo box's ItemsSource
in the DataTemplate
AccountDetail, the problem is gone. However, I do need it inside the CollectionViewSource
because I am applying some filtering to it and I cannot apply filtering to ObjectDataProvider
.
Could someone please explain why is this happening and how am I actually supposed to connect CollectionViewSource
and ObjectDataProvider
? Thank you.
Upvotes: 1
Views: 6121
Reputation: 361522
.
The problem with your code is that every ComboBox is using the same instance of CollectionViewSource; that means, the resource with key "DetailScopes" is shared by all ComboBox, so whenever you select one value from a particular ComboBox, it automatically selects the same value in all ComboBox. It's because the underlying collection which is shared, keeps track of the selected item, and since it changes on selecting from one ComboBox, the CollectionViewSource notifies the change to ALL ComboBox.
So the solution is very simple. All you need to make DetailScopes resource unsharable.
Here is the fix:
<!-- Please note this x:Shared="False" just after x:Key="DetailsScopes" --->
<CollectionViewSource x:Key="DetailScopes" x:Shared="False">
<CollectionViewSource.Source>
<ObjectDataProvider
MethodName="GetValues"
ObjectType="{x:Type system:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="entities:DetailScope" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</CollectionViewSource.Source>
</CollectionViewSource>
Hope it solves your problem!
However, this solution will cause another problem. Let me quote something from MSDN, so that you'll understand what x:Shared does.
x:Shared Attribute
When set to false, modifies WPF resource-retrieval behavior so that requests for the attributed resource create a new instance for each request instead of sharing the same instance for all requests.
Since x:Shared causes to create a new instance (a new copy) of the resource whenever you attempt to access it, that means, the Filter handler method is attached only to the instance which you get in the code-behind, not all the instances.
So in order to work your handler properly, you need to attach the Handler from XAML itself, like this:
<!-- Now please note Filter="DetailsScopesFilter" --->
<CollectionViewSource x:Key="DetailScopes" x:Shared="False" Filter="DetailScopesFilter">
<CollectionViewSource.Source>
<ObjectDataProvider
MethodName="GetValues"
ObjectType="{x:Type system:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="entities:DetailScope" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</CollectionViewSource.Source>
</CollectionViewSource>
Hope it solves all your problems. Let me know if you still face any.:-)
Oh by the way, the following code-behind is not needed anymore. So please remove it.
// Attach filter to the collection view source
detailScopes = this.Resources["DetailScopes"] as CollectionViewSource;
detailScopes.Filter += new FilterEventHandler(DetailScopesFilter);
.
Upvotes: 5