通过 Xamarin Forms 在 XAML 中使用 LINQ

Using LINQ in XAML, with Xamarin Forms

我正在编写一个基于 MVVM 的应用程序,遇到了一个小问题。

我有两个列表视图要在彼此内部创建。 A 级和 B 级将在这些中显示。 包含 A 的视图(我们称它们为 AView 的)的列表是从视图模型中的 ObservableCollection 构建的。

每个AView包含一个B的列表(BView),从技术上讲,整个屏幕是一个列表的列表。

问题是,底层数据不是 A->B,而是 B->A(A 不包含 B 的列表,但 B 有一个对其父 A 的引用),我有一个全局的B 列表。

在代码中并不难,因为我可以轻松地运行 ListB.Where(x => x.A == A),但我还没有找到方法所以在 XAML.

是否有可能这样做,如果可以,方法是什么?我不希望创建具有单独 BindingContext 的单独页面只是为了进行逻辑上简单的显示。

不推荐(我认为甚至不支持)在 Xamarin Forms 中嵌套两个 ListView。

最好的方法可能是使用绑定到嵌套的 ObservableCollection 的分组 ListView。这里的外部集合将是一个 ObservableCollection。每个 ViewModelA 然后从 ObservableCollection 派生自己。所以这是一个集合的集合。

此外,由于您的 ViewModelB.Parent = 某些 ViewModelA 要求,内部集合应更新为 ViewModelB 项的正确子集(即具有 Parent==some ViewModelA),无论何时您的平面全局集合所有 ViewModelB 项目都发生了变化。这将需要在您的 ViewModelA 实现中添加一些额外的逻辑。

具体的解决方案如下:

首先定义三个viewmodel类(ViewModelMain、ViewModelA和ViewModelB)如下:

ViewModelMain:

using System;
using System.Collections.ObjectModel;
using System.Windows.Input;
using Xamarin.Forms;

namespace XamLab1
{
    public class ViewModelMain
    {
        public ObservableCollection<ViewModelA> AItems { get; set; }
        public ObservableCollection<ViewModelB> AllBItems { get; set; } 

        public ViewModelMain()
        {
            AItems = new ObservableCollection<ViewModelA>();
            AllBItems = new ObservableCollection<ViewModelB>();

            this.UpdateCommand = new Command<string>((key) =>
            {
                Update();
            });
        }

        public ICommand UpdateCommand { protected set; get; }

        public void Update()
        {
            // filling collections with dummy test data...

            AItems.Clear();
            AllBItems.Clear();

            for (int i = 0; i < 10; i++)
            {
                long timestamp = DateTime.Now.Ticks;
                var a = new ViewModelA("A-" + i + "-" + timestamp, AllBItems);
                AItems.Add(a);

                for (int j = 0; j < 3; j++)
                {
                    AllBItems.Add(new ViewModelB { NameB = "B-" + i + "-" + j + "-" + timestamp, Parent = a });
                }
            }            
        }
    }
}

ViewModelA:

using System.Collections.ObjectModel;
using System.Linq;

namespace XamLab1
{
    public class ViewModelA : ObservableCollection<ViewModelB>
    {
        public string NameA { get; private set; }

        public ViewModelA(string name, ObservableCollection<ViewModelB> allBItems)
        {
            NameA = name;
            _allBItems = allBItems;
            _allBItems.CollectionChanged += _allBItems_CollectionChanged;
        }

        private ObservableCollection<ViewModelB> _allBItems;

        private void _allBItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            ClearItems();
            foreach (var item in _allBItems.Where(x => x.Parent == this))
            {
                Add(item);
            }
        }

    }

}

和 ViewModelB:

namespace XamLab1
{
    public class ViewModelB
    {
        public string NameB { get; set; }
        public ViewModelA Parent { get; set; }

    }
}

最后,像这样将视图模型绑定到 XAML:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamLab1;assembly=XamLab1"
             x:Class="XamLab1.MainPage">

  <ContentPage.BindingContext>
    <local:ViewModelMain />
  </ContentPage.BindingContext>

  <StackLayout>
      <Button Text="Update" Command="{Binding UpdateCommand}"></Button>
      <ListView ItemsSource="{Binding AItems}" IsGroupingEnabled="True" GroupDisplayBinding="{Binding NameA}" GroupShortNameBinding="{Binding NameA}" HasUnevenRows="True">
        <ListView.GroupHeaderTemplate>
          <DataTemplate>
            <ViewCell>
              <StackLayout VerticalOptions="FillAndExpand" Padding="5" BackgroundColor="#707070">
                <Label Text="{Binding NameA}"/>
              </StackLayout>
            </ViewCell>
          </DataTemplate>
        </ListView.GroupHeaderTemplate>
        <ListView.ItemTemplate>
          <DataTemplate>
            <ViewCell>
              <StackLayout VerticalOptions="FillAndExpand" Padding="20, 5, 5, 5">
                <Label Text="{Binding NameB}" />
              </StackLayout>
            </ViewCell>
          </DataTemplate>
        </ListView.ItemTemplate>
      </ListView>
    </StackLayout>

</ContentPage>

现在 运行 生成的应用程序并按“更新”按钮:您应该会看到 ListView 正在使用新的 ViewModel 数据进行刷新。

请注意:此解决方案仍可进行一些优化。例如,每当全局 BItems 集合发生变化时更新所有内部集合可能不是很有效。某种更懒惰的更新可能是有序的。此外,如果您的 AllBItems 集合很大,您可以考虑在父级 属性 上使用一些索引机制以加快过滤速度。

Xamarin.Forms 不支持嵌套列表视图。你可以试试,但是里面那个不能滚动,所以没用。

如果 ListA 有一个 ListB 的列表,它是否适合您进行分组的需要?

本质上,您是在 A 的项目中对 B 列表进行分组(在该图像中,我显示您的列表 A 是移动设备列表,而您的 B 列表是通用 OS 中的特定类型).

为此,当您创建列表视图并设置数据 template/source 时,您现在还必须将 IsGroupingEnabled 设置为 true 并将 GroupHeaderTemplate 设置为创建的数据模板,以显示其视觉显示方式(完全相同正如您通常使用自定义视图单元制作数据模板一样。

现在唯一的区别是您的源不再是 A 列表或 B 列表,而是包含列表 B 的列表 A(它扩展了 ObservableCollection)。

您基本上需要让您的 A 视图模型扩展 ObserableCollection,因此 A 是 A 但 A 也是一个 list/can 用作列表。

现在您只需将列表 A(其中每个 A 都有一个列表 B)作为 ListView 的源。