Best practice for instantiating view models

Jan 29, 2012 at 11:00 PM

Hi,
From a View Model I want to allow the user to load several other views, What is the best/recommend method for creating new instances of view models that supports MEF Composition (i.e. satisfies the Imports for the new view model) and also allows Caliburn Micro to automatically wire up the events, so no need to manually attach like in the following.

 <Button cal:Message.Attach="OK" 

View Model Code below

    [Export]
    public class UserSetupViewModel : Screen, IDiscoverableViewModel
    {
        [Import]
        private IWindowManager WindowManager { get; set; }
        private SystemDataRepository SystemDataRepository { get; set; }

        /// <summary>
        /// Clicked Edit User button
        /// </summary>
        public void EditUser(UserItem user)
        {
            //var vm = (UserEditViewModel)IoC.GetInstance(typeof(UserEditViewModel), null); Wont load MEF Imports, but will enable CM auto convention binding
            var vm = new UserEditViewModel(user.UserID, this.SystemDataRepository, user.UserRecord.GroupID);

            var result = WindowManager.ShowDialog(vm);
            if (result.HasValue && result.Value) SystemDataRepository.GetUserGroups("", k => LoadGroupTree(k), null);
        }

        /// <summary>
        /// Clicked Add User button
        /// </summary>
        public void AddUser(UserItem user)
        {
            var vm = new UserEditViewModel(null, this.SystemDataRepository, user.UserRecord.GroupID);

            var result = WindowManager.ShowDialog(vm);
            if (result.HasValue && result.Value) SystemDataRepository.GetUserGroups("", k => LoadGroupTree(k), null);
        }

 

Coordinator
Jan 29, 2012 at 11:44 PM

IoC.GetInstance(), CompositionHelper.GetInstance<T>() or if you are in Silverlight, you can use an ExportFactory<T>.

But, you have to make sure that all parameters in the importing constructor can actually be satisfied from the container. In your case for UserEditViewModel, the first and third parameter cannot be satisfied from the container. Those parameters come from the UserItem that's passed in to EditUser.

For these situations, I typically add a Start() method to the ViewModel that I can use to further initialize the ViewModel. The Start() method would take any number of parameters or sometimes none, and it would as the name says start the ViewModel, which could involve loading data from the database.

So, your case would look something like this:

[Export]
public class UserEditViewModel
{
   [ImportingConstructor]
   public UserEditViewModel(SystemDataRepository repository)
   {

   }

   public UserEditViewModel Start(int userId, int groupId)
   {
      ....
      return this;
   }
}

            var vm = CompositionHelper.GetInstance<UserEditViewModel>(CreationPolicy.NonShared);

            var result = WindowManager.ShowDialog(vm.Start(user.UserID, user.UserRecord.GroupID));

Or here is an example for how you would use an ExportFactory<T>. Unfortunately, we'll have to wait for .NET 4.5 until we can use the ExportFactory in a WPF application, but in Silverlight 4 and 5 you can do this today.

    [Export]
    public class ShellViewModel : Conductor<IScreen>    
    {
        private readonly ExportFactory<LoginViewModel> _loginFactory;
         
        [ImportingConstructor]
        public ShellViewModel(ExportFactory<LoginViewModel> loginFactory)
        {
            _loginFactory = loginFactory;
        }
 
        protected override void OnViewLoaded(object view)
        {
            base.OnViewLoaded(view);
 
            // Launch login dialog
            LoginViewModel login = _loginFactory.CreateExport().Value;
            login.Execute();
        }
    }
Jan 31, 2012 at 8:14 AM

Perfect, Thanks for the help