初始化属性后,是否可以在 Xamarin ViewModel 中设置脏标志?
Is there a way set a dirty flag in a Xamarin ViewModel after the properties are initialized?
我想为我的视图模型中的任何必需属性设置脏标志。我在构造函数中将 IsDirty
初始化为 false。不幸的是,我的属性中的所有 setter 都是在构造函数之后调用的。有没有办法可以在所有 setter 之后将 IsDirty
设置为 false? setter 都有一行 IsDirty=true
;
我在 Xamarin 4.0 中使用 Prism 框架,但 Prism 文档没有关于 ViewModel 生命周期的任何内容。
我编辑的构造函数如下所示:
public SomeDetailsViewModel(INavigationService navigationService) : base(navigationService)
{
Sample = new SampleDTO();
InitializeLookupValues();
_samplesService = new SampleService(BaseUrl);
TextChangedCommand = new Command(() => OnTextChanged());
AddSampleCommand = new Command(() => AddCurrentSample());
CancelCommand = new Command(() => Cancel());
IsDirty = false;
}
编辑 3:
构造函数调用InitializeLookupValues()
。这些似乎是罪魁祸首。
private async Task InitializeLookupValues()
{
App app = Prism.PrismApplicationBase.Current as App;
string baseUrl = app.Properties["ApiBaseAddress"] as string;
_lookupService = new LookupDataService(baseUrl);
int TbId = app.CurrentProtocol.TbId;
int accessionId = CollectionModel.Instance.Accession.AccessionId;
Parts = await _lookupService.GetParts(accessionId);//HACK
Containers = await _lookupService.GetSampleContainers(TbId);
Additives = await _lookupService.GetAdditives(TbId);
UnitsOfMeasure = await _lookupService.GetUnitsOfMeasure();
// with a few more awaits not included.
}
退出构造函数后,每个属性都已设置。他们看起来像这个。
public ObservableCollection<PartDTO> Parts
{
get
{
return parts;
}
set
{
SetProperty(ref parts, value);
}
}
private PartDTO part;
public PartDTO SelectedPart
{
get
{
return part;
}
set
{
SetProperty(ref part, value);
IsDirty = true;
}
}
IsDirty 的定义如下:
private bool isDirty;
public bool IsDirty
{
get
{
return isDirty;
}
set
{
SetProperty(ref isDirty, value);
Sample.DirtyFlag = value;
}
}
我没有明确设置任何属性。我想避免它们被自动初始化,或者在它们之后调用一些东西。
编辑
只是给所有我一直在调试以找出我能做什么的人的便条。我发现在每个数据绑定 属性 中, getter 被调用两次,然后 setter 被调用。我查看了我能找到的生成代码,没有明显的地方可以让数据绑定显式调用 setter.
编辑 2
我以前没有展示过的,现在看来是一条重要的信息,是我用对服务的异步调用填充了 ObservableCollection
。据我所知,由于 XAML 数据绑定, SelectedPart
属性 setter 被调用。如果我调试缓慢,这会在某些地方开始显示。我在上面添加了异步调用。
由于 SetProperty
方法是可覆盖的,您可以注入一些自定义逻辑。当您拥有需要验证对象是否已更改的对象时,这可能非常有用。
public class StatefulObject : Prism.Mvvm.BindableBase
{
private bool _isDirty;
public bool IsDirty
{
get => _isDirty;
private set => SetProperty(ref _isDirty, value);
}
protected override bool SetProperty<T>(ref T storage, T value, Action onChanged, [CallerMemberName] string propertyName = null)
{
var isDirty = base.SetProperty(ref storage, value, onChanged, propertyName);
if(isDirty && propertyName != nameof(isDirty))
{
IsDirty = true;
}
return isDirty;
}
public void Reset() => IsDirty = false;
}
请记住,当您初始化此 IsDirty
中的字段时将为 true,因此在绑定之前您需要调用 Reset
方法将 IsDirty
设置回 false您可以可靠地知道字段何时更改的方式。
请注意,您如何处理这在某种程度上取决于您。例如,您可以使用 Linq 执行此操作...
var fooDTOs = someService.GetDTOs().Select(x => { x.Reset(); return x; });
您还可以强制执行以下模式:
public class FooDTO : StatefulObject
{
public FooDTO(string prop1, string prop2)
{
// Set the properties...
Prop1 = prop1;
// Ensure IsDirty is false;
Reset();
}
}
Is there a way I can set IsDirty
to false after all of the setters?
setter不是自己叫的,一定是有人叫的。你应该确定是谁在做那件事,要么阻止他无缘无故地设置东西(首选),要么让他在完成后重置脏标志。
正如评论中所建议的那样,在 setter 中添加一个断点并查看堆栈跟踪是找到设置源的一个很好的起点......如果我不得不猜测,我会怀疑一些导航相关的回调。
但是您应该尝试确保在构造函数之后初始化视图模型,并且 IsDirty
实际上意味着 "has been changed through the view" 而不是 "maybe changed by the user, might also be just part of a delayed initialization"。
经过您的多次编辑,我的编辑:
您应该修改架构以考虑视图模型的异步初始化。只是 运行 并行并希望最好的事情很少奏效。
您可以将属性设置为只读,直到初始化完成,例如,并在 InitializeLookupValues
结束时将 IsDirty
设置为 false
。
伪代码:
Constructor()
{
Task.Run( async () => await InitializeAsync() );
}
string Property
{
get => _backingField;
set
{
if (_isInitialized && SetProperty( ref _backingField, value ))
_isDirty = true;
}
}
private async Task InitializeAsync()
{
await SomeAsynchronousStuff();
_isInitialized = true;
}
private bool _isInitialized;
private bool _isDirty;
可能,您想将 _isInitialized
作为 属性 暴露给视图以显示一些沙漏,并使用 ManualResetEvent
而不是简单的 bool
.. .但你明白了。
我想为我的视图模型中的任何必需属性设置脏标志。我在构造函数中将 IsDirty
初始化为 false。不幸的是,我的属性中的所有 setter 都是在构造函数之后调用的。有没有办法可以在所有 setter 之后将 IsDirty
设置为 false? setter 都有一行 IsDirty=true
;
我在 Xamarin 4.0 中使用 Prism 框架,但 Prism 文档没有关于 ViewModel 生命周期的任何内容。
我编辑的构造函数如下所示:
public SomeDetailsViewModel(INavigationService navigationService) : base(navigationService)
{
Sample = new SampleDTO();
InitializeLookupValues();
_samplesService = new SampleService(BaseUrl);
TextChangedCommand = new Command(() => OnTextChanged());
AddSampleCommand = new Command(() => AddCurrentSample());
CancelCommand = new Command(() => Cancel());
IsDirty = false;
}
编辑 3:
构造函数调用InitializeLookupValues()
。这些似乎是罪魁祸首。
private async Task InitializeLookupValues()
{
App app = Prism.PrismApplicationBase.Current as App;
string baseUrl = app.Properties["ApiBaseAddress"] as string;
_lookupService = new LookupDataService(baseUrl);
int TbId = app.CurrentProtocol.TbId;
int accessionId = CollectionModel.Instance.Accession.AccessionId;
Parts = await _lookupService.GetParts(accessionId);//HACK
Containers = await _lookupService.GetSampleContainers(TbId);
Additives = await _lookupService.GetAdditives(TbId);
UnitsOfMeasure = await _lookupService.GetUnitsOfMeasure();
// with a few more awaits not included.
}
退出构造函数后,每个属性都已设置。他们看起来像这个。
public ObservableCollection<PartDTO> Parts
{
get
{
return parts;
}
set
{
SetProperty(ref parts, value);
}
}
private PartDTO part;
public PartDTO SelectedPart
{
get
{
return part;
}
set
{
SetProperty(ref part, value);
IsDirty = true;
}
}
IsDirty 的定义如下:
private bool isDirty;
public bool IsDirty
{
get
{
return isDirty;
}
set
{
SetProperty(ref isDirty, value);
Sample.DirtyFlag = value;
}
}
我没有明确设置任何属性。我想避免它们被自动初始化,或者在它们之后调用一些东西。
编辑
只是给所有我一直在调试以找出我能做什么的人的便条。我发现在每个数据绑定 属性 中, getter 被调用两次,然后 setter 被调用。我查看了我能找到的生成代码,没有明显的地方可以让数据绑定显式调用 setter.
编辑 2
我以前没有展示过的,现在看来是一条重要的信息,是我用对服务的异步调用填充了 ObservableCollection
。据我所知,由于 XAML 数据绑定, SelectedPart
属性 setter 被调用。如果我调试缓慢,这会在某些地方开始显示。我在上面添加了异步调用。
由于 SetProperty
方法是可覆盖的,您可以注入一些自定义逻辑。当您拥有需要验证对象是否已更改的对象时,这可能非常有用。
public class StatefulObject : Prism.Mvvm.BindableBase
{
private bool _isDirty;
public bool IsDirty
{
get => _isDirty;
private set => SetProperty(ref _isDirty, value);
}
protected override bool SetProperty<T>(ref T storage, T value, Action onChanged, [CallerMemberName] string propertyName = null)
{
var isDirty = base.SetProperty(ref storage, value, onChanged, propertyName);
if(isDirty && propertyName != nameof(isDirty))
{
IsDirty = true;
}
return isDirty;
}
public void Reset() => IsDirty = false;
}
请记住,当您初始化此 IsDirty
中的字段时将为 true,因此在绑定之前您需要调用 Reset
方法将 IsDirty
设置回 false您可以可靠地知道字段何时更改的方式。
请注意,您如何处理这在某种程度上取决于您。例如,您可以使用 Linq 执行此操作...
var fooDTOs = someService.GetDTOs().Select(x => { x.Reset(); return x; });
您还可以强制执行以下模式:
public class FooDTO : StatefulObject
{
public FooDTO(string prop1, string prop2)
{
// Set the properties...
Prop1 = prop1;
// Ensure IsDirty is false;
Reset();
}
}
Is there a way I can set
IsDirty
to false after all of the setters?
setter不是自己叫的,一定是有人叫的。你应该确定是谁在做那件事,要么阻止他无缘无故地设置东西(首选),要么让他在完成后重置脏标志。
正如评论中所建议的那样,在 setter 中添加一个断点并查看堆栈跟踪是找到设置源的一个很好的起点......如果我不得不猜测,我会怀疑一些导航相关的回调。
但是您应该尝试确保在构造函数之后初始化视图模型,并且 IsDirty
实际上意味着 "has been changed through the view" 而不是 "maybe changed by the user, might also be just part of a delayed initialization"。
经过您的多次编辑,我的编辑:
您应该修改架构以考虑视图模型的异步初始化。只是 运行 并行并希望最好的事情很少奏效。
您可以将属性设置为只读,直到初始化完成,例如,并在 InitializeLookupValues
结束时将 IsDirty
设置为 false
。
伪代码:
Constructor()
{
Task.Run( async () => await InitializeAsync() );
}
string Property
{
get => _backingField;
set
{
if (_isInitialized && SetProperty( ref _backingField, value ))
_isDirty = true;
}
}
private async Task InitializeAsync()
{
await SomeAsynchronousStuff();
_isInitialized = true;
}
private bool _isInitialized;
private bool _isDirty;
可能,您想将 _isInitialized
作为 属性 暴露给视图以显示一些沙漏,并使用 ManualResetEvent
而不是简单的 bool
.. .但你明白了。