我想为视图模型中的任何必需属性设置脏标志。我在构造函数中将IsDirty
初始化为 false。不幸的是,我的属性中的所有 setter 都是在构造函数之后调用的。有没有办法在所有二传手之后将IsDirty
设置为 false?二传手都有一条线IsDirty=true
;
我将 Prism框架与 Xamarin 4.0 一起使用,但 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;
}
}
我还没有显式设置任何属性。我想避免它们被自动初始化,或者在它们之后调用一些东西。
Edit
只是给我一直在调试的每个人的笔记,以找出我能做什么。我发现在每个数据绑定属性中,getter 被调用两次,然后调用 setter。我查看了我能找到的生成代码,并且没有明显的数据绑定显式调用 setter 的位置。
编辑 2
我以前没有显示的内容,现在看起来这是一个关键信息,是我用对服务的异步调用填充ObservableCollection
。据我所知,由于 XAML 数据绑定,调用了SelectedPart
属性setter。如果我慢慢调试,这个开始在某些地方显示。我已经在上面添加了异步调用。
有没有办法在所有二传手之后将
IsDirty
设置为 false?
二传手不是自己叫的,总得有人叫他们。你应该确定谁在这样做,要么阻止他在没有充分理由的情况下设置东西(首选(,要么让他在他完成后重置脏标志。
正如注释中所建议的,在 setter 中添加断点并查看堆栈跟踪是查找设置源的良好起点......如果我不得不猜测,我会怀疑一些与导航相关的回调。
但是你应该尽量确保视图模型是在构造函数之后初始化的,并且IsDirty
实际上意味着"已通过视图更改",而不是"可能被用户更改,也可能只是延迟初始化的一部分"。
在您多次编辑后,我的编辑:
您应该修改体系结构以考虑视图模型的异步初始化。只是并行运行所有内容并希望最好的很少奏效。
例如,您可以将属性设置为只读,直到初始化完成,并在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
......但你明白了。
由于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();
}
}