.Net MAUI数据绑定未传递到自定义组件

3
我在使用自定义组件时遇到了数据绑定的问题。
我创建了一个名为 IncrementValue 的属性,每次按钮点击时都会递增。
当将其与 Label 绑定时,更改会反映出来。但是,当我将其绑定到自定义组件中的可绑定属性时,它们就无法工作了。
在示例中,我构建了一个名为 Card 的自定义组件,其中包含两个可绑定属性 CardTitle 和 CardIncrement。
由于我刚开始接触 MAUI 甚至 Xamarin,所以我是否漏掉了什么?
以下是代码片段的 Github 链接:https://github.com/814k31/DataBindingExample Card.xaml.cs
namespace DataBindingExample;

public partial class Card : VerticalStackLayout
{
    public static readonly BindableProperty CardTitleProperty = BindableProperty.Create(nameof(CardTitle), typeof(string), typeof(Card), string.Empty);
    public static readonly BindableProperty CardIncrementProperty = BindableProperty.Create(nameof(CardIncrement), typeof(int), typeof(Card), 0);

    public string CardTitle
    {
        get => (string)GetValue(CardTitleProperty);
        set => SetValue(CardTitleProperty, value);
    }

    public int CardIncrement
    {
        get => (int)GetValue(CardIncrementProperty);
        set => SetValue(CardIncrementProperty, value);
    }

    public Card()
    {
        InitializeComponent();

        BindingContext = this;
    }
}

Card.xaml

<?xml version="1.0" encoding="utf-8" ?>
<VerticalStackLayout
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:databindingexample="clr-namespace:DataBindingExample"
    x:DataType="databindingexample:Card"
    x:Class="DataBindingExample.Card"
    Spacing="25"
    Padding="30,0"
    VerticalOptions="Center"
    BackgroundColor="red"
>
    <Label
        Text="{Binding CardTitle}"
        SemanticProperties.HeadingLevel="Level1"
        FontSize="32"
        HorizontalOptions="Center"
    />
    <Label
        Text="{Binding CardIncrement}"
        SemanticProperties.HeadingLevel="Level1"
        FontSize="32"
        HorizontalOptions="Center"
    />
</VerticalStackLayout>

MainPage.xml

<?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="DataBindingExample.MainPage"
    xmlns:DataBindingExample="clr-namespace:DataBindingExample"
    xmlns:ViewModels="clr-namespace:DataBindingExample.ViewModels"
    x:DataType="ViewModels:MainPageViewModel"
>
    <ScrollView>
        <VerticalStackLayout
            Spacing="25"
            Padding="30,0"
            VerticalOptions="Center"
        >
            <Label
                Text="{Binding IncrementedValue}"
                SemanticProperties.HeadingLevel="Level2"
                FontSize="18"
                HorizontalOptions="Center"
            />

            <!-- Why doesnt this work? -->
            <DataBindingExample:Card CardIncrement="{Binding IncrementedValue}" />

            <Button
                x:Name="CounterBtn"
                Text="Click Me"
                SemanticProperties.Hint="Counts the number of times you click"
                Command="{Binding IncrementValueCommand}"
                HorizontalOptions="Center"
            />
        </VerticalStackLayout>
    </ScrollView>
</ContentPage>
1个回答

14
当创建自定义组件(包含XAML)时,请勿设置BindingContext = this;
原因:你希望该组件使用与放置它的页面相同的BindingContext。如果在自定义组件中不设置BindingContext,这将自动发生。
然而,移除此行将破坏所有组件的XAML绑定;你需要在XAML中添加一些内容来修复此问题。
换句话说:如何从XAML中引用卡片的属性?请参见下一节。

通过x:Name访问组件属性

解决方案:给自定义组件(卡片)一个x:Name,并将其作为这些绑定的“Source”:

<VerticalStackLayout
    ...
    x:Name="me"     <-- IMPORTANT! Change name as desired.
    x:Class="DataBindingExample.Card"
>
    ...
    <Label Text="{Binding CardIncrement, Source={x:Reference me}}"
    ...

注意这个解决方案有两个部分:
1. 在组件的XAML头部,定义`x:Name="mynamehere"`。
2. 在每个绑定中,说明组件是数据源:`, Source={x:Reference mynamehere}`。
3. 另一种语法是在`Source`之前使用`Path`:`"{Binding Source={x:Reference me}, CardIncrement}"`。
4. 可选地,在任何一种语法中,可以给`Path`加上标签:`"{Binding Source={x:Reference me}, Path=CardIncrement}"`。

可选(罕见):如果自定义组件有“ViewModel”:

重要提示:即使您正在使用MVVM,自定义组件通常不会有一个视图模型。相反,它的行为由包含页面使用BindableProperties来控制。如问题中所示的代码。

这里展示的技术是为那些将组件的数据和数据操作方法分离到一个单独的(“视图模型”)类中的人提供方便。

为了使BindableProperties起作用,请回顾上面的内容:

不要设置BindingContext(以便组件可以轻松访问页面的BindingContext)。

因此,在这种技术中,我们不将ViewModel设置为BindingContext。

如何访问这个ViewModel?

通过将其保存为组件的属性;例如:

public partial class MyComponent : ContentView
{
  private MyViewModel VM;

  public void MyComponent()
  {
    InitializeComponent();

    VM = new MyViewModel();
    //DO NOT DO "BindingContext = VM;"
  }

public class MyViewModel : ObservableObject
{
  [ObservableProperty]
  SomeType someProperty;   // This is field. Property "SomeProperty" is generated.
}

重要提示:组件上需要定义x:Name。请参考答案的前面部分。

然后在XAML中,我们通过使用.符号来访问VM的属性:

  <Label Text="{Binding VM.SomeProperty, Source={x:Reference me}}"

另一种方法是 Source={RelativeSource Self}

  <SomeElement SomeAttribute="{Binding MyProperty, Source={RelativeSource Self}}" />
  <-- OPTIONAL -->
  <SomeElement SomeAttribute="{Binding VM.SomeProperty, Source={RelativeSource Self}}" />

我个人不使用这种语法,因为读者可能不确定"Self"指的是什么。它是"SomeElement"(包含"Binding"表达式的元素)吗?是周围的"DataTemplate"(如果有的话)吗?还是XAML文件的根元素?我不打算回答这个问题;请使用x:Name代替。

1
非常感谢您的精彩回答,解决了我的问题,并且让我更好地理解了这个想法和正在发生的事情。 - 814k31
非常好的答案,谢谢@ToolmakerSteve。如果您有任何关于这个问题更深入的微软文档链接,将不胜感激。 - glihm
我没有找到任何微软文档来展示这一切是如何结合在一起的。请谷歌“xamarin forms binding”。找到两个文档,“data bindings”和“basic bindings”。它们是一个很好的起点。希望它们提到了“Source”属性和“x:Reference”。 - ToolmakerSteve
两个解决我的问题的方法:
  1. 使用像这样的绑定 {Binding Path=MyObject.Name, Source={x:Reference thisContentView}},其中 x:Name="thisContentView"
  2. 同时不使用 BindingContext = this
- undefined

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