In WPF, I have a ViewModel class called Malfunctions, and it has an ObservableCollection of PartMalfunctions. Typically, there are anywhere between 10 to 15 PartMalfunction objects in the ObservableCollection; how many there are depends upon other parameters that are outside the scope of this question.
I have some xaml that has a DataGrid which binds to this ObservableCollection. In the DataGrid I display various properties of the PartMalfuction (ie - description, name, etc.) and I have a Start timer button that the user can click. The Start timer button is bound to the ICommand StopwatchCmd in the PartMalfunction Model class (you can see all of this below in the code).
Here is my question: Do I have the StopwatchCmd in the wrong layer (ie - Does it belong in the Malfunctions ViewModel)? I have really struggled with this and tried to figure it out on my own, but I keep hitting a wall, so to speak, because the StopwatchCmd in the Model class works great! I mean it's able to execute there and perform whatever business rules it needs to and interact with just that instance of the object for which it fired. If I stick it in the ViewModel then it seems like I have to go through more work to get it to do what it's already doing.
Please note that I have left out some code from the Malfunctions ViewModel as it's not related to this question. Here is the code to the Malfunctions ViewModel.
public class Malfunctions : ViewModelBase {
public ObservableCollection<Model.PartMalfunction> AllPartMalfunctions {
get;
private set;
}
}
The Model class for PartMalfunction looks something like this:
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;
}
}
This question could be perceived as being primarily as opinion based, but I do believe it helps less experience developers understand the boundaries of the Model-View-ViewModel.
For me what you think is a Model is in fact a ViewModel, to be more specific the parent ViewModel (Malfunctions) has a collection of child ViewModels (PartMalfunction) exposed as a collection (ObservableCollection), meaning there isn't problem with the ICommand properties on the PartMalfunction class.
If I find a Model class doing a lot of format of data for display (text, dates etc) then it is more likely to be a ViewModel, this kind of thing is the responiblity of the ViewModel. Also for me a Model class does not implment the INotifyPropertyChanged interface, notifications are done using events (or Rx streams) and the subscriber (ViewModel) can then choose how and when to update the UI.
Here is how I organize it in MVVM:
Remember when three tier systems were all the rage? If you think of MVVM as three tiers that may help. IMHO
Which layer should contain ICommand?
The viewmodel should hold the reference, because it handles the business logic of what the command does. But...
The implementation of the command can be held in the View or on the ViewModel based on the needs of the command. I have frequently defined the references in the VM to then provide actual processing in the View which has stateful View values to be processed during runtime.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With