C# WPF 跨视图模型共享模型的最佳实践
C# WPF Best Practice to share a model across viewmodels
- 我正在写一个小的科学程序。它有很多变量作为输入。
- 用户必须在 window(或用户界面)中输入所有输入变量。但是,我无法将所有输入都放在一个 window (XAML) 中。因此,我创建了几个视图,用户只需按下 NEXT 按钮即可在下一个视图中输入数据。
- 所有这些视图都有关联的视图模型。它们都继承自一个基础 ViewModel。
- 所以,我的问题是:我是否将所有变量的属性都写在基础 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
}
}
- 或者我是否为所有输入参数创建一个单独的 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
}
}
- 我正在使用后一种方法。当用户在文本框中输入数据时,我只是将其存储为:
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
- 哪种方法正确?或者,还有更好的方法?
- 我一直在寻找一些最佳实践,但我一直被引导到 EventAggregator 并使用单例。我不认为我需要那些。
- 正如许多人所说,我认为解决方案是 "to pass a model into constructor of a view model."。但我对如何去做有点困惑。这不会在每个 viewModel 中创建一个新实例吗?
- 抱歉,如果这听起来像是一个愚蠢的问题,但过去一周我一直在寻找直接的答案,但尚未找到解决方案。
非常感谢。
你很接近。我看到您正在复制可能导致维护问题的数据存储区域。根据你所说的,我认为你使用 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
{
// ...
}
- 我正在写一个小的科学程序。它有很多变量作为输入。
- 用户必须在 window(或用户界面)中输入所有输入变量。但是,我无法将所有输入都放在一个 window (XAML) 中。因此,我创建了几个视图,用户只需按下 NEXT 按钮即可在下一个视图中输入数据。
- 所有这些视图都有关联的视图模型。它们都继承自一个基础 ViewModel。
- 所以,我的问题是:我是否将所有变量的属性都写在基础 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
}
}
- 或者我是否为所有输入参数创建一个单独的 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
}
}
- 我正在使用后一种方法。当用户在文本框中输入数据时,我只是将其存储为:
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
- 哪种方法正确?或者,还有更好的方法?
- 我一直在寻找一些最佳实践,但我一直被引导到 EventAggregator 并使用单例。我不认为我需要那些。
- 正如许多人所说,我认为解决方案是 "to pass a model into constructor of a view model."。但我对如何去做有点困惑。这不会在每个 viewModel 中创建一个新实例吗?
- 抱歉,如果这听起来像是一个愚蠢的问题,但过去一周我一直在寻找直接的答案,但尚未找到解决方案。
非常感谢。
你很接近。我看到您正在复制可能导致维护问题的数据存储区域。根据你所说的,我认为你使用 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
{
// ...
}