locked
MVVM: How to pass data between ViewModels RRS feed

  • Question

  • I'm a relative newbie to WPM MVVM, and I'm rewriting a Winform app to WPF. The original design is a window containing only a single tab control. Each tab in-turn hosts various controls and functions as a separate "form" (input data, click button, view results).  The business functions of each tab are only loosely related to the other tabs.

    Unfortunately, the code for ALL the tabs is presently "glommed together" (like spaghetti) in a single C# source file. But after analysing it, I found there's not really very much code that's actually actually shared between the various tabs.  

    So I decided to segregate each "form" into separate Views (UserControls) each with an associated ViewModel. The new design will be a main View containing only the tab control (and its associated main iewModel)  plus a View (a UserControl) and associated ViewModel for each tab. 

    But I'm stuck on what would seem to be a simple thing: I need to pass a startup argument "down" the hierarchy from the "main" ViewModel to the "lower-level" ViewModel(s)

    How do I do this in an MVVM-friendly way?  And more generally, how does one share "data" (and perhaps methods?) between ViewModels?

    (examples would be most welcome, since I'm sort of a noob).

    DadCat

    Wednesday, August 29, 2012 10:36 PM

Answers

  • I decided to do it the traditional way: 1) define a thread-safe singleton 2) in Application_Startup, instantiate the singleton and all the ViewModels (at least the VM's that need to share info with each other), then save references to all the VM's in the Singleton. This way, any VM can access any other VM. 

    Works great, although it may not appeal to MVVM purists.

    DadCat

    • Marked as answer by DadCat Wednesday, September 5, 2012 6:56 PM
    Wednesday, September 5, 2012 6:56 PM

All replies

  • It depends a lot on your MVVM design.

    If you're using a View-first approach with a framework (like MVVM Light), then you can use a messaging service, a service locator, or something similar to pass data around.

    With a VM first approach, the VM's can construct and easily reference each other as needed, so it's not too tough to do.  Pushing "down" into the VM is easy - the outer VM can construct the inner VM(s) and just set properties or pass the values in the constructor.


    Reed Copsey, Jr. - http://reedcopsey.com
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    Wednesday, August 29, 2012 10:43 PM
  • Normally you pass initialising data down in through ViewModel parameters - properties, event handler delegates, whole UIElements, whole objects (in a kind of IoC way).

    tab2.Content = new Tab2userControl { DataContext = new MyTab2ViewModel(UserId, OtherStuffNeeded) };

    If it's just binding, then you can attach your VM to an outer most control (like Parent Grid/Canvas), but not the UerControl Itself, leaving you to still get to the parent control's properties:

    {Binding Path=DataContext.OriginalParentProperty, relativeSource={RelativeSource AncestorType=UserControl}

    Check out the following project, it will show you lots of ways to do this, with an example at the end of multiple Tab binding.

    http://code.msdn.microsoft.com/How-to-Build-Manage-and-fdd0074a (please rate/star it if you like it :)

    While I'm at it, I should point you to a very useful "cross-ViewModel" communicator, the Mediator.

    A very popular design pattern for MVVM. Here is an example you can use:

    http://code.msdn.microsoft.com/The-Mediator-Pattern-c2c07b54

    Hope that helps,
    Pete


    #PEJL



    • Edited by Pete LakerMVP Wednesday, August 29, 2012 10:58 PM tweak
    Wednesday, August 29, 2012 10:52 PM
  • I personally like events (Visitor Pattern).  If everyone needs something, then have them register as a listener for that something!  

    delegate void OnNewData(MyClass Data);
    public static event OnNewData NotifyDataReady;
    
    if(NotifyDataReady!=null){
    
       NotifyDataReady(MyData);
    }
    
    
    //all the other classes register
    MainClass.OnNewData += Listner;
    
    public void Listener(Myclass Data){
      MyLocalCopy = Data;
    }

    You can also create a static class with a variable that holds the content.  This would be more of a mediator pattern in that an unrelated helper class holds the content.


    JP Cowboy Coders Unite!

    Thursday, August 30, 2012 11:56 AM
  • I look at the MVVM pattern is just a way to seperate business logic from the GUI and do not adhere to a glorified a purist sort of view of it....so this goes against the grain. 

    Have the lower VM(s) have a reference to the other VM(s) as needed and access its data accordingly. Don't get hung up on what is right and what is wrong. The fact that you are using the MVVM pattern is the major right...but that is just my opinion, I could be wrong. :-) 


    William Wegerson (www.OmegaCoder.Com)


    • Edited by OmegaMan Thursday, August 30, 2012 12:39 PM
    Thursday, August 30, 2012 12:39 PM
  • With menu sort of stuff I would go with messaging and either MVVM light or prism.

    I don't like events because of the dependency it generates and you need to worry about disposing removing them and what you do when running unit tests.

    Messaging removes the tricky bits and I feel makes it all easier.

    Your viewmodel(s) listen for whatever is relevent and the menu/tab/whatever just sends the message.

    You may want to also work out which view in the tabs has focus and make that affect stuff, or maybe one setting affects everything.

    If you need to actually share common data between them, then a repository something to consider. 

    Thursday, August 30, 2012 1:33 PM
  • "With a VM first approach, the VM's can construct and easily reference each other as needed,"

    Could you provide an example? (please!)  It's not clear where one VM would construct another VM.

    At present, I'm creating the MainViewModel in the constructor of MainView:

        public partial class MainView : Window
        {
            public MainView()
            {
                InitializeComponent();

                base.DataContext = new MainViewModel();

            }
        }

    Would I also construct the VM's for the various nested UserControls (eg "Tab1View", etc) in the above constructor?

    But then what about the constructor that's already in Tab1View, which creates Tab1ViewModel:

       public partial class Tab1View : UserControl
        {
            public Tab1View()
            {
                InitializeComponent();

                base.DataContext = new Tab1ViewModel();
            }  

        }

    There must be something fundamental I'm missing...

    DadCat

    Thursday, August 30, 2012 2:04 PM
  • I think your first example (below) is tantalizingly close. But I don't understand where that line would be used.

    tab2.Content = new Tab2userControl { DataContext = new MyTab2ViewModel(UserId, OtherStuffNeeded

    As I said in my reply to Reed, I don't yet understand how the pieces fit together.

    DC

    Thursday, August 30, 2012 2:08 PM
  • I think your first example (below) is tantalizingly close. But I don't understand where that line would be used.

    tab2.Content = new Tab2userControl { DataContext = new MyTab2ViewModel(UserId, OtherStuffNeeded

    As I said in my reply to Reed, I don't yet understand how the pieces fit together.

    DC

    This is a View-First approach.  Basically, in your code, the View (UserControl/Window/etc) is creating the VM (this.DataContext = new ...).

    A VM first approach is where you create the VM, and a DataTemplate (or some other service) wires up the appropriate view automatically.  Instead of building Windows/UserCOntrols/etc, you build the "logic" and let the view autoamtically populate.

    That's actually the approach I took in my series on MVVM here: http://reedcopsey.com/series/windows-forms-to-mvvm/  There is sample code you can look at to see how it all works.



    Reed Copsey, Jr. - http://reedcopsey.com
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    Thursday, August 30, 2012 5:56 PM
  • Create a BaseViewModel and a Collection ObservableCollection<BaseViewModel> _viewModels;

    Create a Property ObservableCollection<BaseViewModel> ViewModels around _viewModels

    Define your View Models like this and add to Collection

    MainViewModel:BaseViewModel

    Tab1ViewModel:BaseViewModel

    Tab2ViewModel:BaseViewModel

    Now you can use this

    Tab1ViewModel vm= (ViewModels.Where(vm => vm is Tab1ViewModel).Count() == 0) ? new  Tab1ViewModel(): (ViewModels.Where(vm => vm is Tab1ViewModel).FirstOrDefault() as Tab1ViewModel;


    Friday, August 31, 2012 5:24 PM
  • I decided to do it the traditional way: 1) define a thread-safe singleton 2) in Application_Startup, instantiate the singleton and all the ViewModels (at least the VM's that need to share info with each other), then save references to all the VM's in the Singleton. This way, any VM can access any other VM. 

    Works great, although it may not appeal to MVVM purists.

    DadCat

    • Marked as answer by DadCat Wednesday, September 5, 2012 6:56 PM
    Wednesday, September 5, 2012 6:56 PM
  • Hello. I'm practising MVVM right now and as I see in Your post, You're disobeying MVVM definition trying to use it. By creating MV in View you tightly bind them, and MVVM says the should be autonomous. YOu must create views and vm's seperately and tight them in app object for example.

    Best regards

    Marcin

    Thursday, November 21, 2013 11:58 AM