Why is CompositeCollection not Freezable?
Asked Answered
R

1

22

I am writing an application using the MVVM pattern. I am providing data to my view by setting my view's DataContext property to an instance of my ViewModel. Generally I just use Binding from there and go about my way.

Recently, I tried to implement a ComboBox with an "extra" element beyond the collection my ViewModel provides that says "Select Item".

<ComboBox>    
    <ComboBox.ItemsSource>    
        <CompositeCollection>
           <ComboBoxItem IsEnabled="False">Select Item</ComboBoxItem>
           <CollectionContainer Collection="{Binding MyItemsCollection}" />    
        </CompositeCollection>
    </ComboBox.ItemsSource>
</ComboBox>

The problem is, CompositeCollection is not a Freezable: Freezable Objects Overview. This causes only the static ComboBoxItem to appear and none of the results from my binding expression.

My initial reaction to the problem was to just implement my own version of CompositeCollection that was Freezable. This, though, begs the following question:

Why isn't CompositeCollection a Freezable in the first place?

My concern is that generally these decisions are made for a reason and I don't feel I know enough about Freezable to say why they didn't inherit from it. I know I can implement this collection, but I'm concerned there will be a measurable difference in performance if I do.

Any help would be appreciated. Thanks!

Also: please note that I realize I can insert a Null or some other special value and provide and template or valueconverter to do what I want. This is not the question I'm interested in... only the question in bold above.

Update:

After some further research brought on by ArsenMkrt's comment, I'm led to believe this was actually an oversight. The evidence is this:

  1. There is a collection that is freezable called FreezableCollection<T>. It does not produce CollectionViews, which makes it inappropriate for my needs directly.
  2. Sam Bent of MSFT says as much in the above link. I cannot find contact information for him yet, but I plan on discussing this with him if I get the chance.

My current plan to get around this problem is to create a new collection with the properties of CompositeCollection and FreezableCollection<T>. I don't know if it'll work yet, but I'm thinking about something like this:

public class BindableCompositeCollection : FreezableCollection<object>, ICollectionViewFactory

If anyone has a better option, I'd like to hear it!

Recrimination answered 27/7, 2009 at 16:5 Comment(3)
may be this will help?social.msdn.microsoft.com/Forums/en-US/wpf/thread/…Advantage
Maybe... it seems to contain a MSFT employee saying that it's a bug. Interesting. Makes me feel a little better about doing this. I'd like to see if anyone has any concerns about performance, though. Log this as an answer so I can give you credit if it turns out that this is all just because someone overlooked this scenario for CompositeCollection.Recrimination
It's the same basic problem as outlined here, with the same workarounds applicable: codeproject.com/KB/WPF/ArtificialInheritanceCxt.aspx. Personally, I prefer the "spy" trick.Upholstery
E
10

I just tried this tonight:

public class State
{
    public string Code { get; set; }
    public string Name { get; set; }
}

public class MyWindowViewModel
{
    ObservableCollection<State> _states = new ObservableCollection<State>
    {
        new State { Code = "FL", Name = "Florida" },
        new State { Code = "CA", Name = "California" },
    };

    public ObservableCollection<State> States
    {
        get
        {
            return _states;
        }
    }
}
<Window x:Class="WpfApplication1.MyWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:app="clr-namespace:WpfApplication1"
        Title="Window1"
        Height="300"
        Width="300">

  <Window.Resources>
    <app:ServiceLocator x:Key="Locator" />
  </Window.Resources>

  <StackPanel>
    <ComboBox x:Name="TestCombo" SelectedIndex="0" DisplayMemberPath="Name" SelectedValuePath="Code">
      <ComboBox.ItemsSource>
        <CompositeCollection>
          <app:State Code="" Name="Select a state..." />
          <app:State Code="TX" Name="Texas" />
          <CollectionContainer Collection="{Binding Source={StaticResource Locator}, Path=MyWindowViewModel.States}" />
        </CompositeCollection>
      </ComboBox.ItemsSource>
    </ComboBox>
  </StackPanel>
</Window>

The key here is to create an instance of your service locator as a static resource then go through it to get to your viewmodel. The service locator can wire up to instances of the ViewModel using Unity or whatever DI you want.

Edit:

Actually in my silverlight app I create the service locator as a static resoure in the App.xaml and then bind my other UserControls/Windows/Pages DataContext to a ViewModel property of the service locator. It should still work the same way for the combo boxes though even if the service locator is instantiated in the App.xaml's resources. I wish there was a silverlight version of CompositeCollection that I could use. This would work great for the app I'm working on. :(

Estuary answered 17/9, 2009 at 3:38 Comment(1)
We have been meaning to go this route based on some recent suggestions on Marlon Grech's blog. I'll try this in the morning!Recrimination

© 2022 - 2024 — McMap. All rights reserved.