0

Xamarin.Forms在(在 iOS 模拟器上测试)中考虑以下稍微复杂的情况:

  • 一个GenericPage超类继承自ContentPage并包含一个BindableProperty
  • 一个Page继承自GenericPagewith a 的类ViewModel,它绑定到BindablePropertyusing aOneWayToSource绑定模式
  • 一个GenericControl超类继承自ContentView并包含一个BindableProperty
  • 一个Control继承自GenericControlwith a 的类ControlViewModel,它绑定到BindablePropertyusing aOneWayToSource绑定模式
  • Control被嵌入到Page类中XAML,并且使用绑定模式从类BindablePropertyGenericControl绑定到属性ViewModelOneWay

在此处输入图像描述

我可以验证从PageBindablePropertyof的“连接”GenericControl确实有效,因为该propertyChanged方法是使用 inGenericControl中的默认值调用BindablePropertyGenericPage。我还可以验证 to 的“连接”GenericControl是否正在使用 in中的默认值调用ControlViewModel属性设置器in 。ControlViewModelBindablePropertyGenericControl

但是,由于某种原因,到达BindablePropertyin的更改GenericControl(来自GenericPage或外部设置的默认值)不会传播到ControlViewModel.


完整代码位于:https ://github.com/mlxyz/Xamarin-Forms-Binding-Repro

通用页面:

 public static readonly BindableProperty TestProperty =
            BindableProperty.Create(nameof(Test), typeof(Vector3), typeof(GenericPage), new Vector3(1, 2, 3));

        public Vector3 Test
        {
            get => (Vector3)GetValue(TestProperty);
            set => SetValue(TestProperty, value);
        }

页:

<views:GenericPage Test="{Binding Test, Mode=OneWayToSource}" x:Name="Root">
<views:GenericPage.BindingContext>
        <viewModels:ViewModel />
    </views:GenericPage.BindingContext>
<ContentPage.Content>

   <controls:Control Test="{Binding Source={x:Reference Root}, Path=BindingContext.Test, Mode=OneWay}" />
</ContentPage.Content>
</views:GenericPage>

视图模型:

public Vector3 Test
        {
            get => _test;
            set
            {
                _test = value;
                OnPropertyChanged();
            }
        }

通用控制:

// bindable property and associated property is defined basically the same as in GenericPage except for propertyChanged property set and different default values
        private static void PropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
        {
            System.Diagnostics.Debug.WriteLine("property changed"); // <- this is called with default values from GenericPage (1,2,3)
        }

控制:

<controls:GenericControl Test="{Binding Test, Mode=OneWayToSource}">
    <controls:GenericControl.BindingContext>
        <viewModels:ControlViewModel />
    </controls:GenericControl.BindingContext>
</controls:GenericControl>

控制视图模型:

 public Vector3 Test
        {
            get => _test;
            set => _test = value; // <- this gets called only with default values from `GenericControl` (4,5,6)
        }
4

1 回答 1

0

我检查了你的样品并找出了原因。

当你设置ControlBindingContext

<controls:GenericControl.BindingContext>
     <viewModels:ControlViewModel />
</controls:GenericControl.BindingContext>

ControlPage1之间的绑定将不再起作用。您需要直接处理Control.xaml.cs中的逻辑。

另外,既然你已经将Page1BindingContext设置为ViewModel,那为什么还要把Test绑定到自己呢?

Test="{Binding Test, Mode=OneWayToSource}"

您应该将所有绑定源放在 ViewModel 中并在其中处理逻辑。

注意:使用 data-binding 时,超类中的属性在子类中不可用。所以你需要在子类中再定义一次。

所以你可以像下面这样修改代码。我向 Page1 添加了一个按钮。单击它时,Test 的值会发生变化。

在第 1 页 .xaml

<?xml version="1.0" encoding="utf-8"?>

<views:GenericPage xmlns="http://xamarin.com/schemas/2014/forms"
                   xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                   xmlns:d="http://xamarin.com/schemas/2014/forms/design"
                   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                   xmlns:controls="clr-namespace:ReproProjectBindingIssue.Controls;assembly=ReproProjectBindingIssue"
                   xmlns:views="clr-namespace:ReproProjectBindingIssue.Views;assembly=ReproProjectBindingIssue"
                   xmlns:viewModels="clr-namespace:ReproProjectBindingIssue.ViewModels;assembly=ReproProjectBindingIssue"
                   mc:Ignorable="d"
                   x:Class="ReproProjectBindingIssue.Views.Page1" x:Name="Root">
    <views:GenericPage.BindingContext>
        <viewModels:ViewModel />
    </views:GenericPage.BindingContext>
    <ContentPage.Content>
        <StackLayout VerticalOptions="CenterAndExpand" HorizontalOptions="Center">
            <Button Text="Change Value"
                   VerticalOptions="CenterAndExpand"
                   HorizontalOptions="CenterAndExpand"
                    Command="{Binding command}"/>

            <controls:Control Test="{Binding Source={x:Reference Root}, Path=BindingContext.Test, Mode=OneWay}" />
        </StackLayout>
    </ContentPage.Content>
</views:GenericPage>

在视图模型中

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows.Input;
using ReproProjectBindingIssue.Annotations;
using Xamarin.Forms;

namespace ReproProjectBindingIssue.ViewModels
{
    public class ViewModel : INotifyPropertyChanged
    {

        public ICommand command { get; set; }

        private Vector3 _test;

        public event PropertyChangedEventHandler PropertyChanged;

        public Vector3 Test
        {
            get => _test;
            set
            {
                _test = value;
                OnPropertyChanged();
            }
        }

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }


        public ViewModel()
        {
            Test = new Vector3(9,10,11);

            command = new Command(()=> {

                Test = new Vector3(12, 13, 14);

            });

        }

    }
}

在 Control.xaml 中

<?xml version="1.0" encoding="UTF-8"?>

<controls:GenericControl xmlns="http://xamarin.com/schemas/2014/forms"
                         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                         xmlns:d="http://xamarin.com/schemas/2014/forms/design"
                         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                         xmlns:controls="clr-namespace:ReproProjectBindingIssue.Controls;assembly=ReproProjectBindingIssue"
                         xmlns:viewModels="clr-namespace:ReproProjectBindingIssue.ViewModels;assembly=ReproProjectBindingIssue"
                         mc:Ignorable="d"

                         x:Name="CustomView"


                         x:Class="ReproProjectBindingIssue.Controls.Control" Test="{Binding Test, Mode=OneWayToSource}">
    <controls:GenericControl.BindingContext>
        <viewModels:ControlViewModel />
    </controls:GenericControl.BindingContext>
    <ContentView.Content>
        <StackLayout VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand">
            <Label  x:Name="label"/>
        </StackLayout>
    </ContentView.Content>
</controls:GenericControl>

在 Control.xaml.cs 中

using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using ReproProjectBindingIssue.ViewModels;
using ReproProjectBindingIssue.Views;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace ReproProjectBindingIssue.Controls
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class Control : GenericControl
    {
        public Control()
        {
            InitializeComponent();
        }


        public static readonly BindableProperty TestProperty =
            BindableProperty.Create(nameof(Test), typeof(Vector3), typeof(Control), new Vector3(4, 5, 6), propertyChanged: PropertyChanged);

        public Vector3 Test
        {
            get => (Vector3)GetValue(TestProperty);
            set => SetValue(TestProperty, value);
        }

        private static void PropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
        {
            var control = bindable as Control;

            Vector3 value = (Vector3)newvalue;

            control.label.Text = "X:" + value.X + " Y:" + value.Y + " Z:" + value.Z;
        }

    }
}
于 2020-07-15T13:04:59.930 回答