Note that there are some explanatory texts on larger screens.

plurals
  1. POHow to automatically update filter and/or sort order on CollectionViewSource, when an individual item's property changes?
    text
    copied!<p>Ok, so this question is related to Windows Phone 7/Silverlight (updated WP7 Tools, Sept 2010), specifically filtering an underlying <code>ObservableCollection&lt;T&gt;</code>.</p> <p>In mucking about with the WP7 template Pivot control application, I've run into an issue whereby changing an underlying item in an <code>ObservableCollection&lt;T&gt;</code>, does not result in the on-screen ListBox being updated. Basically, the sample app has two pivots, the first directly bound to the underlying <code>ObservableCollection&lt;T&gt;</code>, and the second bound to a <code>CollectionViewSource</code> (i.e., representing a filtered view on the underlying <code>ObservableCollection&lt;T&gt;</code>).</p> <p>The underlying items that are being added to the <code>ObservableCollection&lt;T&gt;</code> implement <code>INotifyPropertyChanged</code>, like so:</p> <pre><code>public class ItemViewModel : INotifyPropertyChanged { public string LineOne { get { return _lineOne; } set { if (value != _lineOne) { _lineOne = value; NotifyPropertyChanged("LineOne"); } } } private string _lineOne; public string LineTwo { get { return _lineTwo; } set { if (value != _lineTwo) { _lineTwo = value; NotifyPropertyChanged("LineTwo"); } } } private string _lineTwo; public bool IsSelected { get { return _isSelected; } set { if (value != _isSelected) { _isSelected = value; NotifyPropertyChanged("IsSelected"); } } } private bool _isSelected = false; public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged(String propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } } </code></pre> <p>Then, in the main class, a data collection is concocted (list reduced for brevity, also note that unlike other items, three of the LoadData() entries have IsSelected == true):</p> <pre><code> public class MainViewModel : INotifyPropertyChanged { public MainViewModel() { this.Items = new ObservableCollection&lt;ItemViewModel&gt;(); } public ObservableCollection&lt;ItemViewModel&gt; Items { get; private set; } public bool IsDataLoaded { get; private set; } public void LoadData() { this.Items.Add(new ItemViewModel() { LineOne = "runtime one", IsSelected = true, LineTwo = "Maecenas praesent accumsan bibendum" }); this.Items.Add(new ItemViewModel() { LineOne = "runtime two", LineTwo = "Dictumst eleifend facilisi faucibus" }); this.Items.Add(new ItemViewModel() { LineOne = "runtime three", IsSelected = true, LineTwo = "Habitant inceptos interdum lobortis" }); this.Items.Add(new ItemViewModel() { LineOne = "runtime four", LineTwo = "Nascetur pharetra placerat pulvinar" }); this.Items.Add(new ItemViewModel() { LineOne = "runtime five", IsSelected = true, LineTwo = "Maecenas praesent accumsan bibendum" }); this.Items.Add(new ItemViewModel() { LineOne = "runtime six", LineTwo = "Dictumst eleifend facilisi faucibus" }); this.IsDataLoaded = true; } public event PropertyChangedEventHandler PropertyChanged; public void NotifyPropertyChanged(String propertyName) { if (null != PropertyChanged) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } } </code></pre> <p>In the MainPage.xaml file, the first Pivot has its <code>ItemSource</code> based directly on the <code>ObservableCollection&lt;T&gt;</code> list. Within the second Pivot, the on-screen ListBox has its <code>ItemSource</code> Property set to a <code>CollectionViewSource</code>, whose underlying source is based on the <code>ObservableCollection&lt;T&gt;</code> populated in <code>LoadData()</code> above.</p> <p></p> <pre><code>&lt;phone:PhoneApplicationPage.Resources&gt; &lt;CollectionViewSource x:Key="IsSelectedCollectionView" Filter="CollectionViewSource_SelectedListFilter"&gt; &lt;/CollectionViewSource&gt; &lt;/phone:PhoneApplicationPage.Resources&gt; &lt;!--LayoutRoot is the root grid where all page content is placed--&gt; &lt;Grid x:Name="LayoutRoot" Background="Transparent"&gt; &lt;!--Pivot Control--&gt; &lt;controls:Pivot Title="MY APPLICATION"&gt; &lt;!--Pivot item one--&gt; &lt;controls:PivotItem Header="first"&gt; &lt;!--Double line list with text wrapping--&gt; &lt;ListBox x:Name="FirstListBox" Margin="0,0,-12,0" ItemsSource="{Binding Items}"&gt; &lt;ListBox.ItemTemplate&gt; &lt;DataTemplate&gt; &lt;StackPanel Margin="0,0,0,17" Width="432"&gt; &lt;TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/&gt; &lt;TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/&gt; &lt;/StackPanel&gt; &lt;/DataTemplate&gt; &lt;/ListBox.ItemTemplate&gt; &lt;/ListBox&gt; &lt;/controls:PivotItem&gt; &lt;!--Pivot item two--&gt; &lt;controls:PivotItem Header="second"&gt; &lt;!--Triple line list no text wrapping--&gt; &lt;ListBox x:Name="SecondListBox" Margin="0,0,-12,0" ItemsSource="{Binding Source={StaticResource IsSelectedCollectionView}}"&gt; &lt;ListBox.ItemTemplate&gt; &lt;DataTemplate&gt; &lt;StackPanel Margin="0,0,0,17"&gt; &lt;TextBlock Text="{Binding LineOne}" TextWrapping="NoWrap" Margin="12,0,0,0" Style="{StaticResource PhoneTextExtraLargeStyle}"/&gt; &lt;TextBlock Text="{Binding LineThree}" TextWrapping="NoWrap" Margin="12,-6,0,0" Style="{StaticResource PhoneTextSubtleStyle}"/&gt; &lt;/StackPanel&gt; &lt;/DataTemplate&gt; &lt;/ListBox.ItemTemplate&gt; &lt;/ListBox&gt; &lt;/controls:PivotItem&gt; &lt;/controls:Pivot&gt; &lt;/Grid&gt; &lt;!--Sample code showing usage of ApplicationBar--&gt; &lt;phone:PhoneApplicationPage.ApplicationBar&gt; &lt;shell:ApplicationBar IsVisible="True" IsMenuEnabled="True"&gt; &lt;shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="Button 1" Click="ApplicationBarIconButton_Click"/&gt; &lt;shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" Text="Button 2"/&gt; &lt;shell:ApplicationBar.MenuItems&gt; &lt;shell:ApplicationBarMenuItem Text="MenuItem 1"/&gt; &lt;shell:ApplicationBarMenuItem Text="MenuItem 2"/&gt; &lt;/shell:ApplicationBar.MenuItems&gt; &lt;/shell:ApplicationBar&gt; &lt;/phone:PhoneApplicationPage.ApplicationBar&gt; </code></pre> <p></p> <p>Note that in the MainPage.xaml.cs, the <code>Filter</code> attribute on the <code>CollectionViewSource</code> in the <code>Resources</code> section above is assigned a filter handler, which sifts through those items that have <code>IsSelected</code> set to true:</p> <pre><code>public partial class MainPage : PhoneApplicationPage { public MainPage() { InitializeComponent(); DataContext = App.ViewModel; this.Loaded += new RoutedEventHandler(MainPage_Loaded); } private void MainPage_Loaded(object sender, RoutedEventArgs e) { if (!App.ViewModel.IsDataLoaded) { App.ViewModel.LoadData(); CollectionViewSource isSelectedListView = this.Resources["IsSelectedCollectionView"] as CollectionViewSource; if (isSelectedListView != null) { isSelectedListView .Source = App.ViewModel.Items; } } } private void CollectionViewSource_SelectedListFilter(object sender, System.Windows.Data.FilterEventArgs e) { e.Accepted = ((ItemViewModel)e.Item).IsSelected; } private void ApplicationBarIconButton_Click(object sender, EventArgs e) { ItemViewModel item = App.ViewModel.Items[App.ViewModel.Items.Count - 1]; item.IsSelected = !item.IsSelected; } } </code></pre> <p>Also note that immediately after loading up the data, I obtain the <code>CollectionViewSource</code> and set its data source as the <code>ObservableCollection&lt;T&gt;</code> list, in order that there is base data upon which the filtering can take place.</p> <p>When application loads, the data is displayed as expected, with those items in the <code>ObservableCollection&lt;T&gt;</code> which have <code>IsSelected</code> true, being displayed in the second Pivot:</p> <p><img src="https://i.stack.imgur.com/IcjLW.png" alt="alt text"> <img src="https://i.stack.imgur.com/5C4JY.png" alt="alt text"></p> <p>You'll notice that I've uncommented the Application Bar Icons, the first of which toggles the <code>IsSelected</code> property of the last item in the <code>ObservableCollection&lt;T&gt;</code> when clicked (see the last function in MainPage.xaml.cs). </p> <p><strong>Here is the crux of my question</strong> - when I click the applicable bar icon, I can see when the last item in the list has its <code>IsSelected</code> property set to true, howoever the second Pivot does not display this changed item. I can see that the <code>NotifyPropertyChanged()</code> handler is being fired on the item, however the collection is not picking up this fact, and hence the list box in Pivot 2 does not change to reflect the fact that there should be a new item added to the collection.</p> <p>I'm pretty certain that I'm missing something quite fundamental/basic here, but failing that, does anyone know the best way to get the collection and it's underlying items to play happily together?</p> <p>I suppose this problem also applies to sorting as well as filtering ((in the sense that if a <code>CollectionViewSource</code> is based on sorting, then when a property of an item that is used in the sort changes, the sort order of the collection should reflect this as well))</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload