Xamarin "BindableProperty propertyChanged" 在 "RaisePropertyChanged" 时不触发

Xamarin "BindableProperty propertyChanged" does not fired when "RaisePropertyChanged"

我使用 MvvmCross 和 Xamarin Form。

因此,我使用RaisePropertyChanged来通知View。 但是,RaisePropertyChanged 不会在 ViewA.

中触发 propertyChanged

我不知道从哪里开始调试或检查局部变量...

流量

如果我在某处更改Data.Value,流程如下所示。

  1. 事件 Data.Value已更改 已调用。
  2. ModelA.OnValueChanged 调用 OnPropertyChanged
  3. ViewModelA.OnModelPropertyChanged 调用 RaisePropertyChanged
  4. 期待 ViewA.OnChanged 调用,但失败...

XAML

我 运行 并检查 XAML 绑定是否有效。

<DataTemplate x:Key="ViewB">
    <ViewB Data="{Binding Data}" />
</DataTemplate>

查看

我定义了 BindableProperty 如下。

// this class is abstract!
public abstract class ViewA : MvxContentView
{
        public static readonly BindableProperty DataProperty =
            BindableProperty.Create(
                propertyName: "Property",
                returnType: typeof(Data),
                declaringType: typeof(ViewA),
                defaultValue: null,
                propertyChanged: OnChanged);
        static void OnChanged(BindableObject bindable, object oldValue, object newValue)
        {
            if (newValue is null) { return; }
            
            // some codes
        }
}
// actual class

public partial class ViewB : ViewA
{
        public ViewB()
        {
            InitializeComponent();
        }
}

视图模型

// this is also abstract!
public abstract class ViewModelA<T> : MvxViewModel<T>
{
        protected T _model;
        public Data Data
        {
            get => _model.Data;
        }

        
        public T Model
        {
            get => _Model;
            set
            {
                if (SetProperty(ref _model, value))
                {
                    // Register event handler
                    _model.PropertyChanged += OnModelPropertyChanged;
                }
            }
        }

        private void OnModelPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "DataChanged":
                    {
                        // I expect this will fire 'propertyChanged' of BindableProperty.
                        // But it is not fired...
                        RaisePropertyChanged(() => Data);
                    }
                    break;
            }
        }
}

// actual class
public class ViewModelB : ViewModelA<ModelA>
{
        public ViewModelB() : base()
        {

        }
}

型号

public class LayerModel : INotifyPropertyChanged
{
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        private Data _data;
        public Data Data
        {
            get
            {
                return _data;
            }
            set
            {
                if (_data != value)
                {
                    _data = value;
                    _data.ValueChanged += OnValueChanged;
                    OnPropertyChanged("DataChanged");
                }
            }
        }

        private void OnValueChanged(object sender, EventArgs e)
        {
            OnPropertyChanged(""DataChanged"");
        }
}

数据

public class Data
{
        private int _value;
        public int Value
        {
            get => _value;
            set
            {
                if(_value != value)
                {
                    // 2020.07.06 Edited
                    var evetArg = new DataChangedArgs
                    {
                        OldData = _value;
                        NewData = value;
                    };
                    _value = value;
                    ValueChanged?.Invoke(this, evetArg);
                }
            }
        }

        public event EventHandler ValueChanged;
}

2020.07.06 添加

public class DataChangedArgs : EventArgs
{
    public int OldData { get; set; }  
    public int NewData { get; set; }
}

我建议您简化一些 classes。您创建了一个名为 ValueChanged 的自定义事件,它与 INotifyPropertyChanged 基本相同。推荐你使用接口,有一个class MvxNotifyPropertyChanged(在MvvmCross中)实现了之前的接口

这是一个近似值(这个 ViewModel 不是从 MvxViewModel 继承的,但正如我所说,它是一个近似值)。

***编辑 问题更新: class Data无法修改。

您正在尝试使用 BindableProperty 绑定 Data,在这种情况下,Data 应该实施 INotifyPropertyChanged 以通知更改。我建议您阅读有关 BindableObject.

的内容

针对这种情况我提出以下解决方案:

  • 创建一个继承自 Data 的 class:它将实现 INotifyPropertyChanged 以通知值更改。

结构应类似于:

...
public class MainPageViewModel : MvxNotifyPropertyChanged
{
    public Data Data => _model?.Data;

    private LayerModel _model;
    public LayerModel Model
    {
        get => _model;
        set
        {
            SetProperty(ref _model, value, () =>
            {
                RaisePropertyChanged(nameof(Data));
            });
        }
    }
}

public class LayerModel : MvxNotifyPropertyChanged
{
    private Data _data;

    public Data Data
    {
        get => _data;
        set => SetProperty(ref _data, value);
    }
}

public class Data
{
    private int _value;
    public int Value
    {
        get => _value;
        set
        {
            if (_value == value)
                return;
            var eventArgs = new DataChangedEventArgs(_value, value);
            _value = value;
            ValueChanged?.Invoke(this, eventArgs);
        }
    }
    public event EventHandler<EventArgs> ValueChanged;
}

public class DataChangedEventArgs : EventArgs
{
    public DataChangedEventArgs(int oldData, int newData)
    {
        OldData = oldData;
        NewData = newData;
    }

    public int OldData { get; }
    public int NewData { get; }
}

public class NotificationData : Data, INotifyPropertyChanged
{
    public static NotificationData FromData(Data data)
    {
        return new NotificationData {Value = data.Value};
    }

    public NotificationData()
    {
        ValueChanged += delegate
        {
            OnPropertyChanged(nameof(Value));
        };
    }
    
    public event PropertyChangedEventHandler PropertyChanged;

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

以下是一个带有抽象视图的演示,以及一个使用 MainPageViewModel 的子视图。

抽象视图应该类似于

public abstract class ViewA : ContentPage
{
    public Data ModelA
    {
        get => (Data)GetValue(ModelAProperty);
        set => SetValue(ModelAProperty, value);
    }

    public static readonly BindableProperty ModelAProperty =
        BindableProperty.Create(
            propertyName: nameof(ModelA),
            returnType: typeof(Data),
            declaringType: typeof(ViewA),
            defaultValue: null,
            propertyChanged: OnChanged);
    static void OnChanged(BindableObject bindable, object oldValue, object newValue)
    {
        if (newValue is null) { return; }

        // some codes
    }
}
  1. 子视图。

后面的代码:

...
public partial class MainPage : ViewA
{
    private int _counter = 10;
    private MainPageViewModel ViewModel => (MainPageViewModel) BindingContext;

    public MainPage()
    {
        InitializeComponent();

        //--example initialization
        var data = new Data
        {
            Value = _counter
        };
        ViewModel.Model = new LayerModel
        {
            Data = NotificationData.FromData(data)
        };
    }

    private void Button_OnClicked(object sender, EventArgs e)
    {
        ViewModel.Model.Data.Value = ++_counter;
    }
}
...

XAML:

<?xml version="1.0" encoding="utf-8" ?>
<xamstack:ViewA 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:xamstack="clr-namespace:xamstack;assembly=xamstack"
       mc:Ignorable="d"
       x:Class="xamstack.MainPage"
       ModelA="{Binding Data}">
    <ContentPage.BindingContext>
        <xamstack:MainPageViewModel/>
    </ContentPage.BindingContext>

    <StackLayout Padding="20">
        <Button Text="Increment" Clicked="Button_OnClicked"/>
        <Label>
            <Label.FormattedText>
                <FormattedString>
                    <Span Text="Value: "/>
                    <Span Text="{Binding Path=ModelA.Value, Mode=OneWay, Source={RelativeSource AncestorType={x:Type ContentPage}}}" 
                          TextColor="Red"
                          />
                </FormattedString>
            </Label.FormattedText>
        </Label>
    </StackLayout>
</xamstack:ViewA>

希望符合你的情况。