I'm facing a very difficult problem with binding. The app attached is extremely simple as you can see and is a trimmed down reproduction of the original code in which the bug appears.
To reproduce the problem, launch the app and press the 3 buttons with the + icon starting from left to right. These buttons will add 3 items in the collection. Then press the 4th button to navigate to the second page. On the second page select the TEA element from the combobox. Go back to the main page and press the last button on the right which adds the CAPPUCCINO product to the list. You will get an
Value does not fall within expected range
Navigate
Add
Insert
Insert
CollectionViewSource
GroupByLetter2
CollectionViewSource.Source = null
UserControl.Unloaded
<Page.Resources>
<CollectionViewSource x:Name="cvsProductsLetter" IsSourceGrouped="true" />
</Page.Resources>
<ListView x:Name="gwProducts" ItemsSource="{Binding Source={StaticResource cvsProductsLetter}}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" FontSize="20" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
CollectionViewSource
public sealed partial class MainPage : Page
{
public static ProductCollection _productcollection;
public MainPage()
{
this.InitializeComponent();
NavigationCacheMode = NavigationCacheMode.Enabled;
_productcollection = new App1.ProductCollection();
cvsProductsLetter.Source = _productcollection.GroupByLetter;
}
ProductCollection
public class ProductCollection
{
public ObservableCollection<ProductGroup> _groupsletter;
public ObservableCollection<ProductGroup> GroupByLetter
{
get
{
if (_groupsletter == null)
{
_groupsletter = new ObservableCollection<ProductGroup>();
}
return _groupsletter;
}
}
public void Add(Product newitem)
{
AddToLetterGroup(newitem);
}
private void AddToLetterGroup(Product item)
{
int i;
ProductGroup prodgr = null;
// get group from letter
for(i = 0; i < _groupsletter.Count; i++)
{
if (String.Equals(_groupsletter[i].Key, item.Name[0].ToString(), StringComparison.CurrentCultureIgnoreCase))
{
prodgr = _groupsletter[i];
break;
}
}
//new letter
if (prodgr == null)
{
prodgr = new ProductGroup();
prodgr.Key = item.Name[0].ToString();
prodgr.Add(item);
_groupsletter.Add(prodgr);
}
else
{
prodgr.Add(item);
}
}
}
public class ProductGroup : ObservableCollection<Product>
{
public string Key { get; set; }
}
Product
public class Product : INotifyPropertyChanged
{
private string _name = "";
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get { return _name; }
set
{
if (!String.Equals(_name, value))
{
_name = value;
OnPropertyChanged("Name");
}
}
}
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public override string ToString()
{
return Name;
}
}
<Page.Resources>
<CollectionViewSource x:Name="cvsProductsLetter" IsSourceGrouped="true" />
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ComboBox ItemsSource="{Binding Source={StaticResource cvsProductsLetter}}" FontSize="20" Foreground="Black" />
</Grid>
public sealed partial class SecondPage : Page
{
public SecondPage()
{
this.InitializeComponent();
cvsProductsLetter.Source = App1.MainPage._productcollection.GroupByLetter;
}
Go back to the main page and press the last button on the right which adds the CAPPUCCINO product to the list. You will get an Value does not fall within expected range exception.
I can reproduce this problem in your demo. I did some test and found that on the second Page when you select TEA, then go back to the MainPage
only the T group can be added without exception. And when you select CAPPUCCINO and repeat the steps, C group won't get exception, others will.
I guess it is due to you are sharing the same data model object between pages. For detailed root cause, I need to consult internally.
Currently the simpliest workaround is to empty the cvsProductsLetter
in the OnNavigatingFrom
of Second Page:
SecondPage.xaml.cs:
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
cvsProductsLetter.Source = null;
}
But the recommended way to solve this problem is to create a separated new data model for the second Page:
ProductCollection.cs:
public class ProductCollection
{
...
public void CreateNewGroupByLetter(ObservableCollection<ProductGroup> oldGroupByLetter)
{
if (oldGroupByLetter != null&&this.GroupByLetter!=null)//add this.GroupByLetter!=null to call the setter of GroupByLetter.
{
foreach (var group in oldGroupByLetter)
{
foreach (var product in group)
{
Add(new Product {
Name=product.Name
});
}
}
}
}
}
And MainPage.xaml.cs:
private void btnProdNavigate_Click(object sender, RoutedEventArgs e)
{
ProductCollection newColl = new ProductCollection();
newColl.CreateNewGroupByLetter(_productcollection.GroupByLetter);
this.Frame.Navigate(typeof(SecondPage), newColl);
}
Update:
Here is the link to the modified demo: App1.