Geeks With Blogs
David Vallens

The ViewModel is supposed to supplement the functionality provided by the Model, and coerce it into a form the View can more readily consume. The result being; the View code can be pretty thin and concerned only with view based activities, while the Model can remain uncompromising by the demands placed on it by a View meaning it can better represent the data/concepts its supposed model. 

The Model Class

In part 1 of this series our Model was very simple, just having 2 properties Name & JobTitle. We will now add a new property to the Model and the ViewModel, Boss. The boss is also of the type PersonModel.

public class PersonModel : INotifyPropertyChanged
{
    private string _name;
    private string _jobTitle;

    public string Name
    {
        get ...
        set ...
    }

    public string JobTitle
    {
        get ...
        set ...
    }
        
    public PersonModel Boss
    {
        get ...
        set ...
    }

    ...
}

Attempt 1

This version seems to solve the problem, when we're asked for the Boss property, we create a wrapper and return it, notice we check if for null and return null as opposed to passing null into the VeiwModel wrapper. When Boss is set we do the reverse, extracting the Model from the ViewModel, and setting that into the PersonModel.Boss property.

public class PersonViewModel : INotifyPropertyChanged, IWeakEventListener
{
    private readonly PersonModel _personModel;

    internal PersonViewModel(PersonModel personModel)
    {
        if (personModel == null) throw new ArgumentNullException("personModel");
        _personModel = personModel;
        NotifyPropertyChangedEventManager.AddListener(_personModel, this);
    }

    public string Name
    {
        get { return _personModel.Name; }
        set { _personModel.Name = value; }
    }

    public string JobTitle
    {
        get { return _personModel.JobTitle; }
        set { _personModel.JobTitle = value; }
    }

    public PersonViewModel Boss
    {
        get
        {
            if (_personModel.Boss == null)
                return null;
            else
                return new PersonViewModel(_personModel.Boss);
        }
        set
        {
            if (value == null)
                _personModel.Boss = null;
            else
                _personModel.Boss = value._personModel;
        }
    }

    #region INotifyPropertyChanged Members
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion

    #region IWeakEventListener Members
    public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
    {
        PropertyChangedEventArgs pcArgs = e as PropertyChangedEventArgs;
        if (pcArgs != null)
        {
            OnPropertyChanged(pcArgs.PropertyName);
            return true;
        }
        return false;
    }
    #endregion
}

This works fine for the example shown, of course every time we ask for the Boss, we get a new PersonViewModel instance, but this is OK, as we've implemented the weak event model (if we'd been lazy and not added weak events this could cause significant memory leakage over time see Weak Events in Part 1).

However, if we implement our own properties within the ViewPersonModel, then we have real issues. Every time we ask for the Boss object we get a new wrapper, and if the wrapper contains properties (say a IsVisible property) then each instance returned from the Boss property has its own value. Lets look at that a little closer.

If we add this code to the PersonViewModel

    private bool _isVisible = true;

    public bool IsVisible
    {
        get { return _isVisible; }
        set
        {
            if (_isVisible == value)
                return;
            _isVisible = value;
            OnPropertyChanged("IsVisible");
        }
    }

then we may expect the following to work

PersonViewModel employee = ...

if (employee.Boss != null)
{
    employee.Boss.IsVisible = false;

    if (employee.Boss.IsVisible == true)
        throw new Exception("not what I was expecting....");
}

But it would throw the exception as each time we ask for the Boss, we get a new wrapper, with the IsVisible flag set to true.

Attempt 2

Resolving this issue is a bit more tricky, you may be tempted to keep hold of the PersonViewModel for the Boss property, this appears to solve the problem.

        private PersonViewModel _boss = null;

        public PersonViewModel Boss
        {
            get
            {
                if (_personModel.Boss == null)
                    return null;
                else
                {
                    if (_boss == null)
                    {
                        _boss = new PersonViewModel(_personModel.Boss);
                    }
                    return _boss;
                }
            }
            set
            {
                if (value == null)
                    _personModel.Boss = null;
                else
                    _personModel.Boss = value._personModel;
            }
        }

But it introduces other issues, if the boss changes in the Model, then the ViewModel is left holding a reference to the wrong Boss. You can mitigate this by clearing the cached boss value when the Model.PropertyChanged("Boss") event fires, but you still end up with a problem.

If Sara is the Boss of Sally & Richard, then the you will end up with 2 ViewModels for Sara, 1 that came from Sally & 1 that came from Richard. This kind of problem is a knightmare to track down.

Attempt 3

If we created a central respository of ViewModel Wrappers, we could register each new ViewModel object using the underlying Model object, then everytime we wanted a ViewMode for a given Model, we would always get the same one back.

public static class ViewModelManager
{
    private static readonly Dictionary<object, WeakReference> _viewModelMap = new Dictionary<object, WeakReference>();

    public static object FindViewModel(object model)
    {
        if (model == null)
            return null;

        WeakReference modelViewWR;
        if (_viewModelMap.TryGetValue(model, out modelViewWR))
        {
            object vm = modelViewWR.Target;
            if (vm != null && modelViewWR.IsAlive)
            {
                return vm;
            }
        }
        return null;
    }

    public static void RegisterViewModel(object model, object viewModel)
    {
        _viewModelMap[model] = new WeakReference(viewModel);
    }
}

Our ViewModel has to change a bit as well.

public class PersonViewModel : INotifyPropertyChanged, IWeakEventListener
{
    private readonly PersonModel _personModel;
    private bool _isVisible = true;

    internal static PersonViewModel FindOrCreate(PersonModel personModel)
    {
        PersonViewModel viewModel = ViewModelManager.FindViewModel(personModel) as PersonViewModel;
        if (viewModel == null)
        {
            viewModel = new PersonViewModel(personModel);
            ViewModelManager.RegisterViewModel(personModel, viewModel);
        }
        return viewModel;
    }

    private PersonViewModel(PersonModel personModel)
    {
        if (personModel == null) throw new ArgumentNullException("personModel");
        _personModel = personModel;
        NotifyPropertyChangedEventManager.AddListener(_personModel, this);
    }

    public string Name
    {
        get { return _personModel.Name; }
        set { _personModel.Name = value; }
    }

    public string JobTitle
    {
        get { return _personModel.JobTitle; }
        set { _personModel.JobTitle = value; }
    }

    public PersonViewModel Boss
    {
        get
        {
            if (_personModel.Boss == null)
                return null;
            else
                return FindOrCreate(_personModel.Boss);
        }
        set
        {
            if (value == null)
                _personModel.Boss = null;
            else
                _personModel.Boss = value._personModel;
        }
    }

    public bool IsVisible
    {
        get { return _isVisible; }
        set
        {
            if (_isVisible == value)
                return;
            _isVisible = value;
            OnPropertyChanged("IsVisible");
        }
    }

    #region INotifyPropertyChanged Members
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion

    #region IWeakEventListener Members
    public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
    {
        PropertyChangedEventArgs pcArgs = e as PropertyChangedEventArgs;
        if (pcArgs != null)
        {
            OnPropertyChanged(pcArgs.PropertyName);
            return true;
        }
        return false;
    }
    #endregion
}

We now use the factory method PersonViewModel.FindOrCreate to find or create us a ViewModel from our Model object. The result being there is only 1 ViewModel per Model, so we always get the same one back.

The ViewModel lookup adds a little cost to the operation, but at the end of the day this is a ViewModel, and the manipulation of the UI tends to massivley outweigh this overhead.

The ViewModelManager may also build up a collection dead references, a housekeeping task could be setup to crawl the map discarding entries with invalid WeakReferences.

Also as the ViewModelManager is static it only allows for 1 view type per Model (ie you can't create a PersonAsXmlViewModel and use it on the same Model objects that are mapped to our PersonViewModel). But this is simple to fix by either passing around an instances of the ViewModelManager or using the Type of the ViewModel as part of the key in the dictionary.
I've not implemented this as the intention is to demonstrate the principle, and this complicates the code.

Conclusion

Having a centralised manager to deal with mapping Model objects to ViewModel make your ViewModel objects much more consistent, and behave much more like the underlying Model objects they are pretending to be. The overhead of the dictionary lookup (IMHO) is trivial, but the solution really requires a housekeeping task to cleanup dead references, and also extending to allow multiple ViewModel's to co-exist.

 

 

 

 

Posted on Friday, April 1, 2011 8:42 AM | Back to top


Comments on this post: Writing MVVM ViewModel Wrappers - Part 2

No comments posted yet.
Your comment:
 (will show your gravatar)


Copyright © David Vallens | Powered by: GeeksWithBlogs.net | Join free