Belterius
Belterius

Reputation: 758

Observable Collection issue in ListBox with DisplayMemberPath

I have an ObservableCollection<UsageItem> MyItems; :

EDIT :

public class UsageItem : INotifyPropertyChanged
{
    string _Name;
    public string Name
    {
        get
        {
            return _Name;
        }
        set
        {
            if (_Name != value)
            {
                _Name = value;
                RaisePropertyChanged("Name");
            }
        }
    }

    int _ChargesLeft;
    public int ChargesLeft
    {
        get
        {
            return _ChargesLeft;
        }
        protected set
        {
            if (_ChargesLeft != value)
            {
                _ChargesLeft = value;
                RaisePropertyChanged("ChargesLeft");
            }
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
        protected void RaisePropertyChanged(string prop)
        {
            if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
        }
    public Consummable(string name, int charges)
    {
        this.Name = name;
        this.ChargesLeft = charges;
    }


    public override string DisplayName
    {
        get
        {
            if (ChargesLeft > 1)
            {
                return Name + " " + ChargesLeft + "charges";
            }
            else
            {
                return Name + " " + ChargesLeft + "charge";
            }
        }
    }
    public override void use(Hero hero)
    {
        if(this.ChargesLeft >= 1)
        {
            this.ChargesLeft--;
        }else{
        //Will remove this UsageItem from my ObservableCollection
        ...
       }
    }

}

I've bound my Collection to a ListBox :

<ListBox x:Name="listBoxBackPack"  ItemsSource="{Binding MyItems}" SelectedItem="{Binding SelectedItem}" DisplayMemberPath="DisplayName"/>

I have a button that when clicked call the use function on my SelectedItem, it will decrease the value my ChargesLeft Field or remove the object.

If I click the button while my ChargesLeft value is <= 1 it will remove my Item from my Collection, and my ListBox will be updated as expected, no problem.
But If I use an item with a ChargesLeft >= 1, then I will decrease my field and so in consequence change my DisplayName.
As I still change my Collection I would expect a CollectionChanged Event to fire and my ListBox to be refreshed and display the new DisplayValue of the item, but it does not happen, the ListBox keep displaying the old DisplayValue.
At this moment if I manually do a Listbox.Items.Refresh() (Binded it on a button for testing) it will refresh and correctly display the right DisplayValue, but I can't call this function from my ViewModel, as it has no knowledge of my ListBox

How can I do to automatically force the refresh of my ListBox when I modify an item ?

Upvotes: 0

Views: 648

Answers (1)

mechanic
mechanic

Reputation: 781

CollectionChanged is raised when you add or delete an item or clear the whole collection. Your UsageItem class needs to implement INotifyPropertyChanged itself to track changes in collection items.

https://msdn.microsoft.com/en-us/library/ms668604(v=vs.110).aspx

Note

To fully support transferring data values from binding source objects to binding targets, each object in your collection that supports bindable properties must implement an appropriate property changed notification mechanism such as the INotifyPropertyChanged interface.

Update See a working example (I used Prism but that doesn't matter):

UsageItem.cs:

public class UsageItem : INotifyPropertyChanged
{
    string _Name;
    public string Name
    {
        get
        {
            return _Name;
        }
        set
        {
            if (_Name != value)
            {
                _Name = value;
                RaisePropertyChanged("Name");
                RaisePropertyChanged("DisplayName");
            }
        }
    }

    int _ChargesLeft;
    public int ChargesLeft
    {
        get
        {
            return _ChargesLeft;
        }
        set
        {
            if (_ChargesLeft != value)
            {
                _ChargesLeft = value;
                RaisePropertyChanged("ChargesLeft");
                RaisePropertyChanged("DisplayName");
            }
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged(string prop)
    {
        if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
    }
    public UsageItem(string name, int charges)
    {
        this.Name = name;
        this.ChargesLeft = charges;
    }


    public string DisplayName
    {
        get
        {
            if (ChargesLeft > 1)
            {
                return Name + " " + ChargesLeft + "charges";
            }
            else
            {
                return Name + " " + ChargesLeft + "charge";
            }
        }
    }
}

MainViewModel:

public class MainWindowViewModel : BindableBase
{
    private string _title = "Prism Unity Application";
    public string Title
    {
        get { return _title; }
        set { SetProperty(ref _title, value); }
    }

    public MainWindowViewModel()
    {
        Items = new ObservableCollection<UsageItem>();
        Items.Add(new UsageItem("Bob", 1));
        Items.Add(new UsageItem("Bill", 2));
        Items.Add(new UsageItem("Joe", 3));

        CurrentItem = Items[0];

        UseCommand = new DelegateCommand(Use);
    }

    public ObservableCollection<UsageItem> Items { get; private set; }

    private UsageItem _currentItem;
    public UsageItem CurrentItem
    {
        get { return _currentItem; }
        set { SetProperty(ref _currentItem, value); }
    }

    public DelegateCommand UseCommand { get; private set; }
    private void Use()
    {
        if (CurrentItem.ChargesLeft >= 1)
        {
            CurrentItem.ChargesLeft--;
        }
        else
        {
            //Will remove this UsageItem from my ObservableCollection
            Items.Remove(CurrentItem);
            CurrentItem = Items[0];
        }
    }
}

MainWindow.xaml:

<Window x:Class="UsageLeft.Views.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:prism="http://prismlibrary.com/"
    Title="{Binding Title}"
    Width="525"
    Height="350"
    prism:ViewModelLocator.AutoWireViewModel="True">
<StackPanel>
    <ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding CurrentItem}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding DisplayName}" />
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    <Button Command="{Binding UseCommand}" Content="Use" />
</StackPanel>
</Window>

Upvotes: 2

Related Questions