[英]Xamarin.Forms Binding only updating ViewModel property with default values
在 Xamarin.Forms 中考虑以下有点复杂的情况(在Xamarin.Forms
模拟器上测试):
ContentPage
并包含BindableProperty
的GenericPage
超类Page
class 继承自GenericPage
,其ViewModel
使用OneWayToSource
绑定模式绑定到BindableProperty
ContentView
并包含BindableProperty
的GenericControl
超类Control
class 继承自GenericControl
,其ControlViewModel
使用OneWayToSource
绑定模式绑定到BindableProperty
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 我可以验证从Page
到GenericControl
的BindableProperty
的“连接”确实有效,因为在GenericControl
中使用GenericPage
中的BindableProperty
的默认值调用propertyChanged
方法。 我还可以验证从GenericControl
到ControlViewModel
的“连接”是否正在工作,因为ControlViewModel
中的属性设置器是使用GenericControl
中的BindableProperty
的默认值调用的。
但是,由于某种原因,到达GenericControl
中的BindableProperty
的更改(来自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)
}
我检查了你的样品并找出了原因。
当你设置Control的BindingContext
<controls:GenericControl.BindingContext>
<viewModels:ControlViewModel />
</controls:GenericControl.BindingContext>
Control和Page1之间的绑定将不再起作用。您需要直接处理Control.xaml.cs中的逻辑。
另外,既然你已经将Page1的BindingContext设置为ViewModel ,为什么还要将Test绑定到自身呢?
Test="{Binding Test, Mode=OneWayToSource}"
您应该将所有绑定源放在 ViewModel 中并在其中处理逻辑。
注意:使用数据绑定时,超类中的属性在子类中不可用。 因此,您需要再次在子类中定义它们。
所以你可以像下面这样修改代码。 我向 Page1 添加了一个按钮。 单击它时,Test 的值会发生变化。
<?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);
});
}
}
}
<?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>
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.