ComboBox 绑定到分组集合
ComboBox binding to grouped collection
我遇到了一个非常棘手的绑定问题。如您所见,附加的应用程序非常简单,是出现错误的原始代码的精简复制品。
要重现该问题,请启动该应用程序并按 [=44= 的 3 个按钮]+ 图标从左到右开始。这些按钮将在集合中添加 3 个项目。然后按第 4 个按钮导航到第二页。在第二页 select 组合框中的 TEA 元素。返回主页并按右侧的最后一个按钮,将 CAPPUCCINO 产品添加到列表中。您将得到一个 Value does not fall within expected range
异常。我想知道为什么会发生这种情况,而不仅仅是一种不能真正解决问题的解决方法。如您所见,问题出现在非常特殊的情况下。
注意:
- 删除静态变量并将
Navigate
调用中的集合实例传递给第二页并不能解决问题
- 代替
Add
调用的是一个按顺序插入的调用 Insert
的集合。我删除了它,Insert
不是问题也不是解决方案
- 删除第二页上的
CollectionViewSource
并不能解决问题
为组合框创建另一个镜面反射集合 GroupByLetter2
不能解决问题(参见 test1 分支)
UPDATE:最后我能够在 UserControl.Unloaded
事件上设置 CollectionViewSource.Source = null
并修复它。但是关于工作理论的问题仍然悬而未决。
https://github.com/albertorivelli/app1
这是主页:
<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
class:
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
class
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.
我可以在你的演示中重现这个问题。我做了一些测试,发现在第二页,当你 select TEA,然后回到 MainPage
只有 T组无一例外都可以加。当您 select CAPPUCCINO 并重复这些步骤时,C 组不会出现异常,其他人会。
我猜这是因为您在页面之间共享相同的数据模型对象。详细的root cause需要内部咨询
目前最简单的解决方法是清空第二页 OnNavigatingFrom
中的 cvsProductsLetter
:
SecondPage.xaml.cs:
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
cvsProductsLetter.Source = null;
}
但是解决这个问题的推荐方法是为第二页创建一个单独的新数据模型:
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
});
}
}
}
}
}
和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);
}
更新:
这里是 link 修改后的演示:App1。
我遇到了一个非常棘手的绑定问题。如您所见,附加的应用程序非常简单,是出现错误的原始代码的精简复制品。
要重现该问题,请启动该应用程序并按 [=44= 的 3 个按钮]+ 图标从左到右开始。这些按钮将在集合中添加 3 个项目。然后按第 4 个按钮导航到第二页。在第二页 select 组合框中的 TEA 元素。返回主页并按右侧的最后一个按钮,将 CAPPUCCINO 产品添加到列表中。您将得到一个 Value does not fall within expected range
异常。我想知道为什么会发生这种情况,而不仅仅是一种不能真正解决问题的解决方法。如您所见,问题出现在非常特殊的情况下。
注意:
- 删除静态变量并将
Navigate
调用中的集合实例传递给第二页并不能解决问题 - 代替
Add
调用的是一个按顺序插入的调用Insert
的集合。我删除了它,Insert
不是问题也不是解决方案 - 删除第二页上的
CollectionViewSource
并不能解决问题 为组合框创建另一个镜面反射集合GroupByLetter2
不能解决问题(参见 test1 分支)
UPDATE:最后我能够在 UserControl.Unloaded
事件上设置 CollectionViewSource.Source = null
并修复它。但是关于工作理论的问题仍然悬而未决。
https://github.com/albertorivelli/app1
这是主页:
<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
class:
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
class
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.
我可以在你的演示中重现这个问题。我做了一些测试,发现在第二页,当你 select TEA,然后回到 MainPage
只有 T组无一例外都可以加。当您 select CAPPUCCINO 并重复这些步骤时,C 组不会出现异常,其他人会。
我猜这是因为您在页面之间共享相同的数据模型对象。详细的root cause需要内部咨询
目前最简单的解决方法是清空第二页 OnNavigatingFrom
中的 cvsProductsLetter
:
SecondPage.xaml.cs:
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
cvsProductsLetter.Source = null;
}
但是解决这个问题的推荐方法是为第二页创建一个单独的新数据模型:
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
});
}
}
}
}
}
和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);
}
更新:
这里是 link 修改后的演示:App1。