使用具有不同模型和 DataTemplates 的分组 ListView
Posted
技术标签:
【中文标题】使用具有不同模型和 DataTemplates 的分组 ListView【英文标题】:Use grouped ListView with different models and DataTemplates 【发布时间】:2021-02-04 14:58:03 【问题描述】:我想要这种类型的ListView
,具有不同的ViewCell
类型、不同的数据、不同的标题文本(带有自定义标题单元格),但都在一个ListView
:
**********************
* General info
**********************
| Category: Cabrio
| Type: Sportscar
**********************
* Available models
**********************
| Year: 2007
| Manufacturer: Chevrolet
| Model: Corvette
----------------------
| Year: 2009
| Manufacturer: Dodge
| Model: Charger
----------------------
因此我使用这个Grouping
对象来保存部分(标题)标题和列表:
Grouping.cs:
public class Grouping<K, T> : ObservableCollection<T>
public K Key get; private set;
public Grouping(K key, IEnumerable<T> items)
Key = key;
foreach (var item in items)
this.Items.Add(item);
取自this not available link。
如何在我的ListView
中使用不同的数据?目前我的
MainPage.xaml.cs:
public partial class MainPage : ContentPage
private ObservableCollection<Model.Grouping<string, object>> itemsGrouped;
public MainPage()
InitializeComponent();
this.itemsGrouped = new ObservableCollection<Grouping<string, object>>();
List<Category> categories = new List<Category>();
categories.Add(new Category("Cabrio", "Sportscar"));
this.itemsGrouped.Add(new Grouping<string, Category>("General info", categories));
List<CarInfo> cars = new List<CarInfo>();
cars.Add(new CarInfo("2007", "Chevrolet", "Corvette"));
cars.Add(new CarInfo("2009", "Dodge", "Charger"));
this.itemsGrouped.Add(new Grouping<string, CarInfo>("Available models", cars));
this.mainList.BindingContext = this.itemsGrouped;
参数 1:无法从 'TestGroupedListView.Model.Grouping
' 转换为 'TestGroupedListView.Model.Grouping '
一般的想法是使用DataTemplateSelector
能够使用不同类型的单元格并使用Grouping
具有不同的自定义标题标题。
这是我的完整示例项目代码:
Category.cs:
public class Category
public string CategoryName get; set;
public string TypeOfCar get; set;
public Category(string categoryName, string typeOfCar)
this.CategoryName = categoryName;
this.TypeOfCar = typeOfCar;
CarInfo.cs:
public class CarInfo
public string Year get; set;
public string Manufacturer get; set;
public string Name get; set;
public CarInfo(string year, string manufacturer, string name)
this.Year = year;
this.Manufacturer = manufacturer;
this.Name = name;
GeneralView.xaml:
<?xml version="1.0" encoding="UTF-8"?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TestGroupedListView.CustomView.GeneralView">
<Grid x:Name="mainGrid" Padding="5" VerticalOptions="CenterAndExpand">
</Grid>
</ViewCell>
GeneralView.xaml.cs:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class GeneralView : ViewCell
private List<KeyValuePair<string, string>> dataList;
public GeneralView()
InitializeComponent();
protected override void OnBindingContextChanged()
base.OnBindingContextChanged();
var generalInfo = BindingContext as Category;
if (generalInfo != null)
this.SetupView(generalInfo);
private void SetupView(Category item)
this.dataList = new List<KeyValuePair<string, string>>();
this.dataList.Add(new KeyValuePair<string, string>("Name", item.CategoryName));
this.dataList.Add(new KeyValuePair<string, string>("Type", item.TypeOfCar));
this.mainGrid.ColumnDefinitions.Add(new ColumnDefinition Width = new GridLength(1, GridUnitType.Star) );
this.mainGrid.ColumnDefinitions.Add(new ColumnDefinition Width = new GridLength(1, GridUnitType.Star) );
for (int i = 0; i < dataList.Count; i++)
this.mainGrid.RowDefinitions.Add(new RowDefinition Height = GridLength.Auto );
var keyLabel = new Label()
Text = dataList[i].Key
;
var valueLabel = new Label()
Text = dataList[i].Value
;
this.mainGrid.Children.Add(keyLabel, 0, i);
this.mainGrid.Children.Add(valueLabel, 1, i);
InfoItemView.xaml:
<?xml version="1.0" encoding="UTF-8"?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TestGroupedListView.CustomView.InfoItemView">
<Grid x:Name="mainGrid" Padding="5" VerticalOptions="CenterAndExpand">
</Grid>
</ViewCell>
InfoItemView.xaml.cs:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class InfoItemView : ViewCell
private List<KeyValuePair<string, string>> dataList;
public InfoItemView()
InitializeComponent();
protected override void OnBindingContextChanged()
base.OnBindingContextChanged();
var info = BindingContext as CarInfo;
if (info != null)
this.SetupView(info);
private void SetupView(CarInfo item)
this.dataList = new List<KeyValuePair<string, string>>();
this.dataList.Add(new KeyValuePair<string, string>("Year", item.Year));
this.dataList.Add(new KeyValuePair<string, string>("Manufacturer", item.Manufacturer));
this.dataList.Add(new KeyValuePair<string, string>("Name", item.Name));
this.mainGrid.ColumnDefinitions.Add(new ColumnDefinition Width = new GridLength(1, GridUnitType.Star) );
this.mainGrid.ColumnDefinitions.Add(new ColumnDefinition Width = new GridLength(1, GridUnitType.Star) );
for (int i = 0; i < dataList.Count; i++)
this.mainGrid.RowDefinitions.Add(new RowDefinition Height = GridLength.Auto );
var keyLabel = new Label()
Text = dataList[i].Key
;
var valueLabel = new Label()
Text = dataList[i].Value
;
this.mainGrid.Children.Add(keyLabel, 0, i);
this.mainGrid.Children.Add(valueLabel, 1, i);
在我的实际项目中,这两种细胞类型差异更大,但您应该明白这一点。
ListViewGroupHeader.cs:
public class ListViewGroupHeader : Label
MyDataTemplateSelector.cs:
public class MyDataTemplateSelector : DataTemplateSelector
public DataTemplate CarTemplate get; set;
public DataTemplate GeneralTemplate get; set;
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
if (item is CarInfo)
return this.CarTemplate;
else if (item is Category)
return this.GeneralTemplate;
return new DataTemplate();
MainPage.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:renderer="clr-namespace:TestGroupedListView.CustomRenderer;assembly=TestGroupedListView"
xmlns:customViews="clr-namespace:TestGroupedListView.CustomView;assembly=TestGroupedListView"
xmlns:local="clr-namespace:TestGroupedListView.Helper;assembly=TestGroupedListView"
x:Class="TestGroupedListView.MainPage">
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="carTemplate">
<customViews:InfoItemView />
</DataTemplate>
<DataTemplate x:Key="generalTemplate">
<customViews:GeneralView />
</DataTemplate>
<local:MyDataTemplateSelector x:Key="myDataTemplateSelector" CarTemplate="StaticResource carTemplate" GeneralTemplate="StaticResource generalTemplate" />
</ResourceDictionary>
</ContentPage.Resources>
<ListView x:Name="mainList"
SeparatorColor="StaticResource PrimaryLight"
HasUnevenRows="True"
GroupDisplayBinding="Binding Key"
IsGroupingEnabled="True"
CachingStrategy="RecycleElement"
ItemTemplate="StaticResource myDataTemplateSelector">
<ListView.GroupHeaderTemplate>
<DataTemplate>
<renderer:ListViewGroupHeader Text="Binding Key" Style="StaticResource labelHeader" HeightRequest="40" />
</DataTemplate>
</ListView.GroupHeaderTemplate>
<!--
<ListView.ItemTemplate>
<DataTemplate>
<customViews:InfoItemView />
</DataTemplate>
</ListView.ItemTemplate>
-->
</ListView>
</ContentPage>
就是这样。那行得通吗?还是我应该寻找另一种解决方案?我在 Google 中没有找到关于完全相同问题的信息……(也许是错误的关键字?)
【问题讨论】:
如果你想使用DataTemplateSelector,这使得多个DataTemplates可以应用于相同类型的对象,以自定义特定对象的外观。对于 ListView 自定义标题,请查看customizing grouping。我不清楚您的自定义标题,请提供一张有关此的屏幕截图。 在这里使用TableView 更有意义。 @CherryBu-MSFT:这就是问题:你能在ListView
中使用不同类型的对象吗?自定义标题没什么特别的,但不仅仅是文本和文本颜色。有背景颜色,一些边框等等。您可以在下面我的回答中找到一个简单的版本。
【参考方案1】:
正如FreakyAli 提到的TableView 是一个选项。我完全忘记了这一点,因此我做了一个最小的例子。
MainPage.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"
x:Class="TestGroupedListView.MainPage">
<TableView x:Name="tableData" HasUnevenRows="True">
</TableView>
</ContentPage>
MainPage.xaml.cs:
public partial class MainPage : ContentPage
public MainPage()
InitializeComponent();
this.tableData.Intent = TableIntent.Data;
var category = new Category("Cabrio", "Sportscar");
var generalView = new GeneralView();
generalView.BindingContext = category;
this.tableData.Root = new TableRoot()
new TableSection()
new ViewCell()
View = new ListViewGroupHeader()
Text = "General info",
HeightRequest = 40,
FontAttributes = FontAttributes.Bold,
TextColor = Color.White,
BackgroundColor = Color.Blue,
VerticalTextAlignment = TextAlignment.Center
,
new TableSection()
generalView
,
new TableSection()
new ViewCell()
View = new ListViewGroupHeader()
Text = "Available models",
HeightRequest = 40,
FontAttributes = FontAttributes.Bold,
TextColor = Color.White,
BackgroundColor = Color.Blue,
VerticalTextAlignment = TextAlignment.Center
,
;
List<CarInfo> cars = new List<CarInfo>();
cars.Add(new CarInfo("2007", "Chevrolet", "Corvette"));
cars.Add(new CarInfo("2009", "Dodge", "Charger"));
foreach (var car in cars)
var carInfo = new InfoItemView();
carInfo.BindingContext = car;
carInfo.Tapped += CarInfo_Tapped;
this.tableData.Root.Add(new TableSection() carInfo );
private void CarInfo_Tapped(object sender, EventArgs e)
var carInfo = (InfoItemView)sender;
var car = (CarInfo)carInfo.BindingContext;
System.Diagnostics.Debug.WriteLine(car.Name);
对于自定义标题this comment 帮助了我。可以使用不带标题文本的TableSection
。
结果如下:
我必须重新构建我的应用程序以查看是否满足所有标准和特殊情况,但它看起来很有希望。但我仍然很想知道您是否可以在 ListView
中使用不同类型的对象...另一种选择是在 ScrollView
中使用 StackLayout
并逐个添加元素。
【讨论】:
以上是关于使用具有不同模型和 DataTemplates 的分组 ListView的主要内容,如果未能解决你的问题,请参考以下文章
什么是 ViewModelLocator,与 DataTemplates 相比,它的优缺点是什么?
如何将视图与viewmodel关联或ViewModel的多个DataTemplates?