Notifying property change for exported properties

Topics: Prism v4 - Silverlight 4, Prism v4 - WPF 4
Dec 16, 2010 at 2:54 PM

I have a User object in my application that needs to be displayed in several views. When this object changes all the views should reflect the change. What is the best way to do this? I was trying to export the user object and import it in my views, but any changes to the object do not get reflected in the views (I assume because the export is by value and not by reference). Is there any way to do this using MEF? Or doing some sort of pub/sub using EventAggregator a better way?

This is my code so far which does NOT work:

***** User *****
public class User : NotificationObject
{
    private string _username;
    public string Username
    {
        get { return _username; }
        set
        {
            _username = value;
            this.RaisePropertyChanged("Username");
        }
    }

    private string _firstName;
    public string FirstName
    {
        ...
    }

    private string _lastName;
    public string LastName
    {
        ...
    }
}


***** MyBootStrapper *****
public class MyBootStrapper : MefBootstrapper
{
    ...
    
    protected override void ConfigureContainer()
    {
        base.ConfigureContainer();
        this.Container.ComposeExportedValue(new User());
    }
}


***** MyViewModel *****
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class MyViewModel : NotificationObject
{
    public User User { get; set; }

    [ImportingConstructor]
    public MyViewModel(User user)
    {
        User = user;
    }
}
Dec 16, 2010 at 4:04 PM
Edited Dec 16, 2010 at 4:06 PM

you might want to look at something like this...


public class UserCollection : ObservableCollection<User>

      public UserCollection()
      {

  } 
}
this gets you something that will respond to the changes of the individual properties of your object.  
Obviously there is more to do in this collection object now but you will need to look ObservableCollection<T> to understand it fully.
Your bootstrapper should have nothing to do with the user directly. Also your user hasn't been marked up for [Export], so in your viewmodel, it doesn't know how to process that.
Hope this gets you in the right direction.
Morgan.
Dec 16, 2010 at 5:07 PM
Edited Dec 16, 2010 at 5:18 PM

I assume that the UserCollection would have to be exported. A similar approach I was thinking about was to create a holder object like UserHolder (containing the User object) and exporting that (since I really don't have a collection of users).

BTW, I am effectively exporting the single User object in the bootstrapper (using Container.ComposeExportedValue). I believe that is equivalent to the [Export] markup and seems to be working well.

While I try out these ideas, can anyone suggest other approaches they have used to solve this issue? I would have to think that this is a VERY common use case.

Thanks.

Naresh

Dec 17, 2010 at 6:40 AM

This is how I have solved the issue:

***** UserContext *****
[Export]
[PartCreationPolicy(CreationPolicy.Shared)]
public class UserContext
{
    public User User { get; set; }

    public UserContext()
    {
        User = new User();
    }
}


***** MyViewModel *****
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class MyViewModel : NotificationObject
{
    public UserContext UserContext { get; set; }

    [ImportingConstructor]
    public LoginViewModel(UserContext userContext)
    {
        this.UserContext = userContext;
    }
}

UserContext holds a User object and this wrapper is imported by the view model (the wrapper just allows me to put more objects in the context in the future). Views now bind to UserContext.User.FirstName etc. Any change to User object's properties is reflected on the UI. Hope this helps others facing such issues. Feedback is always welcome!

Thanks.

Naresh

Dec 17, 2010 at 6:19 PM

Hi Naresh,

Thank you for sharing your findings with the rest of the community.

Take into account that, with this approach you will only be notifying changes occurred in your view model to the view. However, when modifying your ViewModel's UserContext property, you won't be propagating the change to the UserContext shared export that you've defined. So if you wish to reflect the changes made to that property throughout components that could be placed in different modules, this approach could probably not be the most appropriate.

However, you might find  Chapter 9: Communicating Between Loosely Coupled Components interesting (more specifically, the Event Aggregation section). Using this, you might reflect any change you produce in any view model not only in the Module they reside, but also across any other module in your application. An approach using the Event Aggregator would imply, for example, publsihing an event indicating that the UserContext has been changed, and subscribing to that event in another place of your application to reflect the changes accordingly.

Thanks,

Miguel Bronzovic
http://blogs.southworks.net/mbronzovic

 

Dec 17, 2010 at 8:07 PM

Good point Miguel! In my current use case, I only need to propagate changes from UserContext to the views - so this approach works. I do have other use cases where I need changes to propagate changes across modules and I will surely be using event aggregation in those cases.

Thanks.

Naresh