Lighty
Lighty

Reputation: 7

MVVM - DataContext & numerous ViewModels

I have been trying to apply the MVVM design pattern to my latest project. I have watched countless videos and read a large number of tutorials, whilst also looking through people's demo code.

I have a Job object and a Task object. I have created a ViewModel for each. I have also created an AllJobs and AllTasks ViewModel that creates an ObservableCollection of JobViewModels and TaskViewModels respectively.

If I set my DataContext of the MainWindow to an instance of AllJobsViewModel then I can access the ObservableCollection named 'AllJobs' and use it as I wish. However, giving that this is the DataContext, how do I then access my AllTasksViewModel AllTasks collection in the same window?

Upvotes: 0

Views: 242

Answers (4)

Scott Nimrod
Scott Nimrod

Reputation: 11595

You can use an EventAggregator or MessageBus.

The idea is to have your view-models subscribe to events that they would like to respond to.

I use the Publish Subscribe pattern for complicated class-dependencies:

ViewModel:

    public class ViewModel : ViewModelBase
    {
        public ViewModel()
        {
            CloseComand = new DelegateCommand((obj) =>
                {
                    MessageBus.Instance.Publish(Messages.REQUEST_DEPLOYMENT_SETTINGS_CLOSED, null);
                });
        }
}

Window:

public partial class SomeWindow : Window
{
    Subscription _subscription = new Subscription();

    public SomeWindow()
    {
        InitializeComponent();

        _subscription.Subscribe(Messages.REQUEST_DEPLOYMENT_SETTINGS_CLOSED, obj =>
            {
                this.Close();
            });
    }
}

You can leverage Bizmonger.Patterns to get the MessageBus.

MessageBus

public class MessageBus
{
    #region Singleton
    static MessageBus _messageBus = null;
    private MessageBus() { }

    public static MessageBus Instance
    {
        get
        {
            if (_messageBus == null)
            {
                _messageBus = new MessageBus();
            }

            return _messageBus;
        }
    }
    #endregion

    #region Members
    List<Observer> _observers = new List<Observer>();
    List<Observer> _oneTimeObservers = new List<Observer>();
    List<Observer> _waitingSubscribers = new List<Observer>();
    List<Observer> _waitingUnsubscribers = new List<Observer>();

    int _publishingCount = 0;
    #endregion

    public void Subscribe(string message, Action<object> response)
    {
        Subscribe(message, response, _observers);
    }

    public void SubscribeFirstPublication(string message, Action<object> response)
    {
        Subscribe(message, response, _oneTimeObservers);
    }

    public int Unsubscribe(string message, Action<object> response)
    {
        var observers = new List<Observer>(_observers.Where(o => o.Respond == response).ToList());
        observers.AddRange(_waitingSubscribers.Where(o => o.Respond == response));
        observers.AddRange(_oneTimeObservers.Where(o => o.Respond == response));

        if (_publishingCount == 0)
        {
            observers.ForEach(o => _observers.Remove(o));
        }

        else
        {
            _waitingUnsubscribers.AddRange(observers);
        }

        return observers.Count;
    }

    public int Unsubscribe(string subscription)
    {
        var observers = new List<Observer>(_observers.Where(o => o.Subscription == subscription).ToList());
        observers.AddRange(_waitingSubscribers.Where(o => o.Subscription == subscription));
        observers.AddRange(_oneTimeObservers.Where(o => o.Subscription == subscription));

        if (_publishingCount == 0)
        {
            observers.ForEach(o => _observers.Remove(o));
        }

        else
        {
            _waitingUnsubscribers.AddRange(observers);
        }

        return observers.Count;
    }

    public void Publish(string message, object payload)
    {
        _publishingCount++;

        Publish(_observers, message, payload);
        Publish(_oneTimeObservers, message, payload);
        Publish(_waitingSubscribers, message, payload);

        _oneTimeObservers.RemoveAll(o => o.Subscription == message);
        _waitingUnsubscribers.Clear();

        _publishingCount--;
    }

    private void Publish(List<Observer> observers, string message, object payload)
    {
        Debug.Assert(_publishingCount >= 0);

        var subscribers = observers.Where(o => o.Subscription.ToLower() == message.ToLower());

        foreach (var subscriber in subscribers)
        {
            subscriber.Respond(payload);
        }
    }

    public IEnumerable<Observer> GetObservers(string subscription)
    {
        var observers = new List<Observer>(_observers.Where(o => o.Subscription == subscription));
        return observers;
    }

    public void Clear()
    {
        _observers.Clear();
        _oneTimeObservers.Clear();
    }

    #region Helpers
    private void Subscribe(string message, Action<object> response, List<Observer> observers)
    {
        Debug.Assert(_publishingCount >= 0);

        var observer = new Observer() { Subscription = message, Respond = response };

        if (_publishingCount == 0)
        {
            observers.Add(observer);
        }
        else
        {
            _waitingSubscribers.Add(observer);
        }
    }
    #endregion
}

}

Subscription

public class Subscription
{
    #region Members
    List<Observer> _observerList = new List<Observer>();
    #endregion

    public void Unsubscribe(string subscription)
    {
        var observers = _observerList.Where(o => o.Subscription == subscription);

        foreach (var observer in observers)
        {
            MessageBus.Instance.Unsubscribe(observer.Subscription, observer.Respond);
        }

        _observerList.Where(o => o.Subscription == subscription).ToList().ForEach(o => _observerList.Remove(o));
    }

    public void Subscribe(string subscription, Action<object> response)
    {
        MessageBus.Instance.Subscribe(subscription, response);
        _observerList.Add(new Observer() { Subscription = subscription, Respond = response });
    }

    public void SubscribeFirstPublication(string subscription, Action<object> response)
    {
        MessageBus.Instance.SubscribeFirstPublication(subscription, response);
    }
}

Upvotes: 0

Thomas Weller
Thomas Weller

Reputation: 11717

You're doing it the wrong direction if you design your ViewModels to mirror your business objects. Rather, a ViewModel should be specific to a view. There should be a strict 1:1 relationship between the ViewModel and its View - in other words: they form a pair.

The model part is independent from this distinction, which depends solely on the business case and the platform you want to target.

Your requirements (the use cases) govern the view part of your application. The business domain governs the design of your model. The ViewModel then is there to mediate between the two. It merely fulfils a technical requirement for the view part of your application.

Upvotes: 1

petchirajan
petchirajan

Reputation: 4352

You don't need to create separate view models for each items. You need to create a single view model which contains all the information you need and make that viewmodel as a DataContext for your main window.

Or else, Create another one viewmodel which contains your both AllJobsViewModel and AllTasksViewModel. And make the new viewmodel as a datacontext for your MainWindow.

Upvotes: 0

walther
walther

Reputation: 13600

There is rarely a 1:1 relation when it comes to models and viewmodels. You can have ViewModels that combine multiple models or even multiple viewmodels into one. I believe that's the preferred solution in your situation as well.

The main point behind MVVM is to separate layers as much as possible, especially UI and the Model. See my latest answer to see what I mean by that What goes into the Model/Viewmodel?

Upvotes: 0

Related Questions