初始化属性后,是否可以在 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.. .但你明白了。