3
\$\begingroup\$

Check me:

Model should do everything. That's where all functionalities and features are accommodated. It has some properties, that I can access and use. Theoretically, it could work as a standalone app (used via command prompt). View does nothing but looks (like a monitor: you always look at it, whereas the real work does your computer). ViewModel supply and modify Model's output to the View and View's user input to the Model.

Am I right?


I have written simple timer application, where I want to follow MVVM. There are ToggleButton whitch start or stop countdown and Slider to set time to count. ToggleButton's content shows remaining time.

View

<Window ...>

<Grid x:Name="grid" Margin="0,-2,0,2">
    <Grid.RowDefinitions>
        <RowDefinition Height="5*"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <!--Definition of MyToggleButton style is not important for this question-->
    <ToggleButton x:Name="MainButton"
                  Margin="5" 
                  Style="{StaticResource MyToggleButton}" 
                  RenderTransformOrigin=".5 .5"
                  Content="{Binding Path=TimeString}"
                  Command="{Binding Path=Command}" CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=IsChecked}"/>

    <!--I define InverseBoolConverter in app resources and it simply interchange true for false and vice versa-->
    <Slider Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Center"
            Maximum="18000" TickFrequency="60" Value="{Binding Path=TimeInMs, Mode=TwoWay}"
            IsEnabled="{Binding Path=IsChecked, ElementName=MainButton, Converter={StaticResource InverseBool}}"
            IsSnapToTickEnabled="True" IsMoveToPointEnabled="True" />
</Grid>
</Window>

In View's code behind I instantine ViewModel and set DataContext on it.

ViewModel

class ViewModel: INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    Model MyModel;
    public ICommand Command
    { get; }
    public string TimeString
    {
        get
        {
            int timeInMin = MyModel.Time / 60;
            return timeInMin != 0 ? timeInMin + " min" : MyModel.Time + "s";
        }
    }
    public int TimeInMs
    {
        get
        {
            return MyModel.Time;
        }
        set
        {
            MyModel.Time = value;
        }
    }

    public ViewModel()
    {
        MyModel = new Model(3600);
        Command = new ToggleCommand(MyModel.Start, MyModel.End);
        MyModel.PropertyChanged += (a,b)=> { PropertyChanged(this, new PropertyChangedEventArgs("")); };
    }
}

Model

class Model : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    Timer myTimer;
    int _time;
    public int Time
    {
        get { return _time; }
        set
        {
            _time = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Time"));
        }
    }

    public Model(int time)
    {
        this.Time = time;
    }

    public void Start()
    {
        myTimer = new Timer(Tic, null, 1000, 1000);
    }
    public void End()
    {
        myTimer?.Dispose();
    }

    void Tic(object state)
    {
        if (Time==0)
        {
            DoSomething();//Not implemented
        }
        Time--;
    }
}

ToggleCommand

class ToggleCommand : ICommand
{
    public event EventHandler CanExecuteChanged;
    Action on;
    Action off;
    Func<bool> when;

    public ToggleCommand(Action on, Action off, Func<bool> when)
    {
        this.on = on;
        this.off = off;
        this.when = when;
    }
    public ToggleCommand(Action on, Action off)
    {
        this.on = on;
        this.off = off;
        this.when = () => { return true; };
    }

    public bool CanExecute(object parameter)
    {
        return when();
    }

    public void Execute(object parameter)
    {
        // Check if Togglebutton is checked
        if ((bool)parameter)
        {
            on();
        }
        else
        {
            off();
        }
    }
}

And what about this line from ViewModel? Is it good approach? (It seems to me a bit weird.)

MyModel.PropertyChanged += (a,b)=> { PropertyChanged(this, new PropertyChangedEventArgs("")); };
\$\endgroup\$
2
\$\begingroup\$

IMHO You are pretty much right and definitely on the right track; but, in my experience of working with multiple enterprise application ranging from medium to very large WPF application -

there is no definite boundary to differentiate between Model, ViewModel, and View; it's more like Agile, it gives you guidelines and direction but is pretty much flexible to adapt as per your needs.

Here's my take on your points -

Model should do everything. That's where all functionalities and features are accommodated. It has some properties, that I can access and use. Theoretically, it could work as a standalone app (used via command prompt).

Mostly yes, that's the main idea of Model. A model can also have methods to update model data and behavior. It's not at all dependent on anything related to a view.

View does nothing but looks (like a monitor: you always look at it, whereas the real work does your computer).

Not always true, a view is supposed to do everything related to a view. You can have a lot of requirements where you need to do things to have a user-friendly view and do data conversions to use/modify VM data and create itself (e.g. Converters). It's especially true when you work with third party controls, you need to write a lot of code in view to make them work or achieve trivial things. Remember that only View in MVVM knows(*can know) about other two entities i.e. VM and Model.

ViewModel supply and modify Model's output to the View and View's user input to the Model. Yes, VM works as a mediator but most imp. thing is that it should also be independent of view; similar to Model it should also be capable to support another type of view or multiple views (e.g. WPF and SL and WP). So it should not have anything specific to a particular type of view.

Now regarding

MyModel.PropertyChanged += (a,b)=> { PropertyChanged(this, new PropertyChangedEventArgs("")); };

This is very subjective to type of application and size of Model; In your example its not a bad thing to do, but in case you had 15-20 properties in your Model and same number of properties in ViewModel to expose them to view, and 10-15 such Models and VM's - then its a totally different game altogether.

Having such uncontrolled change notifications can cause huge performance problems as your view might get updated again and again unnecessarily or take a long time to render due to multiple passes of rendering cycles.

Most importantly it will never be in your full control, it will be very hard to debug and understand why and when UI is updated.

There are two options -

  1. Explicitly expose Model properties, indirectly through your VM and update them only when required; that's a lot of work and hard to maintain.
  2. As your Model implements INotifyPropertyChanged, directly use Model properties in View (through VM i.e. exposing your MyModel object in VM) and your view and model will always remain in sync.

This is highly debatable and a topic of discussion; I would recommend you to read these related SO threads -

In MVVM should the ViewModel or Model implement INotifyPropertyChanged?

In MVVM model should the model implement INotifyPropertyChanged interface?

Another minor but important point, you should always consider having an equality check for every property firing property change i.e. before updating your Time property in Model you should check if the new value is different then existing one or not and only fire property change if its a different value.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.