【问题标题】:Correct way to update property in ViewModel from Model从模型更新 ViewModel 中的属性的正确方法
【发布时间】:2017-05-11 21:05:34
【问题描述】:

我对 WPF 相当陌生。我的理解是模型中的数据发生变化,它应该通知视图模型,并且视图将绑定到视图模型中的属性和类似的东西。它是否正确?如果是这样,我一直在阅读该模型应该实现INotifyPropertyChanged,并且看起来像这样

 public class LoginModel : INotifyPropertyChanged
{

    public event PropertyChangedEventHandler PropertyChanged;

    public void NotifyPropertyChanged(string propName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
    }

    public bool Authenticated { get; set; }
}

在我的 ViewModel 中,我有一个属性“AuthResult”,它应该从模型属性“Authenticated”中获取更新

public partial class view1 : UserControl, INotifyPropertyChanged{
 public bool AuthResult
    {
        get
        {
            return _authVal;
        }
        set
        {
            _authVal = value;
            NotifyPropertyChanged("AuthResult");
        }
    }

public event PropertyChangedEventHandler PropertyChanged;

    public void NotifyPropertyChanged(string propName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
    }
}

我知道当前的实现是不正确的。我发现我应该像这样从我的模型订阅 PropertyChanged 通知:

    LoginModel.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(LoginModel_PropertyChanged);

void LoginModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
    if(e.PropertyName == "Authenticated")
    {
         //do something
    }
}

我看不到应该在哪里更新“AuthResult”属性。我会在 If 语句中做一些类似AuthResult = _model.Authenticated; 的事情吗?

已编辑:

在我的构造函数中?

LoginModel _model;

        public view1(LoginModel model)
        {
            _model = model;
            InitializeComponent();           
        }

【问题讨论】:

    标签: wpf mvvm model notifications viewmodel


    【解决方案1】:

    只需使用 Model 作为 ViewModel 中的成员

    public class ViewModel
    {
        private Model _myModel;
        public Model MyModel 
        {
            get { return _myModel; }
            set
            {
                if (Equals(_myModel, value)) return;
                _myModel = value;
                NotifyPropertyChanged(nameof(MyModel));
            }
        }
    }
    

    然后在xaml中可以绑定Model的属性

    <CheckBox IsChecked="{Binding MyModel.Authenticated}" />
    

    通过这种方法,您的 ViewModel 将围绕您的模型“构建”。
    如果您不希望该模型实现 INotifyPropertyChanged 而不是创建一个“外观”模型类以与上一个示例相同的方式使用它。

    public class ModelFacade : INotifyPropertyChanged
    {
        private Model _myModel;
    
        public bool Authenticated
        {
            get { return _myModel.Authenticated; }
            set
            {
                _myModel.Authenticated = value;
                NotifyPropertyChanged(nameof(Authenticated));
            }
        }
    }
    
    public class ViewModel
    {
        private ModelFacade _myModel;
        public ModelFacade MyModel 
        {
            get { return _myModel; }
            set
            {
                if (Equals(_myModel, value)) return;
                _myModel = value;
                NotifyPropertyChanged(nameof(MyModel));
            }
        }
    }
    

    【讨论】:

    • 好的,然后从视图中的控件绑定到 MyModel 属性?我假设你没有留下 NotifyPropertyChanged() 不完整,它实际上应该是NotifyPropertyChanged("MyModel")?
    • 另外,我希望从模型中获取特定的“Authenticated”属性,您的答案似乎不完整。但我有点理解你来自哪里。
    • 您可以在视图中绑定到MyModel.Authenticated,或者在视图模型中创建一个包装器属性:public bool AuthResult {get {return MyModel.Authenticated; }。在后一种情况下,您需要将NotifyPropertyChanged("AuthResult") 添加到 MyModel 属性的设置器中。
    • @ganjeii,方法NotifyPropertyChanged可以用CallerMemberNameAttribute实现,如果是这样就不需要提供属性名称了。
    【解决方案2】:

    如果模型实现了 INotifyPropertyChanged 接口,您可以直接从视图绑定到它:

    <Button Content="Button" IsEnabled="{Binding Authenticated}" />
    

    请注意,只要将 Authenticated 属性设置为新值,LoginModel 类就必须引发 PropertyChanged 事件。

    您还可以通过视图模型类公开整个模型实体:

    public class ViewModel
    {
        public ViewModel(LoginModel model)
        {
            Model = model;
        }
    
        public LoginModel Model { get; }
    }
    

    ...并像这样绑定到它:

    <Button Content="Button" IsEnabled="{Binding Model.Authenticated}" />
    

    仍然是模型类必须实现 INotifyPropertyChanged 接口并引发更改通知。

    另一个选项是让视图模型包装您希望能够从视图绑定到的模型类的任何属性。然后绑定到视图模型类的属性,该属性又包装模型类的属性,如下所示:

    public class ViewModel
    {
        private readonly LoginModel _model;
        public ViewModel(LoginModel model)
        {
            _model = model;
        }
    
        public bool AuthResult
        {
            get
            {
                return _model.Authenticated;
            }
            set
            {
                _model.Authenticated = value;
                NotifyPropertyChanged("AuthResult");
            }
        }
    }
    
    <Button Content="Button" IsEnabled="{Binding AuthResult}" />
    

    使用后一种方法的好处是视图不依赖于模型类。它只绑定到视图模型类,这就是 MVVM 设计模式的典型实现方式。

    但是,如果您确实绑定到视图模型的(包装器)属性,并且希望在设置模型类的属性时更新视图,则模型必须通知视图模型它已经改变了一种方式或另一个,即它必须引发某种事件或类似事件。这通常意味着实现 INotifyPropertyChanged 接口。然后,视图模型可以订阅模型的 PropertyChanged 事件,并在模型更新时为数据绑定属性引发自己的 PropertyChanged 事件,例如:

    public class ViewModel
    {
        private readonly LoginModel _model;
    
        public ViewModel(LoginModel model)
        {
            if (model == null)
                throw new ArgumentNullException("model");
    
            _model = model;
            _model.PropertyChanged += OnModelChanged;
        }
    
        private void OnModelChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Authenticated")
                NotifyPropertyChanged("AuthResult");
        }
    
        public bool AuthResult
        {
            get
            {
                return _model.Authenticated;
            }
            set
            {
                _model.Authenticated = value;
                NotifyPropertyChanged("AuthResult");
            }
        }
    }
    

    【讨论】:

    • 这个答案最终回答了我的问题,再次感谢mm8。
    • 第二个选项不是 MVVM - 它是“Facade”
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-04
    • 1970-01-01
    • 1970-01-01
    • 2020-07-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多