You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET MAUI通用CollectionView控件开发:动态内容注入问题

.NET MAUI 通用CollectionView控件实现方案

需求与问题

需要开发一款集成SwipeView、通用事件处理的通用CollectionView控件,允许使用页面自定义数据展示的XAML模板。尝试两种方案均遇问题:

  • 方案一:ContentView结合ControlTemplate+ContentPresenter实现,在OnApplyTemplate中设置ItemsSource时,Android端抛Parent already added异常,Windows端抛No installed component found异常。
  • 方案二:继承CollectionView并添加Contents属性,结果仅显示列表最后一项。

尝试的代码示例

方案一:ContentView+ControlTemplate实现

MyCollectionView.xaml

<ContentView x:Class="Pages.MyCollectionView" xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
    <ContentView.ControlTemplate>
        <ControlTemplate>
            <CollectionView x:Name="myCollectionView" ItemsSource="{Binding}">
                <CollectionView.ItemTemplate>
                    <DataTemplate>
                        <SwipeView>
                            <SwipeView.LeftItems>
                                <SwipeItems>
                                    <SwipeItem BackgroundColor="GreenYellow" Clicked=""/>
                                </SwipeItems>
                            </SwipeView.LeftItems>
                            <ContentPresenter/>
                        </SwipeView>
                    </DataTemplate>
                </CollectionView.ItemTemplate>
            </CollectionView>
        </ControlTemplate>
    </ContentView.ControlTemplate>
</ContentView>

MyCollectionView.cs

public partial class MyCollectionView : ContentView
{    
    public List<string> source = new List<string>();
    
    protected override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        var myCollectionView = (CollectionView)GetTemplateChild("myCollectionView");
        if(myCollectionView != null)
        {
            myCollectionView.ItemsSource = source;
        }
    }
}

MainPage.xaml使用示例

<StackLayout>
    <ScrollView>
        <ctrl:MyCollectionView>
            <Grid>
                <Label Text="{Binding Value}"/>
            </Grid>
        </ctrl:MyCollectionView>
    </ScrollView>
</StackLayout>

方案二:继承CollectionView实现

KCollectionView.cs

public class KCollectionView : CollectionView
{
    public View Contents { get; set; }

    public KCollectionView()
    {
        HorizontalOptions = LayoutOptions.FillAndExpand;
        VerticalOptions = LayoutOptions.FillAndExpand;
        ItemTemplate = new DataTemplate(() =>
        {
            SwipeView swipeView = new SwipeView();
            
            SwipeItem favoriteSwipeItem = new SwipeItem
            {
                Text = "Favorite",
                IconImageSource = "favorite.png",
                BackgroundColor = Colors.LightGreen
            };
            favoriteSwipeItem.SetBinding(MenuItem.CommandProperty, new Binding("BindingContext.FavoriteCommand", source: this));
            favoriteSwipeItem.SetBinding(MenuItem.CommandParameterProperty, ".");

            SwipeItem deleteSwipeItem = new SwipeItem
            {
                Text = "Delete",
                IconImageSource = "delete.png",
                BackgroundColor = Colors.LightPink
            };
            deleteSwipeItem.SetBinding(MenuItem.CommandProperty, new Binding("BindingContext.DeleteCommand", source: this));
            deleteSwipeItem.SetBinding(MenuItem.CommandParameterProperty, ".");

            swipeView.LeftItems = new SwipeItems { favoriteSwipeItem, deleteSwipeItem };
            swipeView.Content = Contents;
            return swipeView;
        });
    }
}

使用示例

<KCollectionView ItemsSource="{Binding}">
    <KCollectionView.Contents>
        <Grid HorizontalOptions="FillAndExpand" VerticalOptions="StartAndExpand" BackgroundColor="gray">
            <Grid.RowDefinitions>
                <RowDefinition Height="100"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Label Grid.Row="0" Grid.Column="0" Text="{Binding Weight}"/>
        </Grid>
    </KCollectionView.Contents>
</KCollectionView>

正确实现方案

问题根源:

  1. 方案一:ContentPresenter试图复用同一个View实例到所有列表项,而MAUI中一个View只能属于一个父容器,导致Parent already added异常。
  2. 方案二:Contents是单个View实例,所有列表项共享同一个View,最终只有最后一项能正常显示。

正确做法是通过BindableProperty接收外部传入的DataTemplate,而非View实例,在自定义控件内部将其整合到包含SwipeView的ItemTemplate中:

通用CollectionView控件实现

GenericSwipeCollectionView.cs

public class GenericSwipeCollectionView : CollectionView
{
    // 定义BindableProperty,允许外部传入自定义Item模板
    public static readonly BindableProperty ItemContentTemplateProperty =
        BindableProperty.Create(nameof(ItemContentTemplate), typeof(DataTemplate), typeof(GenericSwipeCollectionView));

    public DataTemplate ItemContentTemplate
    {
        get => (DataTemplate)GetValue(ItemContentTemplateProperty);
        set => SetValue(ItemContentTemplateProperty, value);
    }

    // 定义通用命令的BindableProperty
    public static readonly BindableProperty FavoriteCommandProperty =
        BindableProperty.Create(nameof(FavoriteCommand), typeof(ICommand), typeof(GenericSwipeCollectionView));

    public ICommand FavoriteCommand
    {
        get => (ICommand)GetValue(FavoriteCommandProperty);
        set => SetValue(FavoriteCommandProperty, value);
    }

    public static readonly BindableProperty DeleteCommandProperty =
        BindableProperty.Create(nameof(DeleteCommand), typeof(ICommand), typeof(GenericSwipeCollectionView));

    public ICommand DeleteCommand
    {
        get => (ICommand)GetValue(DeleteCommandProperty);
        set => SetValue(DeleteCommandProperty, value);
    }

    public GenericSwipeCollectionView()
    {
        HorizontalOptions = LayoutOptions.FillAndExpand;
        VerticalOptions = LayoutOptions.FillAndExpand;
        
        // 创建包含SwipeView的ItemTemplate
        ItemTemplate = new DataTemplate(() =>
        {
            SwipeView swipeView = new SwipeView();

            // 收藏按钮
            SwipeItem favoriteItem = new SwipeItem
            {
                Text = "收藏",
                BackgroundColor = Colors.LightGreen
            };
            favoriteItem.SetBinding(MenuItem.CommandProperty, new Binding(nameof(FavoriteCommand), source: this));
            favoriteItem.SetBinding(MenuItem.CommandParameterProperty, new Binding("."));

            // 删除按钮
            SwipeItem deleteItem = new SwipeItem
            {
                Text = "删除",
                BackgroundColor = Colors.LightPink
            };
            deleteItem.SetBinding(MenuItem.CommandProperty, new Binding(nameof(DeleteCommand), source: this));
            deleteItem.SetBinding(MenuItem.CommandParameterProperty, new Binding("."));

            swipeView.LeftItems = new SwipeItems { favoriteItem, deleteItem };

            // 使用外部传入的模板创建Item内容
            if (ItemContentTemplate != null)
            {
                swipeView.Content = ItemContentTemplate.CreateContent() as View;
            }

            return swipeView;
        });
    }
}

使用示例

MainPage.xaml

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:ctrl="clr-namespace:YourNamespace"
             x:Class="YourNamespace.MainPage">
    <StackLayout>
        <ctrl:GenericSwipeCollectionView 
            ItemsSource="{Binding Items}"
            FavoriteCommand="{Binding FavoriteCommand}"
            DeleteCommand="{Binding DeleteCommand}">
            <ctrl:GenericSwipeCollectionView.ItemContentTemplate>
                <DataTemplate>
                    <Grid Padding="10" BackgroundColor="LightGray" Margin="5">
                        <Label Text="{Binding Name}" FontSize="18"/>
                        <Label Text="{Binding Weight}" HorizontalOptions="End" FontSize="14"/>
                    </Grid>
                </DataTemplate>
            </ctrl:GenericSwipeCollectionView.ItemContentTemplate>
        </ctrl:GenericSwipeCollectionView>
    </StackLayout>
</ContentPage>

说明

  • 通过ItemContentTemplate这个BindableProperty,外部页面可以自由定义每个列表项的UI结构,避免了View实例复用的问题。
  • 通用命令(收藏、删除)通过BindableProperty绑定,实现了事件处理的复用。
  • 每个列表项都会通过ItemContentTemplate.CreateContent()创建独立的View实例,不存在父容器冲突问题。

内容的提问来源于stack exchange,提问作者Sandeep Sirohi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 16:54:58