如何使用绑定和DataTemplate或ContentControl在MAUI中展示数据。

7

如何使用绑定和DataTemplate来呈现字符串、数字或视图模型?

我正在寻找 MAUI 中替代 WPF ContentControl 的方法。

ContentView 具有 Content 属性,但该属性的类型是 View。而 ContentPresenter 也具有 Content 属性,但该属性同样是 View 类型。<Ignorable>为什么这不被命名为 ViewPresenter 呢?因为它只能呈现 View??? 有时 MAUI 真奇怪。</Ignorable>

如何通过为每种数据类型定义 DataTemplates 来呈现任何内容?

class PropertyViewModel {
   public string Name {get;set;}
   public object Value {get;set;}
}

<Page.Resources>
    <DataTemplate DataType="System.String">
        <Entry Text="{Binding}/>
    </DataTemplate>
    <DataTemplate DataType="System.Int32">
        <NumberPicker Value="{Binding}/>
    </DataTemplate>
    .. more templates, eg. DatePicker for System.DateOnly
</Page.Resources>

<DockLayout>
    <Label Text="{Binding Name}
    <TemplatedContenView Content={Binding Value}/> 
</DockPanel>
或ContentControl(MAUI中不存在)可以针对不同类型的值使用不同的模板。在WPF中,ContentControl使用ContentTemplate、ContentTemplateSelector,如果没有指定,则查找资源以找到模板。

<Ignorable>我经常感觉在MAUI中我必须不断地重复发明WPF中的标准事物。是的,我知道MAUI不是WPF,但至少应该有类似的概念。从WinForms切换到WPF要容易得多,并且差异相当大。</Ignorable>

Edit1:更详细的示例:

1个回答

7
我是一名WPF开发人员,最近我开始接触MAUI项目。可是每当你要写一个像你提到的这样简单的场景时,似乎你都需要重新发明轮子。当你使用WPF时,你甚至不需要考虑这个问题,它太容易实现了。但是当你使用MAUI时,你必须费尽心思去做这些微小的事情。
我也遇到了相同的问题,而且我没有找到一个简单的内置解决方案。但我想出了一个创意,创建一个带有一些布局的控件,并在其中使用BindableLayout的附加属性。
TemplatedContentPresenter.xaml.cs:
public partial class TemplatedContentPresenter : ContentView
{
    public TemplatedContentPresenter()
    {
        InitializeComponent();
    }

    public static readonly BindableProperty DataTemplateSelectorProperty = BindableProperty.Create(nameof(DataTemplateSelector), typeof(DataTemplateSelector), typeof(TemplatedContentPresenter), null, propertyChanged: DataTemplateSelectorChanged);
    public static readonly BindableProperty DataTemplateProperty = BindableProperty.Create(nameof(DataTemplate), typeof(DataTemplate), typeof(TemplatedContentPresenter), null, propertyChanged: DataTemplateChanged);
    public static readonly BindableProperty DataProperty = BindableProperty.Create(nameof(Data), typeof(object), typeof(TemplatedContentPresenter), null, propertyChanged: DataChanged);

    public DataTemplateSelector DataTemplateSelector
    {
        get =>(DataTemplateSelector)GetValue(DataTemplateSelectorProperty);
        set => SetValue(DataTemplateSelectorProperty, value);
    }

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

    public object Data
    {
        get => GetValue(DataProperty);
        set => SetValue(DataProperty, value);
    }

    private static void DataTemplateSelectorChanged(BindableObject bindable, object oldValue, object newValue)
    {
        if(bindable is TemplatedContentPresenter contentPresenter && newValue is DataTemplateSelector dataTemplateSelector)
        {
            BindableLayout.SetItemTemplateSelector(contentPresenter.HostGrid, dataTemplateSelector);
        }
    }
    private static void DataTemplateChanged(BindableObject bindable, object oldValue, object newValue)
    {
        if (bindable is TemplatedContentPresenter contentPresenter && newValue is DataTemplate dataTemplate)
        {
            BindableLayout.SetItemTemplate(contentPresenter.HostGrid, dataTemplate);
        }
    }

    private static void DataChanged(BindableObject bindable, object oldValue, object newValue)
    {
        if (bindable is TemplatedContentPresenter contentPresenter)
        {
            BindableLayout.SetItemsSource(contentPresenter.HostGrid, new object[] { newValue });
        }
    }
}

TemplatedContentPresenter.xaml:

<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MyApp.TemplatedContentPresenter">
    <Grid x:Name="HostGrid" x:FieldModifier="private" />
</ContentView>

用法:

<Frame WidthRequest="500" HeightRequest="500">
     <controls:TemplatedContentPresenter 
               Data="{Binding}" 
               DataTemplateSelector="{StaticResource CardTemplateSelector}"/>
</Frame>

更新: 在我写答案的同时,我又想到了一个用简单转换器的解决方案: SingleObjectToArray.xaml

    internal class SingleObjectToArray : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return new object[] { value };
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

使用方法:

<Frame>
    <Frame.Resources>
        <converters:SingleObjectToArray x:Key="SingleObjectToArrayConverter"/>
    </Frame.Resources>
    <Grid BindableLayout.ItemsSource="{Binding Converter={StaticResource SingleObjectToArrayConverter}}"
          BindableLayout.ItemTemplateSelector="{StaticResource CardTemplateSelector}" /> 
</Frame>

我一直担心会出现这种情况。你的回答结构清晰,很好地回答了OP的问题。然而,这将导致人们滥用此控件,并使其成为不应该成为的东西。我正在尝试使用MAUI,并研究这个确切的情景。由于使用“ContentView”会引发异常,并且在代码后台指定页面会消除我们所拥有的所有WPF乐趣。这可能是因为移动开发,但在桌面上,这是一步后退。 - XAMlMAX
为什么您没有在第二种方法中使用CollectionView呢? - lorenz albert
这很棒。我仍然想念WPF的一件事情,那就是你可以为给定类型声明一个静态DataTemplate,然后该数据的任何类型的演示都将默认使用该数据模板。你不必使用选择器。在这方面做了什么吗?我认为你必须有某种全局DataTemplateSelector,它会从App资源字典中加载DataTemplates,并按数据类型对它们进行索引。 - Josh Sutterfield
老实说,在你的两种方法中,我更喜欢第一种,因为它让我把所有的逻辑都放在一个类里,需要的时候随时可以使用。第二种方法也很巧妙,但我觉得不够直接……不过,这个答案绝对值得点赞!太棒了! - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接