0

new to WPF and programming here I am trying to update an item in a ListView when a button is pushed if the item already exists in that ListView, for example a user inputs CAR and CAR is already in the ListView the CAR count should go from 1 to 2.

This is my InventoryItem class:

public class InventoryItem
    {
        public string Name { get; set; }
        public int Count { get; set; }
    }

Here is my ViewModel

public class ViewModel : ViewModelBase
    {

        private InventoryItem _item;
        private int _count;
        private ObservableCollection<InventoryItem> _inventoryItems;
        private ICommand _addItem;

        public InventoryItem Item
        {
            get 
            {
                return _item;
            }
            set
            {
                _item = value;
                NotifyPropertyChanged("Item");
            }
        }

        public int Count
        {
            get { return _count; }
            set { _count = value; NotifyPropertyChanged("Count"); }
        }

        public ObservableCollection<InventoryItem> InventoryItems
        {
            get
            {
                return _inventoryItems;
            }
            set
            {
                _inventoryItems = value;
                NotifyPropertyChanged("Items");
            }
        }

        public ICommand AddItem
        {
            get
            {
                if (_addItem == null)
                {
                    _addItem = new RelayCommand(ParamArrayAttribute => this.Submit(), null);
                }
                return _addItem;
            }
        }

        public ViewModel()
        {
            Item = new InventoryItem();
            InventoryItems = new ObservableCollection<InventoryItem>();
            InventoryItems.CollectionChanged += new NotifyCollectionChangedEventHandler(InventoryItems_CollectionChanged);
        }

        void InventoryItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            NotifyPropertyChanged("Items");
            NotifyPropertyChanged("Count");
        }

        private void Submit()
        {
            if (InventoryItems.Any(p => p.Name == Item.Name))
            {
                InventoryItems.First(x => x.Name == Item.Name).ItemCount++;
            }
            else
            {
                InventoryItems.Add(Item);
            }
        }
    }

Here is my View

<ListView x:Name="listBox" ItemsSource="{Binding InventoryItems}" Grid.Row="0" HorizontalAlignment="Stretch" Height="Auto" Margin="10,10,10,60" VerticalAlignment="Stretch">
            <ListView.Resources>
                <Style TargetType="GridViewColumnHeader">
                    <Setter Property="Visibility" Value="Collapsed"/>
                </Style>
            </ListView.Resources>
            <ListView.View>
                <GridView>
                    <GridViewColumn DisplayMemberBinding="{Binding Name}" Width="Auto"/>
                    <GridViewColumn DisplayMemberBinding="{Binding ItemCount}" Width="Auto"/>
                </GridView>
            </ListView.View>
        </ListView>

Whenever an item that exists in the list is typed in it appears that the InventoryItems ObservableCollection is being updated however InventoryItems_CollectionChanged event is not being fired so the ListView is not being updated. Isn't the collection changing so that event should be fired to update the ListView or am I not understanding the Binding and the event?

kyper1234
  • 3
  • 2
  • 1
    Use nameof in your notifypropertychanged. Then you won't accidentally use tye wrong string for a propertychange. Like you are now. "Items" is not the name of your property – Andy Jan 10 '21 at 22:00

1 Answers1

1

You need to notify about property changes in the InventoryItem class to update the changes of ItemCount in the ListView.

Quick solution:

public class InventoryItem : ViewModelBase
{
    public string Name
    {
        get { return _name; }
        set 
        {
            if (_name != value)
            {
                _name = value;
                NotifyPropertyChanged(nameof(Name));
            }
        }
    }
    private string _name;

    public int ItemCount
    {
        get { return _itemCount; }
        set { _itemCount = value; NotifyPropertyChanged(nameof(ItemCount));
        }
    }
    private int _itemCount;
}

}

The ObservableCollection class already contains handling of INotifyCollectionChanged.

You only need this line for the ObservableCollection. You can remove everything else that has to do with "InventoryItems" and the collection in your ViewModel.

    public ObservableCollection<InventoryItem> InventoryItems { get; } = new ObservableCollection<InventoryItem>();

Also: When you add an item to the collection you need to create a new item. Otherwise you are adding the same object, that will not work.

This is my reduced ViewModel to how I think you want it to work:

public class ViewModel : ViewModelBase
{
    private ICommand _addItem;

    public string InputName
    {
        get { return _inputName; }
        set
        {
            if (_inputName != value)
            {
                _inputName = value;
                NotifyPropertyChanged(nameof(InputName));
            }
        }
    }
    private string _inputName;

    public ObservableCollection<InventoryItem> InventoryItems { get; } = new ObservableCollection<InventoryItem>();

    public ICommand AddItem
    {
        get
        {
            if (_addItem == null)
            {
                _addItem = new RelayCommand(ParamArrayAttribute => this.Submit(), null);
            }
            return _addItem;
        }
    }

    private void Submit()
    {
        if (InventoryItems.Any(p => p.Name == InputName))
        {
            InventoryItems.First(x => x.Name == InputName).ItemCount++;
        }
        else
        {
            InventoryItems.Add(new InventoryItem() { Name = InputName, ItemCount = 1 });
        }
    }
}

To complete the picture, I have added following XAML for test:

        <TextBox Text="{Binding InputName}" MinWidth="100" Margin="5"/>
        <Button Content="Add" Command="{Binding AddItem}" Margin="5"/>
RolandJS
  • 717
  • 5
  • 5
  • For your ViewModelBase: Look at this question. Find the "SetProperty" in the answer. It will make your properties much cleaner. https://stackoverflow.com/questions/36149863/how-to-write-a-viewmodelbase-in-mvvm – RolandJS Jan 11 '21 at 08:04