.net MAUI DataTemplate,其 CollectionView 在其 ItemTemplate 中包含 <ContentPresenter />,“未检测到已安装的组件”

.net MAUI DataTemplate with a CollectionView that contains <ContentPresenter /> in its ItemTemplate, "No installed components were detected"

提问人:dpTech 提问时间:11/4/2023 最后编辑:dpTech 更新时间:11/8/2023 访问量:143

问:

我们有许多显示模型列表的视图,在每个视图中,点击项目会显示一个操作表。

一切正常,但我们希望利用 ControlTemplate 来确保视图之间的一致性。第一次尝试似乎有效,但事实证明它之所以有效,是因为 ItemsSource 中只有一个项目。一旦 ItemsSource 包含多个项,就会引发“未检测到已安装的组件”异常。第一项仍然被适当地呈现,点击它按预期运行。

以下是正在播放的文件:

ListViewsTemplate.xaml(控件模板)

<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         x:Class="MauiIssues.Controls.ListViewsTemplate"
         ControlTemplate="{DynamicResource ContainerTemplate}"
         x:Name="this">
<ContentView.Resources>
    <ResourceDictionary>
        <ControlTemplate x:Key="ContainerTemplate">
            <Frame BindingContext="{x:Reference this}" Padding="10, 0, 10, 0">
                <CollectionView ItemsSource="{TemplateBinding ItemsSource}">
                    <CollectionView.ItemsLayout><LinearItemsLayout Orientation="Vertical" /></CollectionView.ItemsLayout>
                    <CollectionView.ItemTemplate>
                        <DataTemplate>
                            <Grid>
                                <Frame>
                                    <Frame.GestureRecognizers>
                                        <TapGestureRecognizer 
                                            Command="{TemplateBinding ItemTapped}"
                                            CommandParameter="{Binding .}" />
                                    </Frame.GestureRecognizers>
                                    <ContentPresenter />
                                </Frame>
                                <Rectangle IsVisible="{OnPlatform false, Android=True}" HeightRequest="2" Fill="Black"/>
                            </Grid>
                        </DataTemplate>
                    </CollectionView.ItemTemplate>
                </CollectionView>
            </Frame>
        </ControlTemplate>
    </ResourceDictionary>
</ContentView.Resources>

ListViewsTemplate.xaml.cs(控件模板)

using System.Collections;
using System.Windows.Input;

namespace MauiIssues.Controls
{
    public partial class ListViewsTemplate : ContentView
    {
        public IEnumerable ItemsSource
        {
            get => (IEnumerable)GetValue(ItemsSourceProperty);
            set => SetValue(ItemsSourceProperty, value);
        }
        public static readonly BindableProperty ItemsSourceProperty =
            BindableProperty.Create(nameof(ItemsSource), typeof(IEnumerable), typeof(ListViewsTemplate), defaultBindingMode: BindingMode.TwoWay);
        
        public ICommand ItemTapped
        {
            get => (ICommand)GetValue(ItemTappedProperty);
            set => SetValue(ItemTappedProperty, value);
        }
        public static readonly BindableProperty ItemTappedProperty =
            BindableProperty.Create(nameof(ItemTapped), typeof(ICommand), typeof(ListViewsTemplate), defaultBindingMode: BindingMode.TwoWay);

        public ListViewsTemplate() { InitializeComponent(); }

    }
}

ListViewsTemplate.xaml(内容页)

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:model="clr-namespace:MauiIssues.Models"
             xmlns:uc="clr-namespace:MauiIssues.Controls"
             xmlns:viewmodel="clr-namespace:MauiIssues.ViewModels"
             x:Class="MauiIssues.Views.ListViewsTemplate"
             x:DataType="viewmodel:ListViewsTemplateViewModel">
    <uc:ListViewsTemplate ItemsSource="{Binding Items}" ItemTapped="{Binding ItemTappedCommand}" VerticalOptions="StartAndExpand" >
        <Label BindingContext="{Binding Source={RelativeSource AncestorType={x:Type model:ListViewsTemplateModel}}}" 
               x:DataType="{x:Type model:ListViewsTemplateModel}" 
               Text="{Binding Name}" />
    </uc:ListViewsTemplate>
</ContentPage>

ListViewsTemplate.xaml.cs(内容页)

using MauiIssues.ViewModels;

namespace MauiIssues.Views
{
    public partial class ListViewsTemplate : ContentPage
    {
        public ListViewsTemplateViewModel ViewModel { get; }

        public ListViewsTemplate()
        {
            base.BindingContext = this.ViewModel = new ListViewsTemplateViewModel();
            InitializeComponent();
        }

        protected override async void OnAppearing()
        {
            await this.ViewModel.OnAppearing();
            base.OnAppearing();
        }
    }
}

ListViewsTemplateViewModel.cs(视图模型)

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using MauiIssues.Models;
using System.Collections.ObjectModel;

namespace MauiIssues.ViewModels
{
    public partial class ListViewsTemplateViewModel : ObservableObject
    {
        public ObservableCollection<ListViewsTemplateModel> Items { get; } = new ();

        [RelayCommand]
        async Task ItemTapped(ListViewsTemplateModel item)
        {
            await App.Current.MainPage.DisplayAlert("tapped item", item.Name, "OK");
        }

        public async Task OnAppearing() 
        {
            this.Items.Add(new ListViewsTemplateModel() { Name = $"Item {this.Items.Count + 1}" });
            await Task.CompletedTask;
        }
    }
}

当您第一次导航到视图时,一切都很好。当您离开并返回视图时,您将获得:

未检测到已安装的组件。

在 System.Runtime.InteropServices.Marshal.ThrowExceptionForHR (Int32 错误代码) 在 WinRT.DelegateExtensions.DynamicInvokeAbi (Delegate del, Object[] invoke_params) 在 ABI。System.Collections.Generic.IVectorMethods1.Add(IObjectReference 对象,T 项) 在 Microsoft.UI.Xaml.Controls.UIElementCollection.Add(UIElement 项) 在 Microsoft.Maui.Handlers.ContentViewHandler.UpdateContent(IContentViewHandler 处理程序) 在 Microsoft.Maui.Handlers.ContentViewHandler.MapContent(IContentViewHandler 处理程序,IContentView 页) 在 Microsoft.Maui.Controls.Element.OnPropertyChanged(字符串属性名) 在 Microsoft.Maui.Controls.BindableObject.SetValueActual(BindableProperty 属性、BindablePropertyContext 上下文、对象值、布尔当前应用、SetValueFlags 属性、布尔静默) 在 Microsoft.Maui.Controls.BindableObject.SetValueCore(BindableProperty 属性、对象值、SetValueFlags 属性、SetValuePrivateFlags privateAttributes) 在 Microsoft.Maui.Controls.BindingExpression.ApplyCore(对象源对象、 绑定对象目标、 绑定属性属性、 布尔值 fromTarget) 在 Microsoft.Maui.Controls.BindingExpression.Apply(对象源对象、 BindableObject 目标、 BindableProperty 属性) 在 Microsoft.Maui.Controls.Binding.d__27.MoveNext() 在 System.Threading.Tasks.Task.<>c.b__128_0 (对象状态) 在 Microsoft.UI.Dispatching.DispatcherQueueSynchronizationContext.<>c__DisplayClass2_0.b__0() --- 从上一个位置---结束堆栈跟踪 at WinRT.ExceptionHelpers.g__Throw|20_0(Int32 小时) 在 ABI。Windows.ApplicationModel.Core.IUnhandledErrorMethods.Propagate (IObjectReference _obj) 在 Windows.ApplicationModel.Core.UnhandledError.Propagate() 在 Microsoft.AppCenter.Utils.ApplicationLifecycleHelperWinUI.<.ctor>b__0_3 (对象发送者,UnhandledErrorDetectedEventArgs eventArgs) --- 从上一个位置---结束堆栈跟踪 在 Microsoft.AppCenter.Utils.ApplicationLifecycleHelperWinUI.<.ctor>b__0_3 (对象发送者,UnhandledErrorDetectedEventArgs eventArgs) 在 WinRT.EventSource__EventHandler1.Do_Abi_Invoke[TAbi](Void* thisPtr、IntPtr 发送器、TAbi 参数)1.Append(IObjectReference obj, T value) at ABI.System.Collections.Generic.IListMethods1.EventState.<GetEventInvoke>b__1_0(Object obj, T e) at ABI.System.EventHandler

更新:

为了更清楚起见,它试图实现的功能与母版页在 asp.net 中执行的操作相同。

页面大师

<body>
   <div class="main">
       <asp:ContentPlaceHolder ID="FilterContent" runat="server" />
       <asp:Button ID="btnSearch" runat="server" Text="Search" />
       <div class="filterseparator" />
       <asp:ContentPlaceHolder ID="GridContent" runat="server" />
       <div class="gridseparator" />
       <asp:ContentPlaceHolder ID="MainContent" runat="server" />
   </div>
</body>

在这里,每个网页都使用母版页,然后将自己的控件放在 ContentPlaceHolders 中,据我们了解,这是 ContentPresenter 的用途。

C# UIChationView Maui ControlTemplate ContentPresenter

评论


答:

1赞 Jianwei Sun - MSFT 11/6/2023 #1

请勿将标签放入:uc:ListViewsTemplate

<uc:ListViewsTemplate ItemsSource="{Binding Items}" ItemTapped="{Binding ItemTappedCommand}" VerticalOptions="StartAndExpand" >
            <!--<Label BindingContext="{Binding Source={RelativeSource AncestorType={x:Type model:ListViewsTemplateModel}}}"
               x:DataType="{x:Type model:ListViewsTemplateModel}"
               Text="{Binding Name}" />-->
</uc:ListViewsTemplate>

因为这样一来,您创建的 ContentView(ListViewsTemplate) 将失去其意义。所以,把它放进去:ListViewsTemplate

.....
<DataTemplate>
    <Grid>
        <Frame>
            <Frame.GestureRecognizers>
                <TapGestureRecognizer Command="{TemplateBinding ItemTapped}" 
                                      CommandParameter="{Binding .}" />
            </Frame.GestureRecognizers>

            <!-- Put it here or anywhere else you like -->
            <Label x:DataType="{x:Type model:ListViewsTemplateModel}" 
                   Text="{Binding Name}" />
        </Frame>
        <Rectangle IsVisible="{OnPlatform false, Android=True}" HeightRequest="2" Fill="Black"/>
    </Grid>
</DataTemplate>
.....

我测试了它,效果很好。

更新

<DataTemplate>
  <Grid>
    <Frame>
      <Frame.GestureRecognizers>
        <TapGestureRecognizer Command="{TemplateBinding ItemTapped}" CommandParameter="{Binding .}"/>
      </Frame.GestureRecognizers>
      
      <!-- use stacklayout -->
      <StackLayout>
        <ContentPresenter/>
        <Label x:DataType="{x:Type model:ListViewsTemplateModel}" 
               Text="{Binding Name}"/>
      </StackLayout>
      
    </Frame>
    <Rectangle IsVisible="{OnPlatform false, Android=True}" HeightRequest="2" Fill="Black"/>
  </Grid>
</DataTemplate>

评论

0赞 dpTech 11/7/2023
但是,我不能再让其他视图使用标签以外的其他内容。例如,另一个视图可能具有标签和图像。
0赞 Jianwei Sun - MSFT 11/7/2023
用于添加任意数量的视图。我更新了答案,希望它能帮助你;)StackLayout
0赞 dpTech 11/7/2023
用 .同样,当只有一个项目时有效,当有多个项目时会引发相同的错误。只是为了确认您仍在使用 ControlTemplate 中的 CollectionView,对吗?<ContentPresenter/><StackLayout><CollectionView ItemsSource="{TemplateBinding ItemsSource}">
0赞 Jianwei Sun - MSFT 11/8/2023
我发布了一个新的答案,希望它能帮助你。
0赞 Jianwei Sun - MSFT 11/8/2023 #2

解决方案 1

删除 : 中的标签:uc:ListViewsTemplate

<uc:ListViewsTemplate ItemsSource="{Binding Items}" ItemTapped="{Binding ItemTappedCommand}" VerticalOptions="StartAndExpand" >
            <!--<Label BindingContext="{Binding Source={RelativeSource AncestorType={x:Type model:ListViewsTemplateModel}}}"
               x:DataType="{x:Type model:ListViewsTemplateModel}"
               Text="{Binding Name}" />-->
</uc:ListViewsTemplate>

在 ListViewsTemplate.xaml 中:

.....
<DataTemplate>
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition/>
      <RowDefinition/>
    </Grid.RowDefinitions>
    <Frame Grid.Row="0">
      <Frame.GestureRecognizers>
        <TapGestureRecognizer Command="{TemplateBinding ItemTapped}" 
                              CommandParameter="{Binding .}"/>
      </Frame.GestureRecognizers>
      <StackLayout>
        <Label x:DataType="{x:Type model:ListViewsTemplateModel}" 
               Text="{Binding Name}"/>
      </StackLayout>
    </Frame>
    <Rectangle Grid.Row="1" IsVisible="{OnPlatform false, Android=True}" HeightRequest="2" Fill="Black"/>
  </Grid>
</DataTemplate>
.....

这是效果

解决方案 2创建一个 ContentPage 并将以下代码放入其中:

.....
<ContentPage.Resources>
  <ResourceDictionary>
    <ControlTemplate x:Key="ContainerTemplate">
      <Grid>
        <Grid.RowDefinitions>
          <RowDefinition/>
          <RowDefinition/>
        </Grid.RowDefinitions>
        <Frame Grid.Row="0">
          <Frame.GestureRecognizers>
            <TapGestureRecognizer Command="{TemplateBinding Path=Parent.BindingContext.ItemTappedCommand}" 
            CommandParameter="{Binding Source={RelativeSource AncestorType={x:Type model:ListViewsTemplateModel}}}"/>
          </Frame.GestureRecognizers>
          <ContentPresenter/>
        </Frame>
        <Rectangle Grid.Row="1" IsVisible="{OnPlatform false, Android=True}" HeightRequest="2" Fill="Black"/>
      </Grid>
    </ControlTemplate>
  </ResourceDictionary>
</ContentPage.Resources>

<Frame Padding="10, 0, 10, 0">
  <CollectionView ItemsSource="{Binding Items}">
    <CollectionView.ItemsLayout>
      <LinearItemsLayout Orientation="Vertical"/>
    </CollectionView.ItemsLayout>
    <CollectionView.ItemTemplate>
      <DataTemplate>
        <ContentView ControlTemplate="{StaticResource ContainerTemplate}">
          <Label x:DataType="{x:Type model:ListViewsTemplateModel}" Text="{Binding Name}"/>
        </ContentView>
      </DataTemplate>
    </CollectionView.ItemTemplate>
  </CollectionView>
</Frame>
.....

.xaml.cs:

public partial class NewPage1 : ContentPage
{
    public ListViewsTemplateViewModel ViewModel { get; }

    public NewPage1()
    {
        base.BindingContext = this.ViewModel = new ListViewsTemplateViewModel();
        InitializeComponent();
    }
    protected override async void OnAppearing()
    {
        await this.ViewModel.OnAppearing();
        base.OnAppearing();
    }
}

还可以将 ContentPage.Resources 的代码放入 App.xaml 中:

<Application.Resources>
  <ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
      <ResourceDictionary Source="Resources/Styles/Colors.xaml"/>
      <ResourceDictionary Source="Resources/Styles/Styles.xaml"/>
    </ResourceDictionary.MergedDictionaries>
    <!-- you can also put codes of ContentPage.Resources into here --> 
    <ControlTemplate x:Key="ContainerTemplate">
            ......
    </ControlTemplate>
    
  </ResourceDictionary>
</Application.Resources>

这是效果

评论

0赞 dpTech 11/9/2023
首先,请允许我说声谢谢你为此付出的时间。与其使用我们尝试的内容并应用您的建议,不如重新开始您的解决方案并突出显示它未涵盖的内容。我们能够让您的解决方案在 Android 上运行,花了太多时间弄清楚为什么它不起作用,结果发现标签无法在 Windows 的屏幕上呈现。如果可以重现 Windows 问题,请创建内部票证。将在以下评论中列出您的方法存在的问题。
0赞 dpTech 11/9/2023
- 我们有 4 个视图,这些视图被单个标记绑定到扩展 BaseNamedModel 的不同模型。您的方法适用于这 4 个。- 我们有 3 个视图,它们有两个标签绑定到模型的不同属性 - 我们有 1 个视图,它有一个标签和一个按钮,标签绑定到属性,按钮绑定到其 ViewModel 的命令 - 我们有 2 个视图,它们有一个标签,它使用多重绑定到多个属性 - 我们有 2 个视图,其中一个标签和一个图像绑定到不同的属性 - 我们有 1 个视图有一个标签但它绑定到“名称”以外的其他内容
0赞 dpTech 11/9/2023
使用您的方法,我们必须定义 6 个不同的 Frames,其中包含 CollectionView。我们试图避免创建完全相同的 6 个 Frame,除了 CollectionView 用于在屏幕上呈现绑定项的控件。
0赞 dpTech 11/9/2023
接受一组代码涵盖的 4 个视图总比没有好,我们发现您的方法可能存在问题。您可以在不离开视图的情况下将 4 个视图中的 1 个添加到 ItemsSource。我们注意到,虽然 ObservableCollection 确实更新了,但屏幕不会呈现新项。中方能否证实?只需向 ContentPage 添加一个按钮,并将其绑定到 ViewModel 中的新命令,该命令将项添加到 ObservableCollection。