WPF data binding with ObservableCollection to label

I have an ObservableCollection that I need to bind to two labels, first to show the number of items in the collection and then show the sum of the values.

The first label is bound to the counts property and the second label is bound directly to the ObservableCollection with a converter to calculate the total of all items

The 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 virtual machine has a collection like this

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

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

      

My converter code

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();
    }
}

      

The problem is adding new items to DataList, ListView and label showing item count is updating, but "lblTotal" is not getting updated with total.

Basically, how do you get your binding to evaluate on ObservableCollection changes? How does this work directly for the ListView or DataGrid, but not for the label?

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

Of course this is a simplified form of my real problem, I don't have access to the ViewModel and the collection, its third party control. I am creating a custom wrapper item and has a relative binding to a view of its internal collection.

+3


source to share


3 answers


The other answers correctly explain why it is not updating. To force it to update, you can change your converter to 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 the binding to MultiBinding

, which will pull Count as well:



<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 tell you that the binding should update when items are added or removed, but you can simply ignore the counter value and not use it.

+2


source


Not updated because its anchor to DataList

and DataList

did not change, the count label is updated because it is anchored to DataList.Count

, which is updated when an item is added to the list.

The only way I can update the label Sum

is to notify the UI that changed DataList

, but this will cause the ListBox to recheck the list and it will be much more expensive than just having a property in your model, update Sum

.



So, I think the best option would be to use a property of your model to calculate the sum using ObservableCollections

CollectionChangedEvent

or in logic that adds items to the list

0


source


It works for ListView

and DataGrid

because it is the ItemsControl

listener ObservableCollection

CollectionChangedEvent

that occurs when the collection itself changes by adding or removing items.

On the other hand, there Label

is ContentControl

that only listens PropertyChangedEvent

. Since your DataList is the same ObservableCollection

after insert as before, no events are generated.

Just saw your edit:

If you are creating a wrapper control, let a third party manage the name and connect to its internal collection CollectionChangedEvent

from your control code behind. This way you can still specify update notifications in your packaging type.

Go with an additional property, it will save you the 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));
    }
}

      

0


source







All Articles