Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF: Commands and ViewModels relation in MVVM

Tags:

c#

mvvm

wpf

I am trying to call the same command from two different view models, but I got stuck while designing them (both command and view models).

First, I created a ViewModel1 view model class:

public class ViewModel1 : DependencyObject
{
    ...

    // The command property
    public ProcessMyString ProcessMyStringCommand { get; set; }

    public ViewModel1()
    {
        // Command gets instantiated 
        this.ProcessMyStringCommand = new ProcessMyString(this);
    }

    internal void ProcessMyString()
    {
        // This is where the actual processing method is called
        // somewhere from the business logic...
        ...
    }

And the ProcessMyString command class:

public class ProcessMyString : ICommand
{
    private ViewModel1 viewModel;

    public ProcessMyString(ViewModel1 viewModel)
    {
        this.viewModel = viewModel;
    }

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

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

    public void Execute(object parameter)
    {
        viewModel.ProcessMyString();
    }
}

Then, I created the second view model class ViewModel2, but when I realized that this view model will also need to use the same command, the command's constructor

public ProcessMyString(ViewModel1 viewModel)

will not work because it takes ViewModel1 parameter and I need to be able to pass both view models. Then, I decided to create ViewModelBase class, and to make both view models extend from it. I modified the command's constructor as well, of course:

// Constructor's parameter is now ViewModelBase
public ProcessMyString(ViewModelBase viewModel)

But that meant that command's method Execute(object parameter) called a method from ViewModelBase now. That is not a good appproach because ViewModel's calls for ProcessMyString() should be reserved for ViewModel1 and ViewModel2 classes only. If I had class ViewModel3 I wouldn't want it to call ProcessMyString() and if I don't extend it from the ViewModelBase that would be fine.

But what happens if I need a command that is shared between ViewModel2 and ViewModel3?

The summation question is: How should I organize my commands and view models to be able to make the view models share the same commands?

like image 446
Boris Avatar asked Feb 24 '23 13:02

Boris


2 Answers

First off, just as a personal preference, I tend to minimize the amount of inheritance I use with my ViewModel's. Complex UI code in non-trivial applications can be tricky enough to follow by anyone besides the original author, the last thing you want to do is make it harder by including a complex object model.

The beauty of WPF using the ICommand interface is that you should be able to favor a more compositional approach, rather an an inheritance model, and use an interface to share common properties.

Here's just a quick run-down of how I might approach this scenario:

public class ProcessStringCommand : ICommand
{
   private readonly IProcessStringViewModel m_viewModel;

   public ProcessStringCommand(IProcessStringViewModel vm)
   {
      m_viewModel = vm;
   }

   public void Execute(object param)
   {
      ProcessString(m_viewModel.ProcessString);
   }

   public bool CanExecute(object param)
   { 
      return true; 
   }

   private void ProcessString(string processString)
   {
      // Put logic here
   }
}

public interface IProcessStringViewModel
{
   public string ProcessString { get; }
}

public class ViewModel1 : ViewModelBase, IProcessStringViewModel
{
   private readonly ICommand m_command;
   private readonly string m_processString;

   public ViewModel1()
   {
      m_command = new ProcessStringCommand(this);
   }

   public string ProcessString
   {
      get { return m_processString; }
   }

   public ICommand ProcessStringCommand
   {
      get { return m_command; }
   }
}

public class ViewModel2 : ViewModelBase, IProcessStringViewModel
{
   private readonly ICommand m_command;
   private readonly string m_processString;       

   public ViewModel2()
   {
      m_command = new ProcessStringCommand(this);
   }

   public string ProcessString
   {
      get { return m_processString; }
   }

   public ICommand ProcessStringCommand
   {
      get { return m_command; }
   }
}

public class ViewModel3 : ViewModelBase
{
   // Whatever you need here.
}
like image 165
jeremyalan Avatar answered Feb 27 '23 04:02

jeremyalan


I will post this answer with the assumption that the ProcessMyString class is unnecessary and should be replaced by a generic command.

First of all, download the library MVVM Light. After that unpack it somewhere and add reference to this library:

(folder with the library)\Mvvm Light Toolkit\Binaries\WPF4\GalaSoft.MvvmLight.WPF4.dll

It contains the RelayCommand class which is that what you need.

At first create a base class which contains your command:

public abstract class ProcessStringViewModel : DependencyObject
{
    // The command property
    public RelayCommand ProcessMyStringCommand { get; set; }
}

I would remove the inheritance from the DependencyObject class, but maybe you use it somehow, so let it be.

The ViewModel1 class can be rewritten in this way:

public class ViewModel1 : ProcessStringViewModel
{
    public ViewModel1()
    {
        // Command gets instantiated 
        this.ProcessMyStringCommand = new RelayCommand(() => this.ProcessMyString());
    }

    internal void ProcessMyString()
    {
    }
}

The ViewModel2 class can call a different function, but the command is the same:

public class ViewModel2 : ProcessStringViewModel
{
    public ViewModel2()
    {
        this.ProcessMyStringCommand = new RelayCommand(SomeOtherFunction);
    }

    private void SomeOtherFunction()
    {
        MessageBox.Show("Call of some function");
    }
}

If you decide to not use the base class and inheritance - you can remove the base class, copy the property to each derived class, and it will work.

like image 26
vortexwolf Avatar answered Feb 27 '23 02:02

vortexwolf