How should I communicate between ViewModels?

2019-03-27 20:29发布

问题:

I am using MVVM Light and have used the packaged messenger system to communicate between view models, however I have hit a bit of a dilemma! Basically when a user clicks on a customer record the corresponding view is opened and with it the CustomerViewModel is instantiated. At this point the CustomerViewModel requires the selected customers ID from the previous view model (ViewAllCustomersViewModel) so that it can get selected customers info which the view binds to (still following?). So initially my thought was too send that ID in a message from the ViewAllCustomersViewModel (where the customer to be viewed is selected) to the CustomerViewModel... HOWEVER, the CustomerViewModel is not instantiated to be able to receive the message until the view is loaded (at which point the message has already been broadcast)!

So, what would be the best way to solve this issue? So far I have considered the CustomerViewModel sending a request to the ViewAllCustomersViewModel once it has been instantiated (basically saying "I am ready to receive the message"), and then the ViewAllCustomersViewModel sending the ID back to the CustomerViewModel... but is this a necessary approach to solve this? It seems a bit ugly to me!

Otherwise, I was thinking is there another way to communicate which can account for the issue I am having? But then isn't this the whole point of the messaging system... to be able to communicate between view models? OR can I force the view model to be instantiated on start up? If so, how would that affect the ViewModelLocator?

I hope I have outlined the issue clearly, I have used fictional view model names for the purpose of the explanation... and please feel free to edit or suggest any additional information that you would like me to add!

回答1:

Did you try to communicate via your model? I was not able to read your topic until the end but this is how I communicate between ViewModels. Both View Models have the instance of session.

public ViewModel1(ISession session)
        {
            _session = session;           
        }

public ViewModel2(ISession session)
        {
            _session = session;           
        }

This way, when you test your application in BDD (behavior driven development), you can test your application without the view. The glue is the model.

As you can see on this picture, you should be able to test your application without the view.



回答2:

I came across the same situation where two view model is communicating each other. I have used Microsoft PRISM framework to publish and Subscribe.

In your case CustomerViewModel is parent View and ViewAllCustomersViewModel is child view.

  1. Download prism framework "Microsoft.Practices.Prism.PubSubEvents.dll" from https://www.nuget.org/packages/Prism.PubSubEvents/

  2. Add prism reference to your project "Microsoft.Practices.Prism.PubSubEvents.dll"

  3. Create some custom class which is used for communication modem.

      class Notifications : PubSubEvent<string>
      {
    
      }
    
  4. Create IEventAggregator eventAggregator singleton instance for your project and initialize it.

      public sealed class SessionInfo
      {
            public  IEventAggregator eventHanlder;
    
            private SessionInfo (){
    
            }
    
            private static SessionInfo _instance = null;
    
            public static SessionInfo Instance{
                    get{
                     lock (lockObj){
                      if (_instance == null) {
                        _instance = new SessionInfo ();
                        _instance.eventHanlder= new EventAggregator();
                       }
                   }
                      return _instance;
                   }
                 }
                }
    
  5. Go to Popover model (ViewAllCustomersViwModel) button events handling and below codes in it.Now it has been published.

In ViewAllCustomersViwModel.cs:

      public void OnSelectedItem(Item item)
     {
            SessionInfo.Instance.eventHanlder.GetEvent<Notification>().Publish(item.id);

      }
  1. These event aggregator has to be subscribe where it is needed. So add below code on your Parent View model (CustomerViewModel)

CustomerViewModel.cs

       public class CustomerViewModel
      {
               public CustomerViewModel()
              {
                  SessionInfo.Instance.eventHanlder.GetEvent<Notifications>().Subscribe(OnReceivedNotification);

               }

        //Handling the notification 
    public void OnReceivedNotification(string itemId)
        {
            Debug.WriteLine("Item Id is :" + itemId);

        }


     }

For more information:

https://sites.google.com/site/greateindiaclub/mobil-apps/windows8/communicationbetweenviewmodelsinwindows8mvvmpattern



回答3:

I believe that standard way is to pass it through View. Depending on how you instantiate your views, it could be DependencyProperty to bind in XAML, constructor parameter, or anything else. Then View passes it to it's ViewModel (pushes it to VM, not the way around: ViewModel should not be aware of View). This way you get a standalone closed component (your View), and external code does not know about it's internal implementation (which is ViewModel).

In XAML it can be something like

<ListBox x:Name="customers" />
<CustomerView Customer="{Binding SelectedItem, ElementName=customers}" />

And then in CustomerPropertyChanged handler you push value to the ViewModel.



回答4:

Personally, I used to use the MVVM-Light Messenger, but found I had way to many messages flying around, and I didn't like the feeling of using a "magical" messenger. What I did is outlined as the answer to the following link

Best Way to Pass Data to new ViewModel when it is initiated.

Now I warn you, I answered my own question, and nobody verfied it as good or bad practice, however it works for my situation and has elimnated the need for MVVM-Light Messenger. Because my program uses multiple threads in my implementation I changed all the entries in the repository to Dictionarys with the CurrentThread.ManagedThreadId as the Key.



回答5:

So far I have considered the CustomerViewModel sending a request to the ViewAllCustomersViewModel once it has been instantiated (basically saying "I am ready to receive the message"), and then the ViewAllCustomersViewModel sending the ID back to the CustomerViewModel...

I would continue with this idea. It keeps the Views, ViewModels and Models all separate and unknowing of the others unlike other answers. Not to say other answers are wrong, or even bad, your option can be defined one or any of: personal preference, team convention, long-term MVVM goal of replacing components/modules, and complexity/ease of coding.

A side-effect to your idea I quoted above, which I prefer, is that you can request at any time as you've already set it up. So if you change when to perform that request very easily, or if you need to request updates, you use the same communication architecture in place.

Lastly, I prefer it because if you change your models or views or viewmodels - you keep the same core concept of communicating information between components.