Kale
Kale

Reputation: 85

MVVM the right way

I was given hints in an other questions on how to implement a MVVM. I had problems passing bind update to the GUI when changes are made in the Student class itself (which occure quiet a lot in my project). Is there a way to easy these things up and have it in a more compact way than implemented yet? Or is this the state of the art to implement MVVM?

class MainWindowViewModel : INotifyPropertyChanged
{
   ObservableCollection<StudentViewModel> studentViewModels = new ObservableCollection<StudentViewModel>();

   public ObservableCollection<StudentViewModel> StudentViewModels
   {
      get { return studentViewModels; }
   }

   public MainWindowViewModel()
   {
      studentViewModels.Add(new StudentViewModel());
      studentViewModels.Add(new StudentViewModel());
      studentViewModels.Add(new StudentViewModel());
   }

   public event PropertyChangedEventHandler PropertyChanged;
   internal void OnPropertyChanged(String propertyName)
   {
      if (PropertyChanged != null)
      {
         PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
      }
   }
}


class StudentViewModel : INotifyPropertyChanged
{
   Student model;
   public String StudentFirstName
   {
      get { return model.StudentFirstName; }
      set { model.StudentFirstName = value; }
   }
   public String StudentLastName
   {
      get { return model.StudentLastName; }
      set { model.StudentLastName = value; }
   }

   public StudentViewModel()
   {
      model = new Student();
      this.model.PropertyChanged += (sender, e) => 
      {
         switch (e.PropertyName)
         {
            case "StudentFirstName": OnPropertyChanged("StudentFirstName"); break;
            case "StudentLastName": OnPropertyChanged("StudentLastName"); break;
            default: break;
         }
      };
   }

   public StudentViewModel(Student model)
   {
      this.model = model;

      this.model.PropertyChanged += (sender, e) =>
      {
         switch (e.PropertyName)
         {
            case "StudentFirstName": OnPropertyChanged("StudentFirstName"); break;
            case "StudentLastName": OnPropertyChanged("StudentLastName"); break;
            default: break;
         }
      ;
   }

   public void changeStudent()
   {
      model.changeStudent();
   }

   public event PropertyChangedEventHandler PropertyChanged;
   internal void OnPropertyChanged(String propertyName)
   {
      if (PropertyChanged != null)
      {
         PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
      }
   }
}



class Student : INotifyPropertyChanged
{
   public String studentFirstName;
   public String StudentFirstName
   {
      get { return studentFirstName; }
      set 
      {
         if (studentFirstName != value)
         {
            studentFirstName = value;
            OnPropertyChanged("StudentFirstName");
         }
      }
   }
   public String studentLastName;
   public String StudentLastName
   {
      get { return this.studentLastName; }
      set
      {
         if (studentLastName != value)
         {
            studentLastName = value;
            OnPropertyChanged("StudentLastName");
         }
      }
   }

   public Student() { }

   public void changeStudent()
   {
      StudentLastName = "McRonald";
   }

   public event PropertyChangedEventHandler PropertyChanged;
   internal void OnPropertyChanged(String propertyName)
   {
      if (PropertyChanged != null)
      {
         PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
      }
   }
}

Upvotes: 2

Views: 2677

Answers (4)

ZanderAdam
ZanderAdam

Reputation: 434

I agree with other answers that you should have a look at a MVVM framework. I use MVVM Foundation for my work.

Wall of text is to follow. I started with MVVM not too long ago as well. This code was of great help for me on my last project.

Recently I had to work on a class which also needed IEditableObject to manage editing/saving/discarding the object as well as PropertyChanged notifications. Since you mention a way "to easy things up" I am going to post the base classe I used to tie MVVM and Editable Object together. It was a big time saver for me for implementing all other classes.

The EditableObject inherits from ObservableObject that is part of MVVM Foundation. It takes a struct for the object you are working with as a typeparam.

If you are familiar with implementation of IEditabeObject, there are editData and backupData variables which hold the data you are currently working with (I am not inheriting from this, I created my own EditableObject). I am using AutoMapper to, basically, create a deep copy (backup) of the data I am working with so that it can be restored. There are other ways to do this (look up Serialization or Value Injection), but I already had AutoMapper in the project so no need for more dlls.

The EditableObject has abstract methods SaveObject and RemoveObject, which you implement to handle database calls, etc, to remove and save the objects. The Edit of your object is done using BeginEdit and DiscardChanges and SaveChanges.

The magic happens with the RaisePropertiesChanged method which raises PropertyChanged methods on all decorated properties in your class. So whenever you edit your object and (lets say) discard the changes. The UI is refreshed with back to original values. It also includes IsEditEnabled flag you can bind your UI to. Originally I used PropertyChanged with an empty string, but this would raise it on all properties. Using attributes I ensure it changes only on the properties that I need to update.

I used your Student Class to implement this and attached the base class below.

Hope it helps!

public class Student: EditableObject<WPF.MVVMBase.Student.StudentData>
    {
        #region Struct

        public struct StudentData
        {
            public string firstName;
            public string lastName;
        }

        #endregion

        #region Public Properties

        [ObservableProperty]
        public string FirstName
        {
            get
            {
                return _editData.firstName;
            }
            set
            {
                _editData.firstName = value;
                this.RaisePropertyChanged("FirstName");
            }
        }

        [ObservableProperty]
        public string LastName
        {
            get
            {
                return _editData.lastName;
            }
            set
            {
                _editData.lastName = value;
                this.RaisePropertyChanged("LastName");
            }
        }

        #endregion

        #region Methods

        protected override bool SaveObject()
        {
            //Save Student Changes to Database

            return true;
        }

        protected override bool RemoveObject()
        {
            //Remove Student from Database

            return true;
        }

        #endregion
    }

This is the EditableObject class

namespace WPF.MVVMBase
{
    /// <summary>
    /// Property Decorator that marks the Property as Observable. This is used by the EditableObject class to determine for which properties to raise the Property Changed method
    /// </summary>
    public class ObservablePropertyAttribute : System.Attribute{};

    /// <summary>
    /// Extends the ObservableObject class. EditableObject implements methods which are used to edit the object as well as raise the Property Changed events.
    /// </summary>
    /// <typeparam name="T">The Struct for the Editable Object</typeparam>
    public abstract class EditableObject<T> : ObservableObject
    {
        #region Private Variables

        bool _IsEditEnabled = false;
        bool _IsSelected = false;

        protected T _editData;
        protected T _backupData;

        #endregion

        #region Public Properties

        /// <summary>
        /// Controls if the Edit is enabled on the Editable Object
        /// </summary>
        public bool IsEditEnabled
        {
            get
            {
                return _IsEditEnabled;
            }
            protected set
            {
                _IsEditEnabled = value;
                this.RaisePropertyChanged("IsEditEnabled");
            }
        }

        /// <summary>
        /// Determines weather the object is Selected. Used with Lists
        /// </summary>
        public bool IsSelected
        {
            get
            {
                return _IsSelected;
            }
            set
            {
                _IsSelected = value;
                this.RaisePropertyChanged("IsSelected");
            }
        }

        #endregion

        #region Constructor

        public EditableObject()
        {
            //Create an instance of the object that will hold the data.
            _editData = Activator.CreateInstance<T>();
        }

        #endregion

        #region Methods

        #region Abstract Methods

        /// <summary>
        /// Handle the object saving. This is called by the SaveChanges method.
        /// </summary>
        /// <returns>Indicates if the object was saved successfully</returns>
        protected abstract bool SaveObject();

        /// <summary>
        /// Handle the object remove. This is called by the Remove method.
        /// </summary>
        /// <returns>Indicates if the object was removed successfully</returns>
        protected abstract bool RemoveObject();

        #endregion

        /// <summary>
        /// Begin editing the object. Sets the IsEditEnabled to true and creates a backup of the Data for restoring.
        /// </summary>
        public void BeginEdit()
        {
            IsEditEnabled = true;
            _backupData = Mapper.DynamicMap<T>(_editData);
        }

        /// <summary>
        /// Discard any changes made to the object. Set the IsEditEnabled flag to false and restore the data from the Backup.
        /// </summary>
        public void DiscardChanges()
        {
            _editData = _backupData;
            IsEditEnabled = false;

            RaisePropertiesChanged(this);
        }

        /// <summary>
        /// Save the changes made to the object. Calls the SaveObject method. If save was successfull IsEditEnabled is set to false and backup data is set to current data.
        /// </summary>
        /// <returns>Indicates if the object was saved successfully</returns>
        public bool SaveChanges()
        {
            bool isSaveSuccessfull = SaveObject();

            if (isSaveSuccessfull == true)
            {
                _backupData = _editData;

                IsEditEnabled = false;

                RaisePropertiesChanged(this);
            }

            return isSaveSuccessfull;
        }

        public bool Remove()
        {
            bool isRemoveSuccessfull = RemoveObject();
            return isRemoveSuccessfull;
        }

        /// <summary>
        /// Raises ObservableObject Property Changed for all the decorated methods in the given object so that the interface can refresh accordingly.
        /// </summary>
        /// <param name="baseObject"></param>
        public void RaisePropertiesChanged(object baseObject)
        {
            PropertyInfo[] properties = baseObject.GetType().GetProperties();
            foreach (PropertyInfo property in properties)
            {
                object[] attributes = property.GetCustomAttributes(true);

                bool isObservableProperty = (from attribute in attributes
                                             where attribute is ObservablePropertyAttribute
                                             select attribute).Count() > 0;

                if (isObservableProperty)
                {
                    RaisePropertyChanged(property.Name);
                }
            }
        }

        #endregion
    }
}

Upvotes: 1

laszlokiss88
laszlokiss88

Reputation: 4071

What if you create a Student proeprty in the StudentViewModelclass? Moreover, a ViewModelBase class could simplify the code (or at least make it shorter).

class MainWindowViewModel : ViewModelBase
{
    public ObservableCollection<StudentViewModel> StudentViewModels { get; private set; }
    public MainWindowViewModel()
    {
        StudentViewModels = new ObservableCollection<StudentViewModel>();
    }
}

class StudentViewModel : ViewModelBase
{
    public Student Student { get; private set; }

    public StudentViewModel()
    {
        Student = new Student();
    }

    public StudentViewModel(Student model)
    {
        Student = model;
    }

    public void ChangeStudent()
    {
        Student.changeStudent();
    }
}

public class Student : ViewModelBase
{
    public String studentFirstName;
    public String StudentFirstName
    {
        get { return studentFirstName; }
        set
        {
            if (studentFirstName != value)
            {
                studentFirstName = value;
                OnPropertyChanged("StudentFirstName");
            }
        }
    }
    public String studentLastName;
    public String StudentLastName
    {
        get { return this.studentLastName; }
        set
        {
            if (studentLastName != value)
            {
                studentLastName = value;
                OnPropertyChanged("StudentLastName");
            }
        }
    }

    public Student() { }

    public void changeStudent()
    {
        StudentLastName = "McRonald";
    }
}

And here is a ViewModelBase class that implements the INotifyPropertyChanged interface:

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged(string propName)
    {
        var eh = PropertyChanged;
        if (eh != null)
        {
            eh(this, new PropertyChangedEventArgs(propName));
        }
    }
}

For testing:

<Grid>
    <ListBox Name="lbStudents" ItemsSource="{Binding StudentViewModels}" >
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Path=Student.StudentLastName}" />
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Grid>

and the constructor of the MainWidnow

 public MainWindow()
    {
        InitializeComponent();

        var viewModel = new MainWindowViewModel();
        var student = new Student { StudentFirstName = "John", StudentLastName = "Doe" };
        viewModel.StudentViewModels.Add(new StudentViewModel(student));

        DataContext = viewModel;
        MouseLeftButtonDown += new MouseButtonEventHandler((object sender, MouseButtonEventArgs e) =>
        {
            viewModel.StudentViewModels[0].ChangeStudent();
        });
    }

If you click on the window, the ChangeStudent method of the first StudentViewModel will be called and the UI will be updated as well.

Upvotes: 1

Patryk Ćwiek
Patryk Ćwiek

Reputation: 14318

First of all, I'd like to recommend using one of the MVVM frameworks (personally I like and use Caliburn.Micro, but there's also MVVM Light and myriad of others).

(I'll use Caliburn.Micro implementation from now on as an example, since that's the one framework I more-or-less know)

Why? Well, it gives you strongly typed NotifyOfPropertyChange(), built in event aggregator, window manager and much, much more. That way you don't have to reinvent the wheel every time. Also the Caliburn.Micro's bootstrapper allows you to bake-in your IoC container of choice, which is not really that easy with WPF if you want to go for MVVM-without-framework way. As a bonus, you can intercept the events from the GUI controls, so you really don't have to write anything in the code-behind.

Some frameworks allow you to bind-by-convention and facilitate commands, but you'd have to read up on that, depending which framework you go with.

Second thing, I'm strongly in favor of re-writing the view-models fully, so that they're stand-alone classes instead of wrappers around the data models. You can use Automapper or ValueInjecter for mapping later on.

So then you have the view model e.g:

public class StudentViewModel : PropertyChangedBase
{
    private string firstName;
    public string FirstName
    {
        get { return firstName; }
        set
        {
            firstName = value;
            NotifyOfPropertyChange(() => FirstName);
        }       
    }

    private string lastName
    public string LastName
    {
        get { return lastName; }
        set
        {
            lastName = value;
            NotifyOfPropertyChange(() => LastName);
        }       
    }
}

And that's it for a view model. Then you use the data-bindings, validation and so on in the view model.

Your Student class can be e.g. a simple DTO or a EF-class or whatever. Let's go with dumb DTO for simplicity:

public class Student
{
    public string FirstName { get;set; }
    public string LastName { get;set; }
}

So you use the DTO only when e.g saving to the database. And that's it. For the 'regular' app use, GUI interaction (bindings) you use the view model.

That's where the Automapper/ValueInjecter come into play, 'cause when you want to 'save' the changes/add a new student anywhere, you have to map the view model to the model, e.g.:

//ValueInjecter
var dataModel = new Student().InjectFrom(this) as Student;
//AutoMapper
var dataModel = Mapper.Map<StudentViewModel, Student>(this);

And that's it. Simple, easy, clean. The way you describe it, you want to change the underlying model. I'd advise against that, operate on view models instead which can notify your UI. You use the models only to 'modify' the data in the data storage (save/update/fetch/delete) or 'transport' the data somehow (e.g. using REST web-service), and use the view models for interaction.

Upvotes: 7

emybob
emybob

Reputation: 1333

well I put my INotifyPropertyChanged code into a base class:

public abstract class PropertyChangedBase: INotifyPropertyChanged
{
    protected PropertyChangedBase()
    {
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string propertyName)
    {
        var propertyChangedEventArgs = new PropertyChangedEventArgs(propertyName);
        PropertyChangedEventHandler changed = PropertyChanged;
        if (changed != null)
        {
            changed(this, propertyChangedEventArgs);
        }
    }
}

Then in each class that requires the property changed event you add it as the base class:

class Student : PropertyChangedBase
{
    public String StudentFirstName
    {
        get { return model.StudentFirstName; }
        set 
          { 
            model.StudentFirstName = value;
            this.OnPropertyChanged("StudentFirstName"); 
          }
    }

    public String StudentLastName
    {
       get { return model.StudentLastName; }
       set 
          { 
             model.StudentLastName = value;
             this.OnPropertyChanged("StudentLastName");
          }
    } 
}

One thing I am a little confused of in your code, is why you have a studentviewmodel? In MVVM, you have your models, which are your object designs - in this case 'Student' and then you have a View, which will be the MainWindow.xaml, and then your ViewModel which is MainWindowViewModel. So really you shouldn't need the studentViewModel. So your MainViewModel should look like this:

class MainWindowViewModel : PropertyChangedBase
{
   ObservableCollection<Student> _Students = new ObservableCollection<Student>();

   public ObservableCollection<Student> Students
   {
      get { return _Students; }
   }

public MainWindowViewModel()
{
  _Students.Add(new Student() { StudentFirstName = "Foo", StudentLastName = "Bar" });
  _Students.Add(new Student() { StudentFirstName = "John", StudentLastName = "Doe" });
  _Students.Add(new Student() { StudentFirstName = "Emy", StudentLastName = "Bob" });
}

Upvotes: 0

Related Questions