ComboBox 绑定到分组集合

ComboBox binding to grouped collection

我遇到了一个非常棘手的绑定问题。如您所见,附加的应用程序非常简单,是出现错误的原始代码的精简复制品。
要重现该问题,请启动该应用程序并按 [=44= 的 3 个按钮]+ 图标从左到右开始。这些按钮将在集合中添加 3 个项目。然后按第 4 个按钮导航到第二页。在第二页 select 组合框中的 TEA 元素。返回主页并按右侧的最后一个按钮,将 CAPPUCCINO 产品添加到列表中。您将得到一个 Value does not fall within expected range 异常。我想知道为什么会发生这种情况,而不仅仅是一种不能真正解决问题的解决方法。如您所见,问题出现在非常特殊的情况下。
注意:

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