Search code examples
c#wpfmvvmicommand

ICommand with ViewModel dependency


I am searching a pattern to keep SOLID principles in my application when I use ICommand. Basically my problem is the command execution has a dependency with the view model but at the same time the view model has a dependency with the command (I inject them by constructor). I would like to keep the viewmodel with properties only, so this is an example of my current implementation:

public class MyViewModel : INotifyPropertyChanged
{
   public ICommand MyCommand { get; private set; }

   public string Message { get; set; } // PropertyChanged ommited

   public MyViewModel()
   {            
   }

   public void SetCommand(ICommand myCommand)
   {
       this.MyCommand = myCommand;
   }

   ....
}

internal interface IMyViewModelCommandManager
{
    void ExectueMyCommand();
}

internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
   private readOnly MyViewModel myViewModel;

   public MyViewModelCommandManager(MyViewModel myViewModel)
   {
       this.myViewModel = myViewModel;
   }

   public ExectueMyCommand()
   {
        MessageBox.Show(this.myViewModel.Message);
   }
}

internal class MyViewModelFactory: IMyViewModelFactory
{
   private readonly IContainerWrapper container;

   public MyViewModelFactory(IContainerWrapper container)
   {
      this.container = container;
   }

   public MyViewModel Create()
   {
       MyViewModel viewModel = new MyViewModel();

       IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("viewModel", viewModel) });

       ICommand myCommand = new DelegateCommand(manager.ExecuteMyCommand);

       viewModel.SetCommand(myCommand);

       return viewModel;
   }
}

So, to avoid use the SetCommand method. I have thought two solutions but I don't know if they are elegant.

The first one is to move the viewmodel dependency from the constructor to the method updating the code in this way:

public class MyViewModel : INotifyPropertyChanged
{
   public ICommand MyCommand { get; private set; }

   public string Message { get; set; } // PropertyChanged ommited

   public MyViewModel(ICommand myCommand)
   {
       this.MyCommand = myCommand;            
   }

   ....
}

internal interface IMyViewModelCommandManager
{
    void ExectueMyCommand(MyViewModel viewModel);
}

internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
   public MyViewModelCommandManager()
   {
       ....
   }

   public ExectueMyCommand(MyViewModel viewModel)
   {
        MessageBox.Show(myViewModel.Message);
   }
}

internal class MyViewModelFactory: IMyViewModelFactory
{
   private readonly IContainerWrapper container;

   public MyViewModelFactory(IContainerWrapper container)
   {
      this.container = container;
   }

   public MyViewModel Create()
   {
       IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(..);

       ICommand myCommand = new DelegateCommand<MyViewModel>(manager.ExecuteMyCommand);

       MyViewModel viewModel = new MyViewModel(myCommand);
       return viewModel;
   }
}

Of course, the xaml code will use CommandParameter:

<Button Content="Show Message" Command="{Binding MyCommand}" CommandParameter="{Binding .}" />

Other solution I have thought is to use a trick creating a Wrapper of the viewModel and the commandManager have a dependency with the Wrapper instead of the viewModel:

internal class MyViewModelCommandContext
   {
      public MyViewModel ViewModel { get; set; }
   }

   public class MyViewModel : INotifyPropertyChanged
    {
       public ICommand MyCommand { get; private set; }

       public string Message { get; set; } // PropertyChanged ommited

       public MyViewModel(ICommand myCommand)
       {
           this.MyCommand = myCommand;            
       }

       ....
    }

    internal interface IMyViewModelCommandManager
    {
        void ExectueMyCommand();
    }

    internal class MyViewModelCommandManager : IMyViewModelCommandManager
    {
       private readonly MyViewModelCommandContext context;

       public MyViewModelCommandManager(MyViewModelCommandContext context)
       {
           this.context = context;
           ....
       }

       public ExectueMyCommand()
       {
            MessageBox.Show(this.context.myViewModel.Message);
       }
    }

    internal class MyViewModelFactory: IMyViewModelFactory
    {
       private readonly IContainerWrapper container;

       public MyViewModelFactory(IContainerWrapper container)
       {
          this.container = container;
       }

       public MyViewModel Create()
       {
           MyViewModelCommandContext context = new MyViewModelCommandContext();

           IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("context", context) });

           ICommand myCommand = new DelegateCommand(manager.ExecuteMyCommand);

           MyViewModel viewModel = new MyViewModel(myCommand);
           context.ViewModel = viewModel;
           return viewModel;
       }
    }

In my opinion the first one is the best solution for this problem, what do you think is the best solution. Would you apply another solution?


Solution

  • Thanks for your opinions.

    I understand you when you say I am creating a complex pattern, but in a big project with a big developers team, if there is not clear patterns with split responsabilities, the code maintenance could be impossible to perform.

    Reading you and your third solution I have thought one possible solution. It seems complexity but, in my opinion, improves the code quality. I will create a commandContext, which only have the viewmodel properties needed for the code, avoiding to have all viewmodel in the command manager. Also I will create a class whose responsability is to mantain the context updated when the viewmodel changes. This is the possible code:

    internal class MyCommandContext
    {
        public string Message { get; set; }
    }
    
    public class MyViewModel : INotifyPropertyChanged
    {
        public ICommand MyCommand { get; private set; }
    
        public string Message { get; set; } // PropertyChanged ommited
    
        public string OtherProperty { get; set; }
    
        public ObservableCollection<MyChildViewModel> Childs { get; set; }
    
        public MyViewModel(ICommand myCommand)
        {
            this.MyCommand = myCommand;            
        }
    
           ....
    }
    
    internal interface IMyViewModelCommandManager
    {
        void ExectueMyCommand();
    }
    
    internal class MyViewModelCommandManager : IMyViewModelCommandManager
    {
       private readonly MyCommandContext context;
    
       public MyViewModelCommandManager(MyViewModelCommandContext context)
       {
           this.context = context;
           ....
       }
    
       public ExectueMyCommand()
       {
            MessageBox.Show(this.context.Message);
       }
    }
    
    internal interface IMyViewModelCommandSynchronizer
    {
        void Initialize();
    }
    
    internal class MyViewModelCommandSynchronizer : IMyViewModelCommandSynchronizer, IDisposable
    {
         private readOnly MyViewModel viewModel;
         private readOnly MyCommandContext context;
    
         MyViewModelCommandSynchronizer(MyViewModel viewModel, MyCommandContext context)
         {
             this.viewModel = viewModel;
             this.context = context;
         }
    
         public void Initialize()
         {
             this.viewModel.PropertyChanged += this.ViewModelOnPropertyChanged;
         }
    
        private void ViewModelOnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Message")
            {
                 this.context.Message = this.viewModel.Message;
            }
        }
    
        // Dispose code to deattach the events.
    }
    
    internal class MyViewModelFactory: IMyViewModelFactory
    {
       private readonly IContainerWrapper container;
    
       public MyViewModelFactory(IContainerWrapper container)
       {
           this.container = container;
       }
    
       public MyViewModel Create()
       {
           MyCommandContext context = new MyCommandContext();
    
           IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("context", context) });
    
           ICommand myCommand = new DelegateCommand(manager.ExecuteMyCommand);
    
           MyViewModel viewModel = new MyViewModel(myCommand);
    
           IMyViewModelCommandSynchronizer synchronizer = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("context", context), new ParameterOverride("viewModel", viewModel) });
    
           synchronizer.Initialize();
    
           return viewModel;
       }
    }