views:

215

answers:

1

This little bit of code will help me describe my problem:

public class Car
{
    ...
}

public class CarQueue : ObservableCollection<Car>
{
    public IEnumerable Brands
    {
        get { return (from Car c in this.Items select c.Brand).Distinct(); }
    }
}

Ok now I have an instance of CarQueue class bound to a DataGrid. When I add a Car object to the queue the datagrid updates fine by itself, but I also have a listbox bound to the 'Brands' property which doesn't update. Here is a simple sequence of code to explain:

CarQueue cq = new CarQueue();
DataGrid1.ItemsSource = cq;
ListBox1.ItemsSource = cq.Brands;  // all above done during window load
...
Car c;
cq.Add(c); // datagrid updates, but not listbox

Does the listbox not update because it is bound to a property with dynamic LINQ query?

One other thing I tried was inheriting INotifyPropertyChanged and adding a new event handler to the CollectionChanged event (in my CarQueue constructor):

this.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(CarQueue_CollectionChanged);

Then in the event handler:

void CarQueue_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
    PropertyChanged(this, new PropertyChangedEventArgs("Brands"));
}

This didn't work either. So does anyone know what the problem is? Thanks

+5  A: 

There are a couple of problems here.

The Brands property is a sequence built on the fly by LINQ when it is asked for it. WPF only asks for it during the initial binding: it has no way of knowing that if it were to ask again it would get a different answer, so it doesn't. To get WPF to track changes to the Brands collection, you would need to expose Brands as a collection, and have INotifyCollectionChanged implemented on that collection -- for example by making Brands an ObservableCollection. One way to do this is using Bindable LINQ.

As an alternative, your second approach, of raising a PropertyChanged event for Brands, can be made to work. However, in order for this to work, you have to bind ItemsSource to Brands. (At the moment, you are assigning it, which means WPF forgets where the collection came from and just keeps its private copy of the values.) To do this, either use the {Binding} markup extension in XAML:

<ListBox ItemsSource="{Binding Brands}" />  <!-- assumes DataContext is cq -->

or use BindingOperations.SetBinding:

BindingOperations.SetBinding(ListBox1, ListBox.ItemsSourceProperty,
  new Binding("Brands") { Source = cq });
itowlson
Perfect. I just made the binding in XAML and it works. Bindable LINQ looks like it would be very useful for me though, so thanks.
AdamD
For the record I noticed that Bindable LINQ doesn't seem to have been updated for a while, but there is another project called Continuous LINQ which seems to be doing some of the same things. Haven't tried this, but you can get it from http://www.codeplex.com/clinq if you want to give it a go.
itowlson