Reputation: 32104
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
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
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:
this
keyword is usually redundant, unless there is another identifier in scope with the same nameIt'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
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