使用具有不同模型和 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的主要内容,如果未能解决你的问题,请参考以下文章

无法通过 DataTemplates 设置控制的可见性

DataTemplates 的动态加载

什么是 ViewModelLocator,与 DataTemplates 相比,它的优缺点是什么?

如何将视图与viewmodel关联或ViewModel的多个DataTemplates?

WPF 用户控件不呈现。 UserControl 使用 DataTemplates 实例化

TabControl 内的 WPF ContentControl 不显示 DataTemplates