Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Which layer should contain ICommand?

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;
    }
}
like image 835
Jagd Avatar asked Nov 29 '25 12:11

Jagd


2 Answers

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.

like image 142
AwkwardCoder Avatar answered Dec 02 '25 00:12

AwkwardCoder


Here is how I organize it in MVVM:

  • Models : only entities, partials of classes which are consumed.
  • View : Xaml and code behind which may show/manipulate Models as provided by the View Model.
  • ViewModel : This layer is where the business logic resides and data (the models) retrieved from the DB are stored. It is the conduit between the View and the Models. It can access databases, create timers, hold ICommands,.... as long as the what it relates to business logic. No direct view processing; per-se (See implementation).

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...

Implementation

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.

like image 26
ΩmegaMan Avatar answered Dec 02 '25 01:12

ΩmegaMan



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!