views:

28

answers:

2

Hello,

In the application I'm building, the user may perform something in one view (backed by a view model) that should trigger an action in one or more other view models. Each of these other vms needs the ability to veto (a.k.a. cancel) the action performed in the first v/vm pair.

Example:

  1. User clicks on an account in a DataGrid control displayed by the accounts list view. DataGrid event handler traps the click and tells vm. Vm notifies other vms of proposed change.
  2. Since user has made unsaved edits to a record in another other view, other vm tells first vm that the proposed selected account change is rejected.
  3. When accounts list vm receives this rejection, it tells DataGrid to keep the selected account set as it was. If no rejection had been received, accounts list vm would have allowed DataGrid selected item change to occur.

A similar scenario would be when the user initiates application shutdown. Interested vms need a way to know that shutdown is proposed and have the option to cancel shutdown.

The view models should be loosely coupled, so direct event subscriptions between them is undesirable.

How would you suggest implementing this intra-view model communication?

Would use an event aggregator to "broadcast" an account changing event be a wise approach? The event argument object would include a bool Canceled property. A subscribing vm that wants to cancel the change would set Canceled = true.

Thank you,
Ben

+1  A: 

I think your last paragraph suggests a good solution.

Using the MVVM Light Toolkit, I would use messaging with a callback to send the message out and allow any number of subscribers to call back with a cancellation.

public class AccountSelectedMessage : NotificationMessageAction<bool>
{
    public AccountSelectedMessage(Account selectedAccount, Action<bool> callback) : base("AccountSelectedWithCancelCallback", callback)
    {
        SelectedAccount = selectedAccount;
    }
    public AccountSelectedMessage(object sender, Account selectedAccount, Action<bool> callback) : base(sender, "AccountSelectedWithCancelCallback", callback)
    {
        SelectedAccount = selectedAccount;
    }
    public AccountSelectedMessage(object sender, object target, Account selectedAccount, Action<bool> callback) : base(sender, target, "AccountSelectedWithCancelCallback", callback)
    {
        SelectedAccount = selectedAccount;
    }

    public Account SelectedAccount { get; private set; }
}

public class AccountListViewModel : ViewModelBase
{
    public RelayCommand<Account> AccountSelectedCommand = new RelayCommand<Account>(AccountSelectedCommandExecute);

    private void AccountSelectedCommandExecute(Account selectedAccount)
    {
        MessengerInstance.Send(new AccountSelectedMessage(this, AccountSelectionCanceled));
    }

    private void AccountSelectionCanceled(bool canceled)
    {
        if (canceled)
        {
            // cancel logic here
        }
    }
}

public class SomeOtherViewModel : ViewModelBase
{
    public SomeOtherViewModel()
    {
        MessengerInstance.Register<AccountSelectedMessage>(this, AccountSelectedMessageReceived);
    }

    private void AccountSelectedMessageReceived(AccountSelectedMessage msg)
    {
        bool someReasonToCancel = true;
        msg.Execute(someReasonToCancel);
    }
}

As you can see, this process would need to be asynchronous and take into account that you don't know how many recipients of the message could cancel, or how long they would take to respond.

Matt Casto
+1  A: 

An EventAggregator is the way to go. We used the one from Prism. But we didn't take all of prism. just the classes related to the EventAggregator. We did create a DomainEvent for service layers assemblies where the GUI references are not available.

jeff