C# WPF 跨视图模型共享模型的最佳实践

C# WPF Best Practice to share a model across viewmodels

  1. 我正在写一个小的科学程序。它有很多变量作为输入。
  2. 用户必须在 window(或用户界面)中输入所有输入变量。但是,我无法将所有输入都放在一个 window (XAML) 中。因此,我创建了几个视图,用户只需按下 NEXT 按钮即可在下一个视图中输入数据。
  3. 所有这些视图都有关联的视图模型。它们都继承自一个基础 ViewModel。
  4. 所以,我的问题是:我是否将所有变量的属性都写在基础 ViewModel 中?像这样:
namespace ScienceProgram
{
    public abstract class BaseViewModel : INotifyPropertyChanged
    {

        #region Usual Boiler-plate stuff for BindableBase

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }       
        #endregion

        #region Properties (All scientific parameters and calculations )

        public double ParamA { get; set; }
        public double ParamB { get; set;  }
        // ...
        // Lots of parameters //
        // ...
        public double ParamA23 { get; set;  }


        public double TotalLength()
        {
            return ParamA + ParamB + ParamA23;                
        }

        // ...
        // Lots of other methods 
        // ...

        #endregion 

    }
}
  1. 或者我是否为所有输入参数创建一个单独的 class(例如 ScienceParameters.cs)并执行以下操作(这就是我正在做的):
namespace ScienceProgram
{
    public abstract class BaseViewModel : INotifyPropertyChanged
    {

        // Global Parameter // Shared across ViewModels //
        public static ScienceParameters scienceParameters = new ScienceParameters ();

        #region Usual Boiler-plate stuff for BindableBase

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }       
        #endregion

    }
}
  1. 我正在使用后一种方法。当用户在文本框中输入数据时,我只是将其存储为:
namespace ScienceProgram
{
    public class UserInputView1ViewModel : BaseViewModel
    {
        #region User Input 
        private double _paramA;
        public double ParamA
        {
            get { return _paramA; }
            set
            {                
                _paramA = value;                
                OnPropertyChanged("ParamA");

                // Store Value // 
                scienceParameters.ParamA = value;
            }
        }
    }
}
        #endregion
  1. 哪种方法正确?或者,还有更好的方法?
  2. 我一直在寻找一些最佳实践,但我一直被引导到 EventAggregator 并使用单例。我不认为我需要那些。
  3. 正如许多人所说,我认为解决方案是 "to pass a model into constructor of a view model."。但我对如何去做有点困惑。这不会在每个 viewModel 中创建一个新实例吗?
  4. 抱歉,如果这听起来像是一个愚蠢的问题,但过去一周我一直在寻找直接的答案,但尚未找到解决方案。

非常感谢。

你很接近。我看到您正在复制可能导致维护问题的数据存储区域。根据你所说的,我认为你使用 BaseViewModel 的方法是不必要的(但如果你按照我在这里的回答,我会告诉你如何使 BaseViewModel 比你使用它的目的更有用)。

如果我理解正确,您有多个视图,因为您希望用户按 'NEXT' 以转到下一个导航视图。每个 View 都有一个关联的 ViewModel。但在幕后,您想将所有属性收集到一个 class 实例中。

对我来说,这里的最佳实践是学习 MVVM 架构。来自 MVVM 的关键学习是理解 "separation of concerns"。主要是,您的数据(即所有用户的输入值,又名 "Model")与您在 UI 中呈现给他们的视图和视图模型无关。阅读本文,您将更好地掌握这一良好做法。

这是我的做法。

1) 创建模型 class (ScienceParameters.cs),这将保存您希望用户输入的所有属性。示例:

public class ScienceParameters
{
        public double ParamA { get; set; }
        public double ParamB { get; set;  }
        // ...
        // Lots of parameters //
        // ...
        public double ParamA23 { get; set;  }


        public double TotalLength()
        {
            return ParamA + ParamB + ParamA23;                
        }  
}

然后在您呈现给用户的每个 UI 上,您将向他们展示一个视图和一个关联的 ViewModel,使他们能够访问此数据的 "see/get" 或 "store/set" 部分.示例:

public class UserInput1ViewModel: INotifyPropertyChanged
{
        public UserInput1ViewModel(ScienceParameters model)
        {
            this.Model = model;
        }

        public ScienceParameters Model { get; private set; }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }       

        public double ParamA
        {
            get { return this.Model.ParamA; }
            set
            {   
                // Store the user's data directly into the Model, not the ViewModel!
                this.Model.ParamA = value;                
                OnPropertyChanged(nameof(this.ParamA));  // <-- avoid magic words like "ParamA" in quotes, this is bad coding and can cause maintenance issues.
            }
        }

}
public class UserInput2ViewModel: INotifyPropertyChanged
{
        public UserInput2ViewModel(ScienceParameters model)
        {
            this.Model = model;
        }

        public ScienceParameters Model { get; private set; }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }       

        public double ParamB
        {
            get { return this.Model.ParamB; }
            set
            {   
                this.Model.ParamB = value;                
                OnPropertyChanged(nameof(this.ParamB));
            }
        }

}

加分项:

如果要消除在每个 ViewModel 中编写 PropertyChanged 垃圾的重复性,则将其放入 BaseViewModel。

public class BaseViewModel : INotifyPropertyChanged
{
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }       
}

然后从您的 ViewModel 中删除所有这些 PropertyChanged 行,而是使用以下基数定义每个 ViewModel:

public class UserInput1ViewModel: BaseViewModel
{
   // ...
}