.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>
正确实现方案
问题根源:
- 方案一:
ContentPresenter试图复用同一个View实例到所有列表项,而MAUI中一个View只能属于一个父容器,导致Parent already added异常。 - 方案二:
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
相关产品推荐
相关产品推荐

