Ankit
Ankit

Reputation: 663

WPF Databinding with ObservableCollection to Label

I have a ObservableCollection which I need to bind to 2 labels, first to show count of items in the collection and second to show the sum of values.

First label is bound to collections count property and second label is bound directly to ObservableCollection with a convertor to calculate total of all items

XAML looks something like this

<Grid>
    <ListBox Name="itemList" ItemsSource="{Binding DataList}"/>
    <Label Name="lblcount" Content="{Binding DataList.Count}" />
    <Label Name="lblTotal" Content="{Binding DataList, Converter={StaticResource calculateTotalConvertor}" />
</Grid>

My VM has a collection like this

    ObservableCollection<int> data = new ObservableCollection<int>();

    public ObservableCollection<int> DataList
    {
        get { return data; }
        set { data = value; }
    }

My convertor code is

public class CalculateTotalConvertor : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        ObservableCollection<int> collection = value as ObservableCollection<int>;

        return collection.Sum();
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Issue is on adding new items in DataList, ListView and label showing count of items gets updated but "lblTotal" doesnt get updated with total count.

Basically how to force your binding to be evaluated on ObservableCollection changes ? How does it work directly for ListView or DataGrid but not for label ?

I know this problem can be solved by creating a property in VM to show total and raise property change when collection gets updated but is there is any better solution than that ?

Of-course this is simplified form of my actual problem, I dont have access to the ViewModel and the collection, its a third party control. I am creating a wrapper user control and have a relative binding with the view to its inner collection.

Upvotes: 4

Views: 2436

Answers (3)

John Bowen
John Bowen

Reputation: 24453

The other answers correctly explain why it is not updating. To force it to update you can change your converter to an IMultiValueConverter:

public class CalculateTotalConvertor : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        ObservableCollection<int> collection = values.FirstOrDefault() as ObservableCollection<int>;

        return collection.Sum();
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Then change your binding to a MultiBinding which also pulls in the Count:

<Label Name="lblTotal">
    <Label.Content>
        <MultiBinding Converter="{StaticResource calculateTotalConvertor}">
            <Binding Path="DataList"/>
            <Binding Path="DataList.Count"/>
        </MultiBinding>
    </Label.Content>
</Label>

Now the second binding will notify that the binding needs to update when items are added or removed, but you can just ignore the count value and not use it.

Upvotes: 3

Zoltan
Zoltan

Reputation: 146

It works for ListView and DataGrid, because these are ItemsControls that listen to the ObservableCollection's CollectionChangedEvent, which is raised when the collection itself is changed by adding or removing items.

The Label on the other hand is a ContentControl that only listens to the PropertyChangedEvent. Since your DataList is the same ObservableCollection after the insertion as it was before, no events are raised.

Just saw your edit:

If you are creating a wrapping control, give the 3rd party control a name and hook up to its inner collection's CollectionChangedEvent from your control's code behind. That way you can still push update notifications to your wrapping view.

Go with the extra property, it will save you some code on the converter. From the code behind:

public partial class MainWindow : Window, INotifyPropertyChanged
{
    ObservableCollection<int> _list = new ObservableCollection<int>();
    int _sum = 0;
    Random rnd = new Random();

    public MainWindow()
    {
        DataList = new ObservableCollection<int>();
        DataList.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(DataList_CollectionChanged);
        DataContext = this;
        InitializeComponent();
    }

    void DataList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                foreach (object number in e.NewItems)
                    _sum += (int)number;
                break;
            case NotifyCollectionChangedAction.Remove:
                foreach (object number in e.OldItems)
                    _sum -= (int)number;
                break;
        }
        OnNotifyPropertyChanged("Sum");
    }

    public int Sum { get { return _sum; } }
    public ObservableCollection<int> DataList { get; set; }

    private void Add_Btn_Click(object sender, RoutedEventArgs e)
    {
        DataList.Add(rnd.Next(0, 256));
    }

    private void Remove_Btn_Click(object sender, RoutedEventArgs e)
    {
        if (DataList.Count == 0)
            return;

        DataList.RemoveAt(DataList.Count - 1);
    }

    public event PropertyChangedEventHandler PropertyChanged;

    void OnNotifyPropertyChanged(string property)
    {
        if (PropertyChanged == null)
            return;

        PropertyChanged(this, new PropertyChangedEventArgs(property));
    }
}

Upvotes: 0

sa_ddam213
sa_ddam213

Reputation: 43596

Its not updating because its bound to DataList and DataList has not changed, The count label updates because its bound to DataList.Count which is updated when an item is added to the list.

The only way I can think of to update the Sum label is to notify the UI that the DataList has changed, but this will cause the ListBox to rebind the list and it will performace will be a lot more expensive than just having a property on your model update the Sum.

So I think the best option would be to use a property on your model to caculate the sum using the ObservableCollections CollectionChangedEvent or in the logic that adds items to the list

Upvotes: 0

Related Questions