简体   繁体   English

Xamarin.Forms 仅绑定更新 ViewModel 属性与默认值

[英]Xamarin.Forms Binding only updating ViewModel property with default values

Consider the following somewhat complex case in Xamarin.Forms (tested on iOS simulator):在 Xamarin.Forms 中考虑以下有点复杂的情况(在Xamarin.Forms模拟器上测试):

  • One GenericPage superclass inheriting from ContentPage and containing a BindableProperty一个继承自ContentPage并包含BindablePropertyGenericPage超类
  • One Page class inheriting from GenericPage with a ViewModel which is bound to the BindableProperty using a OneWayToSource binding mode Page class 继承自GenericPage ,其ViewModel使用OneWayToSource绑定模式绑定到BindableProperty
  • One GenericControl superclass inheriting from ContentView and containing a BindableProperty一个继承自ContentView并包含BindablePropertyGenericControl超类
  • One Control class inheriting from GenericControl with a ControlViewModel which is bound to the BindableProperty using a OneWayToSource binding mode一个Control class 继承自GenericControl ,其ControlViewModel使用OneWayToSource绑定模式绑定到BindableProperty
  • The Control class is embedded in the Page class using XAML and the BindableProperty of GenericControl is bound to the Property from the ViewModel class using a OneWay binding mode The Control class is embedded in the Page class using XAML and the BindableProperty of GenericControl is bound to the Property from the ViewModel class using a OneWay binding mode

在此处输入图像描述

I can verify that the "connection" from Page to the BindableProperty of GenericControl indeed works, as the propertyChanged method is invoked in GenericControl with the default value from the BindableProperty in GenericPage .我可以验证从PageGenericControlBindableProperty的“连接”确实有效,因为在GenericControl中使用GenericPage中的BindableProperty的默认值调用propertyChanged方法。 I can also verify that the "connection" from GenericControl to ControlViewModel is working as the property setter in ControlViewModel is called with the default value from the BindableProperty in GenericControl .我还可以验证从GenericControlControlViewModel的“连接”是否正在工作,因为ControlViewModel中的属性设置器是使用GenericControl中的BindableProperty的默认值调用的。

However, for some reason, the changes which arrive at the BindableProperty in GenericControl (either the default values from GenericPage or externally set) are not propagated to the ControlViewModel .但是,由于某种原因,到达GenericControl中的BindableProperty的更改(来自GenericPage的默认值或外部设置)不会传播到ControlViewModel


Full Code is available under: https://github.com/mlxyz/Xamarin-Forms-Binding-Repro完整代码位于: https://github.com/mlxyz/Xamarin-Forms-Binding-Repro

GenericPage:通用页面:

 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);
        }

Page:页:

<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>

ViewModel:视图模型:

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

GenericControl:通用控制:

// 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)
        }

Control:控制:

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

ControlViewModel:控制视图模型:

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

I checked your sample and found out the cause.我检查了你的样品并找出了原因。

When you set the BindingContext of Control当你设置ControlBindingContext

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

The binding between Control and Page1 will not work any more.You need to handle the logic in Control.xaml.cs directly. ControlPage1之间的绑定将不再起作用。您需要直接处理Control.xaml.cs中的逻辑。

In addition, since you had set the BindingContext of Page1 as ViewModel , why did you still binding the Test to itself?另外,既然你已经将Page1BindingContext设置为ViewModel ,为什么还要将Test绑定到自身呢?

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

You should put all binding source in ViewModel and handle logic in it.您应该将所有绑定源放在 ViewModel 中并在其中处理逻辑。

Note: The property in superclass will not available in subclass when using data-binding.注意:使用数据绑定时,超类中的属性在子类中不可用。 So you need to define them in subclass one more time.因此,您需要再次在子类中定义它们。

So you could modify the code like following.所以你可以像下面这样修改代码。 I add a button to Page1.我向 Page1 添加了一个按钮。 When clicking it, the value of Test will change.单击它时,Test 的值会发生变化。

in Page1.xaml在第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>

in ViewModel在视图模型中

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);

            });

        }

    }
}

in Control.xaml在控制中。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>

in Control.xaml.cs在 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;
        }

    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM