Bob Tway
Bob Tway

Reputation: 9603

Why does raising changes on an individual bound item not refresh that item in an ObservableCollection?

I feel the need to apologise for this question. I'm sure the answer already exists here somewhere, but I'm having trouble understanding what's going on here. I could use some help learning.

My issue is the oft-raised one of getting WPF to react to changes to objects inside ObservableCollections. There's one on my ViewModel called ItemPrices, exposed as a property. The ViewModel implements INotifyPropertyChanged. The collection is of an EF object called Price, which does not.

The collection binds to a ListView. Each item in the collection has a button - clicking on this button changes the state of the Price object bound to that row. This all works fine. What I want to happen is for the image on the button to change when the user clicks on the button.

I went about this by creating a Converter that returns a different source string depending on the properties of the bound Price object. This also works - but only if you close the window and re-open it. I'd like it to react right away when the user clicks the button.

I understand that ObservableCollection only responds to additions and removals and that I need to do something else to notify the ViewModel that the object inside the collection has changed. I wasn't using the SelectedItem on the ListView for anything else, so I figured I'd use that.

So, I added a SelectedPrice propoerty to the ViewModel and bound it to the SelectedItem property in the ListView. Then, in the function executed when the user clicked the button I added SelectedPrice = price - presuming that the call to the OnPropertyChanged would bubble up and cause the item to refresh in the ViewModel.

It didn't work, and I'm not entirely sure why. I've tried a variety of other things such as wrapping the ObservableCollection in an ICollectionView and calling Refresh() on it, and adding INotifyPropertyChanged to the Price - but nothing I do will get that image to refresh right away.

EDIT: I didn't post code, because all the relevant code would have been overwhelming. But some was asked for:

<Button Width="30" HorizontalAlignment="Right" Margin="5" CommandParameter="{Binding}"
    Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.DocumentCommand}" >
        <Image HorizontalAlignment="Center" VerticalAlignment="Center" Source="{Binding Converter={StaticResource PriceDocumentImageConverter} }" />
</Button>

And the converter:

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
    Price price = (Price)value;
    if(price.DocumentId != null)
    {
        return @"/Resources/Icons/Document-View.png";
    }
    return @"/Resources/Icons/Document-Add.png";
}

EDIT2 - INotifyPropertyChanged is on Price, like this:

public partial class Price : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Upvotes: 0

Views: 55

Answers (1)

Willem van Rumpt
Willem van Rumpt

Reputation: 6570

You'll have to implement INotifyPropertyChanged on the Price object. PropetyChanged events don't bubble, it only knows about its own object.

Assuming the button is part of a DataTemplate, you've basically bound the source of the image to "this" (to the Price object itself), where as you actually seem to want to bind it to the state (presumably a property) that changes when the button is clicked. That property is the property that should fire a PropertyChanged event (passing its name as the property name), and the image source should be bound to that property, i.e. (assuming it's called State):

public partial class Price : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public int State
    {
        get { /*...*/ }
        set
        {
             /*...*/
             OnPropertyChanged("State");
        }
    }
}

XAML:

<Image Source="{Binding State, Converter={StaticResource PriceDocumentImageConverter} }" />

And of course you'll have to update the converter, because it will now get the value of the State property passed in.

Upvotes: 3

Related Questions