Xamarin "BindableProperty propertyChanged" 在 "RaisePropertyChanged" 时不触发
Xamarin "BindableProperty propertyChanged" does not fired when "RaisePropertyChanged"
我使用 MvvmCross 和 Xamarin Form。
因此,我使用RaisePropertyChanged来通知View。
但是,RaisePropertyChanged 不会在 ViewA.
中触发 propertyChanged
我不知道从哪里开始调试或检查局部变量...
流量
如果我在某处更改Data.Value,流程如下所示。
- 事件 Data.Value已更改 已调用。
- ModelA.OnValueChanged 调用 OnPropertyChanged
- ViewModelA.OnModelPropertyChanged 调用 RaisePropertyChanged
- 期待 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
}
}
- 子视图。
后面的代码:
...
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>
希望符合你的情况。
我使用 MvvmCross 和 Xamarin Form。
因此,我使用RaisePropertyChanged来通知View。 但是,RaisePropertyChanged 不会在 ViewA.
中触发 propertyChanged我不知道从哪里开始调试或检查局部变量...
流量
如果我在某处更改Data.Value,流程如下所示。
- 事件 Data.Value已更改 已调用。
- ModelA.OnValueChanged 调用 OnPropertyChanged
- ViewModelA.OnModelPropertyChanged 调用 RaisePropertyChanged
- 期待 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
}
}
- 子视图。
后面的代码:
...
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>
希望符合你的情况。