哪一层应该包含 ICommand?
Which layer should contain ICommand?
在 WPF 中,我有一个名为 Malfunctions 的 ViewModel class,它有一个 PartMalfunctions 的 ObservableCollection。通常,ObservableCollection 中有 10 到 15 个 PartMalfunction 对象;有多少取决于超出该问题范围的其他参数。
我有一些 xaml 具有绑定到此 ObservableCollection 的 DataGrid。在 DataGrid 中,我显示了 PartMalfuction 的各种属性(即描述、名称等),并且我有一个用户可以单击的开始计时器按钮。启动计时器按钮绑定到 PartMalfunction 模型 class 中的 ICommand StopwatchCmd(您可以在下面的代码中看到所有这些)。
这是我的问题:我的 StopwatchCmd 是否在错误的层中(即 - 它是否属于故障 ViewModel)? 我真的很难解决这个问题并尝试我自己弄明白了,但我一直在碰壁,可以这么说,因为模型 class 中的 StopwatchCmd 工作得很好!我的意思是它能够在那里执行并执行它需要的任何业务规则,并且只与它触发的对象实例进行交互。如果我将它放在 ViewModel 中,那么我似乎必须做更多的工作才能让它完成它已经在做的事情。
请注意,我遗漏了故障 ViewModel 中的一些代码,因为它与这个问题无关。这是故障视图模型的代码。
public class Malfunctions : ViewModelBase {
public ObservableCollection<Model.PartMalfunction> AllPartMalfunctions {
get;
private set;
}
}
PartMalfunction 的模型 class 看起来像这样:
public class PartMalfunction : INotifyPropertyChanged {
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName) {
if (PropertyChanged != null) {
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
private int _seconds;
private string _stopwatchText = string.Empty;
private bool _isStopwatchInProgress = false;
System.Windows.Threading.DispatcherTimer _timer = new System.Windows.Threading.DispatcherTimer();
RelayCommand _stopwatchCmd;
public ICommand StopwatchCmd {
get {
if (_stopwatchCmd == null)
_stopwatchCmd = new RelayCommand(param => this.StopwatchClick());
return _stopwatchCmd;
}
}
public bool IsStopwatchInProgress {
get {
return _isStopwatchInProgress;
}
set {
_isStopwatchInProgress = value;
OnPropertyChanged("IsStopwatchInProgress");
}
}
public string StopwatchText {
get {
return _stopwatchText;
}
set {
_stopwatchText = value;
OnPropertyChanged("StopwatchText");
}
}
private void StopwatchClick() {
if (!this.IsStopwatchInProgress) {
// Start the timer
_seconds = 0;
// Will immediately update the timer text to "00:00:00"
this.StopwatchText = GetElapsed();
_timer.Tick += DispatcherTimer_Tick;
_timer.Interval = new TimeSpan(0, 0, 1); // Ticks every second
_timer.Start();
this.IsStopwatchInProgress = true;
}
else {
// Stop the timer
_timer.Stop();
_timer.Tick -= DispatcherTimer_Tick;
_seconds = 0;
this.IsStopwatchInProgress = false;
}
}
private void DispatcherTimer_Tick(object sender, System.EventArgs e) {
_seconds += 1;
this.StopwatchText = GetElapsed();
}
private string GetElapsed() {
int hour = 0, min = 0, sec = 0;
if (_seconds > 59) {
min = (int)_seconds / 60;
sec = _seconds % 60;
if (min > 59) {
hour = (int)min / 60;
min = min % 60;
}
}
else
sec = _seconds;
string elapsed = hour < 10 ? "0" + hour.ToString() : hour.ToString();
elapsed += ":" + (min < 10 ? "0" + min.ToString() : min.ToString());
elapsed += ":" + (sec < 10 ? "0" + sec.ToString() : sec.ToString());
return elapsed;
}
}
这个问题可以被视为主要基于意见,但我相信它有助于经验较少的开发人员理解模型-视图-视图模型的边界。
对我来说,你认为的模型实际上是一个 ViewModel,更具体地说,父 ViewModel (Malfunctions) 有一个子 ViewModel (PartMalfunction) 的集合,作为一个集合 (ObservableCollection) 公开,这意味着没有PartMalfunction class.
上的 ICommand 属性存在问题
如果我发现一个模型 class 为显示做了大量的数据格式(文本、日期等)那么它更有可能是一个 ViewModel,这种事情是 ViewModel 的责任.同样对我来说,模型 class 没有实现 INotifyPropertyChanged 接口,通知是使用事件(或 Rx 流)完成的,然后订阅者(ViewModel)可以选择如何以及何时更新 UI.
以下是我在 MVVM
中的组织方式:
- 模型:仅实体,类 的部分被消耗。
- 视图:Xaml 及其背后的代码可能 show/manipulate 视图模型提供的模型。
- ViewModel :该层是 业务 逻辑所在的位置,并且存储从数据库检索到的数据(模型)。它是视图和模型之间的 管道 。它可以访问数据库、创建计时器、保存 ICommands……只要 what 仅与业务逻辑相关。没有直视处理。
还记得当时风靡一时的三层系统吗?如果您将 MVVM 视为可能有帮助的三层。恕我直言
Which layer should contain ICommand?
视图模型,因为它处理命令的业务逻辑。将来,当一个人拿起代码来维护它时,大多数开发人员首先会看的是那里。
ICommand
有两个有趣的成员。 Execute
,它定义了另一个对象可以请求命令所有者执行的操作,CanExecute
,它定义了另一个对象是否应该请求该操作。这在 ICommand
的属性名称中非常明显,但如果仔细观察,它们会 ICommand
在 ViewModel 层中找到一个完美的家。您的视图模型可以将一些操作公开给未知视图,并控制它们何时被允许执行。由于命令通常是视图模型上的 public 属性,因此您可以轻松地从 WPF 控件绑定到它们,而无需任何紧密耦合。 Button
例如,可以绑定到一个命令来定义它的作用,而无需知道它的 DataContext
(视图模型)是什么类型。
如果您尝试将 ICommand
放在您的视图中,您可能会发现与方法相比它们会很麻烦,因为视图可以访问它们自己的方法。正因为如此,ICommand
不太适合放在View层。
由于您的视图模型将模型从您的模型中抽象出来(理想情况下),您将无法(也不想)在您的模型上放置命令 类。任何处理被呈现或与之交互的逻辑都不属于模型层。
在 WPF 中,我有一个名为 Malfunctions 的 ViewModel class,它有一个 PartMalfunctions 的 ObservableCollection。通常,ObservableCollection 中有 10 到 15 个 PartMalfunction 对象;有多少取决于超出该问题范围的其他参数。
我有一些 xaml 具有绑定到此 ObservableCollection 的 DataGrid。在 DataGrid 中,我显示了 PartMalfuction 的各种属性(即描述、名称等),并且我有一个用户可以单击的开始计时器按钮。启动计时器按钮绑定到 PartMalfunction 模型 class 中的 ICommand StopwatchCmd(您可以在下面的代码中看到所有这些)。
这是我的问题:我的 StopwatchCmd 是否在错误的层中(即 - 它是否属于故障 ViewModel)? 我真的很难解决这个问题并尝试我自己弄明白了,但我一直在碰壁,可以这么说,因为模型 class 中的 StopwatchCmd 工作得很好!我的意思是它能够在那里执行并执行它需要的任何业务规则,并且只与它触发的对象实例进行交互。如果我将它放在 ViewModel 中,那么我似乎必须做更多的工作才能让它完成它已经在做的事情。
请注意,我遗漏了故障 ViewModel 中的一些代码,因为它与这个问题无关。这是故障视图模型的代码。
public class Malfunctions : ViewModelBase {
public ObservableCollection<Model.PartMalfunction> AllPartMalfunctions {
get;
private set;
}
}
PartMalfunction 的模型 class 看起来像这样:
public class PartMalfunction : INotifyPropertyChanged {
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName) {
if (PropertyChanged != null) {
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
private int _seconds;
private string _stopwatchText = string.Empty;
private bool _isStopwatchInProgress = false;
System.Windows.Threading.DispatcherTimer _timer = new System.Windows.Threading.DispatcherTimer();
RelayCommand _stopwatchCmd;
public ICommand StopwatchCmd {
get {
if (_stopwatchCmd == null)
_stopwatchCmd = new RelayCommand(param => this.StopwatchClick());
return _stopwatchCmd;
}
}
public bool IsStopwatchInProgress {
get {
return _isStopwatchInProgress;
}
set {
_isStopwatchInProgress = value;
OnPropertyChanged("IsStopwatchInProgress");
}
}
public string StopwatchText {
get {
return _stopwatchText;
}
set {
_stopwatchText = value;
OnPropertyChanged("StopwatchText");
}
}
private void StopwatchClick() {
if (!this.IsStopwatchInProgress) {
// Start the timer
_seconds = 0;
// Will immediately update the timer text to "00:00:00"
this.StopwatchText = GetElapsed();
_timer.Tick += DispatcherTimer_Tick;
_timer.Interval = new TimeSpan(0, 0, 1); // Ticks every second
_timer.Start();
this.IsStopwatchInProgress = true;
}
else {
// Stop the timer
_timer.Stop();
_timer.Tick -= DispatcherTimer_Tick;
_seconds = 0;
this.IsStopwatchInProgress = false;
}
}
private void DispatcherTimer_Tick(object sender, System.EventArgs e) {
_seconds += 1;
this.StopwatchText = GetElapsed();
}
private string GetElapsed() {
int hour = 0, min = 0, sec = 0;
if (_seconds > 59) {
min = (int)_seconds / 60;
sec = _seconds % 60;
if (min > 59) {
hour = (int)min / 60;
min = min % 60;
}
}
else
sec = _seconds;
string elapsed = hour < 10 ? "0" + hour.ToString() : hour.ToString();
elapsed += ":" + (min < 10 ? "0" + min.ToString() : min.ToString());
elapsed += ":" + (sec < 10 ? "0" + sec.ToString() : sec.ToString());
return elapsed;
}
}
这个问题可以被视为主要基于意见,但我相信它有助于经验较少的开发人员理解模型-视图-视图模型的边界。
对我来说,你认为的模型实际上是一个 ViewModel,更具体地说,父 ViewModel (Malfunctions) 有一个子 ViewModel (PartMalfunction) 的集合,作为一个集合 (ObservableCollection) 公开,这意味着没有PartMalfunction class.
上的 ICommand 属性存在问题如果我发现一个模型 class 为显示做了大量的数据格式(文本、日期等)那么它更有可能是一个 ViewModel,这种事情是 ViewModel 的责任.同样对我来说,模型 class 没有实现 INotifyPropertyChanged 接口,通知是使用事件(或 Rx 流)完成的,然后订阅者(ViewModel)可以选择如何以及何时更新 UI.
以下是我在 MVVM
中的组织方式:
- 模型:仅实体,类 的部分被消耗。
- 视图:Xaml 及其背后的代码可能 show/manipulate 视图模型提供的模型。
- ViewModel :该层是 业务 逻辑所在的位置,并且存储从数据库检索到的数据(模型)。它是视图和模型之间的 管道 。它可以访问数据库、创建计时器、保存 ICommands……只要 what 仅与业务逻辑相关。没有直视处理。
还记得当时风靡一时的三层系统吗?如果您将 MVVM 视为可能有帮助的三层。恕我直言
Which layer should contain ICommand?
视图模型,因为它处理命令的业务逻辑。将来,当一个人拿起代码来维护它时,大多数开发人员首先会看的是那里。
ICommand
有两个有趣的成员。 Execute
,它定义了另一个对象可以请求命令所有者执行的操作,CanExecute
,它定义了另一个对象是否应该请求该操作。这在 ICommand
的属性名称中非常明显,但如果仔细观察,它们会 ICommand
在 ViewModel 层中找到一个完美的家。您的视图模型可以将一些操作公开给未知视图,并控制它们何时被允许执行。由于命令通常是视图模型上的 public 属性,因此您可以轻松地从 WPF 控件绑定到它们,而无需任何紧密耦合。 Button
例如,可以绑定到一个命令来定义它的作用,而无需知道它的 DataContext
(视图模型)是什么类型。
如果您尝试将 ICommand
放在您的视图中,您可能会发现与方法相比它们会很麻烦,因为视图可以访问它们自己的方法。正因为如此,ICommand
不太适合放在View层。
由于您的视图模型将模型从您的模型中抽象出来(理想情况下),您将无法(也不想)在您的模型上放置命令 类。任何处理被呈现或与之交互的逻辑都不属于模型层。