.NET MAUI自定义控件绑定属性设置CollectionView ItemTemplate失败
.NET MAUI自定义控件中通过XAML设置CollectionView ItemTemplate无效的解决方法
我开发了一个内嵌CollectionView的.NET MAUI自定义控件,想要通过控件的绑定属性让使用者在XAML中配置CollectionView的ItemTemplate,但目前通过XAML设置的内容完全无效。已经验证所有绑定都正常,绑定属性本身能工作,但ItemTemplate就是无法通过XAML生效。后台代码直接设置ItemTemplate是正常的,但我希望使用者能通过XAML进行个性化配置。
原自定义控件XAML
<?xml version="1.0" encoding="utf-8" ?> <StackLayout x:Name="DataGridControl" xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="PensionApp.CustomUIControls.DataGrid" xmlns:local="clr-namespace:PensionApp.CustomUIControls" HeightRequest="500" WidthRequest="500" Orientation="Vertical"> <CollectionView x:Name="Datagrid_collection_view" ItemsSource="{Binding CollectionToDisplay ,Source={x:Reference DataGridControl}}"> <CollectionView.ItemTemplate> <DataTemplate > <ContentView Content="{Binding CustomContentTemplate, Source={x:Reference DataGridControl}}" /> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> </StackLayout>
原自定义控件后台代码
namespace PensionApp.CustomUIControls { using System.Collections.ObjectModel; using System.Reflection; using PensionApp; using System.Collections.Specialized; using System.Collections.Generic; using System.Collections; using System.ComponentModel; using Microsoft.Maui.Controls; public partial class DataGrid : StackLayout { public DataGrid() { InitializeComponent(); } public static readonly BindableProperty CollectionToDisplayProperty = BindableProperty.Create( propertyName: nameof(CollectionToDisplay), returnType: typeof(ObservableCollection<Object>), defaultValue: null, declaringType: typeof(DataGrid), defaultBindingMode: BindingMode.TwoWay, propertyChanged: OnCollectionToDisplayChanged ); public ObservableCollection<Object> CollectionToDisplay { get => (ObservableCollection<Object>)GetValue(CollectionToDisplayProperty); set { SetValue(CollectionToDisplayProperty, (ObservableCollection<Object>)value); } } private static void OnCollectionToDisplayChanged(BindableObject bindable, object oldValue, object newValue) { var DataGridControlInstance = (DataGrid)bindable; DataGridControlInstance.Datagrid_collection_view.ItemsSource = (ObservableCollection<object>)newValue; } public static readonly BindableProperty CustomContentTemplateProperty = BindableProperty.Create( propertyName: nameof(CustomContentTemplate), returnType: typeof(View), defaultValue: null, declaringType: typeof(DataGrid), defaultBindingMode: BindingMode.OneWay, propertyChanged: OnCustomContentTemplateChanged ); public View CustomContentTemplate { get => (View)GetValue(CustomContentTemplateProperty); set { SetValue(CustomContentTemplateProperty, (View)value); } } private static void OnCustomContentTemplateChanged(BindableObject bindable, object oldValue, object newValue) { } } }
原MainPage XAML
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="PensionApp.MainPage" xmlns:controls="clr-namespace:PensionApp.CustomUIControls" x:Name="MainPageInstance"> <ScrollView> <VerticalStackLayout Spacing="25" Padding="30,0" VerticalOptions="Center"> <controls:DataGrid CollectionToDisplay="{Binding CollectionToPass,Source={x:Reference MainPageInstance}}"> <controls:DataGrid.CustomContentTemplate> <Grid RowDefinitions="Auto" ColumnDefinitions="Auto,Auto" HeightRequest="20"> <Entry Text="achnou" Grid.Row="0" Grid.Column="0"></Entry> <Entry Text="male" Grid.Row="0" Grid.Column="1"></Entry> </Grid> </controls:DataGrid.CustomContentTemplate> </controls:DataGrid> </VerticalStackLayout> </ScrollView> </ContentPage>
原MainPage后台代码
using System.Collections.ObjectModel; using System.ComponentModel; namespace PensionApp; public partial class MainPage : ContentPage { int count = 0; public ObservableCollection<Object> CollectionToPass { get; set;} public MainPage() { CollectionToPass = new ObservableCollection<Object>(); CollectionToPass.Add(new Somedataexemple("XXXX", "XXXXX")); CollectionToPass.Add(new Somedataexemple("YYYYY", "YYYYY")); CollectionToPass.Add(new Somedataexemple("ZZZZZ", "ZZZZ")); InitializeComponent(); } } public class Somedataexemple : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public Somedataexemple(String first, string last) { First_Name = first; Last_Name = last; } private string first_Name; private string last_Name; public string First_Name { get => first_Name; set{first_Name = value; RaisePropertyChanged(nameof(First_Name));} } public string Last_Name { get => last_Name; set{last_Name = value; RaisePropertyChanged(nameof(Last_Name));} } public void RaisePropertyChanged(string propertyName) {PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));} }
问题根源
核心问题在于你定义的CustomContentTemplate属性类型是View,但CollectionView的ItemTemplate需要的是DataTemplate。直接绑定View会导致所有列表项共用同一个View实例,而且DataTemplate内部的绑定上下文是列表项的数据对象,不是自定义控件本身,所以之前的绑定逻辑无法正确工作。
修复步骤
1. 修改自定义控件的BindableProperty类型
把CustomContentTemplateProperty的类型从View改为DataTemplate,并在属性变化时直接赋值给CollectionView的ItemTemplate:
public static readonly BindableProperty CustomContentTemplateProperty = BindableProperty.Create( propertyName: nameof(CustomContentTemplate), returnType: typeof(DataTemplate), defaultValue: null, declaringType: typeof(DataGrid), defaultBindingMode: BindingMode.OneWay, propertyChanged: OnCustomContentTemplateChanged ); public DataTemplate CustomContentTemplate { get => (DataTemplate)GetValue(CustomContentTemplateProperty); set => SetValue(CustomContentTemplateProperty, value); } private static void OnCustomContentTemplateChanged(BindableObject bindable, object oldValue, object newValue) { var dataGrid = (DataGrid)bindable; dataGrid.Datagrid_collection_view.ItemTemplate = (DataTemplate)newValue; }
2. 简化自定义控件的XAML
移除固定的ItemTemplate嵌套,让外部通过属性传入:
<?xml version="1.0" encoding="utf-8" ?> <StackLayout x:Name="DataGridControl" xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="PensionApp.CustomUIControls.DataGrid" xmlns:local="clr-namespace:PensionApp.CustomUIControls" HeightRequest="500" WidthRequest="500" Orientation="Vertical"> <CollectionView x:Name="Datagrid_collection_view" ItemsSource="{Binding CollectionToDisplay, Source={x:Reference DataGridControl}}"> </CollectionView> </StackLayout>
3. 修改MainPage的XAML使用方式
现在可以直接传入DataTemplate,并且能正确绑定到列表项的数据:
<controls:DataGrid CollectionToDisplay="{Binding CollectionToPass, Source={x:Reference MainPageInstance}}"> <controls:DataGrid.CustomContentTemplate> <DataTemplate> <Grid RowDefinitions="Auto" ColumnDefinitions="Auto,Auto" HeightRequest="20"> <Entry Text="{Binding First_Name}" Grid.Row="0" Grid.Column="0"/> <Entry Text="{Binding Last_Name}" Grid.Row="0" Grid.Column="1"/> </Grid> </DataTemplate> </controls:DataGrid.CustomContentTemplate> </controls:DataGrid>
4. 优化CollectionToDisplay的绑定
原代码中手动赋值ItemsSource的逻辑多余,因为XAML已经绑定了ItemsSource,可移除相关代码简化:
// 移除属性变化事件 // private static void OnCollectionToDisplayChanged(BindableObject bindable, object oldValue, object newValue) // { // var DataGridControlInstance = (DataGrid)bindable; // DataGridControlInstance.Datagrid_collection_view.ItemsSource = (ObservableCollection<object>)newValue; // } // 创建BindableProperty时去掉propertyChanged参数 public static readonly BindableProperty CollectionToDisplayProperty = BindableProperty.Create( propertyName: nameof(CollectionToDisplay), returnType: typeof(ObservableCollection<Object>), defaultValue: null, declaringType: typeof(DataGrid), defaultBindingMode: BindingMode.TwoWay );
内容的提问来源于stack exchange,提问作者W Mernissi
相关产品推荐
相关产品推荐

