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?
IMHO both solutions are overly complicated. SOLID is great, KISS is better.
Your MyViewModelCommandManager is currently directly coupled to MyViewModel since it needs the latter's Message, so what is the advantage of having them separate? Why not simply implement the command inside MyViewModel?
If this would entail injecting too many dependencies into MyViewModel, then think about what you actually need the command to do, and abstract away everything else that isn't needed.
The command displays a message.
The message is held by MyViewModel
You want to display the message outside MyViewModel (maybe other viewmodels also need to display a message and you want to reuse the code?)
So all you really need is some kind of notification from MyViewModel that it wants to display a message, or that something has occured that would result in a message being displayed.
Possible solutions:
Inject an IMessageDisplayService to MyViewModel. MyViewModel calls it with the message.
Inject a callback to MyViewModel similar to above.
Have MyViewModel raise an event with the message as an EventArg.
The inferred responsibilities of the above solutions are subtly different.
means that MyViewModel is in charge. It wants to display a message.
is less explicit. MyViewModel knows it needs to call the callback, but doesn't really know or care what it does.
is like 2 but even more decoupled. Multiple things can subscribe or unsubscribe to the event but MyViewModel remains blissfully ignorant.
All three of these mean that the thing displaying the message has no need to know about MyViewModel. You have decoupled them. It's MyViewModelFactory that does any wiring up required.
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;
}
}
Related
I'm doing a intro c# MVVM project (Caliburn.Micro) and I'm injecting a "job" object into each viewmodel so that the job information is accessible everywhere.
I was hoping that changes to the injected object, which might be affected by a method in one viewmodel, would be reflected in all viewmodels, and this appears to be the case, however how would the bound properties associated with that injected object be updated?
For eg. below: The instance of JobClass is passed around and is changed by the MenuBarViewModel. How would one trigger the NotifyOnPropertyChange() in the property in the MainPanelViewModel so a bound xaml control in the associated view would pick up on the changes to the underlying member.
Or is this not how is should work. Is this what the event aggregator is supposed to manage? I was hoping that passing around the job would simplify things, or is that the global variable issue... Any tips would be welcome!
Hanuman....
// MainWindowViewModel
public class MainWindowViewModel
{
private JobClass _jobClass;
private readonly IEventAggregator _eventAggregator;
public MenuBarViewModel MenuBarViewModel { get; set; }
public MainPanelViewModel MainPanelViewModel { get; set; }
public MainWindowViewModel()
{
_eventAggregator = new EventAggregator();
_jobClass= new JobClass ();
this.MenuBarViewModel = new MenuBarViewModel(_eventAggregator, _jobClass);
this.MainPanelViewModel = new MainPanelViewModel(_eventAggregator, _jobClass);
}
}
// MenuBarViewModel
public class MenuBarViewModel: PropertyChangedBase
{
IEventAggregator _events;
JobClass _jobClass;
public MenuBarViewModel(IEventAggregator eventAggregator, JobClass jobClass)
{
_events = eventAggregator;
_jobClass = JobClass;
}
public SomeMethod()
{
_jobClass.MethodToAddSomedata();
}
// MainPanelViewModel
public class MainPanelViewModel : PropertyChangedBase
{
IEventAggregator _events;
JobClass _jobClass;
public JobClass JobCase
{
get { return _jobClass; }
set
{
_jobClass= value;
NotifyOfPropertyChange(() => JobCase);
}
}
}
In Prism Mvvm, Prism.Unity library, when I replace DelegateCommand with Binding Mvvm Command. It is not working. This is my working code
public class MainPageViewModel : BindableBase
{
private DelegateCommand _navigationCommand;
private INavigationService _navigationService;
public DelegateCommand NavigateCommand => _navigationCommand ?? (_navigationCommand = new DelegateCommand(ExecuteCommand));
public MainPageViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
}
void ExecuteCommand()
{
_navigationService.NavigateAsync("SecondPage");
}
}
Now I make changes in DeletegateCommand, Command not getting fire. This is my modified code
public class MainPageViewModel : BindableBase
{
public ICommand _navigationCommand { private set; get; }
private INavigationService _navigationService;
public MainPageViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
_navigationCommand = new Command(() => ExecuteCommand());
}
void ExecuteCommand()
{
_navigationService.NavigateAsync("SecondPage");
}
}
Well I am not completely sure that this could be the reason but I think your code should look something like this:
public ICommand NavigationCommand { set; get; }
Then set it in the constructor:
public MainPageViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
NavigationCommand = new Command(ExecuteCommand);
}
And your method would look something like below:
private void ExecuteCommand(object obj)
{
_navigationService.NavigateAsync("SecondPage");
}
use object obj if you want to pass any data as Command Parameter
I implement command wrong in XAML due to that I was facing this issue.
Thank you.
I am trying to pass a value to a view model from another view model before navigating to the page attached to that view model.
I was previously passing it to the view, then passing it to the view model. This seems like a clumsy way of doing things.
I am not using any kind of framework so that is not an option.
At the moment the property is set as static and this works but im not sure if this is good practice.
The code:
View model 1:
This command opens the new page:
public void OpenRouteDetails()
{
RouteStopPopOverViewModel.RouteName = "TestRoute";
App.Page.Navigation.PushAsync(new RouteStopPopOverView());
}
View model 2: (RouteStopPopOverViewModel)
public static string RouteName { get; set; }
This does work but I would prefer not to use static as a way to achieve this.
Is there some way to set the RouteName property without using static or passing it through view-> view model.
I have seen some answers about this but they don't seem to answer to question clearly.
Share a controller class between view models.
The same instance has to be supplied to the constructor in both view models.
So you can set values, and listen for events in both view models.
The controller class becomes the intermediary.
public class SharedController : IControlSomething
{
private string _sharedValue;
public string SharedValue
{
get => _sharedValue;
set
{
if (_sharedValue == value)
return;
_sharedValue = value;
OnSharedValueUpdated();
}
}
public event EventHandler SharedValueUpdated;
protected virtual void OnSharedValueUpdated()
{
SharedValueUpdated?.Invoke(this, EventArgs.Empty);
}
}
public class ViewModel1
{
private readonly IControlSomething _controller;
public ViewModel1(IControlSomething controller)
{
// Save to access controller values in commands
_controller = controller;
_controller.SharedValueUpdated += (sender, args) =>
{
// Handle value update event
};
}
}
public class ViewModel2
{
private readonly IControlSomething _controller;
public ViewModel2(IControlSomething controller)
{
// Save to access controller values in commands
_controller = controller;
_controller.SharedValueUpdated += (sender, args) =>
{
// Handle value update event
};
}
}
here the sample you can achieve your requirement easily with navigation
public class ViewModelFrom : BaseViewModel
{
async Task ExecuteCommand()
{
string routeName="value to trasfer";
Navigation.PushAsync(new View(routeName));
}
}
public partial class View : ContentPage
{
public View(string routeName)
{
InitializeComponent();
BindingContext = new ViewModelTo(routeName);
}
}
public class ViewModelTo : BaseViewModel
{
public string RouteName { get; set; }
public ViewModelTo(string routeName)
{
RouteName=routeName;
}
}
If there is a hierarchy you could express that in a parent to both of them.
public class Route
{
private string Name;
}
public class RouteSelectedArgs : EventArgs
{
public Route Selected { get; set; }
}
public interface IRouteSelection
{
event EventHandler<RouteSelectedArgs> RouteSelected;
}
public interface IRouteDetails { }
public class RouteWizard
{
public UserControl view { get; set; }
private IRouteSelection _selection;
private IRouteDetails _details;
public RouteWizard(IRouteSelection selection, IRouteDetails details)
{
_selection = selection;
_details = details;
_selection.RouteSelected += Selection_RouteSelected;
view = MakeView(_selection);
}
private void Selection_RouteSelected(object sender, RouteSelectedArgs e)
{
_selection.RouteSelected -= Selection_RouteSelected;
view = MakeView(_details, e.Selected);
}
private UserControl MakeView(params object[] args)
{
////magic
throw new NotImplementedException();
}
}
As you are using the MVVM pattern, you can use one of the many MVVM Frameworks to achieve this.
I use FreshMvvm and it allow me to pass parameters between view models like this
await CoreMethods.PushPageModel<SecondPageModel>(myParameter, false);
Then in SecondPageModel I can see access the parameters in the Init method
private MyParamType _myParameter;
public override void Init(object initData)
{
base.Init(initData);
var param = initData as MyParamType;
if (param != null)
{
_myParameter = param;
}
}
You can find more details about FreshMvvm here although most MVVM frameworks have similar functionality.
Dear Stackoverflow users,
I am currently developing a application that uses the MVVM pattern together with Caliburn micro.
I faced my first situation where I needed a dialog to be opened which would do some modifications to a ObservableCollection that needed to be visible on my main viewmodel.
I found out about event aggregation for my first time and thought that it would suite my situation. I am however not quite sure if event aggregation is the best solution or if I am using correctly... Therefore my question.
Please keep in mind that the namespaces are left there intentionally incase I got it all wrong.
The setup looks like this:
namespace Company.Product.Presentation.Core.Events
{
public class PersonCollectionChanged
{
[CanBeNull]
public ObservableCollection<Person> PersonCollection { get; set; }
public PersonCollectionChanged(ObservableCollection<Person> personCollection)
{
PersonCollection = personCollection;
}
}
}
ViewModel for the dialog
namespace Company.Product.Presentation.Modules.Selection
{
public class SelectionViewModel : Screen
{
private readonly IViewModelLoader _viewModelLoader;
private readonly IEventAggregator _eventAggregator;
private readonly ObservableCollection<Person> _tempPersonCollection;
private readonly PersonCollectionChanged _personCollectionChanged;
public SelectionViewModel(IViewModelLoader viewModelLoader,
IEventAggregator eventAggregator,
PersonCollectionChanged personCollectionChanged,
ObservableCollection<Person> tempPersonCollection)
{
_viewModelLoader = viewModelLoader;
_eventAggregator = eventAggregator;
_personCollectionChanged = personCollectionChanged;
_tempPersonCollection = tempPersonCollection;
AddPerson(new Person{Name = "Zatixiz"});
_personCollectionChanged.PersonCollection = _tempPersonCollection;
_eventAggregator.PublishOnUIThread(_personCollectionChanged);
}
internal void AddPerson(Person person)
{
_tempPersonCollection.Add(person);
}
}
}
Main viewmodel subscribing to the event:
namespace Company.Product.Presentation.Modules.Main
{
public class MainViewModel : BaseViewModel, IHandle<PersonCollectionChanged>
{
private ObservableCollection<Person> _mainPersonCollection;
public MainViewModel(
IViewModelLoader viewModelLoader,
IEventAggregator eventAggregator,
ObservableCollection<Person> mainPersonCollection) : base(viewModelLoader)
{
_mainPersonCOllection = mainPersonCollection;
eventAggregator.Subscribe(this);
}
public void Handle(PersonCollectionChanged message)
{
_mainPersonCollection = message;
}
}
}
I have a specialized UserControl to play media content called PlayerView.
The control has its own commands (readonly, not provided by client).
public partial class PlayerView
{
public PlayerView()
{
InitializeComponent();
PlayCommand = new RelayCommand(() =>
{
// Play some media: audio/video.
});
}
...
#region PlayCommand property
private static readonly DependencyPropertyKey PlayCommandPropertyKey = DependencyProperty.RegisterReadOnly(
"PlayCommand",
typeof(ICommand),
typeof(PlayerView),
new PropertyMetadata());
public static readonly DependencyProperty PlayCommandProperty = PlayCommandPropertyKey.DependencyProperty;
public ICommand PlayCommand
{
get { return (ICommand)GetValue(PlayCommandProperty); }
private set { SetValue(PlayCommandPropertyKey, value); }
}
#endregion
...
}
The play command of the control works fine from XAML:
<Controls:PlayerView x:Name="PlayerView" />
<Button Command="{Binding ElementName=PlayerView, Path=PlayCommand, Mode=OneWay}" Content="Play" />
But currently, I am implemeting slideshow feature and I would like to execute the play command of the control from the ViewModel.
public class SlideshowViewModel : ViewModelBase
{
// Stores collection of audio/video clips to be played by the PlayerView.
// Assume that this ViewModel should invoke PlayerView PlayCommand.
}
public class MainViewModel : ViewModelBase
{
// Stores a lot of stuff.
public SlideshowViewModel Slideshow { get; }
}
The question is: how the SlideshowViewModel can execute the PlayCommand of this control? Is there a best practice?
If I am understanding your issue correctly, the ViewModel should contain the implementation of the Command, not the View. This would be a truer MVVM implementation, and then the VM can call that command from within itself, if necessary.
edit:
to answer your question,
public partial class PlayerView : IHaveAPlayCommand
{
public PlayerView()
{
this.DataContext = new ViewModel(this);
}
}
public class ViewModel
{
IHaveAPlayCommand view;
public ViewModel(IHaveAPlayCommand view)
{
this.view = view
}
}