2
\$\begingroup\$

Is this a proper MVVM implementation? I copied it off a sample but the source would not open so I have to put stuff in spots? Should I call in different files? My first MVVM and have trouble with the concept.

XAML

<Window x:Class="MVVM01.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MVVM01"
        mc:Ignorable="d"    
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <local:DatetimeToDateConverter x:Key="MyConverter"/>
        <local:ViewModel x:Key="ViewModel"/>
    </Window.Resources>
    <Grid DataContext="{Binding Source={StaticResource ViewModel}}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Row="0" Grid.Column="0" Text="Name" HorizontalAlignment="Center"/>
        <TextBox Grid.Row="0" Grid.Column="1" Width="100" HorizontalAlignment="Center" Text="{Binding Student.Name, Mode=TwoWay}"/>
        <TextBlock Grid.Row="1" Grid.Column="0" Text="Age" HorizontalAlignment="Center"/>
        <TextBox Grid.Row="1" Grid.Column="1" Width="100" HorizontalAlignment="Center" Text="{Binding Student.Age, Mode=TwoWay}"/>
        <Button Content="Submit" Command="{Binding SubmitCommand}" HorizontalAlignment="Right" Grid.Row="2" Grid.Column="0"/>
        <ListView ItemsSource="{Binding Students}" Grid.Row="3" Grid.Column="1" Width="200">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" Width="60"/>
                    <GridViewColumn  Header="Age" DisplayMemberBinding="{Binding Age}" Width="60"/>
                    <GridViewColumn  Header="Joining Date" DisplayMemberBinding="{Binding JoiningDate, Converter={StaticResource MyConverter}}" Width="80" />
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>

C#

namespace MVVM01
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

namespace MVVM01
{
    public class Student
    {   // model
        public string Name { get; set; }
        public int Age { get; set; }
        public DateTime JoiningDate { get; set; }
    }
    public class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
    public class ViewModel : ViewModelBase
    {
        private Student _student;
        private ObservableCollection<Student> _students;
        private ICommand _SubmitCommand;
        public Student Student
        {
            get
            {
                return _student;
            }
            set
            {
                _student = value;
                NotifyPropertyChanged("Student");
            }
        }
        public ObservableCollection<Student> Students
        {
            get
            {
                return _students;
            }
            set
            {
                _students = value;
                NotifyPropertyChanged("Students");
            }
        }
        public ICommand SubmitCommand
        {
            get
            {
                if (_SubmitCommand == null)
                {
                    _SubmitCommand = new RelayCommand(param => this.Submit(),
                        null);
                }
                return _SubmitCommand;
            }
        }
        public ViewModel()
        {
            Student = new Student();
            Students = new ObservableCollection<Student>();
            Students.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Students_CollectionChanged);
        }
        //Whenever new item is added to the collection, am explicitly calling notify property changed
        void Students_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            NotifyPropertyChanged("Students");
        }
        private void Submit()
        {
            Student.JoiningDate = DateTime.Today.Date;
            Students.Add(Student);
            Student = new Student();
        }
    }
    public class RelayCommand : ICommand
    {
        public RelayCommand(Action<object> execute) : this(execute, null)
        {
        }
        public RelayCommand(Action<object> execute, Predicate<object> canExecute)
        {
            if (execute == null)
                throw new ArgumentNullException("execute");
            _execute = execute;
            _canExecute = canExecute;
        }
        public bool CanExecute(object parameter)
        {
            return _canExecute == null ? true : _canExecute(parameter);
        }
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
        public void Execute(object parameter)
        {
            _execute(parameter);
        }
        private readonly Action<object> _execute;
        private readonly Predicate<object> _canExecute;
    }
    [ValueConversion(typeof(object), typeof(string))]
    public class DatetimeToDateConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            DateTime date = (DateTime)value;
            return date.ToString("MM/d/yyyy");
        }
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value;
        }
    }
}
\$\endgroup\$
2
\$\begingroup\$

There are at lots of ways to conform to the MVVM-pattern. The overall idea is that whenever you expose data for viewing or editing you'll need a view model. Where the model holds the state and business logic of the model/application, the view model makes it reachable for a particular view type (e.g. XAML), and each view model should only expose those properties that are useful in the context of the view.

I have the following comments to some details:

As far as I can see your model (Student) is on one 'level' while your viewmodel (ViewModel) is on another (a collection of Students).

One could argue that you need a model class for the collection of Students, but thats for you to deside.

Make your ViewModelBase abstract and subclass all your view models from that.

Because you have input for the Student class, I would create a ViewModel for that class too (StudentVM). Your ViewModel class is just fine for the overall view (the Window) except for the Student and Students properties which I would change to a StudentVM and a Collection of StudentVM respectively. 'ViewModel' is maybe not a very descriptive name for that class.

In XAML make a DataTemplate for StudentVM and use that as a template for a ContentControl.

Make the ViewModel instance as the DataContext of the entire Window, as nothing else is 'going on'.

Below find my changes to your XAML/code:

XAML:

<Window x:Class="CR141751.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:CR141751"
        mc:Ignorable="d"    

        Title="MainWindow" Height="350" Width="525">
  <Window.Resources>
    <!-- DataTemplate for StudentVM -->
    <DataTemplate DataType="{x:Type local:StudentVM}">
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="Auto"/>
          <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
          <RowDefinition Height="Auto"/>
          <RowDefinition Height="Auto"/>
          <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <TextBlock Grid.Row="0" Grid.Column="0" Text="Name" HorizontalAlignment="Center"/>
        <TextBox Grid.Row="0" Grid.Column="1" Width="100" HorizontalAlignment="Center" Text="{Binding Name}"/>
        <TextBlock Grid.Row="1" Grid.Column="0" Text="Age" HorizontalAlignment="Center"/>
        <TextBox Grid.Row="1" Grid.Column="1" Width="100" HorizontalAlignment="Center" Text="{Binding Age}"/>
        <TextBlock Grid.Row="2" Grid.Column="0" Text="JoiningDate" HorizontalAlignment="Center"/>
        <TextBlock Grid.Row="2" Grid.Column="1" Width="100" HorizontalAlignment="Center" Text="{Binding JoiningDate}"/>
      </Grid>
    </DataTemplate>
    <local:DatetimeToDateConverter x:Key="MyConverter"/>
    <!-- See comment below <local:ViewModel x:Key="ViewModel"/>-->
  </Window.Resources>
  <Window.DataContext>
    <!-- If you are sure that the view model is never going to change for the window make it as the one and only DataContext for the entire window -->
    <local:ViewModel />
  </Window.DataContext>
  <!--<Grid DataContext="{Binding Source={StaticResource ViewModel}}" Margin="20">-->
  <Grid Margin="20">
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="Auto"/>
      <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>    
    <Button Content="Add" Command="{Binding AddCommand}" HorizontalAlignment="Right" Grid.Row="0" Grid.Column="0"/>
    <!-- A content Control bound to the Student property of the  ViewModel and the layout of controls in that is determined by the DataTemplate for StudentVM above -->
    <ContentControl Content="{Binding Student}" Grid.Column="1" Grid.Row="1" />
    <!-- The SelectedItem is also bound to Student of ViewModel and is then synchronized with the input fields of the current Student -->
    <ListView ItemsSource="{Binding Students}" SelectedItem="{Binding Student}" Grid.Row="2" Grid.Column="1">
      <ListView.View>
        <GridView>
          <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" Width="80"/>
          <GridViewColumn  Header="Age" DisplayMemberBinding="{Binding Age}" Width="80"/>
          <GridViewColumn  Header="Joining Date" DisplayMemberBinding="{Binding JoiningDate, Converter={StaticResource MyConverter}}" Width="80" />
        </GridView>
      </ListView.View>
    </ListView>
  </Grid>
</Window>

Code behind:

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;

namespace CR141751
{
  /// <summary>
  /// Interaction logic for MainWindow.xaml
  /// </summary>
  public partial class MainWindow : Window
  {
    public MainWindow()
    {
      InitializeComponent();
    }
  }

  #region Model Classes
  public class Student
  {
    public string Name;
    public int Age;
    public DateTime JoiningDate;
  }
  #endregion

  #region View Model Classes
  public abstract class ViewModelBase : INotifyPropertyChanged
  {
    public event PropertyChangedEventHandler PropertyChanged;
    protected void NotifyPropertyChanged(string propertyName)
    {
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
  }

  public class StudentVM : ViewModelBase
  {
    Student _student;

    public StudentVM(Student student)
    {
      _student = student;
    }

    public StudentVM(StudentVM vmStudent)
    {
      _student = new Student { Age = vmStudent.Age, Name = vmStudent.Name, JoiningDate = vmStudent.JoiningDate };
    }

    public StudentVM(string name, int age, DateTime joiningDate)
    {
      _student = new Student { Age = age, Name = name, JoiningDate = joiningDate };
    }

    public string Name
    {
      get { return _student.Name; }
      set
      {
        _student.Name = value;
        NotifyPropertyChanged("Name");
      }
    }

    public int Age
    {
      get { return _student.Age; }
      set
      {
        _student.Age = value;
        NotifyPropertyChanged("Age");
      }
    }

    public DateTime JoiningDate
    {
      get { return _student.JoiningDate; }
      set
      {
        _student.JoiningDate = value;
        NotifyPropertyChanged("JoiningDate");
      }
    }
  }

  public class ViewModel : ViewModelBase
  {
    private StudentVM _student;
    private ObservableCollection<StudentVM> _students;
    private ICommand _addCommand;

    public StudentVM Student
    {
      get
      {
        return _student;
      }
      set
      {
        _student = value;
        NotifyPropertyChanged("Student");
      }
    }

    public ObservableCollection<StudentVM> Students
    {
      get
      {
        return _students;
      }
      set
      {
        _students = value;
        NotifyPropertyChanged("Students");
      }
    }

    // Change the SubmitCommand to an AddCommand as it actually adds a student, not editing an existing
    public ICommand AddCommand
    {
      get
      {
        if (_addCommand == null)
        {
          _addCommand = new RelayCommand(param => this.Add(),
              null);
        }
        return _addCommand;
      }
    }

    public ViewModel()
    {
      Student = new StudentVM("", 0, DateTime.Today);
      Students = new ObservableCollection<StudentVM>();
      //Students.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Students_CollectionChanged);
    }

    // There should be no need for this, because the idea of ObservableCollection<T> is exactly to notify clients of changes 
    ////Whenever new item is added to the collection, am explicitly calling notify property changed
    //void Students_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    //{
    //  NotifyPropertyChanged("Students");
    //}

    private void Add()
    {
      Student = new StudentVM("<name>", 0, DateTime.Today.Date);
      Students.Add(Student);
    }
  }

  #endregion

  public class RelayCommand : ICommand
  {
    public RelayCommand(Action<object> execute) : this(execute, null)
    {
    }

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
      if (execute == null)
        throw new ArgumentNullException("execute");
      _execute = execute;
      _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
      return _canExecute == null ? true : _canExecute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
      add { CommandManager.RequerySuggested += value; }
      remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
      _execute(parameter);
    }

    private readonly Action<object> _execute;
    private readonly Predicate<object> _canExecute;
  }

  [ValueConversion(typeof(object), typeof(string))]
  public class DatetimeToDateConverter : IValueConverter
  {
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
      DateTime date = (DateTime)value;
      return date.ToString("MM/d/yyyy");
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
      return value;
    }
  }
}
\$\endgroup\$
-2
\$\begingroup\$

It has a model, a view and a viewmodel, so yes. It is a "proper" MVVM. Maybe the sample was meant to illustrate the principals rather than build.

\$\endgroup\$
  • 2
    \$\begingroup\$ While this is definitely an answer, it's a little thin. Can you review some of the code of the OP? \$\endgroup\$ – pacmaninbw Sep 21 '16 at 17:12

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.