【问题标题】:Xamarin.Forms Binding only updating ViewModel property with default valuesXamarin.Forms 绑定仅使用默认值更新 ViewModel 属性
【发布时间】:2020-07-15 11:06:46
【问题描述】:

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

  • 一个GenericPage 超类继承自ContentPage 并包含一个BindableProperty
  • 一个Page 类从GenericPage 继承,其中ViewModel 使用OneWayToSource 绑定模式绑定到BindableProperty
  • 一个GenericControl 超类继承自ContentView 并包含一个BindableProperty
  • 一个Control 类继承自GenericControl,其中ControlViewModel 使用OneWayToSource 绑定模式绑定到BindableProperty
  • Control 类使用XAML 嵌入到Page 类中,GenericControlBindableProperty 使用ViewModel 绑定模式绑定到来自ViewModel 类的属性

我可以验证从PageGenericControlBindableProperty 的“连接”确实有效,因为propertyChanged 方法在GenericControl 中调用,默认值来自@ 中的BindableProperty 987654356@。我还可以验证从GenericControlControlViewModel 的“连接”是否作为ControlViewModel 中的属性设置器使用GenericControlBindableProperty 中的默认值调用。

但是,由于某种原因,到达BindableProperty 中的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)
        }

【问题讨论】:

标签: c# xaml xamarin xamarin.forms xamarin.ios


【解决方案1】:

我检查了您的样品并找到了原因。

当你设置ControlBindingContext

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

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

另外,既然你已经将Page1BindingContext设置为ViewModel,为什么还要绑定Test强>对自己?

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

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

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

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

在 Page1 .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;
        }

    }
}

【讨论】:

  • 感谢您的回答,但它并没有解决我的问题。我在 Github 上更新了代码。您将看到,GenericPage 中的 Test 更改将通过所有绑定正确传播,直到它到达 GenericControl 中的 BindableProperty。所以 Control 和 Page 之间的绑定确实有效。但是,一旦设置了默认值,GenericControl 中的BindablePropertyControlViewModel 中的Test 之间的绑定将不起作用。在Control:{Binding Test, Mode=OneWayToSource} 一定是问题所在。手动设置文本 obv。违背了 MVVM 的目的 :)
  • 我的意思是:为什么一旦默认值设置一次,GenericControl 中的BindablePropertyControlViewModel 中的Test 之间的绑定就会停止工作?
  • 如我所说,你需要直接处理Control中的逻辑。
猜你喜欢
  • 1970-01-01
  • 2015-12-08
  • 2016-10-07
  • 2020-09-24
  • 2015-10-17
  • 2014-12-31
  • 1970-01-01
  • 1970-01-01
  • 2022-01-28
相关资源
最近更新 更多