Craig Otis
Craig Otis

Reputation: 32104

Implementing a ListView with a CollectionViewSource - Not Refreshing?

I'm working on setting up a ListView whose Source property is set to an ivar of a class of mine, called Cat.

Each Cat has an ObservableCollection of Trait objects:

private ObservableCollection<Trait> _traits = new ObservableCollection<Trait>();

public ObservableCollection<Trait> Traits
{
get
    {
        return _traits;
    }
}

public void AddTrait(Trait t)
{
    _traits.Add(t);
    // Is this redundant? Is one better than the other?
    this.OnPropertyChanged("_traits");
    this.OnPropertyChanged("Traits");
}

public IEnumerator<Object> GetEnumerator()
{
    return _traits.GetEnumerator();
}

And then I'm assigning the Source property to this Traits collection:

this.CollectionViewSource.Source = CurrentCat.Traits;

This works properly, and the Trait objects are properly displayed in my ListView.

The issue is that changes to this underlying _traits collection do not cause the UI to update properly. For example, this:

void AddTraitButton_Click(object sender, RoutedEventArgs e)
{
    if (this.CurrentCat != null)
    {
        this.CurrentCat.AddTrait(new Trait());
    }
}

Doesn't seem to have any effect immediately in the UI, but if I reset the Source property like so:

var oldSource = this.CollectionViewSource.Source;
this.CollectionViewSource.Source = null;
this.CollectionViewSource.Source = oldSource;

Then the ListView updates properly. But, I'm sure there must be something that I'm missing, as I'd like for the UI to update upon the addition/removal of an item.

Edit: The CollectionViewSource is being applied to the ListView in my XAML file:

<CollectionViewSource x:Name="CollectionViewSource" x:Key="CollectionViewSource" />

...

<ListView x:Name="ItemListView" ItemsSource="{Binding Source={StaticResource CollectionViewSource}}" ...

Upvotes: 1

Views: 9157

Answers (3)

ADM-IT
ADM-IT

Reputation: 4200

You may work exclusively with the ObservableCollection still. Although there is one problem - it would not show the data in IsInDesignMode. Maybe in the future it will improve.

public class MainViewModel : ViewModelBase
{
...
    private ObservableCollection<PartViewModel> _parts;
    public ObservableCollection<PartViewModel> Parts
    {
        get
        {
            if (_parts == null)
            {
                _parts = new ObservableCollection<PartViewModel>();
                _parts.CollectionChanged += _parts_CollectionChanged;
            }
            return _parts;
        }
    }

    object m_ReorderItem;
    int m_ReorderIndexFrom;
    void _parts_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Remove:
                m_ReorderItem = e.OldItems[0];
                m_ReorderIndexFrom = e.OldStartingIndex;
                break;
            case NotifyCollectionChangedAction.Add:
                if (m_ReorderItem == null)
                    return;
                var _ReorderIndexTo = e.NewStartingIndex;
                m_ReorderItem = null;
                break;
        }
    }

    private PartViewModel _selectedItem;
    public PartViewModel SelectedItem
    {
        get
        {
            return _selectedItem;
        }
        set
        {
            if (_selectedItem != value)
            {
                _selectedItem = value;
                RaisePropertyChanged("SelectedItem");
            }
        }
    }
   ...

    #region ViewModelBase

    public override void Cleanup()
    {
        if (_parts != null)
        {
            _parts.CollectionChanged -= _parts_CollectionChanged;
        }
        base.Cleanup();
    }

    #endregion

  }

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <Grid.Resources>
        <CollectionViewSource x:Name="PartsCollection" Source="{Binding Parts}"/>
    </Grid.Resources>

    <ListView Margin="20" CanReorderItems="True" CanDragItems="True" AllowDrop="True" 
              ItemsSource="{Binding Source={StaticResource PartsCollection}}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" SelectionMode="Single">
        <ListView.ItemContainerStyle>
            <Style TargetType="ListViewItem">
                <Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
            </Style>
        </ListView.ItemContainerStyle>
        <ListView.ItemTemplate>
        ...
        </ListView.ItemTemplate>
    </ListView>
</Grid>

Upvotes: 0

Dan J
Dan J

Reputation: 16718

Rather than binding to the CollectionViewSource directly and replacing its Source to force a refresh, I believe you want to bind to the CVS's View property...

<ListView x:Name="ItemListView" 
          ItemsSource="{Binding Source={StaticResource CollectionViewSource}, Path=View}" ...

...and call CollectionViewSource.Refresh() after updating the source collection.

void AddTraitButton_Click(object sender, RoutedEventArgs e)
{
    if (this.CurrentCat != null)
    {
        this.CurrentCat.AddTrait(new Trait());
        this.CollectionViewSource.Refresh();
    }
}

Also, a couple notes, since you seem relatively new to .NET/WPF conventions:

  • The private members of .NET classes are typically referred to as "fields" rather than "ivars" (Objective-C background? :))
  • Prefixing class members with the this keyword is usually redundant, unless there is another identifier in scope with the same name
  • It's worth exploring the MVVM and related patterns if you'll be doing anything non-trivial in WPF; they help you keep your views (XAML objects) as light and easy-to-change as possible.

    In your case, for example, I assume the code you've shown is from the code-behind of whatever Window or UserControl contains your ListView. Following the MVVM pattern would involve creating a separate "ViewModel" class that would contain the Traits collection and expose it via a CollectionViewSource (using the View property, as I've mentioned). Your UserControl would then have an instance of the ViewModel assigned as its DataContext, and the ListView could be bound to the exposed CollectionView.

Upvotes: 0

Kyeotic
Kyeotic

Reputation: 19857

I can't seem to find it now, but I seem to remember some problem with binding to CollectionViewSource. Have you tried binding directly to CurrentCat.Traits and setting this.DataContext = this in the code-behind (I am assuming you aren't using MVVM here)?

<ListView x:Name="ItemListView" ItemsSource="{Binding CurrentCat.Traits}" />

Upvotes: 0

Related Questions