Xamarin Forms ListView 分组内容不绑定

Xamarin Forms ListView Grouping Contents not Binding

我在使用 ListView Grouping 获取要在标签中显示的列表内容时遇到问题。 ListView 的 ItemsSource 和 GroupDisplayBinding 设置正确,但 ListView 内的标签不会显示任何内容(我什至尝试将其设置为文字字符串)。我的所有列表以及我的“列表列表”都正确填充。我翻阅了文档、网站文章和教学视频,但仍然卡住了。有人能指出我正确的方向吗?总的来说,我在数据绑定和 MVVM 方面仍然遇到困难。谢谢。

查看:

<ListView ItemsSource="{Binding OAllLists}"
              GroupDisplayBinding="{Binding Type}"
              IsGroupingEnabled="true"
              HasUnevenRows="True">
        <ListView.ItemTemplate>
            <DataTemplate>
                <Label Text="{Binding Items}">
                </Label>
            </DataTemplate>
        </ListView.ItemTemplate>
</ListView>

视图模型:

class OrganizedViewModel
{
    public ObservableCollection<Categories> OAllLists { get; set; }

    public OrganizedViewModel()
        {
        OAllLists = new ObservableCollection<Categories>();
                foreach(Categories value in Categories.AllLists)
                {
                    OAllLists.Add(value);
                }
        }
} 

型号:

public class Categories
    {
        public static List<Categories> AllLists { get; set; } = new List<Categories>();
        
        public static Categories FruitList { get; set; } = new Categories("Fruit");
        public static Categories VegetableList { get; set; } = new Categories("Vegetables");
        ///Each type of item has its own static Categories object

        public string Type { get; set; }
        public List<string> Items { get; set; } = new List<string>();
    }

整理项目的方法:

class OrganizeIt
{
    public void OrganizeItems(List<string> groceries)
    {
        foreach (string value in groceries) ///Checks each value for keywords
        {
            if (Keywords.fruitKeys.Any(value.Contains))
            {
                Categories.FruitList.Items.Add(value);
            }
            else if (Keywords.vegetableKeys.Any(value.Contains))
            {
                Categories.VegetableList.Items.Add(value);
            }
        }

        ///Adds each type of list to "list of lists" if it contains values
        if (Categories.FruitList.Items.Any())
        {
            Categories.AllLists.Add(FruitItem);
        }
        if (Categories.VegetableList.Items.Any())
        {
            Categories.AllLists.Add(Categories.VegetableList);
        }

编辑

根据评论的推荐新增 class。我还在由 GroupedList 填充的 ViewModel 中创建了另一个 Observable Collection(两者都正常工作)。名称更改是为了清楚起见。

public class Groceries : List<string>
    {
        public string Category { get; set; }

        public static List<Groceries> GroupedList { get; set; } = new List<Groceries>();
        public static Groceries Fruits { get; set; } = new Groceries("Fruit");
        public static Groceries Vegetables { get; set; } = new Groceries("Vegetables");

        public Groceries(string s)
        {
            Category = s;
        }
    }

新视图:

<ListView ItemsSource="{Binding OGroupedList}"
                  GroupDisplayBinding="{Binding Category}"
                  IsGroupingEnabled="true">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Label Text="{Binding .}"
                               VerticalOptions="FillAndExpand"/>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

编辑 2

这就是我现在在 ViewModel 中填充 ObservableCollection 的方式:

class OrganizedViewModel
    {
        public ObservableCollection<Groceries> OGroupedList { get; set; }
        public string Category { get; set; }

         public OrganizedViewModel()
        {
            OGroupedList = new ObservableCollection<Groceries>();
            foreach (Groceries value in Groceries.GroupedList)
            {
                OGroupedList.Add(value);
            }
    }

编辑 3

这是整理物品的方法。它接受一个字符串列表并检查每个列表项以查看它是否包含与特定类别关联的任何关键字(例如“苹果”包含在“2 袋苹果”中)。如果是,则将列表项添加到相应的 Groceries 对象。

class OrganizeIt
{
    public void OrganizeItems(List<string> groceries)
    {
        foreach (string value in groceries)
        {
            if (Keywords.fruitKeys.Any(value.Contains))
            {
                Groceries.Fruits.Add(value);
            }
            else if (Keywords.vegetableKeys.Any(value.Contains))
            {
                Groceries.Vegetables.Add(value);
            }
    }
    if (Groceries.Fruits.Any())
    {
        Groceries.GroupedList.Add(Groceries.Fruits);
    }
    if (Groceries.Vegetables.Any())
    {
        Groceries.GroupedList.Add(Groceries.Vegetables);
    }
}

这里是在 MainPage 上调用该方法的地方。 UnorganizedList 由用户输入填充。

private void SortItems()
{
    OrganizeIt o = new OrganizeIt();
    o.OrganizeItems(UnorganizedList);
}

解决方案

所需要做的就是将标签的绑定更改为 {Binding}(没有句点),并删除“x:DataType”行。在修改后的视图下方,以防对任何人有帮助:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:viewmodels="clr-namespace:GroceryListMobile.ViewModels"
             xmlns:mvvm="clr-namespace:MvvmHelpers;assembly=MvvmHelpers"
             xmlns:model="clr-namespace:GroceryListMobile.Models"
             xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
             
             x:Class="GroceryListMobile.Views.OrganizedView"
             x:Name="Organized">
    <ContentPage.BindingContext>
        <viewmodels:OrganizedViewModel/>
    </ContentPage.BindingContext>

    <ContentPage.Content>

        <ListView ItemsSource="{Binding OGroupedList}"
                  GroupDisplayBinding="{Binding Category}"
                  IsGroupingEnabled="true">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Label Text="{Binding}"
                               VerticalOptions="FillAndExpand"/>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </ContentPage.Content>
</ContentPage>

您需要一个 <ViewCell> 作为 <DataTemplate>:

的 child 节点
<ListView ItemsSource="{Binding OAllLists}"
              GroupDisplayBinding="{Binding Type}"
              IsGroupingEnabled="true"
              HasUnevenRows="True">
        <ListView.ItemTemplate>
            <DataTemplate>
                <ViewCell>
                    <Label Text="{Binding .}">
                    </Label>
                </ViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
</ListView>

ListView.ItemTemplate 始终需要一个作为单元格的 DataTemplate。

编辑 接下来要解决的是用作 ItemSource 的 collection。使用分组时,需要 collection of collections。我想尝试的是改变你的模型 class:

public class Categories : List<string>
    {
        public static List<Categories> AllLists { get; set; } = new List<Categories>();
        
        public static Categories FruitList { get; set; } = new Categories("Fruit");
        public static Categories VegetableList { get; set; } = new Categories("Vegetables");
        ///Each type of item has its own static Categories object

        public string Type { get; set; }
    }

并查看上面 Xaml 中修改后的 Binding。

考虑分组 ListView 的一种方式是它是列表的列表。 “外部”列表元素有一个成员绑定到 GroupDisplayBinding,“内部”列表元素有一个成员绑定到 DataTemplate 中的元素。

在您的例子中,“外部”collection 是 ObservableCollection,因此 GroupDisplayBinding 将绑定到类别上的某些内容。然后每个类别本身都需要是 collection。在修订版中,这是一个列表,因此 DataTemplate 被赋予一个字符串作为 BindingContext。所以 Label 只需要绑定到字符串(因此 Binding ..

代码的其余部分将不得不进行一些调整,因为曾经是类别成员的项目 collection 现在是类别 object 本身(通过继承)。

编辑 2

我创建了一个新应用程序,其页面包含与您拥有的完全相同的 ListView(您的第一个编辑下的新视图),以及与第一个编辑下完全相同的 Groceries class。

我稍微修改了OrganizedViewModel。它似乎没有使用类别,我想确保 OGroupedList 中填充了类别:

    class OrganizedViewModel
    {
        public ObservableCollection<Groceries> OGroupedList { get; set; }

        public OrganizedViewModel()
        {
            OGroupedList = new ObservableCollection<Groceries>();
            OGroupedList.Add(Groceries.Fruits);
            OGroupedList.Add(Groceries.Vegetables);
        }
    }

最后,我在创建页面时在页面的构造函数中为每个类别添加了一些项目:

        public Page1()
        {
            InitializeComponent();

            var bc = new OrganizedViewModel();
            var index = 1;
            foreach (var g in bc.OGroupedList)
            {
                g.Add(g.Category + $" {index++}");
                g.Add(g.Category + $" {index++}");
                g.Add(g.Category + $" {index++}");
                g.Add(g.Category + $" {index++}");
            }

            BindingContext = bc;
        }

对我来说,这是正确显示带有项目名称和组 headers 的列表。因此,无论您看到的是什么问题,都在其他地方。分组 ListView 的基本 class 结构和 Xaml 定义现在是正确的。

我的两个猜测:

  1. 确保正确填充 collection。
  2. 尝试更改为 public class Groceries : ObservableCollection<string>。如果 Groceries 列表可以在页面最初呈现后更改,这一点很重要。

编辑 3

追根究底。以下是可以让您继续前进的评论:

  1. {Binding .}{Binding}之间有细微差别。在您的情况下,{Binding} 有效,但 {Binding .} 无效。

  2. 一个更常见的情况是元素是objects,而不是字符串,所以像这样也解决了它:

public class GroceryItem
{
    public string Name { get; set; }
}
...
public class Groceries: ObservableCollection<GroceryItem>
{
    ...

当然,这需要更改添加到杂货 collection 并且标签需要 Text="{Binding Name}"

  1. 当我尝试 #2 时,Visual Studio 在进行更改后导致 x:DataType 出现问题。它在 IDE 中对绑定上下文的检测不是最好的,因此我不得不删除该行。

  2. 在此处使用静态 collection 时要小心。如果您添加项目、整理、返回并再次整理,应用程序将崩溃,因为它会尝试 re-add 水果和蔬菜 collections。

根据你的代码,要显示的OAllLists,你可以这样创建OAllLists:

OAllLists = new ObservableCollection<Categories>
{ 
    new Categories("FruitList"){"aaa","bbb","ccc},
    new Categories("VegetableList"){"ddd","eee"} 
};

其中“FruitList”是Categories中的类型class,“aaa”是你在其中添加的字符串数组。您可以查看此 link 了解详细信息 (https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/listview/customizing-list-appearance#grouping)