3

I'm currently working on a project, and for the sake of simplifying explanations, let's say that there are two tabs in a TabControl...

In one tab, you add folders to a ListBox.

In the other tab, there is a ListBox that displays all of the items in all of the folders you have added.

Each tab is a ViewModel (this is to simplify code, as dumping all the code in one ViewModel makes it hard to read and understand).

In order for this program to work, both ViewModels need to access the list of items: one because it has to display them, and the other because it has to add to them.

I am having trouble figuring how to do this. I initially thought that sharing data is bad and this shouldn't have occurred in the first place, but then I realised that I can't think of any other way to do this.

I am new to MVVM (this is my first real application using MVVM) and initally started using it, as I couldn't access data between classes, and thought MVVM would somehow sort this issue, but here I am again.

I would appreciate it if someone can tell me how I could do this, and potentially explain it with example code. I am also open to suggestions and constructive criticism to my methods.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291

6 Answers6

2

First of all you should understand what a View in MVVM is. Look of it as a panel. A panel that could be embedded in a Window, TabControl or even in a ListBox item. A panel that also could contain child panels. Basically, if your application isn't just a simple input form there is a great probability it would have more than one View. Don't try to put everything in one View/ViewModel because it would complicate things tremendously later on. You want to have so called hierarchy of Views and their corresponding ViewModels. There will be a lot of Views/ViewModels but they will be relatively simple and easy to maintain (here is small example of switching between views using PRISM but you can get the main idea https://youtu.be/ZfBy2nfykqY?t=45m34s).

Each tab is a ViewModel (this is to simplify code, as dumping all the code in one ViewModel makes it hard to read and understand).

This is the right approach. Here is some pseudo code which describes how your example application scheme should look like:

// MODEL:

public class Model
{
    ObservableCollection<Item> ListOfItems;
}

public class Item
{
}

// VIEWMODELS:

public class MainWindowViewModel
{
    Model Model { get; set; }

    Tab1ViewModel Tab1ViewModel { get; set; }
    Tab2ViewModel Tab2ViewModel { get; set; }

    public MainWindowViewModel()
    {
        Model = new Model();
        Tab1ViewModel = new Tab1ViewModel(Model);
        Tab2ViewModel = new Tab2ViewModel(Model);
    }
}

public class Tab1ViewModel
{
    ObservableCollection<ItemViewModel> ItemViewModels { get; set; } // Bind it to ListBox's ItemsSource

    public Tab1ViewModel(Model m)
    {
        ItemViewModels = new ObservableCollection<ItemViewModel>();
        // Populate ItemViewModels and keep it in sync with ListOfItems model by subscribing to its CollectionChanged event.
    }
}

public class Tab2ViewModel
{
    ObservableCollection<ItemViewModel> ItemViewModels { get; set; } // Bind it to ListBox's ItemsSource

    public Tab2ViewModel(Model m)
    {
        ItemViewModels = new ObservableCollection<ItemViewModel>();
        // Populate ItemViewModels and keep it in sync with ListOfItems model by subscribing to its CollectionChanged event.
    }

}

public class ItemViewModel
{
    Item Item { get; set; } // Model

    public ItemViewModel(Item item)
    {
        Item = item;
    }
}

Now you can display the same data in different Views and perform different operations on it. Every View will automatically be updated because it references the same Model.

You may also use EventAggregator or something similar to communicate between ViewModels.

Try to avoid static classes/Singletons with the data that could be accessed from anywhere in your application because this breaks the Encapsulation principle.

EuKa
  • 46
  • 4
1

You can have a singleton object and get/set its properties from anywhere.

Look at this example;

public sealed class ApplicationState
{
    private static readonly ApplicationState instance = new ApplicationState();

    static ApplicationState()
    {
    }

    private ApplicationState()
    {
    }

    public static ApplicationState Instance
    {
        get
        {
            return instance;
        }
    }


    public string SharedString {get;set;}
}

now you can set this SharedString property from anywhere like;

ApplicationState.Instance.SharedString = "hello from VM1"

and read it from another view model like;

Debug.WriteLine(ApplicationState.Instance.SharedString)

You can take a look at this link to learn more about singletons

you can even make your ApplicationState singleton an ObservableObject and bind to its properties from your views like;

Value="{Binding SharedString, Source={x:Static ApplicationState.Instance}}"
Hasan
  • 2,444
  • 3
  • 30
  • 44
0

as dumping all the code in one ViewModel makes it hard to read and understand).

If one has a main set of data to be used on a page, the page generally has one view model. Unless each tab is a re-usable unit to be used elsewhere, having a VM for each tab page seems unnecessary to me.

hard to read and understand

I beg to differ, that is what comments are used for to provide a logic on usage.

My advice is to bring it to one VM and solve this problem.

both ViewModels need to access the list of items:

You can put static properties on the app of the program and have the VM instances assigned to those statics. Then each VM can access the other VM's data.

I have done similar before where I need to access a VM and I use the common app as an access place.

I am new to MVVM

MVVM is just a 3-tiered data system to separate business logic (the VM) from the entities (Models) from the view of data. Don't get hung up on trying to make MVVM a dogma.

ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
0

Another approach would be using a message system. In PRISM for example you have the IEventAggregator to send messages inside your application. I guess the IEventAggregator is available as stand alone dll.

It's really powerfull and easy to use. You define a message and the ViewModel which can add items send an instance of this message (with the item as argument). The ViewModel with the list can catch this message and adds the item to it's list.

One advantage is that both ViewModels don't have to know each other.

Mighty Badaboom
  • 6,067
  • 5
  • 34
  • 51
0

Use a single parent viewmodel which has references to both the child viewmodels you describe. I assume that in your viewmodel that the user is selecting from you are binding the view to an ObservableCollection.

In you parent viewmodel you can subscribe to the Change notification events on the ObservableCollection in the source viewmodel and then call a method on the second viewmodel to populate the changes.

Example of using ObservableCollection.CollectionChanged event: https://dotnetcodr.com/2015/05/29/getting-notified-when-collection-changes-with-observablecollection-in-c-net/

One other option, depending on which MVVM framework you are using is to use the Messenging pattern to pass messages between disconnected ViewModels - Use MVVM Light's Messenger to Pass Values Between View Model

Community
  • 1
  • 1
benPearce
  • 37,735
  • 14
  • 62
  • 96
0

Just use a mvvm library such as mvvm light, prism ... every mvvm library has structer for comminicate between viewmodels that you can use it. - Dont write new one. Do not reinvent the wheel. - Dont take parameter viewmodel instance in another viewmodel. if you do that it will be pain in you

sam
  • 26
  • 2