Give Some Command to View in Mvvm

What is the MVVM way to call WPF command from another viewmodel?

There's a couple of ways I'd do this.

The first, if the action is a service type of interaction, which I think this is a reasonably good example of, I would describe the action in an interface and inject it as a dependency into the ViewModels that need it.

This is effectively what you are doing, but it's worth abstracting it out into an interface. This provides less tight coupling between the two ViewModels.

Here is an example of wrapping up the functionality in an IPageDisplay interface:

public interface IPageDisplay
{
IPageViewModel GetCurrentPage();
void ChangeViewModel(IPageViewModel newPage);
}

Your ApplicationViewModel implements it and has the exact same methods it did before:

public class ApplicationViewModel: IPageDisplay
{
// implement like you are doing

You're HomeViewModel then takes as an interface, not the 'whole' ViewModel:

class HomeViewModel
{
HomeViewModel(IPageDisplay pageDisplay) {//constructor stuff}

private void LoadOtherView()
{
// Instead of interacting with a whole ViewModel, we just use the interface
_pageDisplay.ChangePageCommand.Execute(new ContactViewModel());
}

This is 'safer' as it's more abstract. You can test HomeViewModel without creating a AppViewModel by just mocking the IPageDisplay. You can change how pages are displayed or the implementation of AppViewModel, you can also display your pages in any other kind of location, by having some other implementation of IPageDisplay.

It's worth noting that any page that needs to perform navigation actions will require an IPageDisplay. It can be troublesome matching up all these dependencies if you have many of them - that's where something like a Dependency Injection framework can really help out.

The second would be a mediator pattern as suggested in the comments. You could have a common mediator PageManager that defines the ChangeViewModel(IPageViewModel newPage); method and fires a ChangeViewModelRequest event or callback. The ApplicationViewModel, and any other ViewModels that want to change the current page accept the PageManager instance as a dependency. ApplicationViewModel listens to the event, the other's call ChangeViewModelRequest to trigger it.

Again, a Dependency Injection will need to be managed effectively if this is in a complex application.

This naturally leads onto the third. Which is a extension of the mediator pattern, an Event Aggregator.

An event aggregator is a generic service that allows all different ViewModels to raise, or subscribe to application wide events. It's definitely worth looking at.

Here, your ApplicationViewModel subscribes to the event:

public class ApplicationViewModel
{
private EventAgregator _eventAggregator;

ApplicationViewModel(EventAgregator eventAggregator)
{
this._eventAggregator = eventAggregator;
_eventAggregator.Subscribe('ChangeViewModelRequest', (EventArgs eventArgs) => ChangeViewModel(eventArgs.Parameter))
}

private void ChangeViewModel(IPageViewModel viewModel)
{
if (!PageViewModels.Contains(viewModel))
PageViewModels.Add(viewModel);

CurrentPageViewModel = PageViewModels.FirstOrDefault(vm => vm == viewModel);
}
}

And the HomeViewModel publishes to the event:

private void LoadOtherView()
{
_eventAggregator.Publish("ChangeViewModelRequest", new EventArgs(new ContactViewModel()));
}

There are plenty of Event Aggregators you can use, some built into MVVM frameworks like Prism.

While, like all the others, this is a dependency - it's a very generic one. Chances are, most of your ViewModels will need access to the aggregator instance and have it as a dependency, as it could be used for almost all inter-view-model communication. Simply having all VMs pass it to any created VMs in the constructor could work for a simple application. But I'd still say something that supports dependency injection (say, factory pattern?) would be worth implementing.

Edit:

Here's what you need for your HomeViewModel:

public class HomeViewModel : IPageViewModel // doesn't implement IPageDisplay
{
private IPageDisplay _pageDisplay;
public HomeViewModel(IPageDisplay pageDisplay)
{
// HomeViewModel doesn't implement IPageDisplay, it *consumes* one
// as a dependency (instead of the previous ApplicationViewModel).
// Note, that the instance you're passing still is the ApplicationViewModel,
// so not much has actually changed - but it means you can have another
// implementation of IPageDisplay. You're only linking the classes together
// by the functionality of displaying a page.
_pageDisplay= pageDisplay;
}

public string Name
{
get
{
return "Home Page";
}
}

private ICommand _loadDashboardCommand;
public ICommand LoadDashboardCommand
{
get
{
if (_loadDashboardCommand == null)
{
_loadDashboardCommand = new RelayCommand(
p => LoadOtherView());
}
return _loadDashboardCommand;
}
}

private void LoadOtherView()
{
// Here you have the context of ApplicatiomViewModel like you required
// but it can be replaced by any other implementation of IPageDisplay
// as you're only linking the little bit of interface, not the whole class

_pageDisplay.ChangeViewModel(new DashboardViewModel());
}
}

}

MVVM pattern: an intermediate View between Command binding and ViewModel execute

You can define another Model and ViewModel for Process Options, and then in the SamplesAnalyzeCommand, display the ProcessOptionsView. When user is done with the ProcessOptionsView, the main ViewModel gets notified (e.g by an event handler) and completes the Process.

Something like this:

internal class SamplesAnalyzeCommand : ICommand {
...
public void Execute(object parameter)
{
this._viewModel.ShowProcessOptions(parameter);
}
}

internal class CachedDataSummaryViewModel {
public string Status {
get {
return this.status;
}
set {
if (!string.Equals(this.status, value)) {
this.status = value;
// Notify property change to UI
}
}
}
...
internal void ShowProcessOptions(object paramter) {
// Model
var processOptions = new ProcessOptionsModel() {
otherInfo = parameter
};
// View-Model
var processOptionsViewModel = new ProcessOptionsViewModel();
processOptionsViewModel.Model = processOptions;
// View
var processOptionsView = new ProcessOptionsView(
processOptionsViewModel
);
// Edit2: Update status
this.Status = "Selecting process options...";

// You can use the event handler or dialog result
processOptionsViewModel.OK += this.PerformProcess;
processOptionsView.ShowDialog();
}
private void PerformProcess(object sender, EventArgs e) {
var processOptionsView = sender as ProcessOptionsView;
var processOptionsModel = processOptionsView.Model;
var processOptions = processOptionsModel.Model;

// Edit2: Update status
this.Status = "Performing process...";

// use processOptions.OtherInfo for initial info
// use processOptions.* for process options info
// and perform the process here

// Edit2: Update status
this.Status = "Process Done.";
}
...
}
class ProcessOptionsModel {
public object OtherInfo {
get;
set;

public int Parameter1 {
get;
set;
}
public IList<ProcessItem> SelectedItems {
get;
set;
}
...
}
class ProcessOptionsViewModel {
public event EventHandler OK;
private SamplesAnalyzeCommand model;
private ICommand okCommand;
public ProcessOptionsViewModel() {
this.okCommand = new OKCommand(this.OnOK);
}
public SamplesAnalyzeCommand Model {
get {
return model;
}
set {
this.model = value;
// Property changed stuff here
}
}
private void OnOK(object parameter) {
if (this.OK != null) {
this.OK = value;
}
}
}
class ProcessOptionsView {
// Interacts with it's view-model and performs OK command if
// user pressed OK or something
}

Hope it helps.

Edit (1):

As blindmeis suggested, you may use some Dialog Service to make the connection between the views.

Edit (2):

Immidiate UI changes after button click can be done in ShowProcessOptions method of the ShowProcessOptions. I don't think you want reflect ui changes of the options window while user works with it, to the main window. UI changes after user closes options window can be done in PerformProcess.

If you want to make an abstraction for options selection (e.g reading from a file) as you mentioned in the comment below, you may define an IOptionsProvider interface, and put ProcessOptionsView and View-Model behind that but still you use the same model.

interface IOptionsProvider {
ProcessOptionsModel GetProcessOptions();
}

class ProcessOptionsView : IOptionsProvider {
public ProcessOptionsModel GetProcessOptions() {
if (this.ShowDialog()) {
return this.ModelView.Model;
}
return null;
}
}

class ProcessOptionsFromFile : IOptionsProvider {
public ProcessOptionsModel GetProcessOptions() {
// Create an instance of ProcessOptionsModel from File
}

}

Note that in this case I removed the OK event since the GetProcessOptions is supposed to block until user closes the main window. If you want a responsive approach in the FromFile case, you may need to work on the async stuff, maybe define GetProcessOptionsAsync instead.

In this case things may get a little bit complicated but I guess it is achievable in this way.

How to show a View from a ViewModel and also set the data context of that view in WPF with MVVM

First, we need to create a view (somewhere) with its datacontext set. Easy enough, we instantiate the view and either pass it the view model (assuming the view sets its data context in its constructor) or set it manually. The view could also have declared the view model in XAML if we so desired.

Method 1:

Window dialog = new ListAllStudentsView(new StudentsViewModel());

Method 2:

Window dialog = new ListAllStudentsView();
dialog.DataContext = new StudentsViewModel();

Method 3:

<Window.DataContext>
<local:StudentsViewModel/>
</Window.DataContext>

Now we need to put this code (and the associated dialog.ShowDialog() somewhere). I see two options, right in the command's execute function, or in the view's code-behind (triggered by an event raised by the command's execute function like "RequestDialog").

I prefer the first, even though it doesn't adhere as rigidly to MVVM because it is a lot simpler, less code, and easier to manage. If you want to be very strict about adhering to MVVM however, I would have the ViewModel raise an event like "RequestDialog" in the command function that the view listens to and runs the constructor and ShowDialog() function.

What is the reason for ICommand in Mvvm?

Wouldn't it be enough to provide public methods and bind the action
from the view directly to this public method? Why ICommand exists?

  1. you can't bind to a method in xaml. You need an object. Therefore you need to wrap the method to an object.

  2. it is a common pattern in UI, that some actions are not available all the time. In Login form the Login action become available only when you enter username. In MS Word, the Copy or Cut actions becomes available only when you select something, otherwise the buttons are disabled and keyboard shortcuts inactive

  3. it is a common pattern, that command can be invoked with different parameters.

Plain eventhandlers does not meet those requirements, But ICommand servers exactly those purposes:

public interface ICommand
{
void Execute(object parameter);
bool CanExecute(object parameter);
event EventHandler CanExecuteChanged;
}
  1. It wraps method to an object
  2. It says whether to command is available or no, so the UI component (typically a button or menuitem) can reflect it
  3. Additionally, it notifies the UI components that the availability of the command has changed, so the UI can reflect it.

Now, let's consider Copy&Paste scenario. Using ICommand the markup can look like this:

<Button Content="Paste" Command="{Binding PasteCommand}" />
<MenuItem Header="Paste" Command="{Binding PasteCommand}" />
public ICommand PasteCommand {get;} = new DelegateCommand(Paste, () => Clipboard != null);

How would it look like without ICommand? To make it easier, lets consider, that XAML would allow to bind to methods:

<Button Content="Paste" Click="{Binding Paste}" IsEnabled="{Binding CanPaste}" />
<MenuItem Header="Paste" Click="{Binding Paste}" IsEnabled="{Binding CanPaste}"/>

public void Paste() {....}

private bool _canPaste;
public bool CanPaste
{
get { return _canPaste }
set
{
if (_canPaste != value)
{
_canPaste = value;
OnNotifyPropertyChanged(nameof(CanPaste);
}
}
}

as you can see, not only it is more verbose, but it's also violation of DRY principle. You need to specify both Paste and CanPaste binding every time you want to use the command. What if you started without CanPaste and later you wanted to add it. Then you would have to add the CanPaste binding every occurrence of the Paste call. I guarantee you, that you would forget it somewhere.

Now, if you did this in WPF:

<Button Content="Paste" Click="Call_ViewModel_Paste" />


//in codebehind:
void Call_ViewModel_Paste(oobject sender, RoutedEventArgs e)
{
ViewModel.Paste();
}

or eventually:

<Button Content="Paste">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:CallMethodAction MethodName="Paste" TargetObject="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>

Both approaches are correct, they follow MVVM priciples and works without ICommand, but as you can see, neither is as elegant as ICommand



Related Topics



Leave a reply



Submit