0

I have a really simple class which I am populating with a LINQ query - all works well;

public class CatSummary : INotifyPropertyChanged
{
    private string _catName;
    public string CatName
    {
        get { return _catName; }
        set { if (_catName != value) { _catName = value; NotifyPropertyChanged("CatName"); } }
    }

    private decimal _catAmount;
    public decimal CatAmount
    {
        get { return _catAmount; }
        set { if (_catAmount != value) { _catAmount = value; NotifyPropertyChanged("CatAmount"); } }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    // Used to notify Silverlight that a property has changed.
    private void NotifyPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

            //MessageBox.Show("NotifyPropertyChanged: " + propertyName);

        }
    }

}

LINQ bit;

        var myOC = new ObservableCollection<CatSummary>();


        var initialQuery = BoughtItemDB.BoughtItems
                         .GroupBy(item => item.ItemCategory)
                         .Select(x => new CatSummary
                          { 
                              CatName = x.Key, 
                              CatAmount = x.Sum(amt => amt.ItemAmount)
                          });

        foreach (var item in initialQuery) myOC.Add(item);

I am trying to bind my WPF control to my custom class in the XAML below;

<ListBox x:Name="boughtItemsListBox" ItemsSource="{Binding CatSummary}" Margin="5,27,-35,100" Width="450" Height="371" Grid.ColumnSpan="2" Grid.RowSpan="2">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid HorizontalAlignment="Stretch" Width="440">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="150" />
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>

                <TextBlock Grid.Column="0" Text="{Binding CatName, StringFormat=g}" TextWrapping="Wrap"  FontSize="{StaticResource PhoneFontSizeSmall}" VerticalAlignment="Top"/>
                <TextBlock Grid.Column="1" Text="{Binding CatAmount, StringFormat=\{0:C\}}" Margin="1" FontSize="{StaticResource PhoneFontSizeSmall}" VerticalAlignment="Top"/>
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

This give me the following error; BindingExpression path error: 'CatSummary' property not found on 'MyApp.SpendAnalysis+CatSummary'

According to what I have read I think I need to make the properties of my class into ObservableCollection properties but this seems to break my LINQ query. I've tried all sorts for this, nor can I find any tutorials to help me understand how this should work. Any advice or pointers gladly received.

MAO
  • 99
  • 2
  • 16

2 Answers2

2

With your XAML bindings, the DataContext of boughtItemsListBox should be an item with a property named CatSummary of type IEnumerable<CatSummary> (or ObservableCollection<CatSummary>, or other implementer of INotifyCollectionChanged, if you want to be able to change the items that make up the list in real time). I'm guessing this is where you're going wrong.

E.g. if you actually have something more like boughtItemsListBox.DataContext = myOC;, then it's looking for myOC.CatSummary and not finding anything; what you need to do is either change the XAML to ItemsSource="{Binding}" or change your code to boughtItemsListBox.ItemsSource = myOC; (and remove the now-useless ItemsSource setting in the XAML).

Tim S.
  • 55,448
  • 7
  • 96
  • 122
1

Assuming you have a MyOC property

private ObservableCollection<CatSummary> _myOC = new ObservableCollection<CatSummary>();
public ObservableCollection<CatSummary> MyOC
{
    get { return _myOC ; }
    set { if (_myOC != value) { _myOC = value; NotifyPropertyChanged("MyOC"); } }
}

bind your ListBox to the ObservableCollection, then each ListBoxItem will have a DataContext of type CatSummary.

<ListBox x:Name="boughtItemsListBox" ItemsSource="{Binding MyOc}" ...

In your LINQ query code

var initialQuery = BoughtItemDB.BoughtItems
                    .GroupBy(item => item.ItemCategory)
                    .Select(x => new CatSummary
                     { 
                         CatName = x.Key, 
                         CatAmount = x.Sum(amt => amt.ItemAmount)
                     });

reassign MyOC

MyOC = new ObservableCollection<CatSummary>(initialQuery.ToList());

or use the existing MyOC collection.

MyOC.Clear();
foreach (var item in initialQuery) MyOC.Add(item);
LPL
  • 16,827
  • 6
  • 51
  • 95
  • I've done what you've suggested, no errors, but the 2 properties, CatName and CatAmount are not pulling through in my XAML and are not displayed. The new MyOC ObservableCollection is pulling through fine. Any ideas? – MAO Apr 18 '12 at 21:03
  • That's because ObservableCollection does not notify on changes in his childs. Try this implementation of an [ObservableCollection that also monitors changes on the elements in collection](http://stackoverflow.com/a/269113/620360) instead. – LPL Apr 18 '12 at 21:26
  • The error I am seeing is 'MyOC' property not found on 'SpendAnalysis+CatSummary' – MAO Apr 18 '12 at 21:27
  • I didn't see the error at first, but saw it in the output log. Apologies. – MAO Apr 18 '12 at 21:39
  • Then the DataContext for the parent of boughtItemsListBox is not right. It should be the class with MyOC property. I assume SpendAnalysis. – LPL Apr 18 '12 at 21:48
  • Sorted, thanks for your help. Everything you suggested worked except setting the ItemSource for boughtItemListBox as per the comment below(boughtItemsListBox.ItemsSource = MyOC;). Thanks again. – MAO Apr 19 '12 at 16:34