通过 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 的源。
我正在编写一个基于 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
此外,由于您的 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 的源。