【问题标题】:Define if a property is loaded or set by the view定义属性是否由视图加载或设置
【发布时间】:2017-05-19 18:11:58
【问题描述】:

我正在尝试使用 Catel 进行“肮脏”实施。
我有一个视图模型,有一个 [Model] 属性和一些 [ViewModelToModel] 映射到它。
我添加了一个布尔成员 _canGetDirty,当设置为 true 时,允许 viewmodel 属性提示服务进行保存。

所以我的逻辑是,如果模型属性更改,_canGetDirty 设置为false,因此视图模型属性更改而不会变脏,当模型更改完成后,我们将_canGetDirty 重新设置为true .

问题是模型属性的PropertyChanged 事件在视图模型属性更改之前被调用,因此_canGetDirty 始终为真,并且每当我加载新模型时都会调用我的服务进行保存。

如何解决这个问题?

public class MyViewModel : ViewModelBase
{
    private IMyService _myService;
    private bool _canGetDirty;

    public MyViewModel(MyModel myModel, IMyService myService)
    {
        MyModel = myModel;
        _myService = myService;
    }

    [Model]
    public MyModel MyModel
    {
        get { return GetValue<MyModel>(MyModelProperty); }
        set
        {
            _canGetDirty = false;
            SetValue(MyModelProperty, value);
        }
    }

    [ViewModelToModel("MyModel")]
    public string Prop1
    {
        get { return GetValue<string>(Prop1Property); }
        set { SetValue(Prop1Property, value); }
    }

    [ViewModelToModel("MyModel")]
    public string Prop2
    {
        get { return GetValue<string>(Prop2Property); }
        set { SetValue(Prop2Property, value); }
    }

    [ViewModelToModel("MyModel")]
    public string Prop3Contains
    {
        get { return GetValue<string>(Prop3Property); }
        set { SetValue(Prop3Property, value); }
    }

    #region Registering
    public static readonly PropertyData Prop1Property = RegisterProperty("Prop1", typeof(string), null, PropertyToSaveChanged);
    public static readonly PropertyData Prop2Property = RegisterProperty("Prop2", typeof(string), null, PropertyToSaveChanged);
    public static readonly PropertyData Prop3Property = RegisterProperty("Prop3", typeof(string), null, PropertyToSaveChanged);
    public static readonly PropertyData MyModelProperty = RegisterProperty("MyModel", typeof(MyModel), null, MyModelChanged);
    #endregion

    #region Property Changed Handlers
    private static void MyModelChanged(object sender, PropertyChangedEventArgs e)
    {
        (sender as MyViewModel)._canGetDirty = true;
    }

    private static void PropertyToSaveChanged(object sender, PropertyChangedEventArgs e)
    {
        var vm = sender as MyViewModel;

        if (vm._canGetDirty)
            vm._myService.AskForSaving();
    }
    #endregion
}

编辑:关于 Catel 在这种情况下如何工作的一些解释。

注册属性变更:

我们注册的属性将通过RegisterProperty 通知更新:

public static readonly PropertyData Prop1Property = RegisterProperty("Prop1",
    typeof(string), null, PropertyToSaveChanged);

最后一个参数是注册属性发生变化时调用的回调函数。

模型属性的自动更新:

我们将一个属性设置为模型:

[Model]
public MyModel MyModel
{
    get { return GetValue<MyModel>(MyModelProperty); }
    set
    {
        _canGetDirty = false;
        SetValue(MyModelProperty, value);
    }
}

这个类包含一些属性(Prop1、Prop2、Prop3)。 Catel 允许我们通过将它们映射到 ViewModelToModel 来自动从视图模型更新它们:

[ViewModelToModel("MyModel")]
public string Prop1
{
    get { return GetValue<string>(Prop1Property); }
    set { SetValue(Prop1Property, value); }
}

【问题讨论】:

    标签: c# wpf mvvm catel dirty-data


    【解决方案1】:

    首先,我推荐使用 Catel.Fody,它可以大大简化您的属性注册(是的,它还支持更改回调 ;-))。

    为什么模型会在外部发生变化?当模型发生变化(被注入到你的ctor中)时,它应该重新创建一个新的VM,因此你可以从一个新的slate开始。

    回到这个问题:你是否测试过你的 setter 是否真的被调用了? Catel 有可能在内部直接调用 SetValue(等于依赖属性的行为),而不是调用 vm 中的包装器。 Catel 是这样工作的:

    1. 您更新模型
    2. 更改回调调用(因此您设置 _canBeDirty => true)
    3. vm 注意到模型已更改并更新暴露/链接属性。

    我的怀疑基本上是你设置 _canBeDirty => true 太早了。

    【讨论】:

    • 模型没有外部变化,只是第一次创建虚拟机。所以你是说在虚拟机注意到变化之前调用回调?这似乎是合理的,但我不知道什么时候应该将 _canBeDirty 设置为 true。我应该反转这个变量的角色(比如重命名它_dirtyProtected)并将其设置为trueSetValue调用SetValue(在其设置器中)之后?
    【解决方案2】:

    假设ViewModelBase 遵守INotifyPropertyChanged 订阅类的INotifyPropertyChanged 事件并在那里设置脏标志而不是订阅单个更改事件。

    根据定义应该在设置任何值之后发生。

    示例

    public MyViewModel(MyModel myModel, IMyService myService)
     {
        ...
        this.PropertyChanged += (sender, args) => 
                                {
                                    if (_canGetDirty)
                                        _myService?.AskForSaving();
                                };
     }
    

    您可以使用args.PropertyName 检查清除模式上的任何竞争条件逻辑。

    【讨论】:

    • 我假设您不熟悉 Catel;我实际上已经在这样做了。我将进行编辑以添加有关 Catel 如何使其工作的说明。我可以去掉 Catel 的糖(我实际上在我的解决方案中这样做了),但我想要一个适用的答案。
    猜你喜欢
    • 2010-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-21
    • 2011-05-23
    • 1970-01-01
    相关资源
    最近更新 更多